1
0
mirror of https://github.com/pavlobu/deskreen.git synced 2025-05-21 01:40:12 -07:00

Writing host tests, adding deskreen icon

This commit is contained in:
Pavlo Buidenkov 2020-12-25 17:21:57 +02:00
parent 9ee64b4933
commit a0862f6fe8
170 changed files with 7611 additions and 2233 deletions

View File

@ -5,6 +5,8 @@
## Howto:
# trigger
### to generate test coverage results
```

View File

@ -6,22 +6,10 @@ import routes from './constants/routes.json';
import App from './containers/App';
import HomePage from './containers/HomePage';
// Lazily load routes and code split with webpacck
const LazyCounterPage = React.lazy(
() => import(/* webpackChunkName: "CounterPage" */ './containers/CounterPage')
);
const CounterPage = (props: Record<string, any>) => (
<React.Suspense fallback={<h1>Loading...</h1>}>
<LazyCounterPage {...props} />
</React.Suspense>
);
export default function Routes() {
return (
<App>
<Switch>
<Route path={routes.COUNTER} component={CounterPage} />
<Route path={routes.HOME} component={HomePage} />
</Switch>
</App>

View File

@ -0,0 +1,138 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`app menu MenyBuilder tests when MenyBuilder created properly when menu from buildDarwinTemplate was created should match a snapshot 1`] = `
Array [
Object {
"label": "Deskreen",
"submenu": Array [
Object {
"label": "About Deskreen",
"selector": "orderFrontStandardAboutPanel:",
},
Object {
"type": "separator",
},
Object {
"label": "Services",
"submenu": Array [],
},
Object {
"type": "separator",
},
Object {
"accelerator": "Command+H",
"label": "Hide Deskreen",
"selector": "hide:",
},
Object {
"accelerator": "Command+Shift+H",
"label": "Hide Others",
"selector": "hideOtherApplications:",
},
Object {
"label": "Show All",
"selector": "unhideAllApplications:",
},
Object {
"type": "separator",
},
Object {
"accelerator": "Command+Q",
"click": [Function],
"label": "Quit",
},
],
},
Object {
"label": "Edit",
"submenu": Array [
Object {
"accelerator": "Command+Z",
"label": "Undo",
"selector": "undo:",
},
Object {
"accelerator": "Shift+Command+Z",
"label": "Redo",
"selector": "redo:",
},
Object {
"type": "separator",
},
Object {
"accelerator": "Command+X",
"label": "Cut",
"selector": "cut:",
},
Object {
"accelerator": "Command+C",
"label": "Copy",
"selector": "copy:",
},
Object {
"accelerator": "Command+V",
"label": "Paste",
"selector": "paste:",
},
Object {
"accelerator": "Command+A",
"label": "Select All",
"selector": "selectAll:",
},
],
},
Object {
"label": "View",
"submenu": Array [
Object {
"accelerator": "Ctrl+Command+F",
"click": [Function],
"label": "Toggle Full Screen",
},
],
},
Object {
"label": "Window",
"submenu": Array [
Object {
"accelerator": "Command+M",
"label": "Minimize",
"selector": "performMiniaturize:",
},
Object {
"accelerator": "Command+W",
"label": "Close",
"selector": "performClose:",
},
Object {
"type": "separator",
},
Object {
"label": "Bring All to Front",
"selector": "arrangeInFront:",
},
],
},
Object {
"label": "Help",
"submenu": Array [
Object {
"click": [Function],
"label": "Learn More",
},
Object {
"click": [Function],
"label": "Documentation",
},
Object {
"click": [Function],
"label": "Community Discussions",
},
Object {
"click": [Function],
"label": "Search Issues",
},
],
},
]
`;

View File

@ -1,4 +1,5 @@
/* istanbul ignore file */
let host;
let protocol;
let port;

View File

@ -1,4 +1,5 @@
/* istanbul ignore file */
import config from './config';
export default (resourceName = '') => {

View File

@ -154,7 +154,9 @@ div.class-allow-device-to-connect-alert
> svg
> path {
color: #a82a2a;
-webkit-animation: blink 0.75s infinite alternate; /* to blink 3 times instead of infinite write just 3 */
-webkit-animation: blink 0.75s infinite alternate;
/* to blink 3 times instead of infinite write just 3 */
-moz-animation: blink 0.75s infinite alternate;
-ms-animation: blink 0.75s infinite alternate;
-o-animation: blink 0.75s infinite alternate;
@ -165,38 +167,47 @@ div.class-allow-device-to-connect-alert
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@-moz-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@-ms-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@-o-keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
}
@keyframes blink {
from {
color: #a82a2a;
}
to {
color: #f55656;
}
@ -412,3 +423,19 @@ a:hover {
text-decoration: none;
cursor: pointer;
}
#new-version-header {
background: rgba(0, 255, 54, 0.48);
width: fit-content;
border-radius: 100px;
padding: 5px;
}
#new-version-header:hover {
background: rgba(0, 255, 54, 0.78);
cursor: pointer;
}
.bp3-tab-list {
height: calc(100vh - 30%);
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Deskreen Viewer</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,6 +1,6 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "Deskreen",
"name": "Deskreen Makes Any Device a Second Screen For Your Computer",
"icons": [
{
"src": "favicon.ico",

View File

@ -1,8 +1,8 @@
sonar.projectKey=deskreen-viewer
sonar.typescript.lcov.reportPaths=coverage/lcov.info
sonar.sources=src
sonar.cpd.exclusions=src/**/mocks/*,src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
sonar.coverage.exclusions=src/**/mocks/*,src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
sonar.cpd.exclusions=src/config/*,src/**/__mocks__/*,src/**/mocks/*,src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
sonar.coverage.exclusions=src/config/*,src/**/__mocks__/*,src/**/mocks/*,src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
sonar.host.url=http://localhost:9000
sonar.login=e3b5f73b8778290f7074c40a4159c32b7f15a8e6
sonar.exclusions=src/serviceWorker.ts,node_modules/**

View File

@ -23,6 +23,7 @@ exports[`should match exact snapshot 1`] = `
<button
className="bp3-button bp3-minimal"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
tabIndex={0}
@ -45,7 +46,7 @@ exports[`should match exact snapshot 1`] = `
<img
alt="logo"
height={42}
src="deskreen_logo_128x128.png"
src="http://localhost/logo192.png"
width={42}
/>
</div>
@ -81,6 +82,7 @@ exports[`should match exact snapshot 1`] = `
<button
className="bp3-button"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
style={

View File

@ -1,6 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import PlayerControlPanel from '.';
import ScreenSharingSource from '../../features/PeerConnection/ScreenSharingSourceEnum';
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
jest.useFakeTimers();
@ -16,7 +17,7 @@ it('should match exact snapshot', () => {
handleClickPlayPause={() => {}}
setVideoQuality={() => {}}
selectedVideoQuality={VideoQuality.Q_100_PERCENT}
screenSharingSourceType={'screen'}
screenSharingSourceType={ScreenSharingSource.SCREEN}
toaster={undefined}
/>
</>

View File

@ -21,13 +21,13 @@ import {
} from '@blueprintjs/core';
import FullScreenEnter from '../../images/fullscreen_24px.svg';
import FullScreenExit from '../../images/fullscreen_exit-24px.svg';
import DeskreenIconPNG from '../../images/deskreen_logo_128x128.png';
import RedHeartTwemojiPNG from '../../images/red_heart_2764_twemoji_120x120.png';
import { Col, Row } from 'react-flexbox-grid';
import screenfull from 'screenfull';
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
import handlePlayerToggleFullscreen from './handlePlayerToggleFullscreen';
import initScreenfullOnChange from './initScreenfullOnChange';
import ScreenSharingSource from '../../features/PeerConnection/ScreenSharingSourceEnum';
const videoQualityButtonStyle: React.CSSProperties = {
width: '100%',
@ -80,11 +80,16 @@ function PlayerControlPanel(props: PlayerControlPanelProps) {
content="Click to visit our website"
position={Position.BOTTOM}
>
<Button minimal>
<Button
minimal
onClick={() => {
window.open('https://www.deskreen.com', '_blank');
}}
>
<Row middle="xs" style={{ opacity: '0.75' }}>
<Col xs={4}>
<img
src={DeskreenIconPNG}
src={window.location.origin + '/logo192.png'}
width={42}
height={42}
alt="logo"
@ -100,7 +105,12 @@ function PlayerControlPanel(props: PlayerControlPanelProps) {
content="If you like Deskreen, consider donating! Deskreen is free and opensource forever! You can help us to make Deskreen even better!"
position={Position.BOTTOM}
>
<Button style={{ borderRadius: '100px' }}>
<Button
style={{ borderRadius: '100px' }}
onClick={() => {
window.open('https://www.patreon.com/deskreen', '_blank');
}}
>
<Row start="xs">
<Col xs>
<img
@ -174,7 +184,7 @@ function PlayerControlPanel(props: PlayerControlPanelProps) {
borderBottom: '1px solid #ffffffa8',
}}
/>
{screenSharingSourceType === 'window' ? (
{screenSharingSourceType === ScreenSharingSource.WINDOW ? (
<Tooltip
content="You can't change video quality when sharing a window. You can change quality only when shering entire screen."
position={Position.BOTTOM}

View File

@ -1,3 +1,5 @@
/* istanbul ignore file */
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
@ -20,7 +22,7 @@ i18n
saveMissing: true,
saveMissingTo: 'all',
fallbackLng: 'en', // TODO: to generate missing keys use false as value here, will be useful when custom nodejs server is created to store missing values
debug: true, // change to true to see debug message logs in browser console
debug: false, // change to true to see debug message logs in browser console
whitelist: ['en', 'ru', 'ua'],
backend: {

View File

@ -0,0 +1,6 @@
enum ConnectionIcon {
FEED = 'feed',
FEED_SUBSCRIBED = 'feed-subscribed'
}
export default ConnectionIcon;

View File

@ -0,0 +1,6 @@
enum LoadingSharingIconEnum {
DESKTOP = 'desktop',
APPLICATION = 'application'
}
export default LoadingSharingIconEnum;

View File

@ -4,6 +4,7 @@ import getRoomIDOfCurrentBrowserWindow from '../../utils/getRoomIDOfCurrentBrows
import Crypto from '../../utils/crypto';
import VideoAutoQualityOptimizer from '../../features/VideoAutoQualityOptimizer';
import changeLanguage from './changeLanguage';
import ConnectionIcon from './ConnectionIconEnum';
export default (params: CreatePeerConnectionUseEffectParams) => {
const {
@ -27,7 +28,7 @@ export default (params: CreatePeerConnectionUseEffectParams) => {
isDarkTheme,
setMyDeviceDetails,
() => {
setConnectionIconType('feed-subscribed');
setConnectionIconType(ConnectionIcon.FEED_SUBSCRIBED);
setIsShownTextPrompt(false);
setIsShownTextPrompt(true);

View File

@ -1,4 +1,5 @@
import handleDisplayingLoadingSharingIconLoop from './handleDisplayingLoadingSharingIconLoop';
import LoadingSharingIconEnum from './LoadingSharingIconEnum';
jest.useFakeTimers();
@ -11,7 +12,7 @@ describe('handleDisplayingLoadingSharingIconLoop callback', () => {
url: undefined,
setIsShownLoadingSharingIcon: jest.fn(),
setLoadingSharingIconType: jest.fn(),
loadingSharingIconType: 'desktop',
loadingSharingIconType: LoadingSharingIconEnum.DESKTOP,
isShownLoadingSharingIcon: false,
} as handleDisplayingLoadingSharingIconLoopParams
});
@ -35,13 +36,13 @@ describe('handleDisplayingLoadingSharingIconLoop callback', () => {
jest.advanceTimersByTime(2000);
expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(false);
expect(testParams.setLoadingSharingIconType).toBeCalledWith('application');
expect(testParams.setLoadingSharingIconType).toBeCalledWith(LoadingSharingIconEnum.APPLICATION);
jest.clearAllMocks();
jest.advanceTimersByTime(2000);
expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(true);
expect(testParams.setLoadingSharingIconType).toBeCalledWith('desktop');
expect(testParams.setLoadingSharingIconType).toBeCalledWith(LoadingSharingIconEnum.DESKTOP);
jest.clearAllMocks();
// ... by this time we are sure, this function works!

View File

@ -1,6 +1,6 @@
export default (
params: handleDisplayingLoadingSharingIconLoopParams
) => {
import LoadingSharingIconEnum from './LoadingSharingIconEnum';
export default (params: handleDisplayingLoadingSharingIconLoopParams) => {
const {
promptStep,
url,
@ -21,7 +21,10 @@ export default (
isShownIcon = !isShownIcon;
setIsShownLoadingSharingIcon(isShownIcon);
if (isShownWithFadingUIEffect) {
currentIcon = currentIcon === 'desktop' ? 'application' : 'desktop';
currentIcon =
currentIcon === LoadingSharingIconEnum.DESKTOP
? LoadingSharingIconEnum.APPLICATION
: LoadingSharingIconEnum.DESKTOP;
setLoadingSharingIconType(currentIcon);
isShownWithFadingUIEffect = false;
} else {

View File

@ -15,6 +15,9 @@ import handleNoConnectionTimeout from './handleNoConnectionTimeout';
import handleCreatePeerConnection from './handleCreatePeerConnection';
import handleRemoveDanglingReactRevealContainer from './handleRemoveDanglingReactRevealContainer';
import handleDisplayingLoadingSharingIconLoop from './handleDisplayingLoadingSharingIconLoop';
import ScreenSharingSource from '../../features/PeerConnection/ScreenSharingSourceEnum';
import ConnectionIcon from './ConnectionIconEnum';
import LoadingSharingIconEnum from './LoadingSharingIconEnum';
function MainView() {
const { isDarkTheme, setIsDarkThemeHook } = useContext(AppContext);
@ -26,7 +29,7 @@ function MainView() {
);
const [connectionIconType, setConnectionIconType] = useState<
ConnectionIconType
>('feed');
>(ConnectionIcon.FEED);
const [myDeviceDetails, setMyDeviceDetails] = useState<DeviceDetails>(
DUMMY_MY_DEVICE_DETAILS
);
@ -36,7 +39,7 @@ function MainView() {
const [url, setUrl] = useState<undefined | MediaStream>(undefined);
const [screenSharingSourceType, setScreenSharingSourceType] = useState<
ScreenSharingSourceType
>('screen');
>(ScreenSharingSource.SCREEN);
const [isWithControls, setIsWithControls] = useState(!screenfull.isEnabled);
const [isShownTextPrompt, setIsShownTextPrompt] = useState(false);
const [isShownLoadingSharingIcon, setIsShownLoadingSharingIcon] = useState(
@ -44,7 +47,7 @@ function MainView() {
);
const [loadingSharingIconType, setLoadingSharingIconType] = useState<
LoadingSharingIconType
>('desktop');
>(LoadingSharingIconEnum.DESKTOP);
const [videoQuality, setVideoQuality] = useState<VideoQuality>(
VideoQuality.Q_AUTO
);

View File

@ -35,6 +35,7 @@ exports[`should match exact snapshot 1`] = `
<button
className="bp3-button bp3-minimal"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
tabIndex={0}
@ -57,7 +58,7 @@ exports[`should match exact snapshot 1`] = `
<img
alt="logo"
height={42}
src="deskreen_logo_128x128.png"
src="http://localhost/logo192.png"
width={42}
/>
</div>
@ -93,6 +94,7 @@ exports[`should match exact snapshot 1`] = `
<button
className="bp3-button"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
style={

View File

@ -1,6 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import PlayerView from '.';
import ScreenSharingSource from '../../features/PeerConnection/ScreenSharingSourceEnum';
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
jest.useFakeTimers();
@ -17,7 +18,7 @@ it('should match exact snapshot', () => {
isPlaying={false}
setVideoQuality={() => {}}
videoQuality={VideoQuality.Q_100_PERCENT}
screenSharingSourceType={'screen'}
screenSharingSourceType={ScreenSharingSource.SCREEN}
streamUrl={undefined}
/>
</>

View File

@ -0,0 +1 @@
type PeerConnection = import('.').default;

View File

@ -0,0 +1,6 @@
enum ScreenSharingSource {
WINDOW = 'window',
SCREEN = 'screen',
}
export default ScreenSharingSource;

View File

@ -96,12 +96,12 @@ export default class PeerConnection {
this.videoQualityChangedCallback();
}
videoQualityChangedCallback() {
if (!this.peer) return;
if (this.videoQuality === VideoQuality.Q_AUTO) {
this.peer?.send(prepareDataMessageToChangeQuality(1));
this.peer.send(prepareDataMessageToChangeQuality(1));
} else {
this.peer?.send(
this.peer.send(
prepareDataMessageToChangeQuality(
VIDEO_QUALITY_TO_DECIMAL[this.videoQuality]
)
@ -128,6 +128,10 @@ export default class PeerConnection {
});
this.peer = peer;
this.peer.on('error', (e) => {
console.error('error in simple peer happened!');
console.error(e);
});
peerConnectionHandlePeer(this);
}
@ -174,7 +178,7 @@ export default class PeerConnection {
}
prepareMessage(payload, this.user, this.partner).then((msg: any) => {
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
})
});
}
receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {

View File

@ -12,6 +12,7 @@ import {
prepareDataMessageToChangeQuality,
prepareDataMessageToGetSharingSourceType,
} from './simplePeerDataMessages';
import ScreenSharingSource from './ScreenSharingSourceEnum';
jest.useFakeTimers();
@ -180,6 +181,7 @@ describe('peerConnectionHandlePeer callback', () => {
peerConnectionHandlePeer(peerConnection);
peerConnection.peer?.emit('stream');
jest.advanceTimersByTime(2000);
peerConnection.videoAutoQualityOptimizer.goodQualityCallback();
expect(peerConnection.videoQuality).toBe(VideoQuality.Q_AUTO);
@ -206,6 +208,7 @@ describe('peerConnectionHandlePeer callback', () => {
peerConnectionHandlePeer(peerConnection);
peerConnection.peer?.emit('stream');
jest.advanceTimersByTime(2000);
peerConnection.videoAutoQualityOptimizer.halfQualityCallbak();
expect(peerConnection.videoQuality).toBe(VideoQuality.Q_AUTO);
@ -250,7 +253,7 @@ describe('peerConnectionHandlePeer callback', () => {
expect(
peerConnection.UIHandler.setScreenSharingSourceTypeCallback
).toBeCalledWith('screen');
).toBeCalledWith(ScreenSharingSource.SCREEN);
});
});
@ -265,7 +268,7 @@ describe('peerConnectionHandlePeer callback', () => {
expect(
peerConnection.UIHandler.setScreenSharingSourceTypeCallback
).toBeCalledWith('window');
).toBeCalledWith(ScreenSharingSource.WINDOW);
});
});

View File

@ -1,10 +1,10 @@
import PeerConnection from '.';
import {
prepareDataMessageToChangeQuality,
prepareDataMessageToGetSharingSourceType,
} from './simplePeerDataMessages';
import { VideoQuality } from '../VideoAutoQualityOptimizer/VideoQualityEnum';
import PeerConnectionPeerIsNullError from './errors/PeerConnectionPeerIsNullError';
import ScreenSharingSource from './ScreenSharingSourceEnum';
export function getSharingShourceType(peerConnection: PeerConnection) {
try {
@ -21,6 +21,7 @@ export default (peerConnection: PeerConnection) => {
peerConnection.peer.on('stream', (stream) => {
peerConnection.setUrlCallback(stream);
setTimeout(() => {
peerConnection.videoAutoQualityOptimizer.setGoodQualityCallback(() => {
if (peerConnection.videoQuality === VideoQuality.Q_AUTO) {
try {
@ -40,6 +41,7 @@ export default (peerConnection: PeerConnection) => {
}
}
});
}, 1000);
peerConnection.videoAutoQualityOptimizer.startOptimizationLoop();
@ -64,8 +66,8 @@ export default (peerConnection: PeerConnection) => {
if (dataJSON.type === 'screen_sharing_source_type') {
peerConnection.screenSharingSourceType = dataJSON.payload.value;
if (
peerConnection.screenSharingSourceType === 'screen' ||
peerConnection.screenSharingSourceType === 'window'
peerConnection.screenSharingSourceType === ScreenSharingSource.SCREEN ||
peerConnection.screenSharingSourceType === ScreenSharingSource.WINDOW
) {
peerConnection.UIHandler.setScreenSharingSourceTypeCallback(
peerConnection.screenSharingSourceType

View File

@ -110,6 +110,8 @@ describe('peerConnectionHandleSocket callback', () => {
peerConnection.socket.emit('connect');
jest.advanceTimersByTime(600);
expect(peerConnection.socket.emit).toBeCalledWith(
'GET_MY_IP',
expect.anything()

View File

@ -1,4 +1,3 @@
import PeerConnection from '.';
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
import {
getBrowserFromUAParser,
@ -39,9 +38,11 @@ export default (peerConnection: PeerConnection) => {
});
peerConnection.socket.on('connect', () => {
setTimeout(() => {
peerConnection.socket.emit('GET_MY_IP', (ip: string) => {
getMyIPCallback(peerConnection, ip, window.navigator.userAgent);
});
}, 500);
});
peerConnection.socket.on('NOT_ALLOWED', () => {

View File

@ -192,6 +192,7 @@ describe('peerConnectionReceiveEncryptedMessage', () => {
peerConnection,
CALL_USER_PAYLOAD
);
jest.advanceTimersByTime(2000);
expect(peerConnection.peer?.signal).toBeCalledWith('1signal');
});

View File

@ -1,4 +1,3 @@
import PeerConnection from '.';
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
import { process as processMessage } from '../../utils/message';
import NullUser from './NullUser';

View File

@ -1,4 +1,3 @@
import PeerConnection from '.';
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
export default (peerConnection: PeerConnection, errorMessage: ErrorMessage) => {

BIN
app/client/src/images/deskreen_logo_128x128.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,8 +1,14 @@
/// <reference types="react-scripts" />
type ConnectionIconType = 'feed' | 'feed-subscribed';
type LoadingSharingIconType = 'desktop' | 'application';
type ScreenSharingSourceType = 'screen' | 'window';
const ConnectionIconEnum = import('./containers/MainView/ConnectionIconEnum').default;
const LoadingSharingIconEnum = import('./containers/MainView/LoadingSharingIconEnum').default;
const ScreenSharingSourceEnum = import('./features/PeerConnection/ScreenSharingSourceEnum').default;
type ConnectionIconType = ConnectionIconEnum.FEED | ConnectionIconEnum.FEED_SUBSCRIBED;
type LoadingSharingIconType = LoadingSharingIconEnum.DESKTOP | LoadingSharingIconEnum.APPLICATION;
type ScreenSharingSourceType =
| ScreenSharingSourceEnum.SCREEN
| ScreenSharingSourceEnum.WINDOW;
type CreatePeerConnectionUseEffectParams = {
isDarkTheme: boolean;
peer: undefined | PeerConnection;

View File

@ -12,11 +12,7 @@ export default class Crypto {
createEncryptDecryptKeys() {
return new Promise<forge.pki.rsa.KeyPair>((resolve) => {
const keypair = forge.pki.rsa.generateKeyPair({
bits: 2048,
e: 0x10001,
workers: -1,
});
const keypair = forge.pki.rsa.generateKeyPair(1024);
resolve(keypair);
});
}

View File

@ -16,15 +16,15 @@ import { Row, Col } from 'react-flexbox-grid';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import CloseOverlayButton from './CloseOverlayButton';
import ConnectedDevicesService from '../features/ConnectedDevicesService';
import SharingSessionsService from '../features/SharingSessionsService';
import SharingSessionService from '../features/SharingSessionService';
import DeviceInfoCallout from './DeviceInfoCallout';
import SharingSourcePreviewCard from './SharingSourcePreviewCard';
import isWithReactRevealAnimations from '../utils/isWithReactRevealAnimations';
import isProduction from '../utils/isProduction';
const sharingSessionsService = remote.getGlobal(
const sharingSessionService = remote.getGlobal(
'sharingSessionService'
) as SharingSessionsService;
) as SharingSessionService;
const connectedDevicesService = remote.getGlobal(
'connectedDevicesService'
) as ConnectedDevicesService;
@ -34,12 +34,12 @@ const Fade = require('react-reveal/Fade');
const disconnectPeerAndDestroySharingSessionBySessionID = (
sharingSessionID: string
) => {
const sharingSession = sharingSessionsService.sharingSessions.get(
const sharingSession = sharingSessionService.sharingSessions.get(
sharingSessionID
);
sharingSession?.disconnectByHostMachineUser();
sharingSession?.destory();
sharingSessionsService.sharingSessions.delete(sharingSessionID);
sharingSession?.destroy();
sharingSessionService.sharingSessions.delete(sharingSessionID);
};
interface ConnectedDevicesListDrawerProps {
@ -206,7 +206,7 @@ export default function ConnectedDevicesListDrawer(
<Col xs={6}>
<SharingSourcePreviewCard
sharingSourceID={
sharingSessionsService.sharingSessions.get(
sharingSessionService.sharingSessions.get(
device.sharingSessionID
)?.desktopCapturerSourceID
}

View File

@ -1,14 +0,0 @@
/* .container {
position: absolute;
top: 30%;
left: 10px;
text-align: center;
} */
.container h2 {
font-size: 5rem;
}
.container a {
font-size: 1.4rem;
}

View File

@ -1,66 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable promise/always-return */
/* eslint-disable promise/catch-or-return */
/* eslint-disable no-async-promise-executor */
/* eslint-disable prettier/prettier */
/* eslint-disable react/jsx-one-expression-per-line */
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ipcRenderer } from 'electron';
import { Grid, Row, Col } from 'react-flexbox-grid';
import {
Button,
} from '@blueprintjs/core';
import routes from '../constants/routes.json';
import styles from './Home.css';
export default function Home(): JSX.Element {
const [signalingServerPort, setSignalingServerPort] = useState('0000');
const { t } = useTranslation();
// const { t, i18n } = useTranslation();
// Example of how to get signaling server port from main process in renderer process
// following this practice, you can also get local server ip address
useEffect(() => {
ipcRenderer.on('sending-port-from-main', (_, message) => {
// ipcRenderer.on('sending-port-from-main', (event, message) => {
setSignalingServerPort(`${message}`);
});
ipcRenderer.invoke('get-signaling-server-port');
}, []);
const onButtonClick = () => {
console.log(t('Language'));
};
return (
<Grid fluid>
<Row middle="xs" center="xs" style={{ height: '100vh' }}>
<Col xs={12} md={6}>
<div className={styles.container} data-tid="container">
<h2 className="bp3-heading">Home</h2>
<br />
<Link id="to-counter" to={routes.COUNTER}>
{' '}
<Button text="to Counter" />
</Link>
<br />
<h3 className="bp3-heading">
{`${t(
'Signaling server is running on port'
)}: ${signalingServerPort}`}
</h3>
<br />
<h3 className="bp3-heading">{`Locales test ${t('Language')}`}</h3>
<br />
<Button type="button" onClick={onButtonClick}>
CLICK ME!
</Button>
</div>
</Col>
</Row>
</Grid>
);
}

View File

@ -1,4 +1,7 @@
import { remote } from 'electron';
/* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { ipcRenderer, remote, shell } from 'electron';
import React, { useContext, useCallback, useEffect, useState } from 'react';
import {
Button,
@ -6,6 +9,7 @@ import {
Classes,
H3,
H6,
H4,
Tabs,
Tab,
Icon,
@ -15,10 +19,10 @@ import {
Checkbox,
} from '@blueprintjs/core';
import { useTranslation } from 'react-i18next';
import { Row } from 'react-flexbox-grid';
import { Col, Row } from 'react-flexbox-grid';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import i18n from 'i18next';
import SharingSessionsService from '../../features/SharingSessionsService';
import SharingSessionService from '../../features/SharingSessionService';
import {
DARK_UI_BACKGROUND,
LIGHT_UI_BACKGROUND,
@ -31,12 +35,15 @@ import i18n_client, {
} from '../../configs/i18next.config.client';
import SettingRowLabelAndInput from './SettingRowLabelAndInput';
import isWithReactRevealAnimations from '../../utils/isWithReactRevealAnimations';
import config from '../../api/config';
const { port } = config;
const Fade = require('react-reveal/Fade');
const sharingSessionsService = remote.getGlobal(
const sharingSessionService = remote.getGlobal(
'sharingSessionService'
) as SharingSessionsService;
) as SharingSessionService;
interface SettingsOverlayProps {
isSettingsOpen: boolean;
@ -63,6 +70,9 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
const { handleClose, isSettingsOpen } = props;
const [latestVersion, setLatestVersion] = useState('');
const [currentVersion, setCurrentVersion] = useState('');
const {
isDarkTheme,
setIsDarkThemeHook,
@ -71,6 +81,23 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
const [languagesList, setLanguagesList] = useState([] as string[]);
useEffect(() => {
const getLatestVersion = async () => {
const gotLatestVersion = await ipcRenderer.invoke('get-latest-version');
if (gotLatestVersion !== '') {
setLatestVersion(gotLatestVersion);
}
};
getLatestVersion();
const getCurrentVersion = async () => {
const gotCurrentVersion = await ipcRenderer.invoke('get-current-version');
if (gotCurrentVersion !== '') {
setCurrentVersion(gotCurrentVersion);
}
};
getCurrentVersion();
}, []);
useEffect(() => {
const tmp: string[] = [];
getLangFullNameToLangISOKeyMap().forEach((_, key) => {
@ -88,10 +115,10 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
setIsDarkThemeHook(true);
}
// TODO: call sharing sessions service here to notify all connected clients about theme change
sharingSessionsService.sharingSessions.forEach((sharingSession) => {
sharingSessionService.sharingSessions.forEach((sharingSession) => {
sharingSession?.appThemeChanged(true);
});
sharingSessionsService.setAppTheme(true);
sharingSessionService.setAppTheme(true);
}, [isDarkTheme, setIsDarkThemeHook]);
const handleToggleLightTheme = useCallback(() => {
@ -100,10 +127,10 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
setIsDarkThemeHook(false);
}
// TODO: call sharing sessions service here to notify all connected clients about theme change
sharingSessionsService.sharingSessions.forEach((sharingSession) => {
sharingSessionService.sharingSessions.forEach((sharingSession) => {
sharingSession?.appThemeChanged(false);
});
sharingSessionsService.setAppTheme(false);
sharingSessionService.setAppTheme(false);
}, [isDarkTheme, setIsDarkThemeHook]);
const onChangeLangueageHTMLSelectHandler = (
@ -118,10 +145,10 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
'English';
i18n.changeLanguage(newLang);
// TODO: call sharing sessions service here to notify all connected clients about language change
sharingSessionsService.sharingSessions.forEach((sharingSession) => {
sharingSessionService.sharingSessions.forEach((sharingSession) => {
sharingSession?.appLanguageChanged(newLang);
});
sharingSessionsService.setAppLanguage(newLang);
sharingSessionService.setAppLanguage(newLang);
}
};
@ -147,6 +174,7 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
const getLanguageChangingHTMLSelect = () => {
return (
<HTMLSelect
disabled // TODO: remove when tranlations are ready
defaultValue={getLangISOKeyToLangFullNameMap().get(i18n.language)}
options={languagesList}
onChange={onChangeLangueageHTMLSelectHandler}
@ -157,9 +185,9 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
const getAutomaticUpdatesCheckboxInput = () => {
return (
<Checkbox
checked
disabled
className={getClassesCallback().checkboxSettings}
label="Enabled"
label="Disabled"
/>
);
};
@ -209,6 +237,64 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
</div>
);
const AboutPanel: React.FC = () => (
<Row center="xs" middle="xs" style={{ height: 'calc(100vh - 40%)' }}>
<div>
<Col xs={12}>
<img
src={`http://127.0.0.1:${port}/logo512.png`}
alt="logo"
style={{ width: '100px' }}
/>
</Col>
<Col xs={12}>
<H3>About Deskreen</H3>
</Col>
<Col xs={12}>
<Text>{`Version: ${currentVersion} (${currentVersion})`}</Text>
</Col>
<Col xs={12}>
<Text>
{`Copyright © ${new Date().getFullYear()} `}
<a
onClick={() => {
shell.openExternal('https://linkedin.com/in/pavlobu');
}}
style={
isDarkTheme
? {}
: {
color: 'blue',
}
}
>
Pavlo (Paul) Buidenkov
</a>
</Text>
</Col>
<Col xs={12}>
<Text>
{`Website: `}
<a
onClick={() => {
shell.openExternal('https://www.deskreen.com');
}}
style={
isDarkTheme
? {}
: {
color: 'blue',
}
}
>
https://www.deskreen.com
</a>
</Text>
</Col>
</div>
</Row>
);
const getTabNavBlockedIPsButton = () => {
return (
<Row middle="xs" className={getClassesCallback().tabNavigationRowButton}>
@ -245,6 +331,18 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
);
};
const getTabNavAboutButton = () => {
return (
<Row middle="xs" className={getClassesCallback().tabNavigationRowButton}>
<Icon
icon="info-sign"
className={getClassesCallback().iconInTablLeftButton}
/>
<Text className="bp3-text-large">About</Text>
</Row>
);
};
return (
<Overlay
onClose={handleClose}
@ -271,6 +369,24 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
onClick={handleClose}
isDefaultStyles
/>
{latestVersion !== '' &&
currentVersion !== '' &&
latestVersion !== currentVersion ? (
<H4
id="new-version-header"
onClick={(e) => {
e.preventDefault();
shell.openExternal(
'https://github.com/pavlobu/deskreen/releases/'
);
}}
>
{/* eslint-disable-next-line react/jsx-one-expression-per-line */}
New version {latestVersion} is available! Click to download.
</H4>
) : (
<></>
)}
<Tabs
animate
id="TabsExample"
@ -281,12 +397,15 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
<Tab id="rx" title="" panel={<GeneralSettingsPanel />}>
{getTabNavGeneralSettingsButton()}
</Tab>
<Tab id="ng" title="" panel={<SecurityPanel />}>
<Tab id="ng" disabled title="" panel={<SecurityPanel />}>
{getTabNavSecurityButton()}
</Tab>
<Tab id="bb" disabled title="" panel={<BlockedIPsPanel />}>
{getTabNavBlockedIPsButton()}
</Tab>
<Tab id="cc" title="" panel={<AboutPanel />}>
{getTabNavAboutButton()}
</Tab>
<Tabs.Expander />
</Tabs>
</div>

View File

@ -161,14 +161,13 @@ exports[`should match exact snapshot 1`] = `
</div>
<div
aria-controls="bp3-tab-panel_TabsExample_ng"
aria-disabled="false"
aria-disabled="true"
aria-expanded="false"
aria-selected="false"
class="bp3-tab"
data-tab-id="ng"
id="bp3-tab-title_TabsExample_ng"
role="tab"
tabindex="0"
>
<div
@ -240,6 +239,47 @@ exports[`should match exact snapshot 1`] = `
</div>
</div>
</div>
<div
aria-controls="bp3-tab-panel_TabsExample_cc"
aria-disabled="false"
aria-expanded="false"
aria-selected="false"
class="bp3-tab"
data-tab-id="cc"
id="bp3-tab-title_TabsExample_cc"
role="tab"
tabindex="0"
>
<div
class="makeStyles-tabNavigationRowButton-24 row middle-xs"
>
<span
class="bp3-icon bp3-icon-info-sign makeStyles-iconInTablLeftButton-25"
icon="info-sign"
>
<svg
data-icon="info-sign"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
info-sign
</desc>
<path
d="M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zM7 3h2v2H7V3zm3 10H6v-1h1V7H6V6h3v6h1v1z"
fill-rule="evenodd"
/>
</svg>
</span>
<div
class="bp3-text-large"
>
About
</div>
</div>
</div>
<div
class="bp3-flex-expander"
/>
@ -424,9 +464,11 @@ exports[`should match exact snapshot 1`] = `
class="row"
>
<div
class="bp3-html-select"
class="bp3-html-select bp3-disabled"
>
<select
disabled=""
>
<select>
<option
value="English"
>
@ -516,16 +558,16 @@ exports[`should match exact snapshot 1`] = `
class="row"
>
<label
class="bp3-control bp3-checkbox makeStyles-checkboxSettings-20"
class="bp3-control bp3-checkbox bp3-disabled makeStyles-checkboxSettings-20"
>
<input
checked=""
disabled=""
type="checkbox"
/>
<span
class="bp3-control-indicator"
/>
Enabled
Disabled
</label>
</div>
</div>
@ -646,14 +688,13 @@ exports[`should match exact snapshot 1`] = `
</div>
<div
aria-controls="bp3-tab-panel_TabsExample_ng"
aria-disabled="false"
aria-disabled="true"
aria-expanded="false"
aria-selected="false"
class="bp3-tab"
data-tab-id="ng"
id="bp3-tab-title_TabsExample_ng"
role="tab"
tabindex="0"
>
<div
@ -725,6 +766,47 @@ exports[`should match exact snapshot 1`] = `
</div>
</div>
</div>
<div
aria-controls="bp3-tab-panel_TabsExample_cc"
aria-disabled="false"
aria-expanded="false"
aria-selected="false"
class="bp3-tab"
data-tab-id="cc"
id="bp3-tab-title_TabsExample_cc"
role="tab"
tabindex="0"
>
<div
class="makeStyles-tabNavigationRowButton-24 row middle-xs"
>
<span
class="bp3-icon bp3-icon-info-sign makeStyles-iconInTablLeftButton-25"
icon="info-sign"
>
<svg
data-icon="info-sign"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
info-sign
</desc>
<path
d="M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zM7 3h2v2H7V3zm3 10H6v-1h1V7H6V6h3v6h1v1z"
fill-rule="evenodd"
/>
</svg>
</span>
<div
class="bp3-text-large"
>
About
</div>
</div>
</div>
<div
class="bp3-flex-expander"
/>
@ -909,9 +991,11 @@ exports[`should match exact snapshot 1`] = `
class="row"
>
<div
class="bp3-html-select"
class="bp3-html-select bp3-disabled"
>
<select
disabled=""
>
<select>
<option
value="English"
>
@ -1001,16 +1085,16 @@ exports[`should match exact snapshot 1`] = `
class="row"
>
<label
class="bp3-control bp3-checkbox makeStyles-checkboxSettings-20"
class="bp3-control bp3-checkbox bp3-disabled makeStyles-checkboxSettings-20"
>
<input
checked=""
disabled=""
type="checkbox"
/>
<span
class="bp3-control-indicator"
/>
Enabled
Disabled
</label>
</div>
</div>
@ -1294,7 +1378,7 @@ exports[`should match exact snapshot 1`] = `
</div>
</Blueprint3.TabTitle>
<Blueprint3.TabTitle
disabled={false}
disabled={true}
id="ng"
key=".1"
onClick={[Function]}
@ -1305,15 +1389,13 @@ exports[`should match exact snapshot 1`] = `
>
<div
aria-controls="bp3-tab-panel_TabsExample_ng"
aria-disabled={false}
aria-disabled={true}
aria-expanded={false}
aria-selected={false}
className="bp3-tab"
data-tab-id="ng"
id="bp3-tab-title_TabsExample_ng"
onClick={[Function]}
role="tab"
tabIndex={0}
>
<Row
className="makeStyles-tabNavigationRowButton-24"
@ -1429,8 +1511,77 @@ exports[`should match exact snapshot 1`] = `
</Row>
</div>
</Blueprint3.TabTitle>
<Component
<Blueprint3.TabTitle
disabled={false}
id="cc"
key=".3"
onClick={[Function]}
panel={<AboutPanel />}
parentId="TabsExample"
selected={false}
title=""
>
<div
aria-controls="bp3-tab-panel_TabsExample_cc"
aria-disabled={false}
aria-expanded={false}
aria-selected={false}
className="bp3-tab"
data-tab-id="cc"
id="bp3-tab-title_TabsExample_cc"
onClick={[Function]}
role="tab"
tabIndex={0}
>
<Row
className="makeStyles-tabNavigationRowButton-24"
middle="xs"
>
<div
className="makeStyles-tabNavigationRowButton-24 row middle-xs"
>
<Blueprint3.Icon
className="makeStyles-iconInTablLeftButton-25"
icon="info-sign"
>
<span
className="bp3-icon bp3-icon-info-sign makeStyles-iconInTablLeftButton-25"
icon="info-sign"
>
<svg
data-icon="info-sign"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
info-sign
</desc>
<path
d="M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zM7 3h2v2H7V3zm3 10H6v-1h1V7H6V6h3v6h1v1z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<Blueprint3.Text
className="bp3-text-large"
ellipsize={false}
tagName="div"
>
<div
className="bp3-text-large"
>
About
</div>
</Blueprint3.Text>
</div>
</Row>
</div>
</Blueprint3.TabTitle>
<Component
key=".4"
>
<div
className="bp3-flex-expander"
@ -1688,6 +1839,7 @@ exports[`should match exact snapshot 1`] = `
icon="translate"
input={
<HTMLSelect
disabled={true}
onChange={[Function]}
options={
Array [
@ -1783,6 +1935,7 @@ exports[`should match exact snapshot 1`] = `
className="row"
>
<HTMLSelect
disabled={true}
onChange={[Function]}
options={
Array [
@ -1793,9 +1946,10 @@ exports[`should match exact snapshot 1`] = `
}
>
<div
className="bp3-html-select"
className="bp3-html-select bp3-disabled"
>
<select
disabled={true}
multiple={false}
onChange={[Function]}
>
@ -1855,9 +2009,9 @@ exports[`should match exact snapshot 1`] = `
icon="automatic-updates"
input={
<Blueprint3.Checkbox
checked={true}
className="makeStyles-checkboxSettings-20"
label="Enabled"
disabled={true}
label="Disabled"
/>
}
label="Automatic Updates"
@ -1945,31 +2099,31 @@ exports[`should match exact snapshot 1`] = `
className="row"
>
<Blueprint3.Checkbox
checked={true}
className="makeStyles-checkboxSettings-20"
label="Enabled"
disabled={true}
label="Disabled"
>
<Control
checked={true}
className="makeStyles-checkboxSettings-20"
disabled={true}
inputRef={[Function]}
label="Enabled"
label="Disabled"
onChange={[Function]}
type="checkbox"
typeClassName="bp3-checkbox"
>
<label
className="bp3-control bp3-checkbox makeStyles-checkboxSettings-20"
className="bp3-control bp3-checkbox bp3-disabled makeStyles-checkboxSettings-20"
>
<input
checked={true}
disabled={true}
onChange={[Function]}
type="checkbox"
/>
<span
className="bp3-control-indicator"
/>
Enabled
Disabled
</label>
</Control>
</Blueprint3.Checkbox>

View File

@ -1,6 +1,6 @@
import { remote } from 'electron';
import React, { useEffect, useState } from 'react';
import { H3, Card, Dialog } from '@blueprintjs/core';
import React, { useCallback, useEffect, useState } from 'react';
import { H3, Card, Dialog, Button } from '@blueprintjs/core';
import { Row, Col } from 'react-flexbox-grid';
import { createStyles, makeStyles } from '@material-ui/core/styles';
import CloseOverlayButton from '../../CloseOverlayButton';
@ -69,7 +69,7 @@ export default function ChooseAppOrScreenOverlay(
setAppsWindowsViewSharingObjectsMap,
] = useState<Map<string, ViewSharingObject>>(new Map());
useEffect(() => {
const handleRefreshSources = useCallback(() => {
if (isEntireScreenToShareChosen) {
const sourcesToShare = desktopCapturerSourcesService.getScreenSources();
const screenViewMap = new Map<string, ViewSharingObject>();
@ -93,6 +93,10 @@ export default function ChooseAppOrScreenOverlay(
}
}, [isEntireScreenToShareChosen]);
useEffect(() => {
handleRefreshSources();
}, [handleRefreshSources, isEntireScreenToShareChosen]);
return (
<Dialog
onClose={handleClose}
@ -140,7 +144,7 @@ export default function ChooseAppOrScreenOverlay(
width: '100%',
}}
>
<Col xs={11}>
<Col xs={9}>
{isEntireScreenToShareChosen ? (
<div>
<H3 style={{ marginBottom: '0px' }}>
@ -155,6 +159,18 @@ export default function ChooseAppOrScreenOverlay(
</div>
)}
</Col>
<Col xs={2}>
<Button
icon="refresh"
intent="warning"
onClick={handleRefreshSources}
style={{
borderRadius: '100px',
}}
>
Refresh
</Button>
</Col>
<Col xs={1}>
<CloseOverlayButton

View File

@ -1,7 +1,7 @@
import { remote } from 'electron';
import React, { useEffect, useState } from 'react';
import { Row, Col } from 'react-flexbox-grid';
import SharingSessionService from '../../../features/SharingSessionsService';
import SharingSessionService from '../../../features/SharingSessionService';
import SharingSourcePreviewCard from '../../SharingSourcePreviewCard';
const sharingSessionService = remote.getGlobal(

View File

@ -97,7 +97,7 @@ exports[`should match exact snapshot 1`] = `
style="width: 100%;"
>
<div
class="col-xs-11"
class="col-xs-9"
>
<div>
<h3
@ -108,6 +108,40 @@ exports[`should match exact snapshot 1`] = `
</h3>
</div>
</div>
<div
class="col-xs-2"
>
<button
class="bp3-button bp3-intent-warning"
style="border-radius: 100px;"
type="button"
>
<span
class="bp3-icon bp3-icon-refresh"
icon="refresh"
>
<svg
data-icon="refresh"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
refresh
</desc>
<path
d="M14.99 6.99c-.55 0-1 .45-1 1 0 3.31-2.69 6-6 6-1.77 0-3.36-.78-4.46-2h1.46c.55 0 1-.45 1-1s-.45-1-1-1h-4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1v-1.74a7.95 7.95 0 006 2.74c4.42 0 8-3.58 8-8 0-.55-.45-1-1-1zm0-7c-.55 0-1 .45-1 1v1.74a7.95 7.95 0 00-6-2.74c-4.42 0-8 3.58-8 8 0 .55.45 1 1 1s1-.45 1-1c0-3.31 2.69-6 6-6 1.77 0 3.36.78 4.46 2h-1.46c-.55 0-1 .45-1 1s.45 1 1 1h4c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
Refresh
</span>
</button>
</div>
<div
class="col-xs-1"
>
@ -210,7 +244,7 @@ exports[`should match exact snapshot 1`] = `
style="width: 100%;"
>
<div
class="col-xs-11"
class="col-xs-9"
>
<div>
<h3
@ -221,6 +255,40 @@ exports[`should match exact snapshot 1`] = `
</h3>
</div>
</div>
<div
class="col-xs-2"
>
<button
class="bp3-button bp3-intent-warning"
style="border-radius: 100px;"
type="button"
>
<span
class="bp3-icon bp3-icon-refresh"
icon="refresh"
>
<svg
data-icon="refresh"
height="16"
viewBox="0 0 16 16"
width="16"
>
<desc>
refresh
</desc>
<path
d="M14.99 6.99c-.55 0-1 .45-1 1 0 3.31-2.69 6-6 6-1.77 0-3.36-.78-4.46-2h1.46c.55 0 1-.45 1-1s-.45-1-1-1h-4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1v-1.74a7.95 7.95 0 006 2.74c4.42 0 8-3.58 8-8 0-.55-.45-1-1-1zm0-7c-.55 0-1 .45-1 1v1.74a7.95 7.95 0 00-6-2.74c-4.42 0-8 3.58-8 8 0 .55.45 1 1 1s1-.45 1-1c0-3.31 2.69-6 6-6 1.77 0 3.36.78 4.46 2h-1.46c-.55 0-1 .45-1 1s.45 1 1 1h4c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1z"
fill-rule="evenodd"
/>
</svg>
</span>
<span
class="bp3-button-text"
>
Refresh
</span>
</button>
</div>
<div
class="col-xs-1"
>
@ -458,10 +526,10 @@ exports[`should match exact snapshot 1`] = `
}
>
<Col
xs={11}
xs={9}
>
<div
className="col-xs-11"
className="col-xs-9"
>
<div>
<Component
@ -485,6 +553,73 @@ exports[`should match exact snapshot 1`] = `
</div>
</div>
</Col>
<Col
xs={2}
>
<div
className="col-xs-2"
>
<Blueprint3.Button
icon="refresh"
intent="warning"
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
>
<button
className="bp3-button bp3-intent-warning"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
type="button"
>
<Blueprint3.Icon
icon="refresh"
key="leftIcon"
>
<span
className="bp3-icon bp3-icon-refresh"
icon="refresh"
>
<svg
data-icon="refresh"
height={16}
viewBox="0 0 16 16"
width={16}
>
<desc>
refresh
</desc>
<path
d="M14.99 6.99c-.55 0-1 .45-1 1 0 3.31-2.69 6-6 6-1.77 0-3.36-.78-4.46-2h1.46c.55 0 1-.45 1-1s-.45-1-1-1h-4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1v-1.74a7.95 7.95 0 006 2.74c4.42 0 8-3.58 8-8 0-.55-.45-1-1-1zm0-7c-.55 0-1 .45-1 1v1.74a7.95 7.95 0 00-6-2.74c-4.42 0-8 3.58-8 8 0 .55.45 1 1 1s1-.45 1-1c0-3.31 2.69-6 6-6 1.77 0 3.36.78 4.46 2h-1.46c-.55 0-1 .45-1 1s.45 1 1 1h4c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1z"
fillRule="evenodd"
key="0"
/>
</svg>
</span>
</Blueprint3.Icon>
<span
className="bp3-button-text"
key="text"
>
Refresh
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</div>
</Col>
<Col
xs={1}
>

View File

@ -3,9 +3,9 @@ import { remote } from 'electron';
import { Text } from '@blueprintjs/core';
import { Row, Col } from 'react-flexbox-grid';
import SharingSourcePreviewCard from '../SharingSourcePreviewCard';
import SharingSessionService from '../../features/SharingSessionsService';
import SharingSessionService from '../../features/SharingSessionService';
import DeviceInfoCallout from '../DeviceInfoCallout';
import SharingSession from '../../features/SharingSessionsService/SharingSession';
import SharingSession from '../../features/SharingSessionService/SharingSession';
const sharingSessionService = remote.getGlobal(
'sharingSessionService'

View File

@ -7,7 +7,7 @@ import ScanQRStep from './ScanQRStep';
import ChooseAppOrScreeenStep from './ChooseAppOrScreeenStep';
import ConfirmStep from './ConfirmStep';
import ConnectedDevicesService from '../../features/ConnectedDevicesService';
import SharingSessionService from '../../features/SharingSessionsService';
import SharingSessionService from '../../features/SharingSessionService';
const sharingSessionService = remote.getGlobal(
'sharingSessionService'

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { clipboard, remote } from 'electron';
import { clipboard, remote, ipcRenderer } from 'electron';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -15,19 +15,17 @@ import { makeStyles, createStyles } from '@material-ui/core';
import { Row, Col } from 'react-flexbox-grid';
import { SettingsContext } from '../../containers/SettingsProvider';
import isProduction from '../../utils/isProduction';
import SharingSessionService from '../../features/SharingSessionsService';
import SharingSessionService from '../../features/SharingSessionService';
import config from '../../api/config';
const { port } = config;
const sharingSessionService = remote.getGlobal(
'sharingSessionService'
) as SharingSessionService;
const LOCAL_LAN_IP =
process.env.RUN_MODE === 'dev' || process.env.NODE_ENV === 'production'
? require('internal-ip').v4.sync()
: '255.255.255.255';
// TODO: change 3131 to user defined port, if it is really defined
const CLIENT_VIEWER_PORT = isProduction() ? '3131' : '3000';
// TODO: change port to user defined port, if it is really defined
const CLIENT_VIEWER_PORT = isProduction() ? port : '3000';
const useStyles = makeStyles(() =>
createStyles({
@ -64,6 +62,7 @@ const ScanQRStep: React.FC = () => {
const { isDarkTheme } = useContext(SettingsContext);
const [roomID, setRoomID] = useState('');
const [LOCAL_LAN_IP, setLocalLanIP] = useState('');
useEffect(() => {
const thisInterval = setInterval(() => {
@ -74,8 +73,16 @@ const ScanQRStep: React.FC = () => {
}
}, 1000);
const ipInterval = setInterval(async () => {
const gotIP = await ipcRenderer.invoke('get-local-lan-ip');
if (gotIP) {
setLocalLanIP(gotIP);
}
}, 1000);
return () => {
clearInterval(thisInterval);
clearInterval(ipInterval);
};
}, []);
@ -94,7 +101,7 @@ const ScanQRStep: React.FC = () => {
borderRadius: '20px',
}}
>
make sure your computer and device are connected to same WiFi
Make sure your computer and device are connected to same WiFi
</span>
</Text>
<Text className="bp3-text">{t('Scan the QR code')}</Text>
@ -114,8 +121,7 @@ const ScanQRStep: React.FC = () => {
fgColor={isDarkTheme ? '#ffffff' : '#000000'}
imageSettings={{
// TODO: change image to app icon
src:
'https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png',
src: `http://127.0.0.1:${CLIENT_VIEWER_PORT}/logo192.png`,
width: 40,
height: 40,
}}
@ -171,8 +177,7 @@ const ScanQRStep: React.FC = () => {
renderAs="svg"
imageSettings={{
// TODO: change image to app icon
src:
'https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png',
src: `http://127.0.0.1:${CLIENT_VIEWER_PORT}/logo192.png`,
width: 25,
height: 25,
}}

View File

@ -92,7 +92,7 @@ exports[`should match exact snapshot on each step 1`] = `
}
}
>
make sure your computer and device are connected to same WiFi
Make sure your computer and device are connected to same WiFi
</span>
</div>
</Blueprint3.Text>
@ -198,7 +198,7 @@ exports[`should match exact snapshot on each step 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
@ -206,7 +206,7 @@ exports[`should match exact snapshot on each step 1`] = `
level="H"
renderAs="svg"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
>
<QRCodeSVG
bgColor="rgba(0,0,0,0.0)"
@ -214,36 +214,36 @@ exports[`should match exact snapshot on each step 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
includeMargin={false}
level="H"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
>
<svg
height={128}
shapeRendering="crispEdges"
viewBox="0 0 33 33"
viewBox="0 0 25 25"
width={128}
>
<path
d="M0,0 h33v33H0z"
d="M0,0 h25v25H0z"
fill="rgba(0,0,0,0.0)"
/>
<path
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h2v1H12zM15 0h2v1H15zM20 0h1v1H20zM23 0h2v1H23zM26,0 h7v1H26zM0 1h1v1H0zM6 1h1v1H6zM8 1h2v1H8zM12 1h1v1H12zM15 1h4v1H15zM22 1h1v1H22zM26 1h1v1H26zM32,1 h1v1H32zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h1v1H8zM10 2h1v1H10zM12 2h1v1H12zM14 2h1v1H14zM17 2h2v1H17zM20 2h2v1H20zM23 2h1v1H23zM26 2h1v1H26zM28 2h3v1H28zM32,2 h1v1H32zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h1v1H9zM11 3h1v1H11zM14 3h1v1H14zM16 3h3v1H16zM23 3h1v1H23zM26 3h1v1H26zM28 3h3v1H28zM32,3 h1v1H32zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM9 4h1v1H9zM11 4h1v1H11zM15 4h3v1H15zM20 4h3v1H20zM24 4h1v1H24zM26 4h1v1H26zM28 4h3v1H28zM32,4 h1v1H32zM0 5h1v1H0zM6 5h1v1H6zM8 5h2v1H8zM11 5h4v1H11zM19 5h1v1H19zM26 5h1v1H26zM32,5 h1v1H32zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22 6h1v1H22zM24 6h1v1H24zM26,6 h7v1H26zM8 7h3v1H8zM12 7h3v1H12zM17 7h1v1H17zM19 7h4v1H19zM24 7h1v1H24zM2 8h3v1H2zM6 8h1v1H6zM8 8h4v1H8zM13 8h2v1H13zM19 8h1v1H19zM25 8h3v1H25zM30,8 h3v1H30zM0 9h1v1H0zM2 9h1v1H2zM8 9h1v1H8zM10 9h2v1H10zM14 9h1v1H14zM17 9h2v1H17zM20 9h1v1H20zM22 9h5v1H22zM29,9 h4v1H29zM0 10h4v1H0zM5 10h2v1H5zM8 10h4v1H8zM13 10h1v1H13zM16 10h5v1H16zM22 10h1v1H22zM24 10h1v1H24zM27 10h1v1H27zM29 10h2v1H29zM1 11h1v1H1zM8 11h2v1H8zM11 11h3v1H11zM16 11h1v1H16zM18 11h1v1H18zM20 11h1v1H20zM23 11h2v1H23zM26 11h1v1H26zM30 11h2v1H30zM1 12h1v1H1zM6 12h1v1H6zM8 12h2v1H8zM12 12h2v1H12zM15 12h2v1H15zM18 12h1v1H18zM20 12h5v1H20zM27 12h1v1H27zM31 12h1v1H31zM4 13h2v1H4zM7 13h1v1H7zM9 13h1v1H9zM13 13h2v1H13zM17 13h1v1H17zM19 13h1v1H19zM23 13h4v1H23zM28 13h1v1H28zM30 13h1v1H30zM32,13 h1v1H32zM0 14h3v1H0zM6 14h3v1H6zM10 14h1v1H10zM12 14h1v1H12zM16 14h2v1H16zM19 14h1v1H19zM22 14h1v1H22zM25 14h1v1H25zM27 14h2v1H27zM30 14h2v1H30zM0 15h6v1H0zM8 15h2v1H8zM12 15h4v1H12zM17 15h1v1H17zM19 15h4v1H19zM25 15h1v1H25zM28 15h1v1H28zM30 15h1v1H30zM0 16h4v1H0zM6 16h3v1H6zM10 16h1v1H10zM13 16h1v1H13zM15 16h3v1H15zM19 16h2v1H19zM24 16h1v1H24zM28 16h1v1H28zM31,16 h2v1H31zM1 17h3v1H1zM9 17h1v1H9zM11 17h2v1H11zM15 17h2v1H15zM20 17h8v1H20zM31,17 h2v1H31zM2 18h2v1H2zM5 18h3v1H5zM9 18h1v1H9zM11 18h2v1H11zM14 18h2v1H14zM18 18h3v1H18zM24 18h1v1H24zM27 18h2v1H27zM30 18h1v1H30zM1 19h2v1H1zM4 19h1v1H4zM8 19h3v1H8zM12 19h3v1H12zM17 19h1v1H17zM19 19h1v1H19zM21 19h1v1H21zM23 19h3v1H23zM27 19h1v1H27zM29 19h2v1H29zM0 20h1v1H0zM3 20h1v1H3zM5 20h2v1H5zM8 20h3v1H8zM12 20h5v1H12zM18 20h1v1H18zM20 20h1v1H20zM23 20h2v1H23zM28 20h2v1H28zM0 21h1v1H0zM2 21h2v1H2zM5 21h1v1H5zM8 21h2v1H8zM11 21h3v1H11zM15 21h2v1H15zM18 21h1v1H18zM21 21h2v1H21zM25 21h1v1H25zM27 21h1v1H27zM29,21 h4v1H29zM0 22h1v1H0zM4 22h3v1H4zM8 22h1v1H8zM11 22h4v1H11zM16 22h2v1H16zM19 22h1v1H19zM21 22h1v1H21zM23 22h2v1H23zM27 22h4v1H27zM0 23h1v1H0zM2 23h1v1H2zM4 23h1v1H4zM7 23h1v1H7zM9 23h1v1H9zM11 23h2v1H11zM14 23h2v1H14zM19 23h3v1H19zM23 23h1v1H23zM28 23h1v1H28zM30 23h2v1H30zM0 24h1v1H0zM2 24h1v1H2zM6 24h2v1H6zM9 24h2v1H9zM13 24h1v1H13zM15 24h2v1H15zM20 24h1v1H20zM23 24h6v1H23zM31 24h1v1H31zM8 25h3v1H8zM13 25h1v1H13zM15 25h3v1H15zM21 25h4v1H21zM28 25h2v1H28zM31,25 h2v1H31zM0 26h7v1H0zM10 26h1v1H10zM12 26h2v1H12zM15 26h1v1H15zM18 26h1v1H18zM20 26h2v1H20zM23 26h2v1H23zM26 26h1v1H26zM28 26h1v1H28zM0 27h1v1H0zM6 27h1v1H6zM9 27h1v1H9zM11 27h1v1H11zM15 27h1v1H15zM18 27h1v1H18zM20 27h3v1H20zM24 27h1v1H24zM28 27h1v1H28zM30 27h1v1H30zM32,27 h1v1H32zM0 28h1v1H0zM2 28h3v1H2zM6 28h1v1H6zM8 28h1v1H8zM11 28h1v1H11zM14 28h1v1H14zM17 28h2v1H17zM20 28h2v1H20zM24 28h6v1H24zM0 29h1v1H0zM2 29h3v1H2zM6 29h1v1H6zM8 29h3v1H8zM13 29h1v1H13zM15 29h2v1H15zM18 29h2v1H18zM21 29h4v1H21zM28 29h2v1H28zM31 29h1v1H31zM0 30h1v1H0zM2 30h3v1H2zM6 30h1v1H6zM8 30h1v1H8zM10 30h1v1H10zM12 30h2v1H12zM17 30h1v1H17zM20 30h2v1H20zM23 30h4v1H23zM29 30h1v1H29zM0 31h1v1H0zM6 31h1v1H6zM9 31h1v1H9zM13 31h2v1H13zM16 31h2v1H16zM20 31h4v1H20zM25 31h1v1H25zM27 31h1v1H27zM30 31h1v1H30zM0 32h7v1H0zM9 32h2v1H9zM12 32h1v1H12zM15 32h1v1H15zM17 32h6v1H17zM24 32h2v1H24zM30 32h2v1H30z"
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h1v1H12zM14 0h2v1H14zM18,0 h7v1H18zM0 1h1v1H0zM6 1h1v1H6zM13 1h1v1H13zM15 1h2v1H15zM18 1h1v1H18zM24,1 h1v1H24zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h3v1H8zM12 2h1v1H12zM14 2h2v1H14zM18 2h1v1H18zM20 2h3v1H20zM24,2 h1v1H24zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h4v1H9zM16 3h1v1H16zM18 3h1v1H18zM20 3h3v1H20zM24,3 h1v1H24zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM8 4h3v1H8zM15 4h1v1H15zM18 4h1v1H18zM20 4h3v1H20zM24,4 h1v1H24zM0 5h1v1H0zM6 5h1v1H6zM9 5h1v1H9zM11 5h1v1H11zM13 5h1v1H13zM18 5h1v1H18zM24,5 h1v1H24zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18,6 h7v1H18zM8 7h2v1H8zM12 7h4v1H12zM5 8h2v1H5zM10 8h5v1H10zM18 8h1v1H18zM20 8h1v1H20zM22 8h1v1H22zM24,8 h1v1H24zM1 9h1v1H1zM3 9h2v1H3zM7 9h2v1H7zM11 9h1v1H11zM13 9h1v1H13zM16 9h2v1H16zM20 9h4v1H20zM3 10h4v1H3zM11 10h4v1H11zM16 10h1v1H16zM18 10h4v1H18zM23,10 h2v1H23zM2 11h1v1H2zM8 11h3v1H8zM12 11h1v1H12zM14 11h3v1H14zM18 11h1v1H18zM21 11h1v1H21zM24,11 h1v1H24zM0 12h2v1H0zM6 12h1v1H6zM8 12h1v1H8zM10 12h3v1H10zM16 12h3v1H16zM24,12 h1v1H24zM0 13h1v1H0zM4 13h1v1H4zM7 13h1v1H7zM11 13h1v1H11zM14 13h1v1H14zM16 13h2v1H16zM23 13h1v1H23zM0 14h1v1H0zM6 14h3v1H6zM11 14h1v1H11zM13 14h2v1H13zM18 14h1v1H18zM20 14h2v1H20zM23,14 h2v1H23zM0 15h1v1H0zM2 15h1v1H2zM5 15h1v1H5zM8 15h4v1H8zM13 15h1v1H13zM15 15h1v1H15zM18 15h1v1H18zM22 15h1v1H22zM24,15 h1v1H24zM0 16h1v1H0zM2 16h1v1H2zM5 16h3v1H5zM10 16h1v1H10zM12 16h1v1H12zM14 16h1v1H14zM16 16h5v1H16zM22 16h1v1H22zM8 17h1v1H8zM10 17h2v1H10zM14 17h1v1H14zM16 17h1v1H16zM20 17h1v1H20zM22 17h1v1H22zM0 18h7v1H0zM14 18h3v1H14zM18 18h1v1H18zM20 18h2v1H20zM24,18 h1v1H24zM0 19h1v1H0zM6 19h1v1H6zM8 19h1v1H8zM10 19h2v1H10zM14 19h1v1H14zM16 19h1v1H16zM20 19h1v1H20zM23,19 h2v1H23zM0 20h1v1H0zM2 20h3v1H2zM6 20h1v1H6zM9 20h3v1H9zM15 20h9v1H15zM0 21h1v1H0zM2 21h3v1H2zM6 21h1v1H6zM12 21h4v1H12zM18 21h2v1H18zM21 21h1v1H21zM24,21 h1v1H24zM0 22h1v1H0zM2 22h3v1H2zM6 22h1v1H6zM9 22h1v1H9zM13 22h1v1H13zM16 22h2v1H16zM22 22h1v1H22zM24,22 h1v1H24zM0 23h1v1H0zM6 23h1v1H6zM10 23h1v1H10zM12 23h2v1H12zM16 23h1v1H16zM18 23h3v1H18zM24,23 h1v1H24zM0 24h7v1H0zM12 24h2v1H12zM15 24h4v1H15zM21 24h1v1H21zM24,24 h1v1H24z"
fill="#000000"
/>
<image
height={10.3125}
height={7.8125}
preserveAspectRatio="none"
width={10.3125}
x={11.34375}
xlinkHref="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png"
y={11.34375}
width={7.8125}
x={8.59375}
xlinkHref="http://127.0.0.1:3000/logo192.png"
y={8.59375}
/>
</svg>
</QRCodeSVG>
@ -414,7 +414,7 @@ exports[`should match exact snapshot on each step 1`] = `
className="bp3-button-text"
key="text"
>
http://255.255.255.255:3000/
http://:3000/
</span>
<Blueprint3.Icon
key="rightIcon"

View File

@ -24,7 +24,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
}
}
>
make sure your computer and device are connected to same WiFi
Make sure your computer and device are connected to same WiFi
</span>
</Blueprint3.Text>
<Blueprint3.Text
@ -53,7 +53,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
@ -61,7 +61,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
level="H"
renderAs="svg"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
/>
</Blueprint3.Button>
</Blueprint3.Tooltip>
@ -99,7 +99,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
}
}
>
http://255.255.255.255:3000/
http://:3000/
</Blueprint3.Button>
</Blueprint3.Tooltip>
<Blueprint3.Dialog
@ -136,7 +136,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
imageSettings={
Object {
"height": 25,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 25,
}
}
@ -144,7 +144,7 @@ exports[`<ScanQRStep /> when rendered should match exact snapshot 1`] = `
level="H"
renderAs="svg"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
width="390px"
/>
</Col>

View File

@ -1,6 +1,7 @@
/* eslint-disable react/destructuring-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react-hooks/rules-of-hooks */
import { shell } from 'electron';
import React, { useCallback, useContext } from 'react';
import { Button, Text, Icon, Position, Tooltip } from '@blueprintjs/core';
import { createStyles, makeStyles } from '@material-ui/core/styles';
@ -80,10 +81,12 @@ export default function TopPanel(props: any) {
>
<Button
style={{
transform: 'translateY(2px)',
marginRight: '10px',
borderRadius: '100px',
}}
onClick={() => {
shell.openExternal('https://www.patreon.com/deskreen');
}}
>
<Row start="xs">
<Col xs>
@ -135,6 +138,9 @@ export default function TopPanel(props: any) {
id="top-panel-help-button"
intent="none"
className={getClassesCallback().topPanelControlButton}
onClick={() => {
shell.openExternal('https://www.deskreen.com/#howtos');
}}
>
<Icon
className={getClassesCallback().topPanelIconOfControlButton}
@ -176,13 +182,26 @@ export default function TopPanel(props: any) {
<Tooltip
content="Click to visit our website"
position={Position.BOTTOM}
>
<Button
minimal
onClick={() => {
shell.openExternal('https://www.deskreen.com');
}}
style={{
borderRadius: '100px',
}}
>
<h4
id="deskreen-top-app-name-header"
className={getClassesCallback().appNameHeader}
style={{
transform: 'translateY(-3px)',
}}
>
Deskreen
</h4>
</Button>
</Tooltip>
</div>
);

View File

@ -25,11 +25,11 @@ exports[`<TopPanel /> should match exact snapshot 1`] = `
transitionDuration={100}
>
<Blueprint3.Button
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
"marginRight": "10px",
"transform": "translateY(2px)",
}
}
>
@ -85,13 +85,28 @@ exports[`<TopPanel /> should match exact snapshot 1`] = `
minimal={false}
position="bottom"
transitionDuration={100}
>
<Blueprint3.Button
minimal={true}
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
>
<h4
className="makeStyles-appNameHeader-17"
id="deskreen-top-app-name-header"
style={
Object {
"transform": "translateY(-3px)",
}
}
>
Deskreen
</h4>
</Blueprint3.Button>
</Blueprint3.Tooltip>
</div>
</Col>
@ -139,6 +154,7 @@ exports[`<TopPanel /> should match exact snapshot 1`] = `
className="makeStyles-topPanelControlButton-61"
id="top-panel-help-button"
intent="none"
onClick={[Function]}
>
<Blueprint3.Icon
className="makeStyles-topPanelIconOfControlButton-70"

View File

@ -1,3 +1,5 @@
/* istanbul ignore file */
export default {
fallbackLng: 'en',
namespace: 'translation',

View File

@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
getLangFullNameToLangISOKeyMap,
getLangISOKeyToLangFullNameMap,
} from './i18next.config.client';
jest.useFakeTimers();
describe('i18next.config.client tests', () => {
beforeEach(() => {});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when getLangFullNameToLangISOKeyMap called', () => {
it('should return proper key map', () => {
// TODO: add more languages here manually when adding new languages in app!
const expectedMap = new Map();
expectedMap.set('English', 'en');
expectedMap.set('Русский', 'ru');
expectedMap.set('Українська', 'ua');
const res = getLangFullNameToLangISOKeyMap();
expect(res).toEqual(expectedMap);
});
});
describe('when getLangISOKeyToLangFullNameMap called', () => {
it('should return proper key map', () => {
// TODO: add more languages here manually when adding new languages in app!
const expectedMap = new Map();
expectedMap.set('en', 'English');
expectedMap.set('ru', 'Русский');
expectedMap.set('ua', 'Українська');
const res = getLangISOKeyToLangFullNameMap();
expect(res).toEqual(expectedMap);
});
});
});

View File

@ -1,3 +1,5 @@
/* istanbul ignore file */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { remote, ipcRenderer } from 'electron';
import i18n from 'i18next';
@ -8,7 +10,6 @@ import settings from 'electron-settings';
import config from './app.lang.config';
import isProduction from '../utils/isProduction';
// TODO: move this outside this file!
export const getLangFullNameToLangISOKeyMap = (): Map<string, string> => {
const res = new Map<string, string>();
// eslint-disable-next-line no-restricted-syntax
@ -20,7 +21,6 @@ export const getLangFullNameToLangISOKeyMap = (): Map<string, string> => {
return res;
};
// TODO: move this outside this file!
export const getLangISOKeyToLangFullNameMap = (): Map<string, string> => {
const res = new Map<string, string>();
// eslint-disable-next-line no-restricted-syntax

View File

@ -1,3 +1,5 @@
/* istanbul ignore file */
import i18n from 'i18next';
import i18nextBackend from 'i18next-node-fs-backend';
import { join } from 'path';

View File

@ -1,12 +0,0 @@
import React from 'react';
import TopPanel from '../components/TopPanel';
import Counter from '../features/counter/Counter';
export default function CounterPage() {
return (
<>
<TopPanel />
<Counter />
</>
);
}

View File

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { useState, useCallback, useContext, useEffect } from 'react';
import { remote } from 'electron';
import { ipcRenderer, remote } from 'electron';
import { makeStyles, createStyles } from '@material-ui/core/styles';
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';
import { Row, Col } from 'react-flexbox-grid';
import { Text } from '@blueprintjs/core';
import { Row, Col, Grid } from 'react-flexbox-grid';
import { Dialog, H3, H4, H5, Icon, Spinner, Text } from '@blueprintjs/core';
import { useToasts } from 'react-toast-notifications';
@ -19,9 +19,9 @@ import ColorlibStepIcon, {
} from '../components/StepperPanel/ColorlibStepIcon';
import ColorlibConnector from '../components/StepperPanel/ColorlibConnector';
import { SettingsContext } from './SettingsProvider';
import SharingSessionService from '../features/SharingSessionsService';
import SharingSessionService from '../features/SharingSessionService';
import ConnectedDevicesService from '../features/ConnectedDevicesService';
import SharingSessionStatusEnum from '../features/SharingSessionsService/SharingSessionStatusEnum';
import SharingSessionStatusEnum from '../features/SharingSessionService/SharingSessionStatusEnum';
import Logger from '../utils/LoggerWithFilePrefix';
const log = new Logger(__filename);
@ -65,12 +65,28 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
const [isAlertOpen, setIsAlertOpen] = useState(false);
const [isUserAllowedConnection, setIsUserAllowedConnection] = useState(false);
const [isNoWiFiError, setisNoWiFiError] = useState(false);
const [
pendingConnectionDevice,
setPendingConnectionDevice,
] = useState<Device | null>(null);
useEffect(() => {
const ipInterval = setInterval(async () => {
const gotIP = await ipcRenderer.invoke('get-local-lan-ip');
if (gotIP === undefined) {
setisNoWiFiError(true);
} else {
setisNoWiFiError(false);
}
}, 1000);
return () => {
clearInterval(ipInterval);
};
}, []);
useEffect(() => {
sharingSessionService
.createWaitingForConnectionSharingSession()
@ -153,7 +169,7 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
const sharingSession =
sharingSessionService.waitingForConnectionSharingSession;
sharingSession?.disconnectByHostMachineUser();
sharingSession?.destory();
sharingSession?.destroy();
sharingSessionService.sharingSessions.delete(sharingSession?.id as string);
sharingSessionService.waitingForConnectionSharingSession = null;
@ -183,7 +199,7 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
const sharingSession =
sharingSessionService.waitingForConnectionSharingSession;
sharingSession.denyConnectionForPartner();
sharingSession.destory();
sharingSession.destroy();
sharingSession.setStatus(SharingSessionStatusEnum.NOT_CONNECTED);
sharingSessionService.sharingSessions.delete(sharingSession.id);
@ -328,6 +344,27 @@ const DeskreenStepper = React.forwardRef((_props, ref) => {
onCancel={handleCancelAlert}
onConfirm={handleConfirmAlert}
/>
<Dialog isOpen={isNoWiFiError} autoFocus usePortal>
<Grid>
<div style={{ padding: '10px' }}>
<Row center="xs" style={{ marginTop: '10px' }}>
<Icon icon="offline" iconSize={50} color="#8A9BA8" />
</Row>
<Row center="xs" style={{ marginTop: '10px' }}>
<H3>No WiFi and LAN connection.</H3>
</Row>
<Row center="xs">
<H5>Deskreen works only with WiFi and LAN networks.</H5>
</Row>
<Row center="xs">
<Spinner size={50} />
</Row>
<Row center="xs" style={{ marginTop: '10px' }}>
<H4>Waiting for connection.</H4>
</Row>
</div>
</Grid>
</Dialog>
</>
);
});

View File

@ -8,3 +8,4 @@ export const remote = {
return '';
},
};
export const ipcRenderer = jest.fn();

View File

@ -998,7 +998,7 @@ exports[`should match exact snapshot 1`] = `
}
}
>
make sure your computer and device are connected to same WiFi
Make sure your computer and device are connected to same WiFi
</span>
</div>
</Blueprint3.Text>
@ -1104,7 +1104,7 @@ exports[`should match exact snapshot 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
@ -1112,7 +1112,7 @@ exports[`should match exact snapshot 1`] = `
level="H"
renderAs="svg"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
>
<QRCodeSVG
bgColor="rgba(0,0,0,0.0)"
@ -1120,36 +1120,36 @@ exports[`should match exact snapshot 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
includeMargin={false}
level="H"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
>
<svg
height={128}
shapeRendering="crispEdges"
viewBox="0 0 33 33"
viewBox="0 0 25 25"
width={128}
>
<path
d="M0,0 h33v33H0z"
d="M0,0 h25v25H0z"
fill="rgba(0,0,0,0.0)"
/>
<path
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h2v1H12zM15 0h2v1H15zM20 0h1v1H20zM23 0h2v1H23zM26,0 h7v1H26zM0 1h1v1H0zM6 1h1v1H6zM8 1h2v1H8zM12 1h1v1H12zM15 1h4v1H15zM22 1h1v1H22zM26 1h1v1H26zM32,1 h1v1H32zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h1v1H8zM10 2h1v1H10zM12 2h1v1H12zM14 2h1v1H14zM17 2h2v1H17zM20 2h2v1H20zM23 2h1v1H23zM26 2h1v1H26zM28 2h3v1H28zM32,2 h1v1H32zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h1v1H9zM11 3h1v1H11zM14 3h1v1H14zM16 3h3v1H16zM23 3h1v1H23zM26 3h1v1H26zM28 3h3v1H28zM32,3 h1v1H32zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM9 4h1v1H9zM11 4h1v1H11zM15 4h3v1H15zM20 4h3v1H20zM24 4h1v1H24zM26 4h1v1H26zM28 4h3v1H28zM32,4 h1v1H32zM0 5h1v1H0zM6 5h1v1H6zM8 5h2v1H8zM11 5h4v1H11zM19 5h1v1H19zM26 5h1v1H26zM32,5 h1v1H32zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22 6h1v1H22zM24 6h1v1H24zM26,6 h7v1H26zM8 7h3v1H8zM12 7h3v1H12zM17 7h1v1H17zM19 7h4v1H19zM24 7h1v1H24zM2 8h3v1H2zM6 8h1v1H6zM8 8h4v1H8zM13 8h2v1H13zM19 8h1v1H19zM25 8h3v1H25zM30,8 h3v1H30zM0 9h1v1H0zM2 9h1v1H2zM8 9h1v1H8zM10 9h2v1H10zM14 9h1v1H14zM17 9h2v1H17zM20 9h1v1H20zM22 9h5v1H22zM29,9 h4v1H29zM0 10h4v1H0zM5 10h2v1H5zM8 10h4v1H8zM13 10h1v1H13zM16 10h5v1H16zM22 10h1v1H22zM24 10h1v1H24zM27 10h1v1H27zM29 10h2v1H29zM1 11h1v1H1zM8 11h2v1H8zM11 11h3v1H11zM16 11h1v1H16zM18 11h1v1H18zM20 11h1v1H20zM23 11h2v1H23zM26 11h1v1H26zM30 11h2v1H30zM1 12h1v1H1zM6 12h1v1H6zM8 12h2v1H8zM12 12h2v1H12zM15 12h2v1H15zM18 12h1v1H18zM20 12h5v1H20zM27 12h1v1H27zM31 12h1v1H31zM4 13h2v1H4zM7 13h1v1H7zM9 13h1v1H9zM13 13h2v1H13zM17 13h1v1H17zM19 13h1v1H19zM23 13h4v1H23zM28 13h1v1H28zM30 13h1v1H30zM32,13 h1v1H32zM0 14h3v1H0zM6 14h3v1H6zM10 14h1v1H10zM12 14h1v1H12zM16 14h2v1H16zM19 14h1v1H19zM22 14h1v1H22zM25 14h1v1H25zM27 14h2v1H27zM30 14h2v1H30zM0 15h6v1H0zM8 15h2v1H8zM12 15h4v1H12zM17 15h1v1H17zM19 15h4v1H19zM25 15h1v1H25zM28 15h1v1H28zM30 15h1v1H30zM0 16h4v1H0zM6 16h3v1H6zM10 16h1v1H10zM13 16h1v1H13zM15 16h3v1H15zM19 16h2v1H19zM24 16h1v1H24zM28 16h1v1H28zM31,16 h2v1H31zM1 17h3v1H1zM9 17h1v1H9zM11 17h2v1H11zM15 17h2v1H15zM20 17h8v1H20zM31,17 h2v1H31zM2 18h2v1H2zM5 18h3v1H5zM9 18h1v1H9zM11 18h2v1H11zM14 18h2v1H14zM18 18h3v1H18zM24 18h1v1H24zM27 18h2v1H27zM30 18h1v1H30zM1 19h2v1H1zM4 19h1v1H4zM8 19h3v1H8zM12 19h3v1H12zM17 19h1v1H17zM19 19h1v1H19zM21 19h1v1H21zM23 19h3v1H23zM27 19h1v1H27zM29 19h2v1H29zM0 20h1v1H0zM3 20h1v1H3zM5 20h2v1H5zM8 20h3v1H8zM12 20h5v1H12zM18 20h1v1H18zM20 20h1v1H20zM23 20h2v1H23zM28 20h2v1H28zM0 21h1v1H0zM2 21h2v1H2zM5 21h1v1H5zM8 21h2v1H8zM11 21h3v1H11zM15 21h2v1H15zM18 21h1v1H18zM21 21h2v1H21zM25 21h1v1H25zM27 21h1v1H27zM29,21 h4v1H29zM0 22h1v1H0zM4 22h3v1H4zM8 22h1v1H8zM11 22h4v1H11zM16 22h2v1H16zM19 22h1v1H19zM21 22h1v1H21zM23 22h2v1H23zM27 22h4v1H27zM0 23h1v1H0zM2 23h1v1H2zM4 23h1v1H4zM7 23h1v1H7zM9 23h1v1H9zM11 23h2v1H11zM14 23h2v1H14zM19 23h3v1H19zM23 23h1v1H23zM28 23h1v1H28zM30 23h2v1H30zM0 24h1v1H0zM2 24h1v1H2zM6 24h2v1H6zM9 24h2v1H9zM13 24h1v1H13zM15 24h2v1H15zM20 24h1v1H20zM23 24h6v1H23zM31 24h1v1H31zM8 25h3v1H8zM13 25h1v1H13zM15 25h3v1H15zM21 25h4v1H21zM28 25h2v1H28zM31,25 h2v1H31zM0 26h7v1H0zM10 26h1v1H10zM12 26h2v1H12zM15 26h1v1H15zM18 26h1v1H18zM20 26h2v1H20zM23 26h2v1H23zM26 26h1v1H26zM28 26h1v1H28zM0 27h1v1H0zM6 27h1v1H6zM9 27h1v1H9zM11 27h1v1H11zM15 27h1v1H15zM18 27h1v1H18zM20 27h3v1H20zM24 27h1v1H24zM28 27h1v1H28zM30 27h1v1H30zM32,27 h1v1H32zM0 28h1v1H0zM2 28h3v1H2zM6 28h1v1H6zM8 28h1v1H8zM11 28h1v1H11zM14 28h1v1H14zM17 28h2v1H17zM20 28h2v1H20zM24 28h6v1H24zM0 29h1v1H0zM2 29h3v1H2zM6 29h1v1H6zM8 29h3v1H8zM13 29h1v1H13zM15 29h2v1H15zM18 29h2v1H18zM21 29h4v1H21zM28 29h2v1H28zM31 29h1v1H31zM0 30h1v1H0zM2 30h3v1H2zM6 30h1v1H6zM8 30h1v1H8zM10 30h1v1H10zM12 30h2v1H12zM17 30h1v1H17zM20 30h2v1H20zM23 30h4v1H23zM29 30h1v1H29zM0 31h1v1H0zM6 31h1v1H6zM9 31h1v1H9zM13 31h2v1H13zM16 31h2v1H16zM20 31h4v1H20zM25 31h1v1H25zM27 31h1v1H27zM30 31h1v1H30zM0 32h7v1H0zM9 32h2v1H9zM12 32h1v1H12zM15 32h1v1H15zM17 32h6v1H17zM24 32h2v1H24zM30 32h2v1H30z"
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h1v1H12zM14 0h2v1H14zM18,0 h7v1H18zM0 1h1v1H0zM6 1h1v1H6zM13 1h1v1H13zM15 1h2v1H15zM18 1h1v1H18zM24,1 h1v1H24zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h3v1H8zM12 2h1v1H12zM14 2h2v1H14zM18 2h1v1H18zM20 2h3v1H20zM24,2 h1v1H24zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h4v1H9zM16 3h1v1H16zM18 3h1v1H18zM20 3h3v1H20zM24,3 h1v1H24zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM8 4h3v1H8zM15 4h1v1H15zM18 4h1v1H18zM20 4h3v1H20zM24,4 h1v1H24zM0 5h1v1H0zM6 5h1v1H6zM9 5h1v1H9zM11 5h1v1H11zM13 5h1v1H13zM18 5h1v1H18zM24,5 h1v1H24zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18,6 h7v1H18zM8 7h2v1H8zM12 7h4v1H12zM5 8h2v1H5zM10 8h5v1H10zM18 8h1v1H18zM20 8h1v1H20zM22 8h1v1H22zM24,8 h1v1H24zM1 9h1v1H1zM3 9h2v1H3zM7 9h2v1H7zM11 9h1v1H11zM13 9h1v1H13zM16 9h2v1H16zM20 9h4v1H20zM3 10h4v1H3zM11 10h4v1H11zM16 10h1v1H16zM18 10h4v1H18zM23,10 h2v1H23zM2 11h1v1H2zM8 11h3v1H8zM12 11h1v1H12zM14 11h3v1H14zM18 11h1v1H18zM21 11h1v1H21zM24,11 h1v1H24zM0 12h2v1H0zM6 12h1v1H6zM8 12h1v1H8zM10 12h3v1H10zM16 12h3v1H16zM24,12 h1v1H24zM0 13h1v1H0zM4 13h1v1H4zM7 13h1v1H7zM11 13h1v1H11zM14 13h1v1H14zM16 13h2v1H16zM23 13h1v1H23zM0 14h1v1H0zM6 14h3v1H6zM11 14h1v1H11zM13 14h2v1H13zM18 14h1v1H18zM20 14h2v1H20zM23,14 h2v1H23zM0 15h1v1H0zM2 15h1v1H2zM5 15h1v1H5zM8 15h4v1H8zM13 15h1v1H13zM15 15h1v1H15zM18 15h1v1H18zM22 15h1v1H22zM24,15 h1v1H24zM0 16h1v1H0zM2 16h1v1H2zM5 16h3v1H5zM10 16h1v1H10zM12 16h1v1H12zM14 16h1v1H14zM16 16h5v1H16zM22 16h1v1H22zM8 17h1v1H8zM10 17h2v1H10zM14 17h1v1H14zM16 17h1v1H16zM20 17h1v1H20zM22 17h1v1H22zM0 18h7v1H0zM14 18h3v1H14zM18 18h1v1H18zM20 18h2v1H20zM24,18 h1v1H24zM0 19h1v1H0zM6 19h1v1H6zM8 19h1v1H8zM10 19h2v1H10zM14 19h1v1H14zM16 19h1v1H16zM20 19h1v1H20zM23,19 h2v1H23zM0 20h1v1H0zM2 20h3v1H2zM6 20h1v1H6zM9 20h3v1H9zM15 20h9v1H15zM0 21h1v1H0zM2 21h3v1H2zM6 21h1v1H6zM12 21h4v1H12zM18 21h2v1H18zM21 21h1v1H21zM24,21 h1v1H24zM0 22h1v1H0zM2 22h3v1H2zM6 22h1v1H6zM9 22h1v1H9zM13 22h1v1H13zM16 22h2v1H16zM22 22h1v1H22zM24,22 h1v1H24zM0 23h1v1H0zM6 23h1v1H6zM10 23h1v1H10zM12 23h2v1H12zM16 23h1v1H16zM18 23h3v1H18zM24,23 h1v1H24zM0 24h7v1H0zM12 24h2v1H12zM15 24h4v1H15zM21 24h1v1H21zM24,24 h1v1H24z"
fill="#000000"
/>
<image
height={10.3125}
height={7.8125}
preserveAspectRatio="none"
width={10.3125}
x={11.34375}
xlinkHref="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png"
y={11.34375}
width={7.8125}
x={8.59375}
xlinkHref="http://127.0.0.1:3000/logo192.png"
y={8.59375}
/>
</svg>
</QRCodeSVG>
@ -1320,7 +1320,7 @@ exports[`should match exact snapshot 1`] = `
className="bp3-button-text"
key="text"
>
http://255.255.255.255:3000/
http://:3000/
</span>
<Blueprint3.Icon
key="rightIcon"
@ -1553,6 +1553,27 @@ exports[`should match exact snapshot 1`] = `
</Blueprint3.Dialog>
</Blueprint3.Alert>
</AllowConnectionForDeviceAlert>
<Blueprint3.Dialog
autoFocus={true}
canOutsideClickClose={true}
isOpen={false}
usePortal={true}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-overlay-scroll-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
lazy={true}
transitionDuration={300}
transitionName="bp3-overlay"
usePortal={true}
/>
</Blueprint3.Dialog>
</ForwardRef>
</Router>
</BrowserRouter>

View File

@ -146,11 +146,11 @@ exports[`should match exact snapshot 1`] = `
<Blueprint3.Button
className=""
key=".0"
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
"marginRight": "10px",
"transform": "translateY(2px)",
}
}
tabIndex={0}
@ -158,13 +158,13 @@ exports[`should match exact snapshot 1`] = `
<button
className="bp3-button"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"borderRadius": "100px",
"marginRight": "10px",
"transform": "translateY(2px)",
}
}
tabIndex={0}
@ -329,16 +329,58 @@ exports[`should match exact snapshot 1`] = `
onFocus={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<Blueprint3.Button
className=""
key=".0"
minimal={true}
onClick={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
tabIndex={0}
>
<button
className="bp3-button bp3-minimal"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"borderRadius": "100px",
}
}
tabIndex={0}
type="button"
>
<Blueprint3.Icon
key="leftIcon"
/>
<span
className="bp3-button-text"
key="text"
>
<h4
className="makeStyles-appNameHeader-133"
id="deskreen-top-app-name-header"
key=".0"
tabIndex={0}
style={
Object {
"transform": "translateY(-3px)",
}
}
>
Deskreen
</h4>
</span>
<Blueprint3.Icon
key="rightIcon"
/>
</button>
</Blueprint3.Button>
</span>
</Blueprint3.ResizeSensor>
</InnerReference>
</Reference>
@ -583,12 +625,14 @@ exports[`should match exact snapshot 1`] = `
id="top-panel-help-button"
intent="none"
key=".0"
onClick={[Function]}
tabIndex={0}
>
<button
className="bp3-button makeStyles-topPanelControlButton-177"
id="top-panel-help-button"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
tabIndex={0}
@ -1876,7 +1920,7 @@ exports[`should match exact snapshot 1`] = `
}
}
>
make sure your computer and device are connected to same WiFi
Make sure your computer and device are connected to same WiFi
</span>
</div>
</Blueprint3.Text>
@ -1982,7 +2026,7 @@ exports[`should match exact snapshot 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
@ -1990,7 +2034,7 @@ exports[`should match exact snapshot 1`] = `
level="H"
renderAs="svg"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
>
<QRCodeSVG
bgColor="rgba(0,0,0,0.0)"
@ -1998,36 +2042,36 @@ exports[`should match exact snapshot 1`] = `
imageSettings={
Object {
"height": 40,
"src": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png",
"src": "http://127.0.0.1:3000/logo192.png",
"width": 40,
}
}
includeMargin={false}
level="H"
size={128}
value="http://255.255.255.255:3000/"
value="http://:3000/"
>
<svg
height={128}
shapeRendering="crispEdges"
viewBox="0 0 33 33"
viewBox="0 0 25 25"
width={128}
>
<path
d="M0,0 h33v33H0z"
d="M0,0 h25v25H0z"
fill="rgba(0,0,0,0.0)"
/>
<path
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h2v1H12zM15 0h2v1H15zM20 0h1v1H20zM23 0h2v1H23zM26,0 h7v1H26zM0 1h1v1H0zM6 1h1v1H6zM8 1h2v1H8zM12 1h1v1H12zM15 1h4v1H15zM22 1h1v1H22zM26 1h1v1H26zM32,1 h1v1H32zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h1v1H8zM10 2h1v1H10zM12 2h1v1H12zM14 2h1v1H14zM17 2h2v1H17zM20 2h2v1H20zM23 2h1v1H23zM26 2h1v1H26zM28 2h3v1H28zM32,2 h1v1H32zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h1v1H9zM11 3h1v1H11zM14 3h1v1H14zM16 3h3v1H16zM23 3h1v1H23zM26 3h1v1H26zM28 3h3v1H28zM32,3 h1v1H32zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM9 4h1v1H9zM11 4h1v1H11zM15 4h3v1H15zM20 4h3v1H20zM24 4h1v1H24zM26 4h1v1H26zM28 4h3v1H28zM32,4 h1v1H32zM0 5h1v1H0zM6 5h1v1H6zM8 5h2v1H8zM11 5h4v1H11zM19 5h1v1H19zM26 5h1v1H26zM32,5 h1v1H32zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22 6h1v1H22zM24 6h1v1H24zM26,6 h7v1H26zM8 7h3v1H8zM12 7h3v1H12zM17 7h1v1H17zM19 7h4v1H19zM24 7h1v1H24zM2 8h3v1H2zM6 8h1v1H6zM8 8h4v1H8zM13 8h2v1H13zM19 8h1v1H19zM25 8h3v1H25zM30,8 h3v1H30zM0 9h1v1H0zM2 9h1v1H2zM8 9h1v1H8zM10 9h2v1H10zM14 9h1v1H14zM17 9h2v1H17zM20 9h1v1H20zM22 9h5v1H22zM29,9 h4v1H29zM0 10h4v1H0zM5 10h2v1H5zM8 10h4v1H8zM13 10h1v1H13zM16 10h5v1H16zM22 10h1v1H22zM24 10h1v1H24zM27 10h1v1H27zM29 10h2v1H29zM1 11h1v1H1zM8 11h2v1H8zM11 11h3v1H11zM16 11h1v1H16zM18 11h1v1H18zM20 11h1v1H20zM23 11h2v1H23zM26 11h1v1H26zM30 11h2v1H30zM1 12h1v1H1zM6 12h1v1H6zM8 12h2v1H8zM12 12h2v1H12zM15 12h2v1H15zM18 12h1v1H18zM20 12h5v1H20zM27 12h1v1H27zM31 12h1v1H31zM4 13h2v1H4zM7 13h1v1H7zM9 13h1v1H9zM13 13h2v1H13zM17 13h1v1H17zM19 13h1v1H19zM23 13h4v1H23zM28 13h1v1H28zM30 13h1v1H30zM32,13 h1v1H32zM0 14h3v1H0zM6 14h3v1H6zM10 14h1v1H10zM12 14h1v1H12zM16 14h2v1H16zM19 14h1v1H19zM22 14h1v1H22zM25 14h1v1H25zM27 14h2v1H27zM30 14h2v1H30zM0 15h6v1H0zM8 15h2v1H8zM12 15h4v1H12zM17 15h1v1H17zM19 15h4v1H19zM25 15h1v1H25zM28 15h1v1H28zM30 15h1v1H30zM0 16h4v1H0zM6 16h3v1H6zM10 16h1v1H10zM13 16h1v1H13zM15 16h3v1H15zM19 16h2v1H19zM24 16h1v1H24zM28 16h1v1H28zM31,16 h2v1H31zM1 17h3v1H1zM9 17h1v1H9zM11 17h2v1H11zM15 17h2v1H15zM20 17h8v1H20zM31,17 h2v1H31zM2 18h2v1H2zM5 18h3v1H5zM9 18h1v1H9zM11 18h2v1H11zM14 18h2v1H14zM18 18h3v1H18zM24 18h1v1H24zM27 18h2v1H27zM30 18h1v1H30zM1 19h2v1H1zM4 19h1v1H4zM8 19h3v1H8zM12 19h3v1H12zM17 19h1v1H17zM19 19h1v1H19zM21 19h1v1H21zM23 19h3v1H23zM27 19h1v1H27zM29 19h2v1H29zM0 20h1v1H0zM3 20h1v1H3zM5 20h2v1H5zM8 20h3v1H8zM12 20h5v1H12zM18 20h1v1H18zM20 20h1v1H20zM23 20h2v1H23zM28 20h2v1H28zM0 21h1v1H0zM2 21h2v1H2zM5 21h1v1H5zM8 21h2v1H8zM11 21h3v1H11zM15 21h2v1H15zM18 21h1v1H18zM21 21h2v1H21zM25 21h1v1H25zM27 21h1v1H27zM29,21 h4v1H29zM0 22h1v1H0zM4 22h3v1H4zM8 22h1v1H8zM11 22h4v1H11zM16 22h2v1H16zM19 22h1v1H19zM21 22h1v1H21zM23 22h2v1H23zM27 22h4v1H27zM0 23h1v1H0zM2 23h1v1H2zM4 23h1v1H4zM7 23h1v1H7zM9 23h1v1H9zM11 23h2v1H11zM14 23h2v1H14zM19 23h3v1H19zM23 23h1v1H23zM28 23h1v1H28zM30 23h2v1H30zM0 24h1v1H0zM2 24h1v1H2zM6 24h2v1H6zM9 24h2v1H9zM13 24h1v1H13zM15 24h2v1H15zM20 24h1v1H20zM23 24h6v1H23zM31 24h1v1H31zM8 25h3v1H8zM13 25h1v1H13zM15 25h3v1H15zM21 25h4v1H21zM28 25h2v1H28zM31,25 h2v1H31zM0 26h7v1H0zM10 26h1v1H10zM12 26h2v1H12zM15 26h1v1H15zM18 26h1v1H18zM20 26h2v1H20zM23 26h2v1H23zM26 26h1v1H26zM28 26h1v1H28zM0 27h1v1H0zM6 27h1v1H6zM9 27h1v1H9zM11 27h1v1H11zM15 27h1v1H15zM18 27h1v1H18zM20 27h3v1H20zM24 27h1v1H24zM28 27h1v1H28zM30 27h1v1H30zM32,27 h1v1H32zM0 28h1v1H0zM2 28h3v1H2zM6 28h1v1H6zM8 28h1v1H8zM11 28h1v1H11zM14 28h1v1H14zM17 28h2v1H17zM20 28h2v1H20zM24 28h6v1H24zM0 29h1v1H0zM2 29h3v1H2zM6 29h1v1H6zM8 29h3v1H8zM13 29h1v1H13zM15 29h2v1H15zM18 29h2v1H18zM21 29h4v1H21zM28 29h2v1H28zM31 29h1v1H31zM0 30h1v1H0zM2 30h3v1H2zM6 30h1v1H6zM8 30h1v1H8zM10 30h1v1H10zM12 30h2v1H12zM17 30h1v1H17zM20 30h2v1H20zM23 30h4v1H23zM29 30h1v1H29zM0 31h1v1H0zM6 31h1v1H6zM9 31h1v1H9zM13 31h2v1H13zM16 31h2v1H16zM20 31h4v1H20zM25 31h1v1H25zM27 31h1v1H27zM30 31h1v1H30zM0 32h7v1H0zM9 32h2v1H9zM12 32h1v1H12zM15 32h1v1H15zM17 32h6v1H17zM24 32h2v1H24zM30 32h2v1H30z"
d="M0 0h7v1H0zM8 0h1v1H8zM10 0h1v1H10zM12 0h1v1H12zM14 0h2v1H14zM18,0 h7v1H18zM0 1h1v1H0zM6 1h1v1H6zM13 1h1v1H13zM15 1h2v1H15zM18 1h1v1H18zM24,1 h1v1H24zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h3v1H8zM12 2h1v1H12zM14 2h2v1H14zM18 2h1v1H18zM20 2h3v1H20zM24,2 h1v1H24zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM9 3h4v1H9zM16 3h1v1H16zM18 3h1v1H18zM20 3h3v1H20zM24,3 h1v1H24zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM8 4h3v1H8zM15 4h1v1H15zM18 4h1v1H18zM20 4h3v1H20zM24,4 h1v1H24zM0 5h1v1H0zM6 5h1v1H6zM9 5h1v1H9zM11 5h1v1H11zM13 5h1v1H13zM18 5h1v1H18zM24,5 h1v1H24zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18,6 h7v1H18zM8 7h2v1H8zM12 7h4v1H12zM5 8h2v1H5zM10 8h5v1H10zM18 8h1v1H18zM20 8h1v1H20zM22 8h1v1H22zM24,8 h1v1H24zM1 9h1v1H1zM3 9h2v1H3zM7 9h2v1H7zM11 9h1v1H11zM13 9h1v1H13zM16 9h2v1H16zM20 9h4v1H20zM3 10h4v1H3zM11 10h4v1H11zM16 10h1v1H16zM18 10h4v1H18zM23,10 h2v1H23zM2 11h1v1H2zM8 11h3v1H8zM12 11h1v1H12zM14 11h3v1H14zM18 11h1v1H18zM21 11h1v1H21zM24,11 h1v1H24zM0 12h2v1H0zM6 12h1v1H6zM8 12h1v1H8zM10 12h3v1H10zM16 12h3v1H16zM24,12 h1v1H24zM0 13h1v1H0zM4 13h1v1H4zM7 13h1v1H7zM11 13h1v1H11zM14 13h1v1H14zM16 13h2v1H16zM23 13h1v1H23zM0 14h1v1H0zM6 14h3v1H6zM11 14h1v1H11zM13 14h2v1H13zM18 14h1v1H18zM20 14h2v1H20zM23,14 h2v1H23zM0 15h1v1H0zM2 15h1v1H2zM5 15h1v1H5zM8 15h4v1H8zM13 15h1v1H13zM15 15h1v1H15zM18 15h1v1H18zM22 15h1v1H22zM24,15 h1v1H24zM0 16h1v1H0zM2 16h1v1H2zM5 16h3v1H5zM10 16h1v1H10zM12 16h1v1H12zM14 16h1v1H14zM16 16h5v1H16zM22 16h1v1H22zM8 17h1v1H8zM10 17h2v1H10zM14 17h1v1H14zM16 17h1v1H16zM20 17h1v1H20zM22 17h1v1H22zM0 18h7v1H0zM14 18h3v1H14zM18 18h1v1H18zM20 18h2v1H20zM24,18 h1v1H24zM0 19h1v1H0zM6 19h1v1H6zM8 19h1v1H8zM10 19h2v1H10zM14 19h1v1H14zM16 19h1v1H16zM20 19h1v1H20zM23,19 h2v1H23zM0 20h1v1H0zM2 20h3v1H2zM6 20h1v1H6zM9 20h3v1H9zM15 20h9v1H15zM0 21h1v1H0zM2 21h3v1H2zM6 21h1v1H6zM12 21h4v1H12zM18 21h2v1H18zM21 21h1v1H21zM24,21 h1v1H24zM0 22h1v1H0zM2 22h3v1H2zM6 22h1v1H6zM9 22h1v1H9zM13 22h1v1H13zM16 22h2v1H16zM22 22h1v1H22zM24,22 h1v1H24zM0 23h1v1H0zM6 23h1v1H6zM10 23h1v1H10zM12 23h2v1H12zM16 23h1v1H16zM18 23h3v1H18zM24,23 h1v1H24zM0 24h7v1H0zM12 24h2v1H12zM15 24h4v1H15zM21 24h1v1H21zM24,24 h1v1H24z"
fill="#000000"
/>
<image
height={10.3125}
height={7.8125}
preserveAspectRatio="none"
width={10.3125}
x={11.34375}
xlinkHref="https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Electron_Software_Framework_Logo.svg/256px-Electron_Software_Framework_Logo.svg.png"
y={11.34375}
width={7.8125}
x={8.59375}
xlinkHref="http://127.0.0.1:3000/logo192.png"
y={8.59375}
/>
</svg>
</QRCodeSVG>
@ -2198,7 +2242,7 @@ exports[`should match exact snapshot 1`] = `
className="bp3-button-text"
key="text"
>
http://255.255.255.255:3000/
http://:3000/
</span>
<Blueprint3.Icon
key="rightIcon"
@ -2431,6 +2475,27 @@ exports[`should match exact snapshot 1`] = `
</Blueprint3.Dialog>
</Blueprint3.Alert>
</AllowConnectionForDeviceAlert>
<Blueprint3.Dialog
autoFocus={true}
canOutsideClickClose={true}
isOpen={false}
usePortal={true}
>
<Blueprint3.Overlay
autoFocus={true}
backdropProps={Object {}}
canEscapeKeyClose={true}
canOutsideClickClose={true}
className="bp3-overlay-scroll-container"
enforceFocus={true}
hasBackdrop={true}
isOpen={false}
lazy={true}
transitionDuration={300}
transitionName="bp3-overlay"
usePortal={true}
/>
</Blueprint3.Dialog>
</ForwardRef>
</div>
<Portal

View File

@ -0,0 +1,132 @@
import ConnectedDevicesService, { nullDevice } from '.';
jest.useFakeTimers();
const testDevice = {
id: '112112112',
sharingSessionID: '12332',
deviceOS: '4123',
deviceType: '43',
deviceIP: '1234',
deviceBrowser: '41234',
deviceScreenWidth: 200,
deviceScreenHeight: 300,
};
describe('ConnectedDevicesService tests', () => {
let service: ConnectedDevicesService;
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
service = new ConnectedDevicesService();
});
describe('when ConnectedDevicesService created properly', () => {
describe('when .resetPendingConnectionDevice() was called', () => {
it('should set .pendingConnectionDevice to nullDevice', () => {
service.pendingConnectionDevice = testDevice;
service.resetPendingConnectionDevice();
expect(service.pendingConnectionDevice).toBe(nullDevice);
});
});
describe('when .getDevices() was called', () => {
it('should return .devices array', () => {
const res = service.getDevices();
expect(res).toBe(service.devices);
});
});
describe('when .removeAllDevices() was called', () => {
it('should make .devices array empty', () => {
service.devices.push(testDevice);
service.removeAllDevices();
expect(service.devices.length).toBe(0);
});
});
describe('when .removeDeviceByID() was called', () => {
it('should remove appropriate device from .devices array', async () => {
const testDevice2 = (JSON.parse(
JSON.stringify(testDevice)
) as unknown) as Device;
service.devices.push(testDevice);
service.devices.push(testDevice2);
await service.removeDeviceByID(testDevice.id);
let isStillInArray = false;
service.devices.forEach((d) => {
if (d.id === testDevice.id) {
isStillInArray = true;
}
});
expect(isStillInArray).toBe(false);
});
});
describe('when .addDevice() was called', () => {
it('should add device to .devices array', () => {
service.addDevice(testDevice);
let isInArray = false;
service.devices.forEach((d) => {
if (d.id === testDevice.id) {
isInArray = true;
}
});
expect(isInArray).toBe(true);
});
});
describe('when .addPendingConnectedDeviceListener() was called', () => {
it('should add listener to .pendingDeviceConnectedListeners array', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const testCallback = (_: Device) => {};
service.addPendingConnectedDeviceListener(testCallback);
let isInArray = false;
service.pendingDeviceConnectedListeners.forEach((c) => {
if (c === testCallback) {
isInArray = true;
}
});
expect(isInArray).toBe(true);
});
});
describe('when .setPendingConnectionDevice() was called', () => {
it('should set passed device as pendingConnectionDevice adn call .emitPendingConnectionDeviceConnected', () => {
service.emitPendingConnectionDeviceConnected = jest.fn();
service.setPendingConnectionDevice(testDevice);
expect(service.pendingConnectionDevice).toBe(testDevice);
expect(service.emitPendingConnectionDeviceConnected).toBeCalled();
});
});
describe('when .emitPendingConnectionDeviceConnected() was called', () => {
it('should call all callbacks in pendingDeviceConnectedListeners', () => {
const testCallback1 = jest.fn();
const testCallback2 = jest.fn();
service.pendingDeviceConnectedListeners = [
testCallback1,
testCallback2,
];
service.emitPendingConnectionDeviceConnected();
expect(testCallback1).toBeCalled();
expect(testCallback2).toBeCalled();
});
});
});
});

View File

@ -1,4 +1,4 @@
const nullDevice: Device = {
export const nullDevice: Device = {
id: '',
sharingSessionID: '',
deviceOS: '',
@ -33,7 +33,7 @@ class ConnectedDevices {
this.devices = this.devices.filter((d) => {
return d.id !== deviceIDToRemove;
});
resolve();
resolve(undefined);
});
}
@ -50,7 +50,7 @@ class ConnectedDevices {
this.emitPendingConnectionDeviceConnected();
}
private emitPendingConnectionDeviceConnected() {
emitPendingConnectionDeviceConnected() {
this.pendingDeviceConnectedListeners.forEach(
(callback: (device: Device) => void) => {
callback(this.pendingConnectionDevice);

View File

@ -1,7 +0,0 @@
import DesktopCapturerSourceType from './DesktopCapturerSourceType';
interface DesktopCapturerSource {
id: string;
type: DesktopCapturerSourceType;
name: string;
}

View File

@ -1,12 +1,6 @@
enum DesktopCapturerSourceType {
WINDOW,
SCREEN,
WINDOW = 'window',
SCREEN = 'screen',
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const getDesktopCapturerSourceTypeFromSourceID = (_id: string) => {
// TODO: implement this function!
return DesktopCapturerSourceType.WINDOW;
};
export default DesktopCapturerSourceType;

View File

@ -0,0 +1,4 @@
interface DesktopCapturerSourceWithType {
source: import('electron').DesktopCapturerSource;
type: import('./DesktopCapturerSourceType').default;
}

View File

@ -0,0 +1,310 @@
import path from 'path';
import { DesktopCapturerSource } from 'electron';
import DesktopCapturerSources, { getSourceTypeFromSourceID } from '.';
/* eslint-disable @typescript-eslint/ban-ts-comment */
import Logger from '../../utils/LoggerWithFilePrefix';
import DesktopCapturerSourceType from './DesktopCapturerSourceType';
jest.useFakeTimers();
jest.mock('../../utils/LoggerWithFilePrefix'); // Logger is now a mock constructor
jest.mock('.', () => ({
__esModule: true, // this property makes it work
default: jest.fn(),
getSourceTypeFromSourceID: jest.requireActual('.').getSourceTypeFromSourceID,
}));
const testScreenSource1Name = 'screen:1234';
const testScreenSource2Name = 'screen:4321';
const testWindowSource1Name = 'window:1234';
const testWindowSource2Name = 'window:4321';
const testScreenSource1 = {
type: DesktopCapturerSourceType.SCREEN,
source: ({
id: 'screen:adfe2',
display_id: '82392',
} as unknown) as DesktopCapturerSource,
};
const testScreenSource2 = {
type: DesktopCapturerSourceType.SCREEN,
source: ({
id: 'screen:adfe212',
display_id: '123234',
} as unknown) as DesktopCapturerSource,
};
const testWindowSource1 = {
type: DesktopCapturerSourceType.WINDOW,
source: ({
id: 'window:a42323',
display_id: '82392',
} as unknown) as DesktopCapturerSource,
};
const testWindowSource2 = {
type: DesktopCapturerSourceType.WINDOW,
source: ({
id: 'window:adfe83292',
display_id: '123234',
} as unknown) as DesktopCapturerSource,
};
jest.mock('electron', () => {
// eslint-disable-next-line global-require
const testScreenSource1a = ({
id: 'screen:adfe2',
display_id: '82392',
} as unknown) as DesktopCapturerSource;
const testScreenSource2a = ({
id: 'screen:adfe212',
display_id: '123234',
} as unknown) as DesktopCapturerSource;
const testWindowSource1a = ({
id: 'window:a42323',
display_id: '82392',
} as unknown) as DesktopCapturerSource;
const testWindowSource2a = ({
id: 'window:adfe83292',
display_id: '123234',
} as unknown) as DesktopCapturerSource;
return {
// __esModule: true,
desktopCapturer: {
getSources: () => {
return new Promise((resolve) => {
resolve([
testScreenSource1a,
testWindowSource1a,
testScreenSource2a,
testWindowSource2a,
]);
});
},
},
};
});
describe('DesktopCapturerSourcesService tests', () => {
let desktopCapturerService: DesktopCapturerSources;
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
// @ts-ignore
Logger.mockClear();
// @ts-ignore
DesktopCapturerSources.mockClear();
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when DesktopCapturerSourcesService was created properly', () => {
it('should create logger properly', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
expect(Logger).toHaveBeenCalledTimes(1);
expect(Logger).toHaveBeenCalledWith(path.join(__dirname, 'index.ts'));
});
it('should call startRefreshDesktopCapturerSourcesLoop', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.startRefreshDesktopCapturerSourcesLoop = jest.fn();
desktopCapturerService.constructor();
expect(
desktopCapturerService.startRefreshDesktopCapturerSourcesLoop
).toHaveBeenCalledTimes(1);
});
it('should call refreshDesktopCapturerSources multiple times', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.refreshDesktopCapturerSources = jest.fn();
for (let i = 1; i < 5; i += 1) {
jest.advanceTimersByTime(6000);
expect(
desktopCapturerService.refreshDesktopCapturerSources
).toHaveBeenCalledTimes(i);
}
});
it('should call startPollForInactiveListenersLoop', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.startPollForInactiveListenersLoop = jest.fn();
desktopCapturerService.constructor();
expect(
desktopCapturerService.startPollForInactiveListenersLoop
).toHaveBeenCalledTimes(1);
});
describe('when .getSourcesMap was called', () => {
it('should return a sources map object', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
const res = desktopCapturerService.getSourcesMap();
expect(desktopCapturerService.sources).toBe(res);
});
});
describe('when .getScreenSources was called', () => {
it('should return sources array which are of type SCREEN only', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.sources.set(
testScreenSource1Name,
testScreenSource1
);
desktopCapturerService.sources.set(
testScreenSource2Name,
testScreenSource2
);
desktopCapturerService.sources.set(
testWindowSource1Name,
testWindowSource1
);
desktopCapturerService.sources.set(
testWindowSource2Name,
testWindowSource2
);
const res = desktopCapturerService.getScreenSources();
expect(res).toEqual([
testScreenSource1.source,
testScreenSource2.source,
]);
});
});
describe('when .getAppWindowSources was called', () => {
it('should return sources array which are of type WINDOW only', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.sources.set(
testScreenSource1Name,
testScreenSource1
);
desktopCapturerService.sources.set(
testScreenSource2Name,
testScreenSource2
);
desktopCapturerService.sources.set(
testWindowSource1Name,
testWindowSource1
);
desktopCapturerService.sources.set(
testWindowSource2Name,
testWindowSource2
);
const res = desktopCapturerService.getAppWindowSources();
expect(res).toEqual([
testWindowSource1.source,
testWindowSource2.source,
]);
});
});
describe('when .getSourceDisplayIDBySourceID was called', () => {
it('should return proper source display_id string', () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.sources.set(
testScreenSource1Name,
testScreenSource1
);
desktopCapturerService.sources.set(
testScreenSource2Name,
testScreenSource2
);
desktopCapturerService.sources.set(
testWindowSource1Name,
testWindowSource1
);
desktopCapturerService.sources.set(
testWindowSource2Name,
testWindowSource2
);
const res = desktopCapturerService.getSourceDisplayIDBySourceID(
testScreenSource1.source.id
);
expect(res).toEqual(testScreenSource1.source.display_id);
});
});
describe('when .getDesktopCapturerSources was called', () => {
it('should resolve with proper map of screen sources', async () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
const testSourcesMap = new Map<string, DesktopCapturerSourceWithType>();
testSourcesMap.set(testScreenSource1.source.id, testScreenSource1);
testSourcesMap.set(testScreenSource2.source.id, testScreenSource2);
testSourcesMap.set(testWindowSource1.source.id, testWindowSource1);
testSourcesMap.set(testWindowSource2.source.id, testWindowSource2);
const res = await desktopCapturerService.getDesktopCapturerSources();
expect(res).toEqual(testSourcesMap);
});
});
describe('when .refreshDesktopCapturerSources was called', () => {
it('should call proper methods to check whether windows are closed and screens disconnected', async () => {
const DesktopCapturerSourcesClass = jest.requireActual('.')
.default as DesktopCapturerSources;
// @ts-ignore
desktopCapturerService = new DesktopCapturerSourcesClass();
desktopCapturerService.checkForClosedWindows = jest.fn();
desktopCapturerService.checkForScreensDisconnected = jest.fn();
desktopCapturerService.constructor();
await desktopCapturerService.refreshDesktopCapturerSources();
expect(desktopCapturerService.checkForClosedWindows).toBeCalled();
expect(desktopCapturerService.checkForScreensDisconnected).toBeCalled();
});
});
});
});
describe('getSourceTypeFromSourceID tests', () => {
it('should return proper source type depending on input type', () => {
const testWindowSource = 'window:1234';
const testScreenSource = 'screen:4321';
expect(getSourceTypeFromSourceID(testWindowSource)).toBe(
DesktopCapturerSourceType.WINDOW
);
expect(getSourceTypeFromSourceID(testScreenSource)).toBe(
DesktopCapturerSourceType.SCREEN
);
});
});

View File

@ -5,19 +5,15 @@ import { desktopCapturer, DesktopCapturerSource } from 'electron';
import Logger from '../../utils/LoggerWithFilePrefix';
import DesktopCapturerSourceType from './DesktopCapturerSourceType';
const log = new Logger(__filename);
const getSourceTypeFromSourceID = (id: string): DesktopCapturerSourceType => {
if (id.includes('screen')) {
export function getSourceTypeFromSourceID(
id: string
): DesktopCapturerSourceType {
if (id.includes(DesktopCapturerSourceType.SCREEN)) {
return DesktopCapturerSourceType.SCREEN;
}
return DesktopCapturerSourceType.WINDOW;
};
}
type DesktopCapturerSourceWithType = {
type: DesktopCapturerSourceType;
source: DesktopCapturerSource;
};
type SourcesDisappearListener = (ids: string[]) => void;
type SharingSessionID = string;
@ -35,6 +31,8 @@ class DesktopCapturerSources {
SourcesDisappearListener[]
>;
log = new Logger(__filename);
constructor() {
this.sources = new Map<string, DesktopCapturerSourceWithType>();
this.lastAvailableScreenIDs = [];
@ -48,18 +46,20 @@ class DesktopCapturerSources {
SourcesDisappearListener[]
>();
setTimeout(() => {
setInterval(() => {
this.refreshDesktopCapturerSources();
}, 5000);
}, 4000);
this.pollForInactiveListeners();
this.startRefreshDesktopCapturerSourcesLoop();
this.startPollForInactiveListenersLoop();
}
getSourcesMap(): Map<string, DesktopCapturerSourceWithType> {
return this.sources;
}
startRefreshDesktopCapturerSourcesLoop() {
setInterval(() => {
this.refreshDesktopCapturerSources();
}, 5000);
}
getScreenSources(): DesktopCapturerSource[] {
const screenSources: DesktopCapturerSource[] = [];
[...this.sources.keys()].forEach((key) => {
@ -110,25 +110,27 @@ class DesktopCapturerSources {
// TODO: implement logic
}
private async updateDesktopCapturerSources() {
this.lastAvailableScreenIDs = [];
this.lastAvailableWindowIDs = [];
async updateDesktopCapturerSources() {
// TODO: implement logic of checking if last sources match new sources,
// TODO: if source is gone, do proper actions and notify user if needed
// this.lastAvailableScreenIDs = [];
// this.lastAvailableWindowIDs = [];
[...this.sources.keys()].forEach((key) => {
const oldSource = this.sources.get(key);
if (!oldSource) return;
if (oldSource.type === DesktopCapturerSourceType.WINDOW) {
this.lastAvailableWindowIDs.push(oldSource.source.id);
} else if (oldSource.type === DesktopCapturerSourceType.SCREEN) {
this.lastAvailableScreenIDs.push(oldSource.source.id);
}
});
// [...this.sources.keys()].forEach((key) => {
// const oldSource = this.sources.get(key);
// if (!oldSource) return;
// if (oldSource.type === DesktopCapturerSourceType.WINDOW) {
// this.lastAvailableWindowIDs.push(oldSource.source.id);
// } else if (oldSource.type === DesktopCapturerSourceType.SCREEN) {
// this.lastAvailableScreenIDs.push(oldSource.source.id);
// }
// });
this.sources = await this.getDesktopCapturerSources();
}
// eslint-disable-next-line class-methods-use-this
private getDesktopCapturerSources(): Promise<
getDesktopCapturerSources(): Promise<
Map<string, DesktopCapturerSourceWithType>
> {
return new Promise<Map<string, DesktopCapturerSourceWithType>>(
@ -136,27 +138,19 @@ class DesktopCapturerSources {
const newSources = new Map<string, DesktopCapturerSourceWithType>();
try {
const capturerSources = await desktopCapturer.getSources({
types: ['window', 'screen'],
types: [
DesktopCapturerSourceType.WINDOW,
DesktopCapturerSourceType.SCREEN,
],
thumbnailSize: { width: 500, height: 500 },
fetchWindowIcons: true, // TODO: use window icons in app UI !
});
// for (const source of capturerSources) {
// newSources.set(source.id, {
// type: getSourceTypeFromSourceID(source.id),
// source,
// });
// }
capturerSources.forEach((source) => {
newSources.set(source.id, {
type: getSourceTypeFromSourceID(source.id),
source,
});
});
// .catch((e) => {
// console.error(e);
// throw new Error('error getting desktopCapturer sources');
// });
resolve(newSources);
} catch (e) {
reject();
@ -165,56 +159,49 @@ class DesktopCapturerSources {
);
}
private refreshDesktopCapturerSources() {
async refreshDesktopCapturerSources() {
// TODO: implement get available sources logic here;
this.updateDesktopCapturerSources()
try {
await this.updateDesktopCapturerSources();
// eslint-disable-next-line promise/always-return
.then(() => {
// eventually run checkers that emit events
this.checkForClosedWindows();
this.checkForScreensDisconnected();
})
.catch((e) => {
log.error(e);
});
} catch (e) {
this.log.error(e);
}
}
private pollForInactiveListeners() {
startPollForInactiveListenersLoop() {
setInterval(() => {
// TODO: implement logic
// if session ID no longer exists in SharingSessionsService -> remove its listener object
setTimeout(() => {
this.pollForInactiveListeners();
}, 1000 * 60 * 60); // runs every hour in infinite loop
}
private checkForClosedWindows() {
checkForClosedWindows() {
// TODO: implement logic
const isSomeWindowsClosed = false;
const closedWindowsIDs: string[] = [];
if (isSomeWindowsClosed) {
this.notifyOnWindowsClosedListeners(closedWindowsIDs);
}
// const isSomeWindowsClosed = false;
// const closedWindowsIDs: string[] = [];
// if (isSomeWindowsClosed) {
// this.notifyOnWindowsClosedListeners(closedWindowsIDs);
// }
}
private notifyOnWindowsClosedListeners(_closedWindowsIDs: string[]) {
notifyOnWindowsClosedListeners(_closedWindowsIDs: string[]) {
// TODO: implement logic
}
private checkForScreensDisconnected() {
checkForScreensDisconnected() {
// TODO: implement logic
const isSomeScreensDisconnected = false;
const disconnectedScreensIDs: string[] = [];
if (isSomeScreensDisconnected) {
this.notifyOnScreensDisconnectedListeners(disconnectedScreensIDs);
}
// const isSomeScreensDisconnected = false;
// const disconnectedScreensIDs: string[] = [];
// if (isSomeScreensDisconnected) {
// this.notifyOnScreensDisconnectedListeners(disconnectedScreensIDs);
// }
}
private notifyOnScreensDisconnectedListeners(
_disconnectedScreensIDs: string[]
) {
notifyOnScreensDisconnectedListeners(_disconnectedScreensIDs: string[]) {
// TODO: implement logic
}
}

View File

@ -0,0 +1,5 @@
import SimplePeer from 'simple-peer';
const NullSimplePeer = new SimplePeer();
export default NullSimplePeer;

View File

@ -0,0 +1 @@
export default { username: '', publicKey: '', privateKey: '' };

View File

@ -0,0 +1,4 @@
interface PartnerPeerUser {
username: string;
publicKey: string;
}

View File

@ -0,0 +1,3 @@
// use import() to prevent cycle import!
// From here: https://stackoverflow.com/questions/39040108/import-class-in-definition-file-d-ts
type PeerConnection = import('.').default;

View File

@ -0,0 +1,6 @@
interface ReceiveEncryptedMessagePayload {
payload: string;
signature: string;
iv: string;
keys: { sessionKey: string; signingKey: string }[];
}

View File

@ -0,0 +1,4 @@
interface SendEncryptedMessagePayload {
type: string;
payload: Record<string, unknown>;
}

View File

@ -0,0 +1,94 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
import createDesktopCapturerStream from './createDesktopCapturerStream';
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
jest.mock('./getDesktopSourceStreamBySourceID', () => {
return jest.fn();
});
const MOCK_MEDIA_STREAM = ({} as unknown) as MediaStream;
const TEST_SCREEN_SOURCE_ID = 'screen:1234fa';
const TEST_WINDOW_SOURCE_ID = 'window:1234fa';
const TEST_DISPLAY_SIZE = { width: 640, height: 480 };
describe('createDesktopCapturerStream callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
// @ts-ignore
getDesktopSourceStreamBySourceID.mockReturnValueOnce(MOCK_MEDIA_STREAM);
process.env.RUN_MODE = 'dev';
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
peerConnection.desktopCapturerSourceID = DesktopCapturerSourceType.SCREEN;
});
afterEach(() => {
process.env.RUN_MODE = 'test';
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when createDesktopCapturerStream called properly', () => {
describe('when source type is screen', () => {
it('should call getDesktopSourceStreamBySourceID with proper parameters and set localStream', async () => {
peerConnection.sourceDisplaySize = { width: 640, height: 480 };
await createDesktopCapturerStream(
peerConnection,
TEST_SCREEN_SOURCE_ID
);
expect(getDesktopSourceStreamBySourceID).toBeCalledWith(
TEST_SCREEN_SOURCE_ID,
TEST_DISPLAY_SIZE.width,
TEST_DISPLAY_SIZE.height,
0.5,
1
);
expect(peerConnection.localStream).toBe(MOCK_MEDIA_STREAM);
});
});
describe('when source type is window', () => {
it('should call getDesktopSourceStreamBySourceID with proper parameters and set localStream', async () => {
await createDesktopCapturerStream(
peerConnection,
TEST_WINDOW_SOURCE_ID
);
expect(getDesktopSourceStreamBySourceID).toBeCalledWith(
TEST_WINDOW_SOURCE_ID
);
expect(peerConnection.localStream).toBe(MOCK_MEDIA_STREAM);
});
});
});
});

View File

@ -0,0 +1,32 @@
/* eslint-disable promise/catch-or-return */
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
import Logger from '../../utils/LoggerWithFilePrefix';
import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
const log = new Logger(__filename);
export default async function createDesktopCapturerStream(
peerConnection: PeerConnection,
sourceID: string
) {
try {
if (process.env.RUN_MODE === 'test') return;
if (sourceID.includes(DesktopCapturerSourceType.SCREEN)) {
const stream = await getDesktopSourceStreamBySourceID(
sourceID,
peerConnection.sourceDisplaySize?.width,
peerConnection.sourceDisplaySize?.height,
0.5,
1
);
peerConnection.localStream = stream;
} else {
// when souce is app window
const stream = await getDesktopSourceStreamBySourceID(sourceID);
peerConnection.localStream = stream;
}
} catch (e) {
log.error(e);
}
}

View File

@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
jest.useFakeTimers();
const TEST_SCREEN_SHARING_SOURCE_ID = 'screen:1234ad';
describe('getDesktopSourceStreamBySourceID callback', () => {
beforeEach(() => {
// eslint-disable-next-line no-global-assign
// @ts-ignore
global.navigator.mediaDevices = { getUserMedia: jest.fn() };
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when getDesktopSourceStreamBySourceID called with default parameters', () => {
it('should handle getUserMedia without width and height (APPLICATION WINDOW SHARING CASE)', () => {
getDesktopSourceStreamBySourceID(TEST_SCREEN_SHARING_SOURCE_ID);
expect(navigator.mediaDevices.getUserMedia).toBeCalledWith({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: TEST_SCREEN_SHARING_SOURCE_ID,
minFrameRate: 15,
maxFrameRate: 60,
},
},
});
});
});
describe('when getDesktopSourceStreamBySourceID called with width and height parameters (SCREEN SHARING CASE)', () => {
it('should handle getUserMedia with width and height', () => {
const TEST_WIDTH = 640;
const TEST_HEIGHT = 480;
getDesktopSourceStreamBySourceID(TEST_SCREEN_SHARING_SOURCE_ID, 640, 480);
expect(navigator.mediaDevices.getUserMedia).toBeCalledWith({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: TEST_SCREEN_SHARING_SOURCE_ID,
minWidth: TEST_WIDTH,
maxWidth: TEST_WIDTH,
minHeight: TEST_HEIGHT,
maxHeight: TEST_HEIGHT,
minFrameRate: 15,
maxFrameRate: 60,
},
},
});
});
});
});

View File

@ -0,0 +1,147 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import handleCreatePeer from './handleCreatePeer';
import createDesktopCapturerStream from './createDesktopCapturerStream';
import NullSimplePeer from './NullSimplePeer';
import handlePeerOnData from './handlePeerOnData';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
jest.mock('./createDesktopCapturerStream', () => {
return jest.fn();
});
jest.mock('./handlePeerOnData');
const TEST_MOCK_LOCAL_STREAM = ({} as unknown) as MediaStream;
function initPeerWithListeners(peerConnection: PeerConnection) {
const listeners: any = {};
peerConnection.peer = ({
on: (eventName: string, callback: (p: any) => void) => {
if (!listeners[eventName]) {
listeners[eventName] = [];
}
listeners[eventName].push(callback);
},
emit: (eventName: string, param: any) => {
if (listeners[eventName]) {
listeners[eventName].forEach((callback: (p: any) => void) => {
callback(param);
});
}
},
} as unknown) as typeof NullSimplePeer;
}
describe('handleCreatePeer callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
// @ts-ignore
createDesktopCapturerStream.mockImplementation(() => {
return new Promise((resolve) => resolve(TEST_MOCK_LOCAL_STREAM));
});
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handleCreatePeer called properly', () => {
it('should call createDesktopCapturerStream', async () => {
await handleCreatePeer(peerConnection);
expect(createDesktopCapturerStream).toBeCalled();
});
it('should make .peer defined', async () => {
await handleCreatePeer(peerConnection);
expect(peerConnection.peer).not.toEqual(NullSimplePeer);
});
it('should add localStream to peer with addStream', async () => {
peerConnection.localStream = TEST_MOCK_LOCAL_STREAM;
await handleCreatePeer(peerConnection);
expect(peerConnection.peer.addStream).toBeCalledWith(
TEST_MOCK_LOCAL_STREAM
);
});
it('should set .peer.on(signal event listner', async () => {
await handleCreatePeer(peerConnection);
expect(peerConnection.peer.on).toBeCalledWith(
'signal',
expect.anything()
);
});
it('should set .peer.on(data event listner', async () => {
await handleCreatePeer(peerConnection);
expect(peerConnection.peer.on).toBeCalledWith('data', expect.anything());
});
it('should resolve with undefined', async () => {
const res = await handleCreatePeer(peerConnection);
expect(res).toBe(undefined);
});
describe('when peer on "signal" even occured', () => {
it('should add signal to .signalsDataToCallUser', async () => {
const TEST_SIGNAL_DATA = '1234';
initPeerWithListeners(peerConnection);
await handleCreatePeer(peerConnection);
peerConnection.peer.emit('signal', TEST_SIGNAL_DATA);
expect(peerConnection.signalsDataToCallUser).toEqual([
TEST_SIGNAL_DATA,
]);
});
});
describe('when peer on "data" even occured', () => {
it('should add signal to .signalsDataToCallUser', async () => {
const TEST_DATA = 'asdfasdfasdf';
initPeerWithListeners(peerConnection);
await handleCreatePeer(peerConnection);
peerConnection.peer.emit('data', TEST_DATA);
expect(handlePeerOnData).toBeCalled();
});
});
});
});

View File

@ -0,0 +1,47 @@
import SimplePeer from 'simple-peer';
import createDesktopCapturerStream from './createDesktopCapturerStream';
import handlePeerOnData from './handlePeerOnData';
// import setSdpMediaBitrate from './setSdpMediaBitrate';
import Logger from '../../utils/LoggerWithFilePrefix';
import NullSimplePeer from './NullSimplePeer';
import simplePeerHandleSdpTransform from './simplePeerHandleSdpTransform';
const log = new Logger(__filename);
export default function handleCreatePeer(peerConnection: PeerConnection) {
return new Promise((resolve, reject) => {
createDesktopCapturerStream(
peerConnection,
peerConnection.desktopCapturerSourceID
)
.then(() => {
// eslint-disable-next-line promise/always-return
if (peerConnection.peer === NullSimplePeer) {
peerConnection.peer = new SimplePeer({
initiator: true,
config: { iceServers: [] },
sdpTransform: simplePeerHandleSdpTransform,
});
}
// eslint-disable-next-line promise/always-return
if (peerConnection.localStream !== null) {
peerConnection.peer.addStream(peerConnection.localStream);
}
peerConnection.peer.on('signal', (data: string) => {
// fired when simple peer and webrtc done preparation to start call on peerConnection machine
peerConnection.signalsDataToCallUser.push(data);
});
peerConnection.peer.on('data', (data) => {
handlePeerOnData(peerConnection, data);
});
resolve(undefined);
})
.catch((e) => {
log.error(e);
reject();
});
});
}

View File

@ -0,0 +1,208 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import handlePeerOnData from './handlePeerOnData';
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
import NullSimplePeer from './NullSimplePeer';
import prepareDataMessageToSendScreenSourceType from './prepareDataMessageToSendScreenSourceType';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
jest.mock('./getDesktopSourceStreamBySourceID', () => {
return jest.fn();
});
const TEST_DATA_SET_VIDEO_QUALITY_05 = `
{
"type": "set_video_quality",
"payload": {
"value": 0.5
}
}
`;
const TEST_DATA_GET_SHARING_SOURCE_TYPE = `
{
"type": "get_sharing_source_type",
"payload": {
}
}
`;
describe('handlePeerOnData callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
peerConnection.desktopCapturerSourceID = DesktopCapturerSourceType.SCREEN;
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handlePeerOnData called properly', () => {
describe('when handlePeerOnData called with set_video_quality data and when sharing source is SCREEN', () => {
it('should create new stream', () => {
handlePeerOnData(peerConnection, TEST_DATA_SET_VIDEO_QUALITY_05);
expect(getDesktopSourceStreamBySourceID).toBeCalled();
});
it('should call replaceTrack() on peer', async () => {
// @ts-ignore
getDesktopSourceStreamBySourceID.mockImplementation(
() =>
(({
getVideoTracks: () => [{ stop: jest.fn() }],
} as unknown) as MediaStream)
);
peerConnection.localStream = ({
getVideoTracks: () => [{ stop: jest.fn() }],
} as unknown) as MediaStream;
peerConnection.peer = ({
replaceTrack: jest.fn(),
} as unknown) as typeof NullSimplePeer;
await handlePeerOnData(peerConnection, TEST_DATA_SET_VIDEO_QUALITY_05);
expect(peerConnection.peer.replaceTrack).toBeCalled();
});
it('should call .stop() on old track to clear memory', async () => {
// @ts-ignore
getDesktopSourceStreamBySourceID.mockImplementation(
() =>
(({
getVideoTracks: () => [{ stop: jest.fn() }],
} as unknown) as MediaStream)
);
const oldTrackStopFunctionMock = jest.fn();
peerConnection.localStream = ({
getVideoTracks: () => [{ stop: oldTrackStopFunctionMock }],
} as unknown) as MediaStream;
peerConnection.peer = ({
replaceTrack: jest.fn(),
} as unknown) as typeof NullSimplePeer;
await handlePeerOnData(peerConnection, TEST_DATA_SET_VIDEO_QUALITY_05);
expect(oldTrackStopFunctionMock).toBeCalled();
});
});
describe('when handlePeerOnData called with set_video_quality data and when sharing source is WINDOW', () => {
it('should NOT create new stream', () => {
peerConnection.desktopCapturerSourceID =
DesktopCapturerSourceType.WINDOW;
handlePeerOnData(peerConnection, TEST_DATA_SET_VIDEO_QUALITY_05);
expect(getDesktopSourceStreamBySourceID).not.toBeCalled();
});
it('should NOT call replaceTrack() on peer', async () => {
peerConnection.desktopCapturerSourceID =
DesktopCapturerSourceType.WINDOW;
// @ts-ignore
getDesktopSourceStreamBySourceID.mockImplementation(
() =>
(({
getVideoTracks: () => [{ stop: jest.fn() }],
} as unknown) as MediaStream)
);
peerConnection.localStream = ({
getVideoTracks: () => [{ stop: jest.fn() }],
} as unknown) as MediaStream;
peerConnection.peer = ({
replaceTrack: jest.fn(),
} as unknown) as typeof NullSimplePeer;
await handlePeerOnData(peerConnection, TEST_DATA_SET_VIDEO_QUALITY_05);
expect(peerConnection.peer.replaceTrack).not.toBeCalled();
});
it('should NOT call .stop() on old track to clear memory', async () => {
peerConnection.desktopCapturerSourceID =
DesktopCapturerSourceType.WINDOW;
// @ts-ignore
getDesktopSourceStreamBySourceID.mockImplementation(
() =>
(({
getVideoTracks: () => [{ stop: jest.fn() }],
} as unknown) as MediaStream)
);
const oldTrackStopFunctionMock = jest.fn();
peerConnection.localStream = ({
getVideoTracks: () => [{ stop: oldTrackStopFunctionMock }],
} as unknown) as MediaStream;
peerConnection.peer = ({
replaceTrack: jest.fn(),
} as unknown) as typeof NullSimplePeer;
await handlePeerOnData(peerConnection, TEST_DATA_SET_VIDEO_QUALITY_05);
expect(oldTrackStopFunctionMock).not.toBeCalled();
});
});
describe('when handlePeerOnData called with get_sharing_source_type data', () => {
describe('when sharing source type is SCREEN', () => {
it('should call peer.send() with proper data', () => {
peerConnection.peer = ({
send: jest.fn(),
} as unknown) as typeof NullSimplePeer;
handlePeerOnData(peerConnection, TEST_DATA_GET_SHARING_SOURCE_TYPE);
expect(peerConnection.peer.send).toBeCalledWith(
prepareDataMessageToSendScreenSourceType(
DesktopCapturerSourceType.SCREEN
)
);
});
});
describe('when sharing source type is WINDOW', () => {
it('should call peer.send() with proper data', () => {
peerConnection.desktopCapturerSourceID =
DesktopCapturerSourceType.WINDOW;
peerConnection.peer = ({
send: jest.fn(),
} as unknown) as typeof NullSimplePeer;
handlePeerOnData(peerConnection, TEST_DATA_GET_SHARING_SOURCE_TYPE);
expect(peerConnection.peer.send).toBeCalledWith(
prepareDataMessageToSendScreenSourceType(
DesktopCapturerSourceType.WINDOW
)
);
});
});
});
});
});

View File

@ -0,0 +1,54 @@
import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
import prepareDataMessageToSendScreenSourceType from './prepareDataMessageToSendScreenSourceType';
export default async function handlePeerOnData(
peerConnection: PeerConnection,
data: string
) {
const dataJSON = JSON.parse(data);
if (dataJSON.type === 'set_video_quality') {
const maxVideoQualityMultiplier = dataJSON.payload.value;
const minVideoQualityMultiplier =
maxVideoQualityMultiplier === 1 ? 0.5 : maxVideoQualityMultiplier;
if (
!peerConnection.desktopCapturerSourceID.includes(
DesktopCapturerSourceType.SCREEN
)
)
return;
const newStream = await getDesktopSourceStreamBySourceID(
peerConnection.desktopCapturerSourceID,
peerConnection.sourceDisplaySize?.width,
peerConnection.sourceDisplaySize?.height,
minVideoQualityMultiplier,
maxVideoQualityMultiplier
);
const newVideoTrack = newStream.getVideoTracks()[0];
const oldTrack = peerConnection.localStream?.getVideoTracks()[0];
if (oldTrack && peerConnection.localStream) {
peerConnection.peer.replaceTrack(
oldTrack,
newVideoTrack,
peerConnection.localStream
);
oldTrack.stop();
}
}
if (dataJSON.type === 'get_sharing_source_type') {
const sourceType = peerConnection.desktopCapturerSourceID.includes(
DesktopCapturerSourceType.SCREEN
)
? DesktopCapturerSourceType.SCREEN
: DesktopCapturerSourceType.WINDOW;
peerConnection.peer.send(
prepareDataMessageToSendScreenSourceType(sourceType)
);
}
}

View File

@ -0,0 +1,199 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import uuid from 'uuid';
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import { process as processMessage } from '../../utils/message';
import NullSimplePeer from './NullSimplePeer';
import handleRecieveEncryptedMessage, {
handleDeviceIPMessage,
} from './handleRecieveEncryptedMessage';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
jest.mock('../../utils/message', () => {
return { process: jest.fn() };
});
jest.mock('uuid', () => {
return {
v4: () => '1234kdkd',
};
});
const TEST_DEVICE_DETAILS_PAYLOAD = {
socketID: '123',
deviceType: 'computer',
os: 'Windows',
browser: 'Chrome 72',
deviceScreenWidth: 640,
deviceScreenHeight: 480,
};
const TEST_DUMMY_ENCRYPTED_MESSAGE_PAYLOAD = ({} as unknown) as ReceiveEncryptedMessagePayload;
describe('handleRecieveEncryptedMessage.ts', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handleRecieveEncryptedMessage called properly', () => {
describe('when processed message type is CALL_ACCEPTED', () => {
it('should call peer.signal() with proper signal data', async () => {
const TEST_SIGNAL_DATA = 'a32sdlf';
// @ts-ignore
processMessage.mockImplementation(() => {
return {
type: 'CALL_ACCEPTED',
payload: {
signalData: TEST_SIGNAL_DATA,
},
};
});
peerConnection.peer = ({
signal: jest.fn(),
} as unknown) as typeof NullSimplePeer;
await handleRecieveEncryptedMessage(
peerConnection,
TEST_DUMMY_ENCRYPTED_MESSAGE_PAYLOAD
);
expect(peerConnection.peer.signal).toBeCalledWith(TEST_SIGNAL_DATA);
});
});
describe('when processed message type is DEVICE_DETAILS', () => {
it('should call socket.emit() to get partner device IP', async () => {
peerConnection.socket = ({
emit: jest.fn(),
} as unknown) as SocketIOClient.Socket;
// @ts-ignore
processMessage.mockImplementation(() => {
return {
type: 'DEVICE_DETAILS',
payload: TEST_DEVICE_DETAILS_PAYLOAD,
};
});
await handleRecieveEncryptedMessage(
peerConnection,
TEST_DUMMY_ENCRYPTED_MESSAGE_PAYLOAD
);
expect(peerConnection.socket.emit).toBeCalledWith(
'GET_IP_BY_SOCKET_ID',
expect.anything(),
expect.anything()
);
});
});
describe('when processed message type is GET_APP_THEME', () => {
it('should call .sendEncryptedMessage with proper payload', async () => {
peerConnection.sendEncryptedMessage = jest.fn();
const TEST_GET_APP_THEME_PAYLOAD = {
type: 'APP_THEME',
payload: { value: peerConnection.appColorTheme },
};
// @ts-ignore
processMessage.mockImplementation(() => {
return {
type: 'GET_APP_THEME',
payload: {},
};
});
await handleRecieveEncryptedMessage(
peerConnection,
TEST_DUMMY_ENCRYPTED_MESSAGE_PAYLOAD
);
expect(peerConnection.sendEncryptedMessage).toBeCalledWith(
TEST_GET_APP_THEME_PAYLOAD
);
});
});
describe('when processed message type is GET_APP_LANGUAGE', () => {
it('should call .sendEncryptedMessage with proper payload', async () => {
peerConnection.sendEncryptedMessage = jest.fn();
const TEST_GET_APP_LANGUAGE_PAYLOAD = {
type: 'APP_LANGUAGE',
payload: { value: peerConnection.appLanguage },
};
// @ts-ignore
processMessage.mockImplementation(() => {
return {
type: 'GET_APP_LANGUAGE',
payload: {},
};
});
await handleRecieveEncryptedMessage(
peerConnection,
TEST_DUMMY_ENCRYPTED_MESSAGE_PAYLOAD
);
expect(peerConnection.sendEncryptedMessage).toBeCalledWith(
TEST_GET_APP_LANGUAGE_PAYLOAD
);
});
});
});
describe('when handleDeviceIPMessage was called properly', () => {
it('should set partnerDeviceDetails with message payload and call device connected callback', async () => {
const TEST_DEVICE_IP = '123.123.123.123';
const TEST_DEVICE_TO_BE_SET = {
deviceIP: TEST_DEVICE_IP,
deviceType: TEST_DEVICE_DETAILS_PAYLOAD.deviceType,
deviceOS: TEST_DEVICE_DETAILS_PAYLOAD.os,
deviceBrowser: TEST_DEVICE_DETAILS_PAYLOAD.browser,
deviceScreenWidth: TEST_DEVICE_DETAILS_PAYLOAD.deviceScreenWidth,
deviceScreenHeight: TEST_DEVICE_DETAILS_PAYLOAD.deviceScreenHeight,
sharingSessionID: peerConnection.sharingSessionID,
id: uuid.v4(),
};
peerConnection.onDeviceConnectedCallback = jest.fn();
handleDeviceIPMessage(TEST_DEVICE_IP, peerConnection, {
type: 'DEVICE_DETAILS',
payload: TEST_DEVICE_DETAILS_PAYLOAD,
});
expect(peerConnection.partnerDeviceDetails).toEqual(
TEST_DEVICE_TO_BE_SET
);
expect(peerConnection.onDeviceConnectedCallback).toBeCalledWith(
TEST_DEVICE_TO_BE_SET
);
});
});
});

View File

@ -0,0 +1,53 @@
import uuid from 'uuid';
import { process as processMessage } from '../../utils/message';
export function handleDeviceIPMessage(
deviceIP: string,
peerConnection: PeerConnection,
message: ProcessedMessage
) {
if (message.type !== 'DEVICE_DETAILS') return;
const device = {
id: uuid.v4(),
deviceIP,
deviceType: message.payload.deviceType,
deviceOS: message.payload.os,
deviceBrowser: message.payload.browser,
deviceScreenWidth: message.payload.deviceScreenWidth,
deviceScreenHeight: message.payload.deviceScreenHeight,
sharingSessionID: peerConnection.sharingSessionID,
};
peerConnection.partnerDeviceDetails = device;
peerConnection.onDeviceConnectedCallback(device);
}
export default async function handleRecieveEncryptedMessage(
peerConnection: PeerConnection,
payload: ReceiveEncryptedMessagePayload
) {
const message = await processMessage(payload, peerConnection.user.privateKey);
if (message.type === 'CALL_ACCEPTED') {
peerConnection.peer.signal(message.payload.signalData);
}
if (message.type === 'DEVICE_DETAILS') {
peerConnection.socket.emit(
'GET_IP_BY_SOCKET_ID',
message.payload.socketID,
(deviceIP: string) => {
handleDeviceIPMessage(deviceIP, peerConnection, message);
}
);
}
if (message.type === 'GET_APP_THEME') {
peerConnection.sendEncryptedMessage({
type: 'APP_THEME',
payload: { value: peerConnection.appColorTheme },
});
}
if (message.type === 'GET_APP_LANGUAGE') {
peerConnection.sendEncryptedMessage({
type: 'APP_LANGUAGE',
payload: { value: peerConnection.appLanguage },
});
}
}

View File

@ -0,0 +1,136 @@
import handleSelfDestroy from './handleSelfDestroy';
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import NullSimplePeer from './NullSimplePeer';
import SharingSession from '../SharingSessionService/SharingSession';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
const TEST_PARTNER = {
username: 'asdfaf',
publicKey: 'afafdsg',
};
const TEST_PARTNER_DEVICE_ID = '123fdsad';
const TEST_SHARING_SESSION = ({
destroy: jest.fn(),
setStatus: jest.fn(),
} as unknown) as SharingSession;
describe('handleSelfDestroy callback', () => {
// let sharingSessionService;
let peerConnection: PeerConnection;
beforeEach(() => {
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
({
unmarkRoomIDAsTaken: jest.fn(),
} as unknown) as RoomIDService,
({
removeDeviceByID: jest.fn(),
} as unknown) as ConnectedDevicesService,
({
sharingSessions: {
get: () => TEST_SHARING_SESSION,
delete: jest.fn(),
},
} as unknown) as SharingSessionService,
{} as DesktopCapturerSourcesService
);
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handleSelfDestroy callback called properly', () => {
it('should set peerConnection to other than it was', () => {
peerConnection.partner = TEST_PARTNER;
handleSelfDestroy(peerConnection);
expect(peerConnection.partner).not.toEqual(TEST_PARTNER);
});
it('should remove device from connectedDevicesService device id', () => {
peerConnection.partnerDeviceDetails.id = TEST_PARTNER_DEVICE_ID;
handleSelfDestroy(peerConnection);
expect(
peerConnection.connectedDevicesService.removeDeviceByID
).toBeCalledWith(TEST_PARTNER_DEVICE_ID);
});
it('should call .destroy() on simple peer', () => {
peerConnection.peer = ({
destroy: jest.fn(),
} as unknown) as typeof NullSimplePeer;
handleSelfDestroy(peerConnection);
expect(peerConnection.peer.destroy).toBeCalled();
});
it('should stop all localStream tracks and set it to null', () => {
const testTrack1 = {
stop: jest.fn(),
};
const testTrack2 = {
stop: jest.fn(),
};
const TEST_LOCAL_STREAM = ({
getTracks: () => [testTrack1, testTrack2],
} as unknown) as MediaStream;
peerConnection.localStream = TEST_LOCAL_STREAM;
handleSelfDestroy(peerConnection);
expect(testTrack1.stop).toBeCalled();
expect(testTrack2.stop).toBeCalled();
expect(peerConnection.localStream).toBeNull();
});
it('should call sharingSession .destroy()', () => {
handleSelfDestroy(peerConnection);
expect(TEST_SHARING_SESSION.destroy).toBeCalled();
});
it('should delete sharing session from sharing session service', () => {
handleSelfDestroy(peerConnection);
expect(
peerConnection.sharingSessionService.sharingSessions.delete
).toBeCalledWith(peerConnection.sharingSessionID);
});
it('should disconnect socket server', () => {
peerConnection.socket = ({
disconnect: jest.fn(),
} as unknown) as SocketIOClient.Socket;
handleSelfDestroy(peerConnection);
expect(peerConnection.socket.disconnect).toBeCalled();
});
});
});

View File

@ -0,0 +1,31 @@
import SharingSessionStatusEnum from '../SharingSessionService/SharingSessionStatusEnum';
import NullSimplePeer from './NullSimplePeer';
import NullUser from './NullUser';
export default function handleSelfDestroy(peerConnection: PeerConnection) {
peerConnection.partner = NullUser;
peerConnection.connectedDevicesService.removeDeviceByID(
peerConnection.partnerDeviceDetails.id
);
if (peerConnection.peer !== NullSimplePeer) {
peerConnection.peer.destroy();
}
if (peerConnection.localStream) {
peerConnection.localStream.getTracks().forEach((track) => {
track.stop();
});
peerConnection.localStream = null;
}
const sharingSession = peerConnection.sharingSessionService.sharingSessions.get(
peerConnection.sharingSessionID
);
sharingSession?.setStatus(SharingSessionStatusEnum.DESTROYED);
sharingSession?.destroy();
peerConnection.sharingSessionService.sharingSessions.delete(
peerConnection.sharingSessionID
);
peerConnection.onDeviceConnectedCallback = () => {};
peerConnection.isCallStarted = false;
peerConnection.socket.disconnect();
peerConnection.roomIDService.unmarkRoomIDAsTaken(peerConnection.roomID);
}

View File

@ -0,0 +1,96 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import setDisplaySizeFromLocalStream from './handleSetDisplaySizeFromLocalStream';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
const TEST_MOCK_DISPLAY_SIZE = {
width: 1280,
height: 640,
};
describe('setDisplaySizeFromLocalStream callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
peerConnection.localStream = ({
getVideoTracks: () => [
{
getSettings: () => {
return TEST_MOCK_DISPLAY_SIZE;
},
},
],
} as unknown) as MediaStream;
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when setDisplaySizeFromLocalStream called properly', () => {
it('should set width and height on .sourceDisplaySize', () => {
setDisplaySizeFromLocalStream(peerConnection);
expect(peerConnection.sourceDisplaySize).toEqual(TEST_MOCK_DISPLAY_SIZE);
});
});
describe('when setDisplaySizeFromLocalStream was NOT called properly', () => {
describe('when localStream is null', () => {
it('should have .sourceDisplaySize as undefined', () => {
peerConnection.localStream = null;
setDisplaySizeFromLocalStream(peerConnection);
expect(peerConnection.sourceDisplaySize).toBe(undefined);
});
});
describe('when peerConnection.localStream.getVideoTracks()[0].getSettings() width or height is undefined', () => {
it('should have .sourceDisplaySize to be undefined', () => {
peerConnection.localStream = ({
getVideoTracks: () => [
{
getSettings: () => {
return {
width: undefined,
height: undefined,
};
},
},
],
} as unknown) as MediaStream;
setDisplaySizeFromLocalStream(peerConnection);
expect(peerConnection.sourceDisplaySize).toBe(undefined);
});
});
});
});

View File

@ -0,0 +1,23 @@
export default function setDisplaySizeFromLocalStream(
peerConnection: PeerConnection
) {
if (
!peerConnection.localStream ||
!peerConnection.localStream.getVideoTracks()[0]
)
return;
if (!peerConnection.localStream.getVideoTracks()[0].getSettings().width)
return;
if (!peerConnection.localStream.getVideoTracks()[0].getSettings().height)
return;
peerConnection.sourceDisplaySize = {
width: peerConnection.localStream.getVideoTracks()[0].getSettings().width
? (peerConnection.localStream.getVideoTracks()[0].getSettings()
.width as number)
: 640,
height: peerConnection.localStream.getVideoTracks()[0].getSettings().height
? (peerConnection.localStream.getVideoTracks()[0].getSettings()
.height as number)
: 480,
};
}

View File

@ -0,0 +1,199 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import handleSocket from './handleSocket';
import handleSocketUserEnter from './handleSocketUserEnter';
import handleSocketUserExit from './handleSocketUserExit';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
jest.mock('./handleSocketUserEnter');
jest.mock('./handleSocketUserExit');
function initSocketWithListeners(peerConnection: PeerConnection) {
const listeners: any = {};
peerConnection.socket = ({
on: (eventName: string, callback: (p: any) => void) => {
if (!listeners[eventName]) {
listeners[eventName] = [];
}
listeners[eventName].push(callback);
},
emit: (eventName: string, param: any) => {
if (listeners[eventName]) {
listeners[eventName].forEach((callback: (p: any) => void) => {
callback(param);
});
}
},
removeAllListeners: () => {},
} as unknown) as SocketIOClient.Socket;
}
describe('handleSocket callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
// @ts-ignore
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
peerConnection.socket = ({
on: jest.fn(),
removeAllListeners: jest.fn(),
} as unknown) as SocketIOClient.Socket;
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handleSocket called properly', () => {
it('should call removeAllListeners', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.removeAllListeners).toBeCalled();
});
it('should call socket.on(connect', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.on).toBeCalledWith(
'connect',
expect.anything()
);
});
it('should call socket.on(disconnect', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.on).toBeCalledWith(
'disconnect',
expect.anything()
);
});
it('should call socket.on(USER_ENTER', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.on).toBeCalledWith(
'USER_ENTER',
expect.anything()
);
});
it('should call socket.on(USER_EXIT', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.on).toBeCalledWith(
'USER_EXIT',
expect.anything()
);
});
it('should call socket.on(ENCRYPTED_MESSAGE', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.on).toBeCalledWith(
'ENCRYPTED_MESSAGE',
expect.anything()
);
});
it('should call socket.on(USER_DISCONNECT', () => {
handleSocket(peerConnection);
expect(peerConnection.socket.on).toBeCalledWith(
'USER_DISCONNECT',
expect.anything()
);
});
describe('when ENCRYPTED_MESSAGE event occured', () => {
it('should call receiveEncryptedMessage on peer connection object with proper payload', () => {
peerConnection.receiveEncryptedMessage = jest.fn();
const TEST_ENCRYPTED_MESSAGE_PAYLOAD = {
test: 'sfss',
};
initSocketWithListeners(peerConnection);
handleSocket(peerConnection);
peerConnection.socket.emit(
'ENCRYPTED_MESSAGE',
TEST_ENCRYPTED_MESSAGE_PAYLOAD
);
expect(peerConnection.receiveEncryptedMessage).toBeCalledWith(
TEST_ENCRYPTED_MESSAGE_PAYLOAD
);
});
});
describe('when USER_DISCONNECT event occured', () => {
it('should call .socket.emit with TOGGLE_LOCK_ROOM event', () => {
peerConnection.toggleLockRoom = jest.fn();
initSocketWithListeners(peerConnection);
handleSocket(peerConnection);
peerConnection.socket.emit('USER_DISCONNECT');
expect(peerConnection.toggleLockRoom).toBeCalledWith(false);
});
});
describe('when USER_ENTER event occured', () => {
it('should call handleSocketUserEnter callback', () => {
initSocketWithListeners(peerConnection);
handleSocket(peerConnection);
peerConnection.socket.emit('USER_ENTER');
expect(handleSocketUserEnter).toBeCalled();
});
});
describe('when USER_EXIT event occured', () => {
it('should call handleSocketUserEnter callback', () => {
initSocketWithListeners(peerConnection);
handleSocket(peerConnection);
peerConnection.socket.emit('USER_EXIT');
expect(handleSocketUserExit).toBeCalled();
});
});
describe('when "disconnect" event occured', () => {
it('should call .selfDestrory() callback', () => {
peerConnection.selfDestroy = jest.fn();
initSocketWithListeners(peerConnection);
handleSocket(peerConnection);
peerConnection.socket.emit('disconnect');
expect(peerConnection.selfDestroy).toBeCalled();
});
});
});
});

View File

@ -0,0 +1,44 @@
import handleSocketUserEnter from './handleSocketUserEnter';
import handleSocketUserExit from './handleSocketUserExit';
export default function handleSocket(peerConnection: PeerConnection) {
peerConnection.socket.removeAllListeners();
peerConnection.socket.on('disconnect', () => {
peerConnection.selfDestroy();
});
peerConnection.socket.on('connect', () => {
// peerConnection.emitUserEnter();
});
peerConnection.socket.on(
'USER_ENTER',
(payload: { users: PartnerPeerUser[] }) => {
handleSocketUserEnter(peerConnection, payload);
}
);
peerConnection.socket.on('USER_EXIT', () => {
handleSocketUserExit(peerConnection);
});
peerConnection.socket.on(
'ENCRYPTED_MESSAGE',
(payload: ReceiveEncryptedMessagePayload) => {
peerConnection.receiveEncryptedMessage(payload);
}
);
peerConnection.socket.on('USER_DISCONNECT', () => {
peerConnection.toggleLockRoom(false);
});
// socketConnection.on('TOGGLE_LOCK_ROOM', payload => {
// peerConnection.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload);
// });
// socketConnection.on('ROOM_LOCKED', payload => {
// peerConnection.props.openModal('Room Locked');
// });
}

View File

@ -0,0 +1,119 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import handleSocketUserEnter from './handleSocketUserEnter';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
const TEST_PARTNER_USER = {
username: 'asdfasdf',
publicKey: 'key:asdfasdffff',
};
const TEST_PAYLOAD = {
users: [TEST_PARTNER_USER],
};
function initSocketWithListeners(peerConnection: PeerConnection) {
const listeners: any = {};
peerConnection.socket = ({
on: (eventName: string, callback: (p: any) => void) => {
if (!listeners[eventName]) {
listeners[eventName] = [];
}
listeners[eventName].push(callback);
},
emit: (eventName: string, param: any) => {
if (listeners[eventName]) {
listeners[eventName].forEach((callback: (p: any) => void) => {
callback(param);
});
}
},
removeAllListeners: () => {},
} as unknown) as SocketIOClient.Socket;
}
describe('handleSocketUserEnter callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
// @ts-ignore
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
peerConnection.socket = ({
on: jest.fn(),
removeAllListeners: jest.fn(),
} as unknown) as SocketIOClient.Socket;
initSocketWithListeners(peerConnection);
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handleSocketUserEnter called properly', () => {
it('should set .partner to partner user', () => {
handleSocketUserEnter(peerConnection, TEST_PAYLOAD);
expect(peerConnection.partner).toBe(TEST_PARTNER_USER);
});
it('should set .sendEncryptedMessage with proper payload as it is an owner of room', () => {
const TEST_SEND_MESSAGE_PAYLOAD = {
type: 'ADD_USER',
payload: {
username: peerConnection.user.username,
publicKey: peerConnection.user.publicKey,
isOwner: true,
id: peerConnection.user.username,
},
};
peerConnection.sendEncryptedMessage = jest.fn();
handleSocketUserEnter(peerConnection, TEST_PAYLOAD);
expect(peerConnection.sendEncryptedMessage).toBeCalledWith(
TEST_SEND_MESSAGE_PAYLOAD
);
});
it('should call toggleLockRoom with true', () => {
peerConnection.toggleLockRoom = jest.fn();
handleSocketUserEnter(peerConnection, TEST_PAYLOAD);
expect(peerConnection.toggleLockRoom).toBeCalledWith(true);
});
it('should call emitUserEnter with true', () => {
peerConnection.emitUserEnter = jest.fn();
handleSocketUserEnter(peerConnection, TEST_PAYLOAD);
expect(peerConnection.emitUserEnter).toBeCalled();
});
});
});

View File

@ -0,0 +1,29 @@
export default (
peerConnection: PeerConnection,
payload: { users: PartnerPeerUser[] }
) => {
const filteredPartner = payload.users.filter((user: PartnerPeerUser) => {
return peerConnection.user.publicKey !== user.publicKey;
});
if (filteredPartner[0] === undefined) return;
[peerConnection.partner] = filteredPartner;
peerConnection.sendEncryptedMessage({
type: 'ADD_USER',
payload: {
username: peerConnection.user.username,
publicKey: peerConnection.user.publicKey,
isOwner: true,
id: peerConnection.user.username,
},
});
if (peerConnection.partner.publicKey !== '') {
// peerConnection.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
// peerConnection.isSocketRoomLocked = true;
peerConnection.toggleLockRoom(true);
peerConnection.emitUserEnter();
}
};

View File

@ -0,0 +1,61 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import handleSocketUserExit from './handleSocketUserExit';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
jest.useFakeTimers();
jest.mock('simple-peer');
describe('handleSocketUserExit callback', () => {
let peerConnection: PeerConnection;
beforeEach(() => {
// @ts-ignore
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME,
TEST_APP_LANGUAGE,
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
{} as DesktopCapturerSourcesService
);
peerConnection.socket = ({
on: jest.fn(),
removeAllListeners: jest.fn(),
} as unknown) as SocketIOClient.Socket;
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when handleSocketUserExit called properly', () => {
it('should call toggleLockRoom and selfDestroy', () => {
peerConnection.isSocketRoomLocked = true;
peerConnection.isCallStarted = true;
peerConnection.toggleLockRoom = jest.fn();
peerConnection.selfDestroy = jest.fn();
handleSocketUserExit(peerConnection);
expect(peerConnection.toggleLockRoom).toBeCalledWith(false);
expect(peerConnection.selfDestroy).toBeCalled();
});
});
});

View File

@ -0,0 +1,9 @@
export default (peerConnection: PeerConnection) => {
if (peerConnection.isSocketRoomLocked) {
peerConnection.toggleLockRoom(false);
if (peerConnection.isCallStarted) {
// TODO: display toast device is gone ....
peerConnection.selfDestroy();
}
}
};

View File

@ -0,0 +1,473 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { ipcRenderer } from 'electron';
import PeerConnection from '.';
import RoomIDService from '../../server/RoomIDService';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionService from '../SharingSessionService';
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
import {
TEST_APP_LANGUAGE,
TEST_APP_THEME,
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
} from './mocks/testVars';
import setDisplaySizeFromLocalStream from './handleSetDisplaySizeFromLocalStream';
import handleSelfDestroy from './handleSelfDestroy';
import handleRecieveEncryptedMessage from './handleRecieveEncryptedMessage';
import handleCreatePeer from './handleCreatePeer';
import { prepare as prepareMessage } from '../../utils/message';
jest.useFakeTimers();
jest.mock('simple-peer');
const TEST_SOURCE_DISPLAY_SIZE = {
width: 640,
height: 480,
};
const TEST_DATA_TO_SEND_IN_ENCRYPTED_MESSAGE = 'oji23oi12p34';
jest.mock('electron', () => {
return {
ipcRenderer: {
invoke: jest.fn().mockImplementation(() => {
return TEST_SOURCE_DISPLAY_SIZE;
}),
},
};
});
jest.mock('./handleSetDisplaySizeFromLocalStream');
jest.mock('./handleSelfDestroy');
jest.mock('../../utils/message', () => {
return {
prepare: jest.fn().mockReturnValue({
toSend: TEST_DATA_TO_SEND_IN_ENCRYPTED_MESSAGE,
}),
};
});
jest.mock('./handleRecieveEncryptedMessage');
jest.mock('./handleCreatePeer');
const TEST_DISPLAY_ID = '21';
describe('PeerConnection index.ts tests', () => {
let peerConnection: PeerConnection;
const mockGetSourceDisplayIDBySourceID = jest.fn().mockImplementation(() => {
return TEST_DISPLAY_ID;
});
beforeEach(() => {
peerConnection = new PeerConnection(
TEST_ROOM_ID,
TEST_SHARING_SESSION_ID,
TEST_USER,
TEST_APP_THEME, // TODO getAppTheme
TEST_APP_LANGUAGE, // TODO getLanguage
{} as RoomIDService,
{} as ConnectedDevicesService,
{} as SharingSessionService,
({} as unknown) as DesktopCapturerSourcesService
);
peerConnection.displayID = 'screen:123idid';
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('when PeerConnection constructor was called', () => {
it('should be created with internal properties correclty', () => {
expect(peerConnection.roomIDService).toBeDefined();
expect(peerConnection.connectedDevicesService).toBeDefined();
expect(peerConnection.sharingSessionService).toBeDefined();
});
describe('when setAppLanguage was called', () => {
it('should set peerConnection app language and call notifyClientWithNewLanguage', () => {
const TEST_APP_LANG = 'ua';
const mockNotify = jest.fn();
peerConnection.notifyClientWithNewLanguage = mockNotify;
peerConnection.setAppLanguage(TEST_APP_LANG);
expect(mockNotify).toBeCalled();
expect(peerConnection.appLanguage).toBe(TEST_APP_LANG);
});
});
describe('when setAppTheme was called', () => {
it('should set peerConnection theme and call notifyClientWithNewColorTheme', () => {
const APP_THEME = true;
const mockNotify = jest.fn();
peerConnection.notifyClientWithNewColorTheme = mockNotify;
peerConnection.setAppTheme(APP_THEME);
expect(mockNotify).toBeCalled();
expect(peerConnection.appColorTheme).toBe(APP_THEME);
});
});
describe('when notifyClientWithNewLanguage was called', () => {
it('should call sendEncryptedMessage with proper payload', () => {
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.notifyClientWithNewLanguage();
expect(peerConnection.sendEncryptedMessage).toBeCalledWith({
type: 'APP_LANGUAGE',
payload: { value: peerConnection.appLanguage },
});
});
});
describe('when notifyClientWithNewColorTheme was called', () => {
it('should call sendEncryptedMessage with proper payload', () => {
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.notifyClientWithNewColorTheme();
expect(peerConnection.sendEncryptedMessage).toBeCalledWith({
type: 'APP_THEME',
payload: { value: peerConnection.appColorTheme },
});
});
});
describe('when setDesktopCapturerSourceID was called', () => {
it('should set .desktopCapturerSourceID and call other callbacks', () => {
const testSourceID = 'screen:asdfsffs1234';
process.env.RUN_MODE = 'dev';
peerConnection.setDisplayIDByDesktopCapturerSourceID = jest.fn();
peerConnection.handleCreatePeerAfterDesktopCapturerSourceIDWasSet = jest.fn();
peerConnection.setDesktopCapturerSourceID(testSourceID);
process.env.RUN_MODE = 'test';
expect(peerConnection.desktopCapturerSourceID).toBe(testSourceID);
expect(
peerConnection.setDisplayIDByDesktopCapturerSourceID
).toBeCalled();
expect(
peerConnection.handleCreatePeerAfterDesktopCapturerSourceIDWasSet
).toBeCalled();
});
});
describe('when setDisplayIDByDesktopCapturerSourceID was called', () => {
describe('when desktopCapture source id is screen', () => {
it('should set .desktopCapturerSourceID and call other callbacks', () => {
peerConnection.desktopCapturerSourceID = 'screen:asdfa2';
peerConnection.setDisplaySizeRetreivedFromMainProcess = jest.fn();
peerConnection.desktopCapturerSourcesService = ({
getSourceDisplayIDBySourceID: mockGetSourceDisplayIDBySourceID,
} as unknown) as DesktopCapturerSourcesService;
peerConnection.setDisplayIDByDesktopCapturerSourceID();
expect(
peerConnection.setDisplaySizeRetreivedFromMainProcess
).toBeCalled();
expect(mockGetSourceDisplayIDBySourceID).toBeCalled();
expect(peerConnection.displayID).toBe(TEST_DISPLAY_ID);
});
});
describe('when desktopCapture source id is window', () => {
it('should not set anything', () => {
peerConnection.desktopCapturerSourceID = 'window:asdfa2';
peerConnection.setDisplaySizeRetreivedFromMainProcess = jest.fn();
peerConnection.desktopCapturerSourcesService = ({
getSourceDisplayIDBySourceID: mockGetSourceDisplayIDBySourceID,
} as unknown) as DesktopCapturerSourcesService;
peerConnection.setDisplayIDByDesktopCapturerSourceID();
expect(
peerConnection.setDisplaySizeRetreivedFromMainProcess
).not.toBeCalled();
expect(mockGetSourceDisplayIDBySourceID).not.toBeCalled();
expect(peerConnection.displayID).not.toBe(TEST_DISPLAY_ID);
});
});
});
describe('when setDisplaySizeRetreivedFromMainProcess was called', () => {
it('should call .invoke on ipcRenderer with proper parameters', async () => {
await peerConnection.setDisplaySizeRetreivedFromMainProcess();
expect(ipcRenderer.invoke).toBeCalledWith(
'get-display-size-by-display-id',
peerConnection.displayID
);
expect(peerConnection.sourceDisplaySize).toBe(TEST_SOURCE_DISPLAY_SIZE);
});
describe('when .invoke returned "undefined"', () => {
it('should not set sourceDisplaySize', async () => {
// @ts-ignore
ipcRenderer.invoke.mockImplementation(() => {
return 'undefined';
});
await peerConnection.setDisplaySizeRetreivedFromMainProcess();
expect(ipcRenderer.invoke).toBeCalledWith(
'get-display-size-by-display-id',
peerConnection.displayID
);
expect(peerConnection.sourceDisplaySize).not.toBe(
TEST_SOURCE_DISPLAY_SIZE
);
});
});
});
describe('when handleCreatePeerAfterDesktopCapturerSourceIDWasSet was called', () => {
describe('when .sourceDisplaySize is defined', () => {
it('should call setDisplaySizeFromLocalStream', async () => {
peerConnection.createPeer = jest.fn();
peerConnection.sourceDisplaySize = TEST_SOURCE_DISPLAY_SIZE;
await peerConnection.handleCreatePeerAfterDesktopCapturerSourceIDWasSet();
expect(peerConnection.createPeer).toBeCalled();
expect(setDisplaySizeFromLocalStream).not.toBeCalled();
});
});
describe('when .sourceDisplaySize is NOT defined', () => {
it('should call setDisplaySizeFromLocalStream', async () => {
peerConnection.createPeer = jest.fn();
await peerConnection.handleCreatePeerAfterDesktopCapturerSourceIDWasSet();
expect(peerConnection.createPeer).toBeCalled();
expect(setDisplaySizeFromLocalStream).toBeCalled();
});
});
});
describe('when setOnDeviceConnectedCallback was called properly', () => {
it('should set onDeviceConnectedCallback', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const testCallback = (_: Device) => {};
peerConnection.setOnDeviceConnectedCallback(testCallback);
expect(peerConnection.onDeviceConnectedCallback).toBe(testCallback);
});
});
describe('when denyConnectionForPartner was called properly', () => {
it('should call sendEncryptedMessage with proper payload and call .disconnectPartner', async () => {
const testPayload = {
type: 'DENY_TO_CONNECT',
payload: {},
};
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.disconnectPartner = jest.fn();
await peerConnection.denyConnectionForPartner();
expect(peerConnection.sendEncryptedMessage).toBeCalledWith(testPayload);
expect(peerConnection.disconnectPartner).toBeCalled();
});
});
describe('when sendUserAllowedToConnect was called properly', () => {
it('should call sendEncryptedMessage with proper payload', () => {
const testPayload = {
type: 'ALLOWED_TO_CONNECT',
payload: {},
};
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.sendUserAllowedToConnect();
expect(peerConnection.sendEncryptedMessage).toBeCalledWith(testPayload);
});
});
describe('when disconnectByHostMachineUser was called properly', () => {
it('should call sendEncryptedMessage with proper payload and call .disconnectPartner and .selfDestroy', async () => {
const testPayload = {
type: 'DISCONNECT_BY_HOST_MACHINE_USER',
payload: {},
};
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.disconnectPartner = jest.fn();
peerConnection.selfDestroy = jest.fn();
await peerConnection.disconnectByHostMachineUser();
expect(peerConnection.sendEncryptedMessage).toBeCalledWith(testPayload);
expect(peerConnection.disconnectPartner).toBeCalled();
expect(peerConnection.selfDestroy).toBeCalled();
});
});
describe('when disconnectPartner was called properly', () => {
it('should call sendEncryptedMessage with proper payload', () => {
const testEmitData = {
ip: peerConnection.partnerDeviceDetails.deviceIP,
};
peerConnection.socket = ({
emit: jest.fn(),
} as unknown) as SocketIOClient.Socket;
peerConnection.disconnectPartner();
expect(peerConnection.socket.emit).toBeCalledWith(
'DISCONNECT_SOCKET_BY_DEVICE_IP',
testEmitData
);
expect(peerConnection.partnerDeviceDetails).toEqual({});
});
});
describe('when selfDestroy was called', () => {
it('should call handleSelfDestroy', () => {
peerConnection.selfDestroy();
expect(handleSelfDestroy).toBeCalled();
});
});
describe('when emitUserEnter was called', () => {
describe('when .socket is defined', () => {
it('should call socket emit with proper parameters', () => {
const testEmitData = {
username: peerConnection.user.username,
publicKey: peerConnection.user.publicKey,
};
peerConnection.socket = ({
emit: jest.fn(),
} as unknown) as SocketIOClient.Socket;
peerConnection.emitUserEnter();
expect(peerConnection.socket.emit).toBeCalledWith(
'USER_ENTER',
testEmitData
);
});
});
});
describe('when sendEncryptedMessage was called', () => {
describe('when it was NOT called properly', () => {
it('should not call "prepare" from message.ts if socket is not defined', () => {
peerConnection.socket = (undefined as unknown) as SocketIOClient.Socket;
peerConnection.sendEncryptedMessage(
({} as unknown) as SendEncryptedMessagePayload
);
expect(prepareMessage).not.toBeCalled();
});
it('should not call "prepare" from message.ts if user is not defined', () => {
peerConnection.user = (undefined as unknown) as LocalPeerUser;
peerConnection.sendEncryptedMessage(
({} as unknown) as SendEncryptedMessagePayload
);
expect(prepareMessage).not.toBeCalled();
});
it('should not call "prepare" from message.ts if partner is not defined', () => {
peerConnection.partner = (undefined as unknown) as LocalPeerUser;
peerConnection.sendEncryptedMessage(
({} as unknown) as SendEncryptedMessagePayload
);
expect(prepareMessage).not.toBeCalled();
});
});
describe('when it was called properly', () => {
it('should call "prepare" from message.ts and .socket.emit(ENCRYPTED_MESSAGE', async () => {
const testPayload = ({} as unknown) as SendEncryptedMessagePayload;
peerConnection.socket = ({
emit: jest.fn(),
} as unknown) as SocketIOClient.Socket;
peerConnection.partner = TEST_USER;
await peerConnection.sendEncryptedMessage(testPayload);
expect(prepareMessage).toBeCalledWith(
testPayload,
TEST_USER,
TEST_USER
);
expect(peerConnection.socket.emit).toBeCalledWith(
'ENCRYPTED_MESSAGE',
TEST_DATA_TO_SEND_IN_ENCRYPTED_MESSAGE
);
});
});
});
describe('when receiveEncryptedMessage was called', () => {
describe('when peerConnection user is NOT defined', () => {
it('should NOT call handleRecieveEncryptedMessage', () => {
const testPayload = {} as ReceiveEncryptedMessagePayload;
peerConnection.user = (undefined as unknown) as LocalPeerUser;
peerConnection.receiveEncryptedMessage(testPayload);
expect(handleRecieveEncryptedMessage).not.toBeCalled();
});
});
describe('when peerConnection user is defined', () => {
it('should call handleRecieveEncryptedMessage', () => {
const testPayload = {} as ReceiveEncryptedMessagePayload;
peerConnection.receiveEncryptedMessage(testPayload);
expect(handleRecieveEncryptedMessage).toBeCalled();
});
});
});
describe('when callPeer was called', () => {
describe('when it was called when call already started', () => {
it('should NOT call .sendEncryptedMessage', () => {
process.env.RUN_MODE = 'dev';
peerConnection.isCallStarted = true;
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.signalsDataToCallUser = ['asdfasdf'];
peerConnection.callPeer();
process.env.RUN_MODE = 'test';
expect(peerConnection.sendEncryptedMessage).not.toBeCalled();
});
});
describe('when it was called when call NOT started', () => {
it('should call .sendEncryptedMessage', () => {
process.env.RUN_MODE = 'dev';
peerConnection.sendEncryptedMessage = jest.fn();
peerConnection.signalsDataToCallUser = ['asdfasdf'];
peerConnection.callPeer();
process.env.RUN_MODE = 'test';
expect(peerConnection.sendEncryptedMessage).toBeCalled();
});
});
});
describe('when createPeer was called', () => {
it('should call handleCreatePeer callback', () => {
peerConnection.createPeer();
expect(handleCreatePeer).toBeCalled();
});
});
});
});

View File

@ -1,53 +1,25 @@
/* eslint-disable promise/catch-or-return */
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/lines-between-class-members */
import { remote, ipcRenderer } from 'electron';
import uuid from 'uuid';
import SimplePeer from 'simple-peer';
import {
prepare as prepareMessage,
process as processMessage,
} from '../../utils/message';
import { ipcRenderer } from 'electron';
import { prepare as prepareMessage } from '../../utils/message';
import DeskreenCrypto from '../../utils/crypto';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionStatusEnum from '../SharingSessionsService/SharingSessionStatusEnum';
import RoomIDService from '../../server/RoomIDService';
import SharingSessionsService from '../SharingSessionsService';
import SharingSessionService from '../SharingSessionService';
import connectSocket from '../../server/connectSocket';
import Logger from '../../utils/LoggerWithFilePrefix';
import DesktopCapturerSources from '../DesktopCapturerSourcesService';
import setSdpMediaBitrate from './setSdpMediaBitrate';
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
import prepareDataMessageToSendScreenSourceType from './prepareDataMessageToSendScreenSourceType';
const log = new Logger(__filename);
interface PartnerPeerUser {
username: string;
publicKey: string;
}
interface ReceiveEncryptedMessagePayload {
payload: string;
signature: string;
iv: string;
keys: { sessionKey: string; signingKey: string }[];
}
interface SendEncryptedMessagePayload {
type: string;
payload: Record<string, unknown>;
}
import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
import handleCreatePeer from './handleCreatePeer';
import handleSocket from './handleSocket';
import handleRecieveEncryptedMessage from './handleRecieveEncryptedMessage';
import handleSelfDestroy from './handleSelfDestroy';
import NullUser from './NullUser';
import NullSimplePeer from './NullSimplePeer';
import setDisplaySizeFromLocalStream from './handleSetDisplaySizeFromLocalStream';
import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
type DisplaySize = { width: number; height: number };
const desktopCapturerSourcesService = remote.getGlobal(
'desktopCapturerSourcesService'
) as DesktopCapturerSources;
const nullUser = { username: '', publicKey: '', privateKey: '' };
const nullSimplePeer = new SimplePeer();
export default class PeerConnection {
sharingSessionID: string;
roomID: string;
@ -55,7 +27,7 @@ export default class PeerConnection {
crypto: DeskreenCrypto;
user: LocalPeerUser;
partner: PartnerPeerUser;
peer = nullSimplePeer;
peer = NullSimplePeer;
desktopCapturerSourceID: string;
localStream: MediaStream | null;
isSocketRoomLocked: boolean;
@ -64,10 +36,9 @@ export default class PeerConnection {
isCallStarted: boolean;
roomIDService: RoomIDService;
connectedDevicesService: ConnectedDevicesService;
sharingSessionsService: SharingSessionsService;
sharingSessionService: SharingSessionService;
desktopCapturerSourcesService: DesktopCapturerSourcesService;
onDeviceConnectedCallback: (device: Device) => void;
prevStreamWidth: number;
prevStreamHeight: number;
displayID: string;
sourceDisplaySize: DisplaySize | undefined;
appLanguage: string;
@ -81,31 +52,35 @@ export default class PeerConnection {
appLanguage: string,
roomIDService: RoomIDService,
connectedDevicesService: ConnectedDevicesService,
sharingSessionsService: SharingSessionsService
sharingSessionsService: SharingSessionService,
desktopCapturerSourcesService: DesktopCapturerSourcesService
) {
this.roomIDService = roomIDService;
this.connectedDevicesService = connectedDevicesService;
this.sharingSessionsService = sharingSessionsService;
this.sharingSessionService = sharingSessionsService;
this.sharingSessionID = sharingSessionID;
this.isSocketRoomLocked = false;
this.roomID = encodeURI(roomID);
this.crypto = new DeskreenCrypto();
this.socket = connectSocket(this.roomID);
this.user = user;
this.partner = nullUser;
this.partner = NullUser;
this.desktopCapturerSourceID = '';
this.signalsDataToCallUser = [];
this.isCallStarted = false;
this.localStream = null;
this.prevStreamWidth = -1;
this.prevStreamHeight = -1;
this.displayID = '';
this.sourceDisplaySize = undefined;
this.appLanguage = appLanguage;
this.appColorTheme = appColorTheme;
this.desktopCapturerSourcesService = desktopCapturerSourcesService;
this.onDeviceConnectedCallback = () => {};
this.initSocketWhenUserCreatedCallback();
handleSocket(this);
window.addEventListener('beforeunload', () => {
this.socket.emit('USER_DISCONNECT');
});
}
setAppLanguage(lang: string) {
@ -132,32 +107,44 @@ export default class PeerConnection {
});
}
setDesktopCapturerSourceID(id: string) {
async setDesktopCapturerSourceID(id: string) {
this.desktopCapturerSourceID = id;
if (process.env.RUN_MODE === 'test') return;
if (id.includes('screen')) {
this.displayID = desktopCapturerSourcesService.getSourceDisplayIDBySourceID(
id
this.setDisplayIDByDesktopCapturerSourceID();
this.handleCreatePeerAfterDesktopCapturerSourceIDWasSet();
}
setDisplayIDByDesktopCapturerSourceID() {
if (
!this.desktopCapturerSourceID.includes(DesktopCapturerSourceType.SCREEN)
)
return;
this.displayID = this.desktopCapturerSourcesService.getSourceDisplayIDBySourceID(
this.desktopCapturerSourceID
);
if (this.displayID !== '') {
ipcRenderer
.invoke('get-display-size-by-display-id', this.displayID)
.then((size: DisplaySize | 'undefined') => {
this.setDisplaySizeRetreivedFromMainProcess();
}
}
async setDisplaySizeRetreivedFromMainProcess() {
const size: DisplaySize | 'undefined' = await ipcRenderer.invoke(
'get-display-size-by-display-id',
this.displayID
);
if (size !== 'undefined') {
this.sourceDisplaySize = size;
}
return size;
})
.then(async () => {
await this.createPeer();
this.setDisplaySizeFromLocalStream();
return undefined;
});
}
} else {
this.createPeer();
async handleCreatePeerAfterDesktopCapturerSourceIDWasSet() {
await this.createPeer();
if (!this.sourceDisplaySize) {
setDisplaySizeFromLocalStream(this);
}
}
@ -165,32 +152,12 @@ export default class PeerConnection {
this.onDeviceConnectedCallback = callback;
}
setDisplaySizeFromLocalStream() {
if (!this.localStream || !this.localStream.getVideoTracks()[0]) return;
if (!this.localStream.getVideoTracks()[0].getSettings().width) return;
if (!this.localStream.getVideoTracks()[0].getSettings().height) return;
this.sourceDisplaySize = {
width: this.localStream.getVideoTracks()[0].getSettings().width
? (this.localStream.getVideoTracks()[0].getSettings().width as number)
: 640,
height: this.localStream.getVideoTracks()[0].getSettings().height
? (this.localStream.getVideoTracks()[0].getSettings().height as number)
: 480,
};
}
denyConnectionForPartner() {
this.sendEncryptedMessage({
async denyConnectionForPartner() {
await this.sendEncryptedMessage({
type: 'DENY_TO_CONNECT',
payload: {},
})
// eslint-disable-next-line promise/always-return
.then(() => {
this.disconnectPartner();
})
.catch((e) => {
log.error(e);
});
this.disconnectPartner();
}
sendUserAllowedToConnect() {
@ -200,19 +167,13 @@ export default class PeerConnection {
});
}
disconnectByHostMachineUser() {
this.sendEncryptedMessage({
async disconnectByHostMachineUser() {
await this.sendEncryptedMessage({
type: 'DISCONNECT_BY_HOST_MACHINE_USER',
payload: {},
})
// eslint-disable-next-line promise/always-return
.then(() => {
this.disconnectPartner();
this.selfDestrory();
})
.catch((e) => {
log.error(e);
});
this.disconnectPartner();
this.selfDestroy();
}
disconnectPartner() {
@ -223,106 +184,11 @@ export default class PeerConnection {
this.partnerDeviceDetails = {} as Device;
}
initSocketWhenUserCreatedCallback() {
this.socket.removeAllListeners();
this.socket.on('disconnect', () => {
this.selfDestrory();
});
this.socket.on('connect', () => {
// this.emitUserEnter();
});
this.socket.on('USER_ENTER', (payload: { users: PartnerPeerUser[] }) => {
const filteredPartner = payload.users.filter((user: PartnerPeerUser) => {
return this.user.publicKey !== user.publicKey;
});
if (filteredPartner[0] === undefined) return;
[this.partner] = filteredPartner;
this.sendEncryptedMessage({
type: 'ADD_USER',
payload: {
username: this.user.username,
publicKey: this.user.publicKey,
isOwner: true,
id: this.user.username,
},
});
if (this.partner.publicKey !== '') {
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {
this.isSocketRoomLocked = true;
this.emitUserEnter();
});
}
});
this.socket.on('USER_EXIT', () => {
if (this.isSocketRoomLocked) {
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
this.isSocketRoomLocked = false;
if (this.isCallStarted) {
// TODO: display toast device is gone ....
this.selfDestrory();
}
}
});
this.socket.on(
'ENCRYPTED_MESSAGE',
(payload: ReceiveEncryptedMessagePayload) => {
this.receiveEncryptedMessage(payload);
}
);
this.socket.on('USER_DISCONNECT', () => {
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
});
// socketConnection.on('TOGGLE_LOCK_ROOM', payload => {
// this.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload);
// });
// socketConnection.on('ROOM_LOCKED', payload => {
// this.props.openModal('Room Locked');
// });
window.addEventListener('beforeunload', () => {
this.socket.emit('USER_DISCONNECT');
});
}
selfDestrory() {
this.partner = nullUser;
this.connectedDevicesService.removeDeviceByID(this.partnerDeviceDetails.id);
if (this.peer !== nullSimplePeer) {
this.peer.destroy();
}
if (this.localStream) {
this.localStream.getTracks().forEach((track) => {
track.stop();
});
this.localStream = null;
}
const sharingSession = this.sharingSessionsService.sharingSessions.get(
this.sharingSessionID
);
sharingSession?.setStatus(SharingSessionStatusEnum.DESTROYED);
sharingSession?.destory();
this.sharingSessionsService.sharingSessions.delete(this.sharingSessionID);
this.onDeviceConnectedCallback = () => {};
this.isCallStarted = false;
this.socket.disconnect();
this.roomIDService.unmarkRoomIDAsTaken(this.roomID);
selfDestroy() {
handleSelfDestroy(this);
}
emitUserEnter() {
if (!this.socket) return;
this.socket.emit('USER_ENTER', {
username: this.user.username,
publicKey: this.user.publicKey,
@ -337,44 +203,9 @@ export default class PeerConnection {
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
}
async receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
if (!this.user) return;
const message = await processMessage(payload, this.user.privateKey);
if (message.type === 'CALL_ACCEPTED') {
this.peer.signal(message.payload.signalData);
}
if (message.type === 'DEVICE_DETAILS') {
this.socket.emit(
'GET_IP_BY_SOCKET_ID',
message.payload.socketID,
(deviceIP: string) => {
const device = {
id: uuid.v4(),
deviceIP,
deviceType: message.payload.deviceType,
deviceOS: message.payload.os,
deviceBrowser: message.payload.browser,
deviceScreenWidth: message.payload.deviceScreenWidth,
deviceScreenHeight: message.payload.deviceScreenHeight,
sharingSessionID: this.sharingSessionID,
};
this.partnerDeviceDetails = device;
this.onDeviceConnectedCallback(device);
}
);
}
if (message.type === 'GET_APP_THEME') {
this.sendEncryptedMessage({
type: 'APP_THEME',
payload: { value: this.appColorTheme },
});
}
if (message.type === 'GET_APP_LANGUAGE') {
this.sendEncryptedMessage({
type: 'APP_LANGUAGE',
payload: { value: this.appLanguage },
});
}
handleRecieveEncryptedMessage(this, payload);
}
callPeer() {
@ -393,111 +224,11 @@ export default class PeerConnection {
}
createPeer() {
return new Promise((resolve) => {
this.createDesktopCapturerStream(this.desktopCapturerSourceID).then(
() => {
const peer = new SimplePeer({
initiator: true,
// trickle: true,
// stream: this.localStream,
// allowHalfTrickle: false,
config: { iceServers: [] },
sdpTransform: (sdp) => {
let newSDP = sdp;
newSDP = setSdpMediaBitrate(
newSDP as string,
'video',
500000
) as typeof sdp;
return newSDP;
},
});
// eslint-disable-next-line promise/always-return
if (this.localStream !== null) {
peer.addStream(this.localStream);
return handleCreatePeer(this);
}
peer.on('signal', (data: string) => {
// fired when simple peer and webrtc done preparation to start call on this machine
this.signalsDataToCallUser.push(data);
});
this.peer = peer;
this.peer.on('data', async (data) => {
const dataJSON = JSON.parse(data);
if (dataJSON.type === 'set_video_quality') {
const maxVideoQualityMultiplier = dataJSON.payload.value;
const minVideoQualityMultiplier =
maxVideoQualityMultiplier === 1
? 0.5
: maxVideoQualityMultiplier;
if (!this.desktopCapturerSourceID.includes('screen')) return;
const newStream = await getDesktopSourceStreamBySourceID(
this.desktopCapturerSourceID,
this.sourceDisplaySize?.width,
this.sourceDisplaySize?.height,
minVideoQualityMultiplier,
maxVideoQualityMultiplier
);
const newVideoTrack = newStream.getVideoTracks()[0];
const oldTrack = this.localStream?.getVideoTracks()[0];
if (oldTrack && this.localStream) {
peer.replaceTrack(oldTrack, newVideoTrack, this.localStream);
oldTrack.stop();
}
}
if (dataJSON.type === 'get_sharing_source_type') {
const sourceType = this.desktopCapturerSourceID.includes('screen')
? 'screen'
: 'window';
this.peer.send(
prepareDataMessageToSendScreenSourceType(sourceType)
);
}
});
resolve(undefined);
}
);
});
}
// TODO: move outside this file
createDesktopCapturerStream(sourceID: string) {
return new Promise((resolve) => {
try {
if (process.env.RUN_MODE === 'test') resolve(undefined);
if (!sourceID.includes('screen')) {
getDesktopSourceStreamBySourceID(sourceID).then((stream) => {
this.localStream = stream;
resolve(undefined);
return stream;
});
} else {
// when screen source id
getDesktopSourceStreamBySourceID(
sourceID,
this.sourceDisplaySize?.width,
this.sourceDisplaySize?.height,
0.5,
1
).then((stream) => {
this.localStream = stream;
resolve(undefined);
return stream;
});
}
} catch (e) {
log.error(e);
}
});
toggleLockRoom(isConnected: boolean) {
this.socket.emit('TOGGLE_LOCK_ROOM');
this.isSocketRoomLocked = isConnected;
}
}

View File

@ -0,0 +1,109 @@
// eslint-disable-next-line import/prefer-default-export
export const INPUTtestSdpMediaBitrate = `
v=0
o=- 5730467698688819135 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 114 115 116
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:PY+h
a=ice-pwd:eYoy9PHXsilgXAbK7MSIMUJc
a=ice-options:trickle
a=fingerprint:sha-256 73:1D:63:11:3E:2F:A4:AA:ED:37:4B:D6:0F:A2:60:7A:A3:9B:EC:D9:D1:AF:C3:E0:53:59:4A:E1:D5:A9:EF:2D
a=setup:active
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 urn:3gpp:video-orientation
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
b=AS:30
a=ice-ufrag:PY+h
a=ice-pwd:eYoy9PHXsilgXAbK7MSIMUJc
a=ice-options:trickle
a=fingerprint:sha-256 73:1D:63:11:3E:2F:A4:AA:ED:37:4B:D6:0F:A2:60:7A:A3:9B:EC:D9:D1:AF:C3:E0:53:59:4A:E1:D5:A9:EF:2D
a=setup:active
a=mid:1
a=sctp-port:5000
a=max-message-size:262144
`;

View File

@ -0,0 +1,110 @@
// eslint-disable-next-line import/prefer-default-export
export const OUTPUTtestSdpMediaBitrate = `
v=0
o=- 5730467698688819135 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 114 115 116
c=IN IP4 0.0.0.0
b=AS:500000
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:PY+h
a=ice-pwd:eYoy9PHXsilgXAbK7MSIMUJc
a=ice-options:trickle
a=fingerprint:sha-256 73:1D:63:11:3E:2F:A4:AA:ED:37:4B:D6:0F:A2:60:7A:A3:9B:EC:D9:D1:AF:C3:E0:53:59:4A:E1:D5:A9:EF:2D
a=setup:active
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 urn:3gpp:video-orientation
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
b=AS:30
a=ice-ufrag:PY+h
a=ice-pwd:eYoy9PHXsilgXAbK7MSIMUJc
a=ice-options:trickle
a=fingerprint:sha-256 73:1D:63:11:3E:2F:A4:AA:ED:37:4B:D6:0F:A2:60:7A:A3:9B:EC:D9:D1:AF:C3:E0:53:59:4A:E1:D5:A9:EF:2D
a=setup:active
a=mid:1
a=sctp-port:5000
a=max-message-size:262144
`;

View File

@ -0,0 +1,9 @@
export const TEST_ROOM_ID = '1';
export const TEST_SHARING_SESSION_ID = '123';
export const TEST_USER = {
username: 'asd',
publicKey: 'nvxm,zv',
privateKey: '14234',
};
export const TEST_APP_THEME = false;
export const TEST_APP_LANGUAGE = 'en';

View File

@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { INPUTtestSdpMediaBitrate } from './mocks/INPUTvideo500000testSdpMediaBitrate';
import { OUTPUTtestSdpMediaBitrate } from './mocks/OUTPUTvideo500000testSdpMediaBitrate';
import setSdpMediaBitrate from './setSdpMediaBitrate';
describe('when setSdpMediaBitrate is called', () => {
it('should return proper sdp media bitrate', () => {
const res = setSdpMediaBitrate(INPUTtestSdpMediaBitrate, 'video', 500000);
expect(res).toEqual(OUTPUTtestSdpMediaBitrate);
});
});

Some files were not shown because too many files have changed in this diff Show More