Writing host tests, adding deskreen icon
@ -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>
|
||||
|
138
app/__snapshots__/menu.spec.ts.snap
Normal 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",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
`;
|
@ -1,4 +1,5 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
let host;
|
||||
let protocol;
|
||||
let port;
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import config from './config';
|
||||
|
||||
export default (resourceName = '') => {
|
||||
|
@ -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%);
|
||||
}
|
||||
|
BIN
app/app.icns
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 15 KiB |
@ -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>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 46 KiB |
@ -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",
|
||||
|
@ -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/**
|
||||
|
@ -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={
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
|
@ -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}
|
||||
|
@ -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: {
|
||||
|
6
app/client/src/containers/MainView/ConnectionIconEnum.ts
Normal file
@ -0,0 +1,6 @@
|
||||
enum ConnectionIcon {
|
||||
FEED = 'feed',
|
||||
FEED_SUBSCRIBED = 'feed-subscribed'
|
||||
}
|
||||
|
||||
export default ConnectionIcon;
|
@ -0,0 +1,6 @@
|
||||
enum LoadingSharingIconEnum {
|
||||
DESKTOP = 'desktop',
|
||||
APPLICATION = 'application'
|
||||
}
|
||||
|
||||
export default LoadingSharingIconEnum;
|
@ -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);
|
||||
|
@ -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!
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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={
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
|
1
app/client/src/features/PeerConnection/PeerConnection.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
type PeerConnection = import('.').default;
|
@ -0,0 +1,6 @@
|
||||
enum ScreenSharingSource {
|
||||
WINDOW = 'window',
|
||||
SCREEN = 'screen',
|
||||
}
|
||||
|
||||
export default ScreenSharingSource;
|
@ -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) {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -110,6 +110,8 @@ describe('peerConnectionHandleSocket callback', () => {
|
||||
|
||||
peerConnection.socket.emit('connect');
|
||||
|
||||
jest.advanceTimersByTime(600);
|
||||
|
||||
expect(peerConnection.socket.emit).toBeCalledWith(
|
||||
'GET_MY_IP',
|
||||
expect.anything()
|
||||
|
@ -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', () => {
|
||||
|
@ -192,6 +192,7 @@ describe('peerConnectionReceiveEncryptedMessage', () => {
|
||||
peerConnection,
|
||||
CALL_USER_PAYLOAD
|
||||
);
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(peerConnection.peer?.signal).toBeCalledWith('1signal');
|
||||
});
|
||||
|
@ -1,4 +1,3 @@
|
||||
import PeerConnection from '.';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import { process as processMessage } from '../../utils/message';
|
||||
import NullUser from './NullUser';
|
||||
|
@ -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
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 11 KiB |
12
app/client/src/react-app-env.d.ts
vendored
@ -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;
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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}
|
||||
>
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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,
|
||||
}}
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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"
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
export default {
|
||||
fallbackLng: 'en',
|
||||
namespace: 'translation',
|
||||
|
45
app/configs/i18next.config.client.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import i18n from 'i18next';
|
||||
import i18nextBackend from 'i18next-node-fs-backend';
|
||||
import { join } from 'path';
|
||||
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
@ -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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -8,3 +8,4 @@ export const remote = {
|
||||
return '';
|
||||
},
|
||||
};
|
||||
export const ipcRenderer = jest.fn();
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
132
app/features/ConnectedDevicesService/index.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
@ -1,7 +0,0 @@
|
||||
import DesktopCapturerSourceType from './DesktopCapturerSourceType';
|
||||
|
||||
interface DesktopCapturerSource {
|
||||
id: string;
|
||||
type: DesktopCapturerSourceType;
|
||||
name: string;
|
||||
}
|
@ -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;
|
||||
|
4
app/features/DesktopCapturerSourcesService/DesktopCapturerSourceWithType.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
interface DesktopCapturerSourceWithType {
|
||||
source: import('electron').DesktopCapturerSource;
|
||||
type: import('./DesktopCapturerSourceType').default;
|
||||
}
|
310
app/features/DesktopCapturerSourcesService/index.spec.ts
Normal 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
|
||||
);
|
||||
});
|
||||
});
|
@ -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
|
||||
}
|
||||
}
|
||||
|
5
app/features/PeerConnection/NullSimplePeer.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import SimplePeer from 'simple-peer';
|
||||
|
||||
const NullSimplePeer = new SimplePeer();
|
||||
|
||||
export default NullSimplePeer;
|
1
app/features/PeerConnection/NullUser.ts
Normal file
@ -0,0 +1 @@
|
||||
export default { username: '', publicKey: '', privateKey: '' };
|
4
app/features/PeerConnection/PartnerPeerUser.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
interface PartnerPeerUser {
|
||||
username: string;
|
||||
publicKey: string;
|
||||
}
|
3
app/features/PeerConnection/PeerConnection.d.ts
vendored
Normal 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;
|
6
app/features/PeerConnection/ReceiveEncryptedMessagePayload.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
interface ReceiveEncryptedMessagePayload {
|
||||
payload: string;
|
||||
signature: string;
|
||||
iv: string;
|
||||
keys: { sessionKey: string; signingKey: string }[];
|
||||
}
|
4
app/features/PeerConnection/SendEncryptedMessagePayload.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
interface SendEncryptedMessagePayload {
|
||||
type: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
32
app/features/PeerConnection/createDesktopCapturerStream.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
147
app/features/PeerConnection/handleCreatePeer.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
47
app/features/PeerConnection/handleCreatePeer.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
208
app/features/PeerConnection/handlePeerOnData.spec.ts
Normal 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
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
54
app/features/PeerConnection/handlePeerOnData.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
53
app/features/PeerConnection/handleRecieveEncryptedMessage.ts
Normal 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 },
|
||||
});
|
||||
}
|
||||
}
|
136
app/features/PeerConnection/handleSelfDestroy.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
31
app/features/PeerConnection/handleSelfDestroy.ts
Normal 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);
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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,
|
||||
};
|
||||
}
|
199
app/features/PeerConnection/handleSocket.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
44
app/features/PeerConnection/handleSocket.ts
Normal 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');
|
||||
// });
|
||||
}
|
119
app/features/PeerConnection/handleSocketUserEnter.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
29
app/features/PeerConnection/handleSocketUserEnter.ts
Normal 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();
|
||||
}
|
||||
};
|
61
app/features/PeerConnection/handleSocketUserExit.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
9
app/features/PeerConnection/handleSocketUserExit.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
};
|
473
app/features/PeerConnection/index.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
`;
|
@ -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
|
||||
`;
|
9
app/features/PeerConnection/mocks/testVars.ts
Normal 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';
|
12
app/features/PeerConnection/setSdpMediaBitrate.spec.ts
Normal 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);
|
||||
});
|
||||
});
|