mirror of
https://github.com/pavlobu/deskreen.git
synced 2025-05-28 05:10:09 -07:00
Merge pull request #14 from pavlobu/tests
Client Tests and Github Action fixes
This commit is contained in:
commit
7fb1114035
46
.github/workflows/codecov.yml
vendored
46
.github/workflows/codecov.yml
vendored
@ -8,54 +8,20 @@ jobs:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2.3.1
|
||||
|
||||
# - name: Install Node.js, NPM and Yarn
|
||||
# env:
|
||||
# ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
# uses: actions/setup-node@v1.4.2
|
||||
# with:
|
||||
# node-version: 14
|
||||
|
||||
# - name: yarn install from npmjs registry
|
||||
# env:
|
||||
# ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
# run: |
|
||||
# npm config set registry https://registry.npmjs.org
|
||||
# cd app/client
|
||||
# yarn install --no-lockfile
|
||||
# cd ..
|
||||
# yarn install --no-lockfile
|
||||
# cd ..
|
||||
# yarn install --no-lockfile
|
||||
|
||||
- name: configure app/client for yarn cache
|
||||
- name: install yarn dependencies in app/client using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./app/client
|
||||
|
||||
- name: configure project root for yarn cache
|
||||
- name: install yarn dependencies in ./ using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./
|
||||
|
||||
- name: ./app/client yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: ./app/client
|
||||
- name: ./ yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: ./
|
||||
|
||||
# - name: Configure private AWS npm registry and install packages from it
|
||||
# env:
|
||||
# ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
# if: ${{ failure() }}
|
||||
# run: |
|
||||
# npm config set registry https://packages.deskreen.com/
|
||||
# npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"
|
||||
# npm config set always-auth true
|
||||
# cd app/client
|
||||
# yarn install --frozen-lockfile
|
||||
# cd ../..
|
||||
# yarn install --frozen-lockfile
|
||||
- name: install yarn dependencies in ./app using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./app
|
||||
|
||||
- name: yarn build
|
||||
run: yarn build
|
||||
|
69
.github/workflows/release.yml
vendored
69
.github/workflows/release.yml
vendored
@ -41,37 +41,52 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2.3.1
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
uses: actions/setup-node@v1.4.2
|
||||
- name: install yarn dependencies in app/client using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
node-version: 14
|
||||
working-directory: ./app/client
|
||||
|
||||
- name: yarn install from npmjs registry
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
run: |
|
||||
npm config set registry https://registry.npmjs.org
|
||||
cd app/client
|
||||
yarn install --no-lockfile
|
||||
cd ..
|
||||
yarn install --no-lockfile
|
||||
cd ..
|
||||
yarn install --no-lockfile
|
||||
- name: install yarn dependencies in ./ using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./
|
||||
|
||||
- name: Configure private AWS npm registry and install packages from it
|
||||
- name: install yarn dependencies in ./app using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./app
|
||||
|
||||
- name: yarn build
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
npm config set registry https://packages.deskreen.com/
|
||||
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"
|
||||
npm config set always-auth true
|
||||
cd app/client
|
||||
yarn install --frozen-lockfile
|
||||
cd ../..
|
||||
yarn install --frozen-lockfile
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: yarn build
|
||||
|
||||
- name: yarn lint
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: yarn lint
|
||||
|
||||
- name: yarn tsc
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: yarn tsc
|
||||
|
||||
- name: yarn test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: yarn test
|
||||
|
||||
- name: yarn build-ux
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: yarn build-ux
|
||||
|
||||
- name: yarn test-ux
|
||||
uses: GabrielBB/xvfb-action@v1.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
run: yarn test-ux
|
||||
|
||||
- name: yarn package-ci
|
||||
env:
|
||||
|
39
.github/workflows/test.yml
vendored
39
.github/workflows/test.yml
vendored
@ -16,37 +16,20 @@ jobs:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2.3.1
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
uses: actions/setup-node@v1.4.2
|
||||
- name: install yarn dependencies in app/client using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
node-version: 14
|
||||
working-directory: ./app/client
|
||||
|
||||
- name: yarn install from npmjs registry
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
run: |
|
||||
npm config set registry https://registry.npmjs.org
|
||||
cd app/client
|
||||
yarn install --no-lockfile
|
||||
cd ..
|
||||
yarn install --no-lockfile
|
||||
cd ..
|
||||
yarn install --no-lockfile
|
||||
- name: install yarn dependencies in ./ using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./
|
||||
|
||||
- name: Configure private AWS npm registry and install packages from it
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
npm config set registry https://packages.deskreen.com/
|
||||
npm set //packages.deskreen.com/:_authToken="${{ secrets.NPMRC_USER_TOKEN }}"
|
||||
npm config set always-auth true
|
||||
cd app/client
|
||||
yarn install --frozen-lockfile
|
||||
cd ../..
|
||||
yarn install --frozen-lockfile
|
||||
- name: install yarn dependencies in ./app using cache
|
||||
uses: bahmutov/npm-install@v1.6.0
|
||||
with:
|
||||
working-directory: ./app
|
||||
|
||||
# following step does code signing when `electron-builder --publish always` (look in package.json)
|
||||
- name: yarn package-ci
|
||||
|
@ -4,7 +4,7 @@ let protocol;
|
||||
let port;
|
||||
|
||||
if (!host && !protocol && !port) {
|
||||
host = 'localhost';
|
||||
host = '127.0.0.1';
|
||||
protocol = 'http';
|
||||
port = 3131; // TODO: read port from signaling server api
|
||||
}
|
||||
|
@ -143,6 +143,10 @@ body
|
||||
left: -40px !important;
|
||||
}
|
||||
|
||||
div.class-allow-device-to-connect-alert {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* ALLOW CONNECTION ALERT BLINK ANIMATION START */
|
||||
div.class-allow-device-to-connect-alert
|
||||
> div.bp3-alert-body
|
||||
|
@ -25,6 +25,7 @@
|
||||
"react-reveal": "^1.2.2",
|
||||
"react-scripts": "3.4.3",
|
||||
"react-spinners": "^0.9.0",
|
||||
"react-test-renderer": "^17.0.1",
|
||||
"screenfull": "^5.0.2",
|
||||
"shortid": "^2.2.15",
|
||||
"socket.io-client": "^2.3.0",
|
||||
|
@ -1,7 +1,8 @@
|
||||
sonar.projectKey=deskreen-viewer
|
||||
sonar.typescript.lcov.reportPaths=coverage/lcov.info
|
||||
sonar.sources=src
|
||||
sonar.coverage.exclusions=src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
|
||||
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.host.url=http://localhost:9000
|
||||
sonar.login=e3b5f73b8778290f7074c40a4159c32b7f15a8e6
|
||||
sonar.exclusions=src/serviceWorker.ts,node_modules/**
|
||||
|
@ -1,332 +1,9 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
useContext,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from './config/i18n';
|
||||
import { H3, Position, Toaster } from '@blueprintjs/core';
|
||||
import { Grid, Row, Col } from 'react-flexbox-grid';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import ReactPlayer from 'react-player';
|
||||
import screenfull from 'screenfull';
|
||||
import Crypto from './utils/crypto';
|
||||
import './App.css';
|
||||
import PeerConnection from './features/PeerConnection';
|
||||
import VideoAutoQualityOptimizer from './features/VideoAutoQualityOptimizer';
|
||||
import ConnectingIndicator from './components/ConnectingIndicator';
|
||||
import MyDeviceInfoCard from './components/MyDeviceInfoCard';
|
||||
import {
|
||||
DARK_UI_BACKGROUND,
|
||||
LIGHT_UI_BACKGROUND,
|
||||
} from './constants/styleConstants';
|
||||
import { AppContext } from './providers/AppContextProvider';
|
||||
import PlayerControlPanel from './components/PlayerControlPanel';
|
||||
import { VideoQuality } from './features/PeerConnection/VideoQualityEnum';
|
||||
import { REACT_PLAYER_WRAPPER_ID } from './constants/appConstants';
|
||||
import { TFunction } from 'i18next';
|
||||
import ErrorDialog from './components/ErrorDialog';
|
||||
import { ErrorMessage } from './components/ErrorDialog/ErrorMessageEnum';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
const Slide = require('react-reveal/Slide');
|
||||
|
||||
function getPromptContent(step: number, t: TFunction) {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
<H3>
|
||||
{t(
|
||||
'Waiting for user to click ALLOW button on screen sharing device...'
|
||||
)}
|
||||
</H3>
|
||||
);
|
||||
case 2:
|
||||
return <H3>Connected!</H3>;
|
||||
case 3:
|
||||
return (
|
||||
<H3>
|
||||
{t(
|
||||
'Wating for user to select source to share from screen sharing device...'
|
||||
)}
|
||||
</H3>
|
||||
);
|
||||
default:
|
||||
return <H3>Error occured :(</H3>;
|
||||
}
|
||||
}
|
||||
import React from 'react';
|
||||
import MainView from './containers/MainView';
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { isDarkTheme, setIsDarkThemeHook } = useContext(AppContext);
|
||||
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState(false);
|
||||
|
||||
const [toaster, setToaster] = useState<undefined | Toaster>();
|
||||
|
||||
const refHandlers = {
|
||||
toaster: (ref: Toaster) => {
|
||||
setToaster(ref);
|
||||
},
|
||||
};
|
||||
|
||||
const player = useRef(null);
|
||||
const [promptStep, setPromptStep] = useState(1);
|
||||
const [dialogErrorMessage, setDialogErrorMessage] = useState<ErrorMessage>(
|
||||
ErrorMessage.UNKNOWN_ERROR
|
||||
);
|
||||
const [connectionIconType, setConnectionIconType] = useState<
|
||||
'feed' | 'feed-subscribed'
|
||||
>('feed');
|
||||
const [myDeviceDetails, setMyDeviceDetails] = useState<DeviceDetails>({
|
||||
myIP: '',
|
||||
myOS: '',
|
||||
myDeviceType: '',
|
||||
myBrowser: '',
|
||||
});
|
||||
|
||||
const [playing, setPlaying] = useState(true);
|
||||
const [isFullScreenOn, setIsFullScreenOn] = useState(false);
|
||||
const [url, setUrl] = useState();
|
||||
const [screenSharingSourceType, setScreenSharingSourceType] = useState<
|
||||
'screen' | 'window'
|
||||
>('screen');
|
||||
const [isWithControls, setIsWithControls] = useState(!screenfull.isEnabled);
|
||||
const [isShownTextPrompt, setIsShownTextPrompt] = useState(false);
|
||||
const [isShownSpinnerIcon, setIsShownSpinnerIcon] = useState(false);
|
||||
const [spinnerIconType, setSpinnerIconType] = useState<
|
||||
'desktop' | 'application'
|
||||
>('desktop');
|
||||
const [videoQuality, setVideoQuality] = useState<VideoQuality>(
|
||||
VideoQuality.Q_AUTO
|
||||
);
|
||||
const [peer, setPeer] = useState<undefined | PeerConnection>();
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
i18n.changeLanguage(lng);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!peer) return;
|
||||
if (!peer.isStreamStarted) return;
|
||||
peer.setVideoQuality(videoQuality);
|
||||
}, [videoQuality, peer]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.backgroundColor = isDarkTheme
|
||||
? DARK_UI_BACKGROUND
|
||||
: LIGHT_UI_BACKGROUND;
|
||||
}, [isDarkTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!peer) {
|
||||
const _peer = new PeerConnection(
|
||||
setUrl,
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
isDarkTheme,
|
||||
setMyDeviceDetails,
|
||||
() => {
|
||||
setConnectionIconType('feed-subscribed');
|
||||
|
||||
setIsShownTextPrompt(false);
|
||||
setIsShownTextPrompt(true);
|
||||
setPromptStep(2);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsShownTextPrompt(false);
|
||||
setIsShownTextPrompt(true);
|
||||
setPromptStep(3);
|
||||
}, 2000);
|
||||
},
|
||||
setScreenSharingSourceType,
|
||||
setIsDarkThemeHook,
|
||||
changeLanguage,
|
||||
setDialogErrorMessage,
|
||||
setIsErrorDialogOpen
|
||||
);
|
||||
|
||||
setPeer(_peer);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsShownTextPrompt(true);
|
||||
}, 100);
|
||||
}
|
||||
}, [setIsDarkThemeHook, isDarkTheme, peer]);
|
||||
|
||||
useEffect(() => {
|
||||
// infinite use effect
|
||||
setTimeout(() => {
|
||||
setIsShownSpinnerIcon(!isShownSpinnerIcon);
|
||||
setSpinnerIconType(
|
||||
spinnerIconType === 'desktop' ? 'application' : 'desktop'
|
||||
);
|
||||
}, 1500);
|
||||
}, [isShownSpinnerIcon, spinnerIconType]);
|
||||
|
||||
const handlePlayPause = useCallback(() => {
|
||||
setPlaying(!playing);
|
||||
}, [playing]);
|
||||
|
||||
useEffect(() => {
|
||||
if (url !== undefined) {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
document.querySelector('.container > .react-reveal').style.display =
|
||||
'none';
|
||||
}, 1000);
|
||||
}
|
||||
}, [url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (promptStep === 3) {
|
||||
// start infinite use effect
|
||||
setIsShownSpinnerIcon(true);
|
||||
}
|
||||
}, [promptStep]);
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Slide
|
||||
id="connection-prompts-slide"
|
||||
bottom
|
||||
when={url === undefined ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 10,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
boxShadow: `0 0 0 5px ${isDarkTheme ? '#000' : '#A7B6C2'}`,
|
||||
backgroundColor: isDarkTheme
|
||||
? DARK_UI_BACKGROUND
|
||||
: LIGHT_UI_BACKGROUND,
|
||||
}}
|
||||
>
|
||||
<Row
|
||||
bottom="xs"
|
||||
style={{
|
||||
height: '50vh',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
<Row center="xs" style={{ width: '100%', margin: '0 auto' }}>
|
||||
<Col
|
||||
xs={12}
|
||||
style={{
|
||||
marginBottom: '50px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Row center="xs" style={{ width: '100%', margin: '0 auto' }}>
|
||||
<Col md={6} xl={4}>
|
||||
<MyDeviceInfoCard deviceDetails={myDeviceDetails} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Fade
|
||||
opposite
|
||||
when={isShownTextPrompt}
|
||||
duration={2000}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<div id="prompt-text" style={{ fontSize: '20px' }}>
|
||||
{getPromptContent(promptStep, t)}
|
||||
</div>
|
||||
</Fade>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Row>
|
||||
<Fade
|
||||
opposite
|
||||
when={isShownTextPrompt}
|
||||
duration={500}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<ConnectingIndicator
|
||||
currentStep={promptStep}
|
||||
connectionIconType={connectionIconType}
|
||||
isShownSelectingSharingIcon={isShownSpinnerIcon}
|
||||
selectingSharingIconType={spinnerIconType}
|
||||
/>
|
||||
</Fade>
|
||||
</div>
|
||||
</Slide>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<PlayerControlPanel
|
||||
onSwitchChangedCallback={(isEnabled) => setIsWithControls(isEnabled)}
|
||||
isDefaultPlayerTurnedOn={isWithControls}
|
||||
handleClickFullscreen={() => {
|
||||
if (!screenfull.isEnabled) return;
|
||||
// @ts-ignore Property 'request' does not exist on type '{ isEnabled: false; }'.
|
||||
screenfull.request(findDOMNode(player.current));
|
||||
setIsFullScreenOn(!isFullScreenOn);
|
||||
}}
|
||||
handleClickPlayPause={handlePlayPause}
|
||||
isPlaying={playing}
|
||||
setVideoQuality={setVideoQuality}
|
||||
selectedVideoQuality={videoQuality}
|
||||
screenSharingSourceType={screenSharingSourceType}
|
||||
toaster={toaster}
|
||||
/>
|
||||
<div
|
||||
id="video-container"
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id="player-wrapper-id"
|
||||
className="player-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingTop: '56.25%',
|
||||
}}
|
||||
>
|
||||
<ReactPlayer
|
||||
ref={player}
|
||||
id={REACT_PLAYER_WRAPPER_ID}
|
||||
playing={playing}
|
||||
playsinline={true}
|
||||
controls={isWithControls}
|
||||
muted={true}
|
||||
url={(url as unknown) as MediaStream}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<canvas id="comparison-canvas" style={{ display: 'none' }}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<Toaster ref={refHandlers.toaster} position={Position.TOP_LEFT} />
|
||||
<ErrorDialog
|
||||
errorMessage={dialogErrorMessage}
|
||||
isOpen={isErrorDialogOpen}
|
||||
/>
|
||||
</Grid>
|
||||
<MainView />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* istanbul ignore file */
|
||||
let host;
|
||||
let protocol;
|
||||
let port;
|
||||
|
34
app/client/src/api/generator.spec.ts
Normal file
34
app/client/src/api/generator.spec.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import generator from './generator';
|
||||
import { TEST_PORT, TEST_HOST, TEST_PROTOCOL } from './mocks/generatorTestVariables';
|
||||
|
||||
// how to use local variables in jest mock to get rid of hoisting mocks to top most code block:
|
||||
//stackoverflow.com/questions/44649699/service-mocked-with-jest-causes-the-module-factory-of-jest-mock-is-not-allowe
|
||||
jest.mock('./config', () => {
|
||||
const generatorTestVariables = require('./mocks/generatorTestVariables');
|
||||
|
||||
return {
|
||||
host: generatorTestVariables.TEST_HOST,
|
||||
protocol: generatorTestVariables.TEST_PROTOCOL,
|
||||
port: generatorTestVariables.TEST_PORT,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
describe('generator.ts', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('when generator() is called properly', () => {
|
||||
it('should produce correct string', () => {
|
||||
const roomID = '333';
|
||||
|
||||
const result = generator(roomID);
|
||||
|
||||
expect(result).toMatch(
|
||||
`${TEST_PROTOCOL}://${TEST_HOST}:${TEST_PORT}/${roomID}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,4 +1,3 @@
|
||||
/* istanbul ignore file */
|
||||
import config from './config';
|
||||
|
||||
export default (resourceName = '') => {
|
||||
|
3
app/client/src/api/mocks/generatorTestVariables.ts
Normal file
3
app/client/src/api/mocks/generatorTestVariables.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const TEST_PORT = '3232';
|
||||
export const TEST_HOST = '123.123.123.123';
|
||||
export const TEST_PROTOCOL = 'http';
|
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import LoadingSharingIcon from './LoadingSharingIcon';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = renderer.create(
|
||||
<>
|
||||
<LoadingSharingIcon
|
||||
loadingSharingIconType="desktop"
|
||||
isShownLoadingSharingIcon
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
expect(subject).toMatchSnapshot();
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Icon } from '@blueprintjs/core';
|
||||
import { Col, Row } from 'react-flexbox-grid';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
|
||||
interface SelectSharingIconProps {
|
||||
loadingSharingIconType: LoadingSharingIconType;
|
||||
isShownLoadingSharingIcon: boolean;
|
||||
}
|
||||
|
||||
function LoadingSharingIcon(props: SelectSharingIconProps) {
|
||||
const { isDarkTheme } = useContext(AppContext);
|
||||
|
||||
const {
|
||||
loadingSharingIconType: selectingSharingIconType,
|
||||
isShownLoadingSharingIcon: isShownSelectingSharingIcon,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Row
|
||||
center="xs"
|
||||
top="xs"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
<Col>
|
||||
<Row
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
}}
|
||||
center="xs"
|
||||
>
|
||||
<Col xs={8} md={4}>
|
||||
<PropagateLoader
|
||||
loading
|
||||
size={18}
|
||||
color={isDarkTheme ? '#BFCCD6' : '#5C7080'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row center="xs">
|
||||
<Col>
|
||||
<Fade opposite when={isShownSelectingSharingIcon} duration={500}>
|
||||
<Icon
|
||||
icon={selectingSharingIconType}
|
||||
iconSize={60}
|
||||
color={isDarkTheme ? '#BFCCD6' : '#5C7080'}
|
||||
/>
|
||||
</Fade>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoadingSharingIcon;
|
@ -1,66 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Icon } from '@blueprintjs/core';
|
||||
import { Col, Row } from 'react-flexbox-grid';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
|
||||
interface SelectSharingIconProps {
|
||||
selectingSharingIconType: 'desktop' | 'application';
|
||||
isShownSelectingSharingIcon: boolean;
|
||||
}
|
||||
|
||||
function SelectSharingIcon(props: SelectSharingIconProps) {
|
||||
const { isDarkTheme } = useContext(AppContext);
|
||||
|
||||
const { selectingSharingIconType, isShownSelectingSharingIcon } = props;
|
||||
|
||||
return (
|
||||
<Row
|
||||
center="xs"
|
||||
top="xs"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
<Col>
|
||||
<Row
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
}}
|
||||
center="xs"
|
||||
>
|
||||
<Col xs={8} md={4}>
|
||||
<PropagateLoader
|
||||
loading
|
||||
size={18}
|
||||
color={isDarkTheme ? '#BFCCD6' : '#5C7080'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row center="xs">
|
||||
<Col>
|
||||
<Fade
|
||||
opposite
|
||||
when={isShownSelectingSharingIcon}
|
||||
duration={500}
|
||||
>
|
||||
<Icon
|
||||
icon={selectingSharingIconType}
|
||||
iconSize={60}
|
||||
color={isDarkTheme ? '#BFCCD6' : '#5C7080'}
|
||||
/>
|
||||
</Fade>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectSharingIcon;
|
@ -0,0 +1,93 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className="row center-xs top-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "50px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-8 col-md-4"
|
||||
>
|
||||
<div
|
||||
className="css-9ebb9a"
|
||||
>
|
||||
<div
|
||||
className="css-1nih24x"
|
||||
/>
|
||||
<div
|
||||
className="css-182ruc5"
|
||||
/>
|
||||
<div
|
||||
className="css-fapzgm"
|
||||
/>
|
||||
<div
|
||||
className="css-fqj3wr"
|
||||
/>
|
||||
<div
|
||||
className="css-srr2wk"
|
||||
/>
|
||||
<div
|
||||
className="css-1netspe"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"opacity": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-desktop"
|
||||
icon="desktop"
|
||||
>
|
||||
<svg
|
||||
data-icon="desktop"
|
||||
fill="#5C7080"
|
||||
height={60}
|
||||
viewBox="0 0 20 20"
|
||||
width={60}
|
||||
>
|
||||
<desc>
|
||||
desktop
|
||||
</desc>
|
||||
<path
|
||||
d="M19 0H1C.45 0 0 .45 0 1v13c0 .55.45 1 1 1h5.67l-.5 3H5c-.55 0-1 .45-1 1s.45 1 1 1h10c.55 0 1-.45 1-1s-.45-1-1-1h-1.17l-.5-3H19c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-1 13H2V2h16v11z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Text } from '@blueprintjs/core';
|
||||
import { Row } from 'react-flexbox-grid';
|
||||
import ConnectingIndicatorIcon from './ConnectingIndicatorIcon';
|
||||
import SelectSharingIcon from './SelectSharingIcon';
|
||||
import LoadingSharingIcon from './LoadingSharingIcon';
|
||||
|
||||
const basePulsingCircleStyles = {
|
||||
borderRadius: '100%',
|
||||
@ -18,9 +18,9 @@ const basePulsingCircleStyles = {
|
||||
|
||||
function getConnectingStepContent(
|
||||
currentStep: number,
|
||||
connectionIconType: 'feed' | 'feed-subscribed',
|
||||
selectingSharingIconType: 'desktop' | 'application',
|
||||
isShownSelectingSharingIcon: boolean,
|
||||
connectionIconType: ConnectionIconType,
|
||||
loadingSharingIconType: LoadingSharingIconType,
|
||||
isShownLoadingSharingIcon: boolean,
|
||||
) {
|
||||
|
||||
const pulsingCircle1Styles = {
|
||||
@ -68,9 +68,9 @@ function getConnectingStepContent(
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<SelectSharingIcon
|
||||
isShownSelectingSharingIcon={isShownSelectingSharingIcon}
|
||||
selectingSharingIconType={selectingSharingIconType}
|
||||
<LoadingSharingIcon
|
||||
isShownLoadingSharingIcon={isShownLoadingSharingIcon}
|
||||
loadingSharingIconType={loadingSharingIconType}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
@ -80,9 +80,9 @@ function getConnectingStepContent(
|
||||
|
||||
interface ConnectingIndicatorProps {
|
||||
currentStep: number;
|
||||
connectionIconType: 'feed' | 'feed-subscribed';
|
||||
connectionIconType: ConnectionIconType;
|
||||
isShownSelectingSharingIcon: boolean;
|
||||
selectingSharingIconType: 'desktop' | 'application';
|
||||
selectingSharingIconType: LoadingSharingIconType;
|
||||
}
|
||||
|
||||
function ConnectingIndicator(props: ConnectingIndicatorProps) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
export enum ErrorMessage {
|
||||
UNKNOWN_ERROR = 'An unknonw error uccured.',
|
||||
DENY_TO_CONNECT = 'You were not allowed to connect.',
|
||||
DICONNECTED = 'You were disconnected.',
|
||||
DISCONNECTED = 'You were disconnected.',
|
||||
NOT_ALLOWED = 'You were not allowed to connect.',
|
||||
}
|
||||
|
@ -0,0 +1,393 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className="bp3-card bp3-elevation-4"
|
||||
>
|
||||
<div
|
||||
className="row middle-xs between-xs"
|
||||
>
|
||||
<div
|
||||
className="col-xs-12 col-md-3"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<div
|
||||
className="row middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"opacity": "0.75",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-4"
|
||||
>
|
||||
<img
|
||||
alt="logo"
|
||||
height={42}
|
||||
src="deskreen_logo_128x128.png"
|
||||
width={42}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs-8"
|
||||
>
|
||||
<h5
|
||||
className="bp3-heading"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "0px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Deskreen
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<div
|
||||
className="row start-xs"
|
||||
>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<img
|
||||
alt="heart"
|
||||
height={16}
|
||||
src="red_heart_2764_twemoji_120x120.png"
|
||||
style={
|
||||
Object {
|
||||
"transform": "translateY(2px)",
|
||||
}
|
||||
}
|
||||
width={16}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"transform": "translateY(2px) translateX(-5px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Donate!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs-12 col-md-6"
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "42px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#137CBD",
|
||||
"borderRadius": "20px",
|
||||
"height": "100%",
|
||||
"width": "190px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"minWidth": "70px",
|
||||
"width": "85px",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<div
|
||||
className="row"
|
||||
>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-pause"
|
||||
icon="pause"
|
||||
>
|
||||
<svg
|
||||
data-icon="pause"
|
||||
fill="white"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
pause
|
||||
</desc>
|
||||
<path
|
||||
d="M6 3H4c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm6 0h-2c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
>
|
||||
Pause
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className="bp3-divider"
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #ffffffa8",
|
||||
"borderRight": "1px solid #ffffffa8",
|
||||
"height": "20px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-cog"
|
||||
icon="cog"
|
||||
>
|
||||
<svg
|
||||
data-icon="cog"
|
||||
fill="white"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
cog
|
||||
</desc>
|
||||
<path
|
||||
d="M15.19 6.39h-1.85c-.11-.37-.27-.71-.45-1.04l1.36-1.36c.31-.31.31-.82 0-1.13l-1.13-1.13a.803.803 0 00-1.13 0l-1.36 1.36c-.33-.17-.67-.33-1.04-.44V.79c0-.44-.36-.8-.8-.8h-1.6c-.44 0-.8.36-.8.8v1.86c-.39.12-.75.28-1.1.47l-1.3-1.3c-.3-.3-.79-.3-1.09 0L1.82 2.91c-.3.3-.3.79 0 1.09l1.3 1.3c-.2.34-.36.7-.48 1.09H.79c-.44 0-.8.36-.8.8v1.6c0 .44.36.8.8.8h1.85c.11.37.27.71.45 1.04l-1.36 1.36c-.31.31-.31.82 0 1.13l1.13 1.13c.31.31.82.31 1.13 0l1.36-1.36c.33.18.67.33 1.04.44v1.86c0 .44.36.8.8.8h1.6c.44 0 .8-.36.8-.8v-1.86c.39-.12.75-.28 1.1-.47l1.3 1.3c.3.3.79.3 1.09 0l1.09-1.09c.3-.3.3-.79 0-1.09l-1.3-1.3c.19-.35.36-.71.48-1.1h1.85c.44 0 .8-.36.8-.8v-1.6a.816.816 0 00-.81-.79zm-7.2 4.6c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="bp3-divider"
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #ffffffa8",
|
||||
"borderRight": "1px solid #ffffffa8",
|
||||
"height": "20px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<img
|
||||
alt="fullscreen-toggle"
|
||||
height={16}
|
||||
src="fullscreen_24px.svg"
|
||||
style={
|
||||
Object {
|
||||
"filter": "invert(100%) sepia(100%) saturate(0%) hue-rotate(127deg) brightness(107%) contrast(102%)",
|
||||
"transform": "scale(1.5) translateY(1px)",
|
||||
}
|
||||
}
|
||||
width={16}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs-12 col-md-3"
|
||||
>
|
||||
<div
|
||||
className="row end-xs"
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
>
|
||||
<label
|
||||
className="bp3-control bp3-switch bp3-disabled bp3-inline bp3-align-right"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "0px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="bp3-control-indicator"
|
||||
>
|
||||
<div
|
||||
className="bp3-control-indicator-child"
|
||||
>
|
||||
<div
|
||||
className="bp3-switch-inner-text"
|
||||
>
|
||||
ON
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bp3-control-indicator-child"
|
||||
>
|
||||
<div
|
||||
className="bp3-switch-inner-text"
|
||||
>
|
||||
ON
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
Default Video Player
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,33 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
// IMPORTANT! leave upper blank line so this file is ignored for coverage!!! More on this issue here
|
||||
// https://github.com/facebook/create-react-app/issues/6106#issuecomment-550076629
|
||||
import { REACT_PLAYER_WRAPPER_ID } from "../../constants/appConstants";
|
||||
|
||||
export default () => {
|
||||
const player = document.querySelector(
|
||||
`#${REACT_PLAYER_WRAPPER_ID} > video`
|
||||
);
|
||||
if (!player) return;
|
||||
// @ts-ignore
|
||||
if (player.requestFullScreen) {
|
||||
// @ts-ignore
|
||||
player.requestFullScreen();
|
||||
// @ts-ignore
|
||||
} else if (player.webkitRequestFullScreen) {
|
||||
// @ts-ignore
|
||||
player.webkitRequestFullScreen();
|
||||
// @ts-ignore
|
||||
} else if (player.mozRequestFullScreen) {
|
||||
// @ts-ignore
|
||||
player.mozRequestFullScreen();
|
||||
// @ts-ignore
|
||||
} else if (player.msRequestFullscreen) {
|
||||
// @ts-ignore
|
||||
player.msRequestFullscreen();
|
||||
// @ts-ignore
|
||||
} else if (player.webkitEnterFullscreen) {
|
||||
// @ts-ignore
|
||||
player.webkitEnterFullscreen(); //for iphone this code worked
|
||||
}
|
||||
};
|
25
app/client/src/components/PlayerControlPanel/index.spec.tsx
Normal file
25
app/client/src/components/PlayerControlPanel/index.spec.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import PlayerControlPanel from '.';
|
||||
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = renderer.create(
|
||||
<>
|
||||
<PlayerControlPanel
|
||||
onSwitchChangedCallback={() => {}}
|
||||
isPlaying
|
||||
isDefaultPlayerTurnedOn
|
||||
handleClickFullscreen={() => {}}
|
||||
handleClickPlayPause={() => {}}
|
||||
setVideoQuality={() => {}}
|
||||
selectedVideoQuality={VideoQuality.Q_100_PERCENT}
|
||||
screenSharingSourceType={'screen'}
|
||||
toaster={undefined}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
expect(subject).toMatchSnapshot();
|
||||
});
|
@ -1,9 +1,8 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
/* istanbul ignore file */
|
||||
|
||||
// IMPORTANT! leave upper blank line so this file is ignored for coverage!!! More on this issue here
|
||||
// https://github.com/facebook/create-react-app/issues/6106#issuecomment-550076629
|
||||
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import {
|
||||
Alignment,
|
||||
Button,
|
||||
@ -26,8 +25,9 @@ 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/PeerConnection/VideoQualityEnum';
|
||||
import { REACT_PLAYER_WRAPPER_ID } from '../../constants/appConstants';
|
||||
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
import handlePlayerToggleFullscreen from './handlePlayerToggleFullscreen';
|
||||
import initScreenfullOnChange from './initScreenfullOnChange';
|
||||
|
||||
const videoQualityButtonStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
@ -42,12 +42,11 @@ interface PlayerControlPanelProps {
|
||||
handleClickPlayPause: () => void;
|
||||
setVideoQuality: (q: VideoQuality) => void;
|
||||
selectedVideoQuality: VideoQuality;
|
||||
screenSharingSourceType: 'screen' | 'window';
|
||||
screenSharingSourceType: ScreenSharingSourceType;
|
||||
toaster: undefined | Toaster;
|
||||
}
|
||||
|
||||
function PlayerControlPanel(props: PlayerControlPanelProps) {
|
||||
|
||||
const {
|
||||
isPlaying,
|
||||
onSwitchChangedCallback,
|
||||
@ -65,40 +64,11 @@ function PlayerControlPanel(props: PlayerControlPanelProps) {
|
||||
const [isFullScreenOn, setIsFullScreenOn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!screenfull.isEnabled) return;
|
||||
// @ts-ignore
|
||||
screenfull.on('change', () => {
|
||||
// @ts-ignore
|
||||
setIsFullScreenOn(screenfull.isFullscreen);
|
||||
});
|
||||
initScreenfullOnChange(setIsFullScreenOn);
|
||||
}, []);
|
||||
|
||||
const handleClickFullscreenWhenDefaultPlayerIsOn = useCallback(() => {
|
||||
const player = document.querySelector(
|
||||
`#${REACT_PLAYER_WRAPPER_ID} > video`
|
||||
);
|
||||
if (!player) return;
|
||||
// @ts-ignore
|
||||
if (player.requestFullScreen) {
|
||||
// @ts-ignore
|
||||
player.requestFullScreen();
|
||||
// @ts-ignore
|
||||
} else if (player.webkitRequestFullScreen) {
|
||||
// @ts-ignore
|
||||
player.webkitRequestFullScreen();
|
||||
// @ts-ignore
|
||||
} else if (player.mozRequestFullScreen) {
|
||||
// @ts-ignore
|
||||
player.mozRequestFullScreen();
|
||||
// @ts-ignore
|
||||
} else if (player.msRequestFullscreen) {
|
||||
// @ts-ignore
|
||||
player.msRequestFullscreen();
|
||||
// @ts-ignore
|
||||
} else if (player.webkitEnterFullscreen) {
|
||||
// @ts-ignore
|
||||
player.webkitEnterFullscreen(); //for iphone this code worked
|
||||
}
|
||||
handlePlayerToggleFullscreen();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -113,7 +83,12 @@ function PlayerControlPanel(props: PlayerControlPanelProps) {
|
||||
<Button minimal>
|
||||
<Row middle="xs" style={{ opacity: '0.75' }}>
|
||||
<Col xs={4}>
|
||||
<img src={DeskreenIconPNG} width={42} height={42} alt="logo" />
|
||||
<img
|
||||
src={DeskreenIconPNG}
|
||||
width={42}
|
||||
height={42}
|
||||
alt="logo"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<H5 style={{ marginBottom: '0px' }}>Deskreen</H5>
|
||||
@ -125,7 +100,7 @@ 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>
|
||||
<Button style={{ borderRadius: '100px' }}>
|
||||
<Row start="xs">
|
||||
<Col xs>
|
||||
<img
|
||||
@ -217,7 +192,7 @@ function PlayerControlPanel(props: PlayerControlPanelProps) {
|
||||
{Object.values(VideoQuality).map(
|
||||
(q: VideoQuality) => {
|
||||
return (
|
||||
<Row>
|
||||
<Row key={q}>
|
||||
<Button
|
||||
minimal
|
||||
active={selectedVideoQuality === q}
|
||||
|
@ -0,0 +1,10 @@
|
||||
import screenfull from "screenfull";
|
||||
|
||||
export default (setIsFullScreenOn: (_: boolean) => void) => {
|
||||
if (!screenfull.isEnabled) return;
|
||||
// @ts-ignore
|
||||
screenfull.on('change', () => {
|
||||
// @ts-ignore
|
||||
setIsFullScreenOn(screenfull.isFullscreen);
|
||||
});
|
||||
};
|
@ -6,3 +6,10 @@ export const VIDEO_QUALITY_TO_DECIMAL = {
|
||||
'80%': 0.80, // Q_80_PERCENT
|
||||
'100%': 1, // Q_100_PERCENT
|
||||
}
|
||||
export const COMPARISON_CANVAS_ID = 'comparison-canvas';
|
||||
export const DUMMY_MY_DEVICE_DETAILS = {
|
||||
myIP: '',
|
||||
myOS: '',
|
||||
myDeviceType: '',
|
||||
myBrowser: '',
|
||||
};
|
||||
|
@ -0,0 +1,933 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`when getPromptContent is called should match exact snapshot on each step 1`] = `
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"boxShadow": "0 0 0 5px #A7B6C2",
|
||||
"height": "100vh",
|
||||
"left": 0,
|
||||
"opacity": undefined,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
"zIndex": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row bottom-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "50px",
|
||||
"textAlign": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-md-6 col-xl-4"
|
||||
>
|
||||
<div
|
||||
className="bp3-card bp3-elevation-3"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"marginBottom": "30px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
My Device Info:
|
||||
</h3>
|
||||
<div
|
||||
className="bp3-callout"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Type:
|
||||
|
||||
</div>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#00f99273",
|
||||
"borderRadius": "20px",
|
||||
"fontWeight": 900,
|
||||
"paddingLeft": "10px",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device IP:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Browser:
|
||||
|
||||
</div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device OS:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
These details should match with the ones that you see in alert popup on screen sharing device.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="prompt-text"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
Error occured :(
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row top-xs"
|
||||
id="connecting-screen"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Error occured :(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`when getPromptContent is called should match exact snapshot on each step 2`] = `
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"boxShadow": "0 0 0 5px #A7B6C2",
|
||||
"height": "100vh",
|
||||
"left": 0,
|
||||
"opacity": undefined,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
"zIndex": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row bottom-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "50px",
|
||||
"textAlign": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-md-6 col-xl-4"
|
||||
>
|
||||
<div
|
||||
className="bp3-card bp3-elevation-3"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"marginBottom": "30px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
My Device Info:
|
||||
</h3>
|
||||
<div
|
||||
className="bp3-callout"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Type:
|
||||
|
||||
</div>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#00f99273",
|
||||
"borderRadius": "20px",
|
||||
"fontWeight": 900,
|
||||
"paddingLeft": "10px",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device IP:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Browser:
|
||||
|
||||
</div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device OS:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
These details should match with the ones that you see in alert popup on screen sharing device.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="prompt-text"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
Waiting for user to click ALLOW button on screen sharing device...
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row top-xs"
|
||||
id="connecting-screen"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
id="pulsing-circle-1"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(43, 149, 214, 0.7)",
|
||||
"borderRadius": "100%",
|
||||
"height": "100px",
|
||||
"left": "0",
|
||||
"marginLeft": "auto",
|
||||
"marginRight": "auto",
|
||||
"position": "absolute",
|
||||
"right": "0",
|
||||
"textAlign": "center",
|
||||
"width": "100px",
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
id="pulsing-circle-2"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#2B95D6",
|
||||
"borderRadius": "100%",
|
||||
"height": "100px",
|
||||
"left": "0",
|
||||
"marginLeft": "auto",
|
||||
"marginRight": "auto",
|
||||
"position": "absolute",
|
||||
"right": "0",
|
||||
"textAlign": "center",
|
||||
"width": "100px",
|
||||
"zIndex": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-feed"
|
||||
icon="feed"
|
||||
style={
|
||||
Object {
|
||||
"transform": "translateX(10px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
data-icon="feed"
|
||||
fill="white"
|
||||
height={50}
|
||||
viewBox="0 0 20 20"
|
||||
width={50}
|
||||
>
|
||||
<desc>
|
||||
feed
|
||||
</desc>
|
||||
<path
|
||||
d="M2.5 15a2.5 2.5 0 000 5 2.5 2.5 0 000-5zm.5-5c-.55 0-1 .45-1 1s.45 1 1 1c2.76 0 5 2.24 5 5 0 .55.45 1 1 1s1-.45 1-1c0-3.87-3.13-7-7-7zM3 0c-.55 0-1 .45-1 1s.45 1 1 1c8.28 0 15 6.72 15 15 0 .55.45 1 1 1s1-.45 1-1C20 7.61 12.39 0 3 0zm0 5c-.55 0-1 .45-1 1s.45 1 1 1c5.52 0 10 4.48 10 10 0 .55.45 1 1 1s1-.45 1-1C15 10.37 9.63 5 3 5z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`when getPromptContent is called should match exact snapshot on each step 3`] = `
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"boxShadow": "0 0 0 5px #A7B6C2",
|
||||
"height": "100vh",
|
||||
"left": 0,
|
||||
"opacity": undefined,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
"zIndex": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row bottom-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "50px",
|
||||
"textAlign": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-md-6 col-xl-4"
|
||||
>
|
||||
<div
|
||||
className="bp3-card bp3-elevation-3"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"marginBottom": "30px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
My Device Info:
|
||||
</h3>
|
||||
<div
|
||||
className="bp3-callout"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Type:
|
||||
|
||||
</div>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#00f99273",
|
||||
"borderRadius": "20px",
|
||||
"fontWeight": 900,
|
||||
"paddingLeft": "10px",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device IP:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Browser:
|
||||
|
||||
</div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device OS:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
These details should match with the ones that you see in alert popup on screen sharing device.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="prompt-text"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
Connected!
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row top-xs"
|
||||
id="connecting-screen"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="pulse-3-once"
|
||||
id="pulsing-circle-3"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#15B371",
|
||||
"borderRadius": "100%",
|
||||
"height": "100px",
|
||||
"left": "0",
|
||||
"marginLeft": "auto",
|
||||
"marginRight": "auto",
|
||||
"position": "absolute",
|
||||
"right": "0",
|
||||
"textAlign": "center",
|
||||
"width": "100px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-feed"
|
||||
icon="feed"
|
||||
style={
|
||||
Object {
|
||||
"transform": "translateX(10px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
data-icon="feed"
|
||||
fill="white"
|
||||
height={50}
|
||||
viewBox="0 0 20 20"
|
||||
width={50}
|
||||
>
|
||||
<desc>
|
||||
feed
|
||||
</desc>
|
||||
<path
|
||||
d="M2.5 15a2.5 2.5 0 000 5 2.5 2.5 0 000-5zm.5-5c-.55 0-1 .45-1 1s.45 1 1 1c2.76 0 5 2.24 5 5 0 .55.45 1 1 1s1-.45 1-1c0-3.87-3.13-7-7-7zM3 0c-.55 0-1 .45-1 1s.45 1 1 1c8.28 0 15 6.72 15 15 0 .55.45 1 1 1s1-.45 1-1C20 7.61 12.39 0 3 0zm0 5c-.55 0-1 .45-1 1s.45 1 1 1c5.52 0 10 4.48 10 10 0 .55.45 1 1 1s1-.45 1-1C15 10.37 9.63 5 3 5z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`when getPromptContent is called should match exact snapshot on each step 4`] = `
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"boxShadow": "0 0 0 5px #A7B6C2",
|
||||
"height": "100vh",
|
||||
"left": 0,
|
||||
"opacity": undefined,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
"zIndex": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row bottom-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "50px",
|
||||
"textAlign": "center",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-md-6 col-xl-4"
|
||||
>
|
||||
<div
|
||||
className="bp3-card bp3-elevation-3"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(240, 248, 250, 1)",
|
||||
"marginBottom": "30px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
My Device Info:
|
||||
</h3>
|
||||
<div
|
||||
className="bp3-callout"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Type:
|
||||
|
||||
</div>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#00f99273",
|
||||
"borderRadius": "20px",
|
||||
"fontWeight": 900,
|
||||
"paddingLeft": "10px",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device IP:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device Browser:
|
||||
|
||||
</div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Device OS:
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bp3-text-muted"
|
||||
>
|
||||
These details should match with the ones that you see in alert popup on screen sharing device.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="prompt-text"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3
|
||||
className="bp3-heading"
|
||||
>
|
||||
Wating for user to select source to share from screen sharing device...
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row top-xs"
|
||||
id="connecting-screen"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs top-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "50px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-8 col-md-4"
|
||||
>
|
||||
<div
|
||||
className="css-9ebb9a"
|
||||
>
|
||||
<div
|
||||
className="css-1nih24x"
|
||||
/>
|
||||
<div
|
||||
className="css-182ruc5"
|
||||
/>
|
||||
<div
|
||||
className="css-fapzgm"
|
||||
/>
|
||||
<div
|
||||
className="css-fqj3wr"
|
||||
/>
|
||||
<div
|
||||
className="css-srr2wk"
|
||||
/>
|
||||
<div
|
||||
className="css-1netspe"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row center-xs"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="react-reveal"
|
||||
style={
|
||||
Object {
|
||||
"opacity": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-application"
|
||||
icon="application"
|
||||
>
|
||||
<svg
|
||||
data-icon="application"
|
||||
fill="#5C7080"
|
||||
height={60}
|
||||
viewBox="0 0 20 20"
|
||||
width={60}
|
||||
>
|
||||
<desc>
|
||||
application
|
||||
</desc>
|
||||
<path
|
||||
d="M3.5 9h9c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-9c-.28 0-.5.22-.5.5s.22.5.5.5zm0 2h5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-5c-.28 0-.5.22-.5.5s.22.5.5.5zM19 1H1c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm-1 16H2V6h16v11zM3.5 13h7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-7c-.28 0-.5.22-.5.5s.22.5.5.5z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
32
app/client/src/containers/ConnectionPrompts/index.spec.tsx
Normal file
32
app/client/src/containers/ConnectionPrompts/index.spec.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import ConnectionPrompts from '.';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const TEST_DEVICE_DETAILS = {
|
||||
myIP: '',
|
||||
myOS: '',
|
||||
myDeviceType: '',
|
||||
myBrowser: '',
|
||||
};
|
||||
describe('when getPromptContent is called', () => {
|
||||
it('should match exact snapshot on each step', () => {
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
const subject = renderer.create(
|
||||
<>
|
||||
<ConnectionPrompts
|
||||
myDeviceDetails={TEST_DEVICE_DETAILS}
|
||||
isShownTextPrompt
|
||||
promptStep={i}
|
||||
connectionIconType="feed"
|
||||
isShownSpinnerIcon
|
||||
spinnerIconType="application"
|
||||
isOpen
|
||||
/>
|
||||
</>
|
||||
);
|
||||
expect(subject).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
});
|
125
app/client/src/containers/ConnectionPrompts/index.tsx
Normal file
125
app/client/src/containers/ConnectionPrompts/index.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
import {
|
||||
DARK_UI_BACKGROUND,
|
||||
LIGHT_UI_BACKGROUND,
|
||||
} from '../../constants/styleConstants';
|
||||
import MyDeviceInfoCard from '../../components/MyDeviceInfoCard';
|
||||
import { TFunction } from 'i18next';
|
||||
import { H3 } from '@blueprintjs/core';
|
||||
import ConnectingIndicator from '../../components/ConnectingIndicator';
|
||||
|
||||
const Slide = require('react-reveal/Slide');
|
||||
|
||||
interface ConnectionPropmptsProps {
|
||||
myDeviceDetails: DeviceDetails;
|
||||
isShownTextPrompt: boolean;
|
||||
promptStep: number;
|
||||
connectionIconType: ConnectionIconType;
|
||||
isShownSpinnerIcon: boolean;
|
||||
spinnerIconType: LoadingSharingIconType;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
function getPromptContent(step: number, t: TFunction) {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
<H3>
|
||||
{t(
|
||||
'Waiting for user to click ALLOW button on screen sharing device...'
|
||||
)}
|
||||
</H3>
|
||||
);
|
||||
case 2:
|
||||
return <H3>Connected!</H3>;
|
||||
case 3:
|
||||
return (
|
||||
<H3>
|
||||
{t(
|
||||
'Wating for user to select source to share from screen sharing device...'
|
||||
)}
|
||||
</H3>
|
||||
);
|
||||
default:
|
||||
return <H3>Error occured :(</H3>;
|
||||
}
|
||||
}
|
||||
|
||||
function ConnectionPropmpts(props: ConnectionPropmptsProps) {
|
||||
const {
|
||||
myDeviceDetails,
|
||||
promptStep,
|
||||
connectionIconType,
|
||||
isShownSpinnerIcon,
|
||||
spinnerIconType,
|
||||
isOpen
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { isDarkTheme } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Slide
|
||||
id="connection-prompts-slide"
|
||||
bottom
|
||||
when={isOpen}
|
||||
duration={1000}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 10,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
boxShadow: `0 0 0 5px ${isDarkTheme ? '#000' : '#A7B6C2'}`,
|
||||
backgroundColor: isDarkTheme ? DARK_UI_BACKGROUND : LIGHT_UI_BACKGROUND,
|
||||
}}
|
||||
>
|
||||
<Row
|
||||
bottom="xs"
|
||||
style={{
|
||||
height: '50vh',
|
||||
width: '100%',
|
||||
marginRight: '0px',
|
||||
marginLeft: '0px',
|
||||
}}
|
||||
>
|
||||
<Row center="xs" style={{ width: '100%', margin: '0 auto' }}>
|
||||
<Col
|
||||
xs={12}
|
||||
style={{
|
||||
marginBottom: '50px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Row center="xs" style={{ width: '100%', margin: '0 auto' }}>
|
||||
<Col md={6} xl={4}>
|
||||
<MyDeviceInfoCard deviceDetails={myDeviceDetails} />
|
||||
</Col>
|
||||
</Row>
|
||||
<div id="prompt-text" style={{ fontSize: '20px' }}>
|
||||
{getPromptContent(promptStep, t)}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Row>
|
||||
<ConnectingIndicator
|
||||
currentStep={promptStep}
|
||||
connectionIconType={connectionIconType}
|
||||
isShownSelectingSharingIcon={isShownSpinnerIcon}
|
||||
selectingSharingIconType={spinnerIconType}
|
||||
/>
|
||||
</div>
|
||||
</Slide>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectionPropmpts;
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should match exact snapshot 1`] = `ShallowWrapper {}`;
|
5
app/client/src/containers/MainView/changeLanguage.ts
Normal file
5
app/client/src/containers/MainView/changeLanguage.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import i18n from "../../config/i18n";
|
||||
|
||||
export default (lng: string) => {
|
||||
i18n.changeLanguage(lng);
|
||||
};
|
@ -0,0 +1,64 @@
|
||||
import PeerConnection from '../../features/PeerConnection';
|
||||
import PeerConnectionUIHandler from '../../features/PeerConnection/PeerConnectionUIHandler';
|
||||
import getRoomIDOfCurrentBrowserWindow from '../../utils/getRoomIDOfCurrentBrowserWindow';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../../features/VideoAutoQualityOptimizer';
|
||||
import changeLanguage from './changeLanguage';
|
||||
|
||||
export default (params: CreatePeerConnectionUseEffectParams) => {
|
||||
const {
|
||||
isDarkTheme,
|
||||
peer,
|
||||
setIsDarkThemeHook,
|
||||
setMyDeviceDetails,
|
||||
setConnectionIconType,
|
||||
setIsShownTextPrompt,
|
||||
setPromptStep,
|
||||
setScreenSharingSourceType,
|
||||
setDialogErrorMessage,
|
||||
setIsErrorDialogOpen,
|
||||
setUrl,
|
||||
setPeer,
|
||||
} = params;
|
||||
|
||||
return () => {
|
||||
if (!peer) {
|
||||
const UIHandler = new PeerConnectionUIHandler(
|
||||
isDarkTheme,
|
||||
setMyDeviceDetails,
|
||||
() => {
|
||||
setConnectionIconType('feed-subscribed');
|
||||
|
||||
setIsShownTextPrompt(false);
|
||||
setIsShownTextPrompt(true);
|
||||
setPromptStep(2);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsShownTextPrompt(false);
|
||||
setIsShownTextPrompt(true);
|
||||
setPromptStep(3);
|
||||
}, 2000);
|
||||
},
|
||||
setScreenSharingSourceType,
|
||||
setIsDarkThemeHook,
|
||||
changeLanguage,
|
||||
setDialogErrorMessage,
|
||||
setIsErrorDialogOpen
|
||||
);
|
||||
|
||||
const _peer = new PeerConnection(
|
||||
getRoomIDOfCurrentBrowserWindow(),
|
||||
setUrl,
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
UIHandler
|
||||
);
|
||||
|
||||
setPeer(_peer);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsShownTextPrompt(true);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
import handleDisplayingLoadingSharingIconLoop from './handleDisplayingLoadingSharingIconLoop';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('handleDisplayingLoadingSharingIconLoop callback', () => {
|
||||
let testParams: handleDisplayingLoadingSharingIconLoopParams;
|
||||
|
||||
beforeEach(() => {
|
||||
testParams = {
|
||||
promptStep: 3,
|
||||
url: undefined,
|
||||
setIsShownLoadingSharingIcon: jest.fn(),
|
||||
setLoadingSharingIconType: jest.fn(),
|
||||
loadingSharingIconType: 'desktop',
|
||||
isShownLoadingSharingIcon: false,
|
||||
} as handleDisplayingLoadingSharingIconLoopParams
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('when it was called with promptStep 3 and url undefined', () => {
|
||||
it('it should start loop, the ui should change with interval, showing and hiding icons', () => {
|
||||
const callback = handleDisplayingLoadingSharingIconLoop(testParams);
|
||||
callback();
|
||||
|
||||
// imitation of infinite loop
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(true);
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(false);
|
||||
expect(testParams.setLoadingSharingIconType).toBeCalledWith('application');
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(true);
|
||||
expect(testParams.setLoadingSharingIconType).toBeCalledWith('desktop');
|
||||
jest.clearAllMocks();
|
||||
|
||||
// ... by this time we are sure, this function works!
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
export default (
|
||||
params: handleDisplayingLoadingSharingIconLoopParams
|
||||
) => {
|
||||
const {
|
||||
promptStep,
|
||||
url,
|
||||
setIsShownLoadingSharingIcon,
|
||||
loadingSharingIconType,
|
||||
isShownLoadingSharingIcon,
|
||||
setLoadingSharingIconType,
|
||||
} = params;
|
||||
return () => {
|
||||
let interval: NodeJS.Timeout;
|
||||
if (promptStep === 3 && url === undefined) {
|
||||
setIsShownLoadingSharingIcon(true);
|
||||
|
||||
let currentIcon = loadingSharingIconType;
|
||||
let isShownIcon = isShownLoadingSharingIcon;
|
||||
let isShownWithFadingUIEffect = false;
|
||||
interval = setInterval(() => {
|
||||
isShownIcon = !isShownIcon;
|
||||
setIsShownLoadingSharingIcon(isShownIcon);
|
||||
if (isShownWithFadingUIEffect) {
|
||||
currentIcon = currentIcon === 'desktop' ? 'application' : 'desktop';
|
||||
setLoadingSharingIconType(currentIcon);
|
||||
isShownWithFadingUIEffect = false;
|
||||
} else {
|
||||
isShownWithFadingUIEffect = true;
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
};
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
import { DUMMY_MY_DEVICE_DETAILS } from "../../constants/appConstants";
|
||||
|
||||
export default (
|
||||
myDeviceDetails: DeviceDetails,
|
||||
setIsErrorDialogOpen: (_: boolean) => void
|
||||
) => {
|
||||
return () => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (myDeviceDetails === DUMMY_MY_DEVICE_DETAILS) {
|
||||
setIsErrorDialogOpen(true);
|
||||
}
|
||||
}, 10000);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
};
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
export default (
|
||||
url: undefined | MediaStream
|
||||
) => {
|
||||
return () => {
|
||||
if (url !== undefined) {
|
||||
setTimeout(() => {
|
||||
// @ts-ignore
|
||||
document.querySelector('.container > .react-reveal').style.display =
|
||||
'none';
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
};
|
13
app/client/src/containers/MainView/handleSetVideoQuality.ts
Normal file
13
app/client/src/containers/MainView/handleSetVideoQuality.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import PeerConnection from "../../features/PeerConnection";
|
||||
import { VideoQuality } from "../../features/VideoAutoQualityOptimizer/VideoQualityEnum";
|
||||
|
||||
export default (
|
||||
videoQuality: VideoQuality,
|
||||
peer: PeerConnection | undefined
|
||||
) => {
|
||||
return () => {
|
||||
if (!peer) return;
|
||||
if (!peer.isStreamStarted) return;
|
||||
peer.setVideoQuality(videoQuality);
|
||||
};
|
||||
};
|
38
app/client/src/containers/MainView/index.spec.tsx
Normal file
38
app/client/src/containers/MainView/index.spec.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
import Enzyme, { shallow } from 'enzyme';
|
||||
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import MainView from '.';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// Mock react-i18next
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
ns: ['common'],
|
||||
defaultNS: 'common',
|
||||
resources: {
|
||||
en: {
|
||||
common: {},
|
||||
},
|
||||
},
|
||||
debug: false,
|
||||
});
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
jest.useFakeTimers();
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = shallow(
|
||||
<Suspense fallback={<div>loading</div>}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<MainView />
|
||||
</I18nextProvider>
|
||||
</Suspense>
|
||||
);
|
||||
expect(subject).toMatchSnapshot();
|
||||
});
|
126
app/client/src/containers/MainView/index.tsx
Normal file
126
app/client/src/containers/MainView/index.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import React, { useEffect, useState, useContext, useCallback } from 'react';
|
||||
import { Grid } from 'react-flexbox-grid';
|
||||
import screenfull from 'screenfull';
|
||||
import './index.css';
|
||||
import PeerConnection from '../../features/PeerConnection';
|
||||
import { AppContext } from '../../providers/AppContextProvider';
|
||||
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
import ErrorDialog from '../../components/ErrorDialog';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import ConnectionPropmpts from '../../containers/ConnectionPrompts';
|
||||
import PlayerView from '../../containers/PlayerView';
|
||||
import handleSetVideoQuality from './handleSetVideoQuality';
|
||||
import { DUMMY_MY_DEVICE_DETAILS } from '../../constants/appConstants';
|
||||
import handleNoConnectionTimeout from './handleNoConnectionTimeout';
|
||||
import handleCreatePeerConnection from './handleCreatePeerConnection';
|
||||
import handleRemoveDanglingReactRevealContainer from './handleRemoveDanglingReactRevealContainer';
|
||||
import handleDisplayingLoadingSharingIconLoop from './handleDisplayingLoadingSharingIconLoop';
|
||||
|
||||
function MainView() {
|
||||
const { isDarkTheme, setIsDarkThemeHook } = useContext(AppContext);
|
||||
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState(false);
|
||||
|
||||
const [promptStep, setPromptStep] = useState(1);
|
||||
const [dialogErrorMessage, setDialogErrorMessage] = useState<ErrorMessage>(
|
||||
ErrorMessage.UNKNOWN_ERROR
|
||||
);
|
||||
const [connectionIconType, setConnectionIconType] = useState<
|
||||
ConnectionIconType
|
||||
>('feed');
|
||||
const [myDeviceDetails, setMyDeviceDetails] = useState<DeviceDetails>(
|
||||
DUMMY_MY_DEVICE_DETAILS
|
||||
);
|
||||
|
||||
const [playing, setPlaying] = useState(true);
|
||||
const [isFullScreenOn, setIsFullScreenOn] = useState(false);
|
||||
const [url, setUrl] = useState<undefined | MediaStream>(undefined);
|
||||
const [screenSharingSourceType, setScreenSharingSourceType] = useState<
|
||||
ScreenSharingSourceType
|
||||
>('screen');
|
||||
const [isWithControls, setIsWithControls] = useState(!screenfull.isEnabled);
|
||||
const [isShownTextPrompt, setIsShownTextPrompt] = useState(false);
|
||||
const [isShownLoadingSharingIcon, setIsShownLoadingSharingIcon] = useState(
|
||||
false
|
||||
);
|
||||
const [loadingSharingIconType, setLoadingSharingIconType] = useState<
|
||||
LoadingSharingIconType
|
||||
>('desktop');
|
||||
const [videoQuality, setVideoQuality] = useState<VideoQuality>(
|
||||
VideoQuality.Q_AUTO
|
||||
);
|
||||
const [peer, setPeer] = useState<undefined | PeerConnection>();
|
||||
|
||||
useEffect(handleSetVideoQuality(videoQuality, peer), [videoQuality, peer]);
|
||||
|
||||
useEffect(handleNoConnectionTimeout(myDeviceDetails, setIsErrorDialogOpen), [
|
||||
myDeviceDetails,
|
||||
]);
|
||||
|
||||
useEffect(
|
||||
handleCreatePeerConnection({
|
||||
isDarkTheme,
|
||||
peer,
|
||||
setIsDarkThemeHook,
|
||||
setMyDeviceDetails,
|
||||
setConnectionIconType,
|
||||
setIsShownTextPrompt,
|
||||
setPromptStep,
|
||||
setScreenSharingSourceType,
|
||||
setDialogErrorMessage,
|
||||
setIsErrorDialogOpen,
|
||||
setUrl,
|
||||
setPeer,
|
||||
}),
|
||||
[setIsDarkThemeHook, isDarkTheme, peer]
|
||||
);
|
||||
|
||||
const handlePlayPause = useCallback(() => {
|
||||
setPlaying(!playing);
|
||||
}, [playing]);
|
||||
|
||||
useEffect(handleRemoveDanglingReactRevealContainer(url), [url]);
|
||||
|
||||
useEffect(
|
||||
handleDisplayingLoadingSharingIconLoop({
|
||||
promptStep,
|
||||
url,
|
||||
setIsShownLoadingSharingIcon,
|
||||
loadingSharingIconType,
|
||||
isShownLoadingSharingIcon,
|
||||
setLoadingSharingIconType,
|
||||
}),
|
||||
[promptStep, url]
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<ConnectionPropmpts
|
||||
isOpen={url === undefined ? true : false}
|
||||
myDeviceDetails={myDeviceDetails}
|
||||
isShownTextPrompt={isShownTextPrompt}
|
||||
promptStep={promptStep}
|
||||
connectionIconType={connectionIconType}
|
||||
spinnerIconType={loadingSharingIconType}
|
||||
isShownSpinnerIcon={isShownLoadingSharingIcon}
|
||||
/>
|
||||
<PlayerView
|
||||
streamUrl={url}
|
||||
screenSharingSourceType={screenSharingSourceType}
|
||||
setIsWithControls={setIsWithControls}
|
||||
isWithControls={isWithControls}
|
||||
isFullScreenOn={isFullScreenOn}
|
||||
setIsFullScreenOn={setIsFullScreenOn}
|
||||
handlePlayPause={handlePlayPause}
|
||||
isPlaying={playing}
|
||||
setVideoQuality={setVideoQuality}
|
||||
videoQuality={videoQuality}
|
||||
/>
|
||||
<ErrorDialog
|
||||
errorMessage={dialogErrorMessage}
|
||||
isOpen={isErrorDialogOpen}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainView;
|
@ -0,0 +1,448 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"height": "100vh",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="bp3-card bp3-elevation-4"
|
||||
>
|
||||
<div
|
||||
className="row middle-xs between-xs"
|
||||
>
|
||||
<div
|
||||
className="col-xs-12 col-md-3"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<div
|
||||
className="row middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"opacity": "0.75",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="col-xs-4"
|
||||
>
|
||||
<img
|
||||
alt="logo"
|
||||
height={42}
|
||||
src="deskreen_logo_128x128.png"
|
||||
width={42}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs-8"
|
||||
>
|
||||
<h5
|
||||
className="bp3-heading"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "0px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Deskreen
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": "100px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<div
|
||||
className="row start-xs"
|
||||
>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<img
|
||||
alt="heart"
|
||||
height={16}
|
||||
src="red_heart_2764_twemoji_120x120.png"
|
||||
style={
|
||||
Object {
|
||||
"transform": "translateY(2px)",
|
||||
}
|
||||
}
|
||||
width={16}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"transform": "translateY(2px) translateX(-5px)",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
Donate!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs-12 col-md-6"
|
||||
>
|
||||
<div
|
||||
className="row center-xs"
|
||||
style={
|
||||
Object {
|
||||
"height": "42px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#137CBD",
|
||||
"borderRadius": "20px",
|
||||
"height": "100%",
|
||||
"width": "190px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="row center-xs middle-xs"
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"minWidth": "70px",
|
||||
"width": "85px",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<div
|
||||
className="row"
|
||||
>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-play"
|
||||
icon="play"
|
||||
>
|
||||
<svg
|
||||
data-icon="play"
|
||||
fill="white"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
play
|
||||
</desc>
|
||||
<path
|
||||
d="M12 8c0-.35-.19-.64-.46-.82l.01-.02-6-4-.01.02A.969.969 0 005 3c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1 .21 0 .39-.08.54-.18l.01.02 6-4-.01-.02c.27-.18.46-.47.46-.82z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
>
|
||||
Play
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className="bp3-divider"
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #ffffffa8",
|
||||
"borderRight": "1px solid #ffffffa8",
|
||||
"height": "20px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<span
|
||||
className="bp3-icon bp3-icon-cog"
|
||||
icon="cog"
|
||||
>
|
||||
<svg
|
||||
data-icon="cog"
|
||||
fill="white"
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
>
|
||||
<desc>
|
||||
cog
|
||||
</desc>
|
||||
<path
|
||||
d="M15.19 6.39h-1.85c-.11-.37-.27-.71-.45-1.04l1.36-1.36c.31-.31.31-.82 0-1.13l-1.13-1.13a.803.803 0 00-1.13 0l-1.36 1.36c-.33-.17-.67-.33-1.04-.44V.79c0-.44-.36-.8-.8-.8h-1.6c-.44 0-.8.36-.8.8v1.86c-.39.12-.75.28-1.1.47l-1.3-1.3c-.3-.3-.79-.3-1.09 0L1.82 2.91c-.3.3-.3.79 0 1.09l1.3 1.3c-.2.34-.36.7-.48 1.09H.79c-.44 0-.8.36-.8.8v1.6c0 .44.36.8.8.8h1.85c.11.37.27.71.45 1.04l-1.36 1.36c-.31.31-.31.82 0 1.13l1.13 1.13c.31.31.82.31 1.13 0l1.36-1.36c.33.18.67.33 1.04.44v1.86c0 .44.36.8.8.8h1.6c.44 0 .8-.36.8-.8v-1.86c.39-.12.75-.28 1.1-.47l1.3 1.3c.3.3.79.3 1.09 0l1.09-1.09c.3-.3.3-.79 0-1.09l-1.3-1.3c.19-.35.36-.71.48-1.1h1.85c.44 0 .8-.36.8-.8v-1.6a.816.816 0 00-.81-.79zm-7.2 4.6c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="bp3-divider"
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #ffffffa8",
|
||||
"borderRight": "1px solid #ffffffa8",
|
||||
"height": "20px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className="bp3-popover-wrapper"
|
||||
>
|
||||
<span
|
||||
className="bp3-popover-target"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-minimal"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="bp3-button-text"
|
||||
>
|
||||
<img
|
||||
alt="fullscreen-toggle"
|
||||
height={16}
|
||||
src="fullscreen_24px.svg"
|
||||
style={
|
||||
Object {
|
||||
"filter": "invert(100%) sepia(100%) saturate(0%) hue-rotate(127deg) brightness(107%) contrast(102%)",
|
||||
"transform": "scale(1.5) translateY(1px)",
|
||||
}
|
||||
}
|
||||
width={16}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col-xs-12 col-md-3"
|
||||
>
|
||||
<div
|
||||
className="row end-xs"
|
||||
>
|
||||
<div
|
||||
className="col-xs-12"
|
||||
>
|
||||
<label
|
||||
className="bp3-control bp3-switch bp3-disabled bp3-inline bp3-align-right"
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": "0px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="bp3-control-indicator"
|
||||
>
|
||||
<div
|
||||
className="bp3-control-indicator-child"
|
||||
>
|
||||
<div
|
||||
className="bp3-switch-inner-text"
|
||||
>
|
||||
OFF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="bp3-control-indicator-child"
|
||||
>
|
||||
<div
|
||||
className="bp3-switch-inner-text"
|
||||
>
|
||||
OFF
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
Default Video Player
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="video-container"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 auto",
|
||||
"position": "relative",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="player-wrapper"
|
||||
id="player-wrapper-id"
|
||||
style={
|
||||
Object {
|
||||
"paddingTop": "56.25%",
|
||||
"position": "relative",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
id="react-player-wrapper-id"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "black",
|
||||
"height": "100%",
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<canvas
|
||||
id="comparison-canvas"
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
26
app/client/src/containers/PlayerView/index.spec.tsx
Normal file
26
app/client/src/containers/PlayerView/index.spec.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import PlayerView from '.';
|
||||
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = renderer.create(
|
||||
<>
|
||||
<PlayerView
|
||||
isWithControls={false}
|
||||
setIsWithControls={() => {}}
|
||||
isFullScreenOn={false}
|
||||
setIsFullScreenOn={() => {}}
|
||||
handlePlayPause={() => {}}
|
||||
isPlaying={false}
|
||||
setVideoQuality={() => {}}
|
||||
videoQuality={VideoQuality.Q_100_PERCENT}
|
||||
screenSharingSourceType={'screen'}
|
||||
streamUrl={undefined}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
expect(subject).toMatchSnapshot();
|
||||
});
|
116
app/client/src/containers/PlayerView/index.tsx
Normal file
116
app/client/src/containers/PlayerView/index.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { Position, Toaster } from '@blueprintjs/core';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import ReactPlayer from 'react-player';
|
||||
import screenfull from 'screenfull';
|
||||
import PlayerControlPanel from '../../components/PlayerControlPanel';
|
||||
import {
|
||||
COMPARISON_CANVAS_ID,
|
||||
REACT_PLAYER_WRAPPER_ID,
|
||||
} from '../../constants/appConstants';
|
||||
import { VideoQuality } from '../../features/VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
|
||||
interface PlayerViewProps {
|
||||
isWithControls: boolean;
|
||||
setIsWithControls: (_: boolean) => void;
|
||||
isFullScreenOn: boolean;
|
||||
setIsFullScreenOn: (_: boolean) => void;
|
||||
handlePlayPause: () => void;
|
||||
isPlaying: boolean;
|
||||
setVideoQuality: (_: VideoQuality) => void;
|
||||
videoQuality: VideoQuality;
|
||||
screenSharingSourceType: ScreenSharingSourceType;
|
||||
streamUrl: undefined | MediaStream;
|
||||
}
|
||||
|
||||
function PlayerView(props: PlayerViewProps) {
|
||||
const {
|
||||
screenSharingSourceType,
|
||||
setIsWithControls,
|
||||
isWithControls,
|
||||
isFullScreenOn,
|
||||
setIsFullScreenOn,
|
||||
handlePlayPause,
|
||||
isPlaying,
|
||||
setVideoQuality,
|
||||
videoQuality,
|
||||
streamUrl,
|
||||
} = props;
|
||||
|
||||
const player = useRef(null);
|
||||
const [toaster, setToaster] = useState<undefined | Toaster>();
|
||||
|
||||
const refHandlers = {
|
||||
toaster: (ref: Toaster) => {
|
||||
setToaster(ref);
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<PlayerControlPanel
|
||||
onSwitchChangedCallback={(isEnabled) => setIsWithControls(isEnabled)}
|
||||
isDefaultPlayerTurnedOn={isWithControls}
|
||||
handleClickFullscreen={() => {
|
||||
if (!screenfull.isEnabled) return;
|
||||
const playerElement = document.querySelector(`#${REACT_PLAYER_WRAPPER_ID}`);
|
||||
if (!playerElement) return;
|
||||
screenfull.request(playerElement);
|
||||
setIsFullScreenOn(!isFullScreenOn);
|
||||
}}
|
||||
handleClickPlayPause={handlePlayPause}
|
||||
isPlaying={isPlaying}
|
||||
setVideoQuality={setVideoQuality}
|
||||
selectedVideoQuality={videoQuality}
|
||||
screenSharingSourceType={screenSharingSourceType}
|
||||
toaster={toaster}
|
||||
/>
|
||||
<div
|
||||
id="video-container"
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id="player-wrapper-id"
|
||||
className="player-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingTop: '56.25%',
|
||||
}}
|
||||
>
|
||||
<ReactPlayer
|
||||
ref={player}
|
||||
id={REACT_PLAYER_WRAPPER_ID}
|
||||
playing={isPlaying}
|
||||
playsinline={true}
|
||||
controls={isWithControls}
|
||||
muted={true}
|
||||
url={streamUrl}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<canvas id={COMPARISON_CANVAS_ID} style={{ display: 'none' }}></canvas>
|
||||
</div>
|
||||
<Toaster ref={refHandlers.toaster} position={Position.TOP_LEFT} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlayerView;
|
1
app/client/src/features/PeerConnection/NullUser.ts
Normal file
1
app/client/src/features/PeerConnection/NullUser.ts
Normal file
@ -0,0 +1 @@
|
||||
export default { username: '', publicKey: '', privateKey: '' };
|
4
app/client/src/features/PeerConnection/PartnerPeerUser.d.ts
vendored
Normal file
4
app/client/src/features/PeerConnection/PartnerPeerUser.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
interface PartnerPeerUser {
|
||||
username: string;
|
||||
publicKey: string;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
|
||||
export default class PeerConnectionUIHandler {
|
||||
isDarkTheme: boolean;
|
||||
|
||||
setMyDeviceDetails: (details: DeviceDetails) => void;
|
||||
|
||||
hostAllowedToConnectCallback: () => void;
|
||||
|
||||
setScreenSharingSourceTypeCallback: (s: ScreenSharingSourceType) => void;
|
||||
|
||||
setIsDarkThemeCallback: (val: boolean) => void;
|
||||
|
||||
setAppLanguageCallback: (newLang: string) => void;
|
||||
|
||||
setDialogErrorMessageCallback: (message: ErrorMessage) => void;
|
||||
|
||||
setIsErrorDialogOpen: (val: boolean) => void;
|
||||
|
||||
errorDialogMessage = ErrorMessage.UNKNOWN_ERROR;
|
||||
|
||||
constructor(
|
||||
isDarkTheme: boolean,
|
||||
setMyDeviceDetails: (details: DeviceDetails) => void,
|
||||
hostAllowedToConnectCallback: () => void,
|
||||
setScreenSharingSourceTypeCallback: (s: ScreenSharingSourceType) => void,
|
||||
setIsDarkThemeCallback: (val: boolean) => void,
|
||||
setAppLanguageCallback: (newLang: string) => void,
|
||||
setDialogErrorMessageCallback: (message: ErrorMessage) => void,
|
||||
setIsErrorDialogOpen: (val: boolean) => void
|
||||
) {
|
||||
this.isDarkTheme = isDarkTheme;
|
||||
this.hostAllowedToConnectCallback = hostAllowedToConnectCallback;
|
||||
this.setMyDeviceDetails = setMyDeviceDetails;
|
||||
this.setScreenSharingSourceTypeCallback = setScreenSharingSourceTypeCallback;
|
||||
this.setIsDarkThemeCallback = setIsDarkThemeCallback;
|
||||
this.setAppLanguageCallback = setAppLanguageCallback;
|
||||
this.setDialogErrorMessageCallback = setDialogErrorMessageCallback;
|
||||
this.setIsErrorDialogOpen = setIsErrorDialogOpen;
|
||||
}
|
||||
}
|
6
app/client/src/features/PeerConnection/ReceiveEncryptedMessagePayload.d.ts
vendored
Normal file
6
app/client/src/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 }[];
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export default class PeerConnectionPartnerIsNotDefinedError extends Error {
|
||||
constructor() {
|
||||
super('partner should be defined!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, PeerConnectionPartnerIsNotDefinedError.prototype);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export default class PeerConnectionPeerIsNullError extends Error {
|
||||
constructor() {
|
||||
super('peer of PeerConnection should not be null!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, PeerConnectionPeerIsNullError.prototype);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export default class PeerConnectionSocketNotDefined extends Error {
|
||||
constructor() {
|
||||
super('socket should be defined!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, PeerConnectionSocketNotDefined.prototype);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export default class PeerConnectionUserIsNotDefinedError extends Error {
|
||||
constructor() {
|
||||
super('user should be defined!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, PeerConnectionUserIsNotDefinedError.prototype);
|
||||
}
|
||||
}
|
237
app/client/src/features/PeerConnection/index.spec.ts
Normal file
237
app/client/src/features/PeerConnection/index.spec.ts
Normal file
@ -0,0 +1,237 @@
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('../../utils/crypto.ts');
|
||||
jest.mock('../VideoAutoQualityOptimizer');
|
||||
jest.mock('simple-peer');
|
||||
jest.mock('./setAndShowErrorDialogMessage');
|
||||
jest.mock('./peerConnectionReceiveEncryptedMessage');
|
||||
|
||||
import SimplePeer from 'simple-peer';
|
||||
import PeerConnection from '.';
|
||||
import { VIDEO_QUALITY_TO_DECIMAL } from '../../constants/appConstants';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import { VideoQuality } from '../VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
import NullUser from './NullUser';
|
||||
import PeerConnectionPartnerIsNotDefinedError from './errors/PeerConnectionPartnerIsNotDefinedError';
|
||||
import PeerConnectionSocketNotDefined from './errors/PeerConnectionSocketNotDefined';
|
||||
import PeerConnectionUIHandler from './PeerConnectionUIHandler';
|
||||
import PeerConnectionUserIsNotDefinedError from './errors/PeerConnectionUserIsNotDefinedError';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
import { prepareDataMessageToChangeQuality } from './simplePeerDataMessages';
|
||||
import peerConnectionReceiveEncryptedMessage from './peerConnectionReceiveEncryptedMessage';
|
||||
|
||||
const SEND_ENCRYPTED_MESSAGE_DUMMY_PAYLOAD = {
|
||||
type: 'DUMMY_MESSAGE',
|
||||
payload: {},
|
||||
};
|
||||
|
||||
const RECEIVE_ENCRYPTED_MESSAGE_DUMMY_PAYLOAD = {
|
||||
payload: '',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [{ sessionKey: '', signingKey: '' }],
|
||||
};
|
||||
|
||||
describe('PeerConnection class', () => {
|
||||
let peerConnection: PeerConnection;
|
||||
|
||||
beforeEach(() => {
|
||||
peerConnection = new PeerConnection(
|
||||
'123',
|
||||
jest.fn(),
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn()
|
||||
)
|
||||
);
|
||||
peerConnection.peer = new SimplePeer();
|
||||
peerConnection.peer.send = jest.fn();
|
||||
|
||||
const listeners = new Map<string, any[]>();
|
||||
|
||||
peerConnection.socket = {
|
||||
on: jest.fn().mockImplementation((s: string, callback: any) => {
|
||||
if (!listeners.has(s)) {
|
||||
listeners.set(s, []);
|
||||
}
|
||||
listeners.get(s)?.push(callback);
|
||||
}),
|
||||
emit: jest.fn().mockImplementation((s: string, any: any) => {
|
||||
listeners.forEach((callbacks, key) => {
|
||||
callbacks.forEach((callback) => {
|
||||
if (key === s) {
|
||||
callback(any);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
};
|
||||
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('when new PeerConnection is created with not corrent roomId', () => {
|
||||
it('should change UI accordingly and notify user that error occured', () => {
|
||||
peerConnection = new PeerConnection(
|
||||
'',
|
||||
jest.fn(),
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn()
|
||||
)
|
||||
);
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when new PeerConnection was created properly', () => {
|
||||
describe('when setVideoQuality is called properly', () => {
|
||||
it('should set video quality properly', () => {
|
||||
peerConnection.videoQualityChangedCallback = jest.fn();
|
||||
const newVideoQuality = VideoQuality.Q_100_PERCENT;
|
||||
peerConnection.setVideoQuality(newVideoQuality);
|
||||
|
||||
expect(peerConnection.videoQualityChangedCallback).toBeCalled();
|
||||
expect(peerConnection.videoQuality).toBe(newVideoQuality);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when videoQualityChangedCallback is called and videoQuality of peer is VideoQuality.Q_AUTO', () => {
|
||||
it('should call peerConnection.peer.send with proper message', () => {
|
||||
peerConnection.videoQuality = VideoQuality.Q_AUTO;
|
||||
peerConnection.videoQualityChangedCallback();
|
||||
|
||||
expect(peerConnection.peer?.send).toBeCalledWith(
|
||||
prepareDataMessageToChangeQuality(1)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when videoQualityChangedCallback is called and videoQuality of peer is NOT VideoQuality.Q_AUTO', () => {
|
||||
it('should call peerConnection.peer.send with proper message', () => {
|
||||
peerConnection.videoQuality = VideoQuality.Q_25_PERCENT;
|
||||
peerConnection.videoQualityChangedCallback();
|
||||
|
||||
expect(peerConnection.peer?.send).toBeCalledWith(
|
||||
prepareDataMessageToChangeQuality(
|
||||
VIDEO_QUALITY_TO_DECIMAL[peerConnection.videoQuality]
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when initApp is called when socket is not defined', () => {
|
||||
it('should throw an appropriate error', () => {
|
||||
peerConnection.socket = undefined;
|
||||
try {
|
||||
peerConnection.initApp(NullUser, '');
|
||||
fail('it should have thrown an error here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new PeerConnectionSocketNotDefined());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when initApp is called properly', () => {
|
||||
it('should call emit on socket with USER_ENTER', () => {
|
||||
peerConnection.initApp(NullUser, '');
|
||||
|
||||
expect(peerConnection.socket.emit).toBeCalledWith(
|
||||
'USER_ENTER',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sendEncryptedMessage is called when socket is NOT defined', () => {
|
||||
it('should throw an appropriate error', () => {
|
||||
peerConnection.socket = undefined;
|
||||
try {
|
||||
peerConnection.sendEncryptedMessage(
|
||||
SEND_ENCRYPTED_MESSAGE_DUMMY_PAYLOAD
|
||||
);
|
||||
fail('it should have thrown an error here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new PeerConnectionSocketNotDefined());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sendEncryptedMessage is called when user is NOT defined', () => {
|
||||
it('should throw an appropriate error', () => {
|
||||
try {
|
||||
peerConnection.sendEncryptedMessage(
|
||||
SEND_ENCRYPTED_MESSAGE_DUMMY_PAYLOAD
|
||||
);
|
||||
fail('it should have thrown an error here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new PeerConnectionUserIsNotDefinedError());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when sendEncryptedMessage is called when partner is NOT defined', () => {
|
||||
it('should throw an appropriate error', () => {
|
||||
peerConnection.user = {
|
||||
username: 'af',
|
||||
privateKey: 'af',
|
||||
publicKey: 'af',
|
||||
};
|
||||
try {
|
||||
peerConnection.sendEncryptedMessage(
|
||||
SEND_ENCRYPTED_MESSAGE_DUMMY_PAYLOAD
|
||||
);
|
||||
fail('it should have thrown an error here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new PeerConnectionPartnerIsNotDefinedError());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when receiveEncryptedMessage is called', () => {
|
||||
it('should call peerConnectionReceiveEncryptedMessage callback', () => {
|
||||
peerConnection.receiveEncryptedMessage(
|
||||
RECEIVE_ENCRYPTED_MESSAGE_DUMMY_PAYLOAD
|
||||
);
|
||||
|
||||
expect(peerConnectionReceiveEncryptedMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when createUserAndInitSocket is called and when socket is NOT defined', () => {
|
||||
it('should throw an appropriate error', () => {
|
||||
peerConnection.socket = undefined;
|
||||
try {
|
||||
peerConnection.createUserAndInitSocket();
|
||||
fail('it should have thrown an error here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new PeerConnectionSocketNotDefined());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,25 +1,26 @@
|
||||
import shortId from 'shortid';
|
||||
// import pixelmatch from 'pixelmatch';
|
||||
import SimplePeer from 'simple-peer';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { connect as connectSocket } from '../../utils/socket';
|
||||
import {
|
||||
prepare as prepareMessage,
|
||||
process as processMessage,
|
||||
} from '../../utils/message';
|
||||
import { prepare as prepareMessage } from '../../utils/message';
|
||||
import setSdpMediaBitrate from './setSdpMediaBitrate';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import {
|
||||
getBrowserFromUAParser,
|
||||
getDeviceTypeFromUAParser,
|
||||
getOSFromUAParser,
|
||||
} from '../../utils/userAgentParserHelpers';
|
||||
import { VideoQuality } from './VideoQualityEnum';
|
||||
import prepareDataMessageToChangeQuality from './prepareDataMessageToChangeQuality';
|
||||
import { VideoQuality } from '../VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
import { prepareDataMessageToChangeQuality } from './simplePeerDataMessages';
|
||||
import { VIDEO_QUALITY_TO_DECIMAL } from './../../constants/appConstants';
|
||||
import prepareDataMessageToGetSharingSourceType from './prepareDataMessageToGetSharingSourceType';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import areWeTestingWithJest from '../../utils/areWeTestingWithJest';
|
||||
import peerConnectionHandleSocket from './peerConnectionHandleSocket';
|
||||
import peerConnectionHandlePeer from './peerConnectionHandlePeer';
|
||||
import peerConnectionReceiveEncryptedMessage from './peerConnectionReceiveEncryptedMessage';
|
||||
import startSocketConnectedCheckingLoop from './startSocketConnectedCheckingLoop';
|
||||
import NullUser from './NullUser';
|
||||
import PeerConnectionUIHandler from './PeerConnectionUIHandler';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
import PeerConnectionSocketNotDefined from './errors/PeerConnectionSocketNotDefined';
|
||||
import PeerConnectionUserIsNotDefinedError from './errors/PeerConnectionUserIsNotDefinedError';
|
||||
import PeerConnectionPartnerIsNotDefinedError from './errors/PeerConnectionPartnerIsNotDefinedError';
|
||||
|
||||
interface LocalPeerUser {
|
||||
username: string;
|
||||
@ -27,25 +28,11 @@ interface LocalPeerUser {
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
interface PartnerPeerUser {
|
||||
username: string;
|
||||
publicKey: string;
|
||||
}
|
||||
|
||||
const nullUser = { username: '', publicKey: '', privateKey: '' };
|
||||
|
||||
interface SendEncryptedMessagePayload {
|
||||
type: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ReceiveEncryptedMessagePayload {
|
||||
payload: string;
|
||||
signature: string;
|
||||
iv: string;
|
||||
keys: { sessionKey: string; signingKey: string }[];
|
||||
}
|
||||
|
||||
export default class PeerConnection {
|
||||
roomId: string;
|
||||
|
||||
@ -53,33 +40,22 @@ export default class PeerConnection {
|
||||
|
||||
crypto: Crypto;
|
||||
|
||||
user: LocalPeerUser = nullUser;
|
||||
user: LocalPeerUser = NullUser;
|
||||
|
||||
partner: PartnerPeerUser = nullUser;
|
||||
partner: PartnerPeerUser = NullUser;
|
||||
|
||||
peer: null | SimplePeer.Instance = null;
|
||||
|
||||
myIP = '';
|
||||
myDeviceDetails: DeviceDetails = {
|
||||
myIP: '',
|
||||
myOS: '',
|
||||
myDeviceType: '',
|
||||
myBrowser: '',
|
||||
};
|
||||
|
||||
myOS: any;
|
||||
setUrlCallback: (url: any) => void;
|
||||
|
||||
myDeviceType: any;
|
||||
|
||||
myBrowser: any;
|
||||
|
||||
mousePos: any;
|
||||
|
||||
setUrlCallback: any;
|
||||
|
||||
private uaParser: UAParser;
|
||||
|
||||
canvas: any;
|
||||
|
||||
video: any;
|
||||
|
||||
prevFrame: any;
|
||||
|
||||
largeMismatchFramesCount: number;
|
||||
uaParser: UAParser;
|
||||
|
||||
screenSharingSourceType: string | undefined = undefined;
|
||||
|
||||
@ -87,74 +63,32 @@ export default class PeerConnection {
|
||||
|
||||
videoAutoQualityOptimizer: VideoAutoQualityOptimizer;
|
||||
|
||||
isDarkTheme: boolean;
|
||||
|
||||
isStreamStarted: boolean = false;
|
||||
|
||||
setMyDeviceDetails: (details: DeviceDetails) => void;
|
||||
|
||||
hostAllowedToConnectCallback: () => void;
|
||||
|
||||
setScreenSharingSourceTypeCallback: (s: 'screen' | 'window') => void;
|
||||
|
||||
setIsDarkThemeCallback: (val: boolean) => void;
|
||||
|
||||
setAppLanguageCallback: (newLang: string) => void;
|
||||
|
||||
setDialogErrorMessageCallback: (message: ErrorMessage) => void;
|
||||
|
||||
setIsErrorDialogOpen: (val: boolean) => void;
|
||||
|
||||
errorDialogMessage = ErrorMessage.UNKNOWN_ERROR;
|
||||
UIHandler: PeerConnectionUIHandler;
|
||||
|
||||
constructor(
|
||||
setUrlCallback: any,
|
||||
roomId: string,
|
||||
setUrlCallback: (url: any) => void,
|
||||
crypto: Crypto,
|
||||
videoAutoQualityOptimizer: VideoAutoQualityOptimizer,
|
||||
isDarkTheme: boolean,
|
||||
setMyDeviceDetailsCallback: (details: DeviceDetails) => void,
|
||||
hostAllowedToConnectCallback: () => void,
|
||||
setScreenSharingSourceTypeCallback: (s: 'screen' | 'window') => void,
|
||||
setIsDarkThemeCallback: (val: boolean) => void,
|
||||
setAppLanguageCallback: (newLang: string) => void,
|
||||
setDialogErrorMessageCallback: (message: ErrorMessage) => void,
|
||||
setIsErrorDialogOpen: (val: boolean) => void
|
||||
UIHandler: PeerConnectionUIHandler
|
||||
) {
|
||||
this.setUrlCallback = setUrlCallback;
|
||||
this.crypto = crypto;
|
||||
this.videoAutoQualityOptimizer = videoAutoQualityOptimizer;
|
||||
this.isDarkTheme = isDarkTheme;
|
||||
this.setMyDeviceDetails = setMyDeviceDetailsCallback;
|
||||
this.hostAllowedToConnectCallback = hostAllowedToConnectCallback;
|
||||
this.roomId = encodeURI(window.location.pathname.replace('/', ''));
|
||||
this.UIHandler = UIHandler;
|
||||
this.roomId = roomId;
|
||||
this.socket = connectSocket(this.roomId);
|
||||
this.uaParser = new UAParser();
|
||||
this.createUserAndInitSocket();
|
||||
this.createPeer();
|
||||
this.setScreenSharingSourceTypeCallback = setScreenSharingSourceTypeCallback;
|
||||
this.setIsDarkThemeCallback = setIsDarkThemeCallback;
|
||||
this.setAppLanguageCallback = setAppLanguageCallback;
|
||||
this.setDialogErrorMessageCallback = setDialogErrorMessageCallback;
|
||||
this.setIsErrorDialogOpen = setIsErrorDialogOpen;
|
||||
|
||||
this.video = null;
|
||||
this.canvas = null;
|
||||
this.largeMismatchFramesCount = 0;
|
||||
|
||||
if (!this.roomId || this.roomId === '') {
|
||||
setDialogErrorMessageCallback(ErrorMessage.UNKNOWN_ERROR);
|
||||
setIsErrorDialogOpen(true);
|
||||
setAndShowErrorDialogMessage(this, ErrorMessage.NOT_ALLOWED);
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
if (!this.socket.connected) {
|
||||
if (this.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR) {
|
||||
this.setDialogErrorMessageCallback(ErrorMessage.DENY_TO_CONNECT);
|
||||
this.setIsErrorDialogOpen(true);
|
||||
this.errorDialogMessage = ErrorMessage.DENY_TO_CONNECT;
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
startSocketConnectedCheckingLoop(this);
|
||||
}
|
||||
|
||||
setVideoQuality(videoQuality: VideoQuality) {
|
||||
@ -162,28 +96,25 @@ export default class PeerConnection {
|
||||
this.videoQualityChangedCallback();
|
||||
}
|
||||
|
||||
setErrorDialogMessage(message: ErrorMessage) {
|
||||
this.errorDialogMessage = message;
|
||||
}
|
||||
|
||||
videoQualityChangedCallback() {
|
||||
if (this.videoQuality !== VideoQuality.Q_AUTO) {
|
||||
if (this.videoQuality === VideoQuality.Q_AUTO) {
|
||||
this.peer?.send(prepareDataMessageToChangeQuality(1));
|
||||
} else {
|
||||
this.peer?.send(
|
||||
prepareDataMessageToChangeQuality(
|
||||
VIDEO_QUALITY_TO_DECIMAL[this.videoQuality]
|
||||
)
|
||||
);
|
||||
} else {
|
||||
this.peer?.send(prepareDataMessageToChangeQuality(1));
|
||||
}
|
||||
}
|
||||
|
||||
createPeer() {
|
||||
// When we are testing with jest, SimplePeer() can not be created, so we just return
|
||||
if (areWeTestingWithJest()) return;
|
||||
|
||||
const peer = new SimplePeer({
|
||||
initiator: false,
|
||||
// trickle: true,
|
||||
// stream: null,
|
||||
// allowHalfTrickle: false,
|
||||
config: { iceServers: [] },
|
||||
sdpTransform: (sdp) => {
|
||||
let newSDP = sdp;
|
||||
@ -196,58 +127,14 @@ export default class PeerConnection {
|
||||
},
|
||||
});
|
||||
|
||||
peer.on('stream', (stream) => {
|
||||
this.videoAutoQualityOptimizer.setGoodQualityCallback(() => {
|
||||
if (this.videoQuality === VideoQuality.Q_AUTO) {
|
||||
this.peer?.send(prepareDataMessageToChangeQuality(1));
|
||||
}
|
||||
});
|
||||
|
||||
this.videoAutoQualityOptimizer.setHalfQualityCallbak(() => {
|
||||
if (this.videoQuality === VideoQuality.Q_AUTO) {
|
||||
this.peer?.send(prepareDataMessageToChangeQuality(0.5));
|
||||
}
|
||||
});
|
||||
|
||||
this.videoAutoQualityOptimizer.startOptimizationLoop();
|
||||
|
||||
this.setUrlCallback(stream);
|
||||
setTimeout(() => {
|
||||
this.peer?.send(prepareDataMessageToGetSharingSourceType());
|
||||
}, 1000);
|
||||
|
||||
this.isStreamStarted = true;
|
||||
});
|
||||
|
||||
peer.on('signal', (data) => {
|
||||
// fired when webrtc done preparation to start call on this machine
|
||||
this.sendEncryptedMessage({
|
||||
type: 'CALL_ACCEPTED',
|
||||
payload: {
|
||||
signalData: data,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
peer.on('data', (data) => {
|
||||
const dataJSON = JSON.parse(data);
|
||||
|
||||
if (dataJSON.type === 'screen_sharing_source_type') {
|
||||
this.screenSharingSourceType = dataJSON.payload.value;
|
||||
if (
|
||||
this.screenSharingSourceType === 'screen' ||
|
||||
this.screenSharingSourceType === 'window'
|
||||
) {
|
||||
this.setScreenSharingSourceTypeCallback(this.screenSharingSourceType);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.peer = peer;
|
||||
peerConnectionHandlePeer(this);
|
||||
}
|
||||
|
||||
initApp(user: LocalPeerUser, myIP: string) {
|
||||
if (!this.socket) return;
|
||||
if (!this.socket) {
|
||||
throw new PeerConnectionSocketNotDefined();
|
||||
}
|
||||
this.socket.emit('USER_ENTER', {
|
||||
username: user.username,
|
||||
publicKey: user.publicKey,
|
||||
@ -275,152 +162,36 @@ export default class PeerConnection {
|
||||
});
|
||||
}
|
||||
|
||||
async sendEncryptedMessage(payload: SendEncryptedMessagePayload) {
|
||||
if (!this.socket) return;
|
||||
if (!this.user) return;
|
||||
if (!this.partner) return;
|
||||
const msg = (await prepareMessage(payload, this.user, this.partner)) as any;
|
||||
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
|
||||
sendEncryptedMessage(payload: SendEncryptedMessagePayload) {
|
||||
if (!this.socket) {
|
||||
throw new PeerConnectionSocketNotDefined();
|
||||
}
|
||||
if (!this.user || this.user === NullUser) {
|
||||
throw new PeerConnectionUserIsNotDefinedError();
|
||||
}
|
||||
if (!this.partner || this.partner === NullUser) {
|
||||
throw new PeerConnectionPartnerIsNotDefinedError();
|
||||
}
|
||||
prepareMessage(payload, this.user, this.partner).then((msg: any) => {
|
||||
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
|
||||
})
|
||||
}
|
||||
|
||||
async receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
|
||||
if (!this.user) return;
|
||||
const message = (await processMessage(
|
||||
payload,
|
||||
this.user.privateKey
|
||||
)) as any;
|
||||
if (message.type === 'CALL_USER') {
|
||||
this.peer?.signal(message.payload.signalData);
|
||||
}
|
||||
if (message.type === 'DENY_TO_CONNECT') {
|
||||
if (this.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR) {
|
||||
this.setDialogErrorMessageCallback(ErrorMessage.DENY_TO_CONNECT);
|
||||
this.setIsErrorDialogOpen(true);
|
||||
this.errorDialogMessage = ErrorMessage.DENY_TO_CONNECT;
|
||||
}
|
||||
}
|
||||
if (message.type === 'DISCONNECT_BY_HOST_MACHINE_USER') {
|
||||
if (this.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR) {
|
||||
this.setDialogErrorMessageCallback(ErrorMessage.DICONNECTED);
|
||||
this.setIsErrorDialogOpen(true);
|
||||
this.errorDialogMessage = ErrorMessage.DICONNECTED;
|
||||
}
|
||||
}
|
||||
if (message.type === 'ALLOWED_TO_CONNECT') {
|
||||
this.hostAllowedToConnectCallback();
|
||||
}
|
||||
if (message.type === 'APP_THEME') {
|
||||
if (this.isDarkTheme !== message.payload.value) {
|
||||
this.setIsDarkThemeCallback(message.payload.value);
|
||||
this.isDarkTheme = message.payload.value;
|
||||
}
|
||||
}
|
||||
if (message.type === 'APP_LANGUAGE') {
|
||||
this.setAppLanguageCallback(message.payload.value);
|
||||
}
|
||||
receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
|
||||
peerConnectionReceiveEncryptedMessage(this, payload);
|
||||
}
|
||||
|
||||
createUserAndInitSocket() {
|
||||
if (!this.socket) return;
|
||||
if (!this.socket) {
|
||||
throw new PeerConnectionSocketNotDefined();
|
||||
}
|
||||
|
||||
this.socket.removeAllListeners();
|
||||
|
||||
const userCreatedCallback = (createdUser: LocalPeerUser) => {
|
||||
this.user = createdUser;
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
// this.props.toggleSocketConnected(false);
|
||||
if (this.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR) {
|
||||
this.setDialogErrorMessageCallback(ErrorMessage.DICONNECTED);
|
||||
this.setIsErrorDialogOpen(true);
|
||||
this.errorDialogMessage = ErrorMessage.DICONNECTED;
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.socket.emit('GET_MY_IP', (ip: string) => {
|
||||
this.myIP = ip;
|
||||
this.uaParser.setUA(window.navigator.userAgent);
|
||||
this.myOS = getOSFromUAParser(this.uaParser);
|
||||
this.myDeviceType = getDeviceTypeFromUAParser(this.uaParser);
|
||||
this.myBrowser = getBrowserFromUAParser(this.uaParser);
|
||||
|
||||
this.initApp(createdUser, ip);
|
||||
});
|
||||
});
|
||||
|
||||
this.socket.on('NOT_ALLOWED', () => {
|
||||
if (this.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR) {
|
||||
this.setDialogErrorMessageCallback(ErrorMessage.NOT_ALLOWED);
|
||||
this.setIsErrorDialogOpen(true);
|
||||
this.errorDialogMessage = ErrorMessage.NOT_ALLOWED;
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('USER_ENTER', (payload: { users: PartnerPeerUser[] }) => {
|
||||
const filteredPartner = payload.users.filter((v) => {
|
||||
return createdUser.publicKey !== v.publicKey;
|
||||
});
|
||||
|
||||
this.partner = filteredPartner[0];
|
||||
|
||||
this.sendEncryptedMessage({
|
||||
type: 'ADD_USER',
|
||||
payload: {
|
||||
username: createdUser.username,
|
||||
publicKey: createdUser.publicKey,
|
||||
isOwner: true,
|
||||
id: createdUser.username,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: send device details as strings here!
|
||||
this.sendEncryptedMessage({
|
||||
type: 'DEVICE_DETAILS',
|
||||
payload: {
|
||||
socketID: this.socket.io.engine.id,
|
||||
os: this.myOS,
|
||||
deviceType: this.myDeviceType,
|
||||
browser: this.myBrowser,
|
||||
deviceScreenWidth: window.screen.width,
|
||||
deviceScreenHeight: window.screen.height,
|
||||
},
|
||||
});
|
||||
|
||||
this.sendEncryptedMessage({ type: 'GET_APP_THEME', payload: {} });
|
||||
this.sendEncryptedMessage({ type: 'GET_APP_LANGUAGE', payload: {} });
|
||||
|
||||
setTimeout(() => {
|
||||
this.setMyDeviceDetails({
|
||||
myIP: this.myIP,
|
||||
myOS: this.myOS,
|
||||
myBrowser: this.myBrowser,
|
||||
myDeviceType: this.myDeviceType,
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.socket.on('USER_EXIT', (payload: any) => {
|
||||
// this.props.receiveUnencryptedMessage('USER_EXIT', payload);
|
||||
});
|
||||
|
||||
this.socket.on(
|
||||
'ENCRYPTED_MESSAGE',
|
||||
(payload: ReceiveEncryptedMessagePayload) => {
|
||||
this.receiveEncryptedMessage(payload);
|
||||
}
|
||||
);
|
||||
|
||||
this.socket.on('ROOM_LOCKED', (payload: any) => {
|
||||
// TODO: call ROOM LOCKED callback to change react component contain ROOM LOCKED message
|
||||
// @ts-ignore
|
||||
// document.querySelector('#my-ip')?.innerHTML = 'ROOM LOCKED';
|
||||
if (this.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR) {
|
||||
this.setDialogErrorMessageCallback(ErrorMessage.DENY_TO_CONNECT);
|
||||
this.setIsErrorDialogOpen(true);
|
||||
this.errorDialogMessage = ErrorMessage.UNKNOWN_ERROR;
|
||||
}
|
||||
});
|
||||
peerConnectionHandleSocket(this);
|
||||
|
||||
window.addEventListener('beforeunload', (_) => {
|
||||
this.socket.emit('USER_DISCONNECT');
|
||||
|
@ -0,0 +1,2 @@
|
||||
export const INPUTtestWindowNavigatorUserAgent =
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36';
|
@ -0,0 +1,109 @@
|
||||
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,6 @@
|
||||
export const OUTPUTDeviceDetailsFromUAParsed: DeviceDetails = {
|
||||
myBrowser: 'Chrome 87.0.4280.88',
|
||||
myDeviceType: 'computer',
|
||||
myIP: '123.123.123.123',
|
||||
myOS: 'Mac OS 10.15.6',
|
||||
};
|
@ -0,0 +1,110 @@
|
||||
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
|
||||
`;
|
@ -0,0 +1,300 @@
|
||||
import SimplePeer from 'simple-peer';
|
||||
import PeerConnection from '.';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import { VideoQuality } from '../VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
import peerConnectionHandlePeer, {
|
||||
getSharingShourceType,
|
||||
} from './peerConnectionHandlePeer';
|
||||
import PeerConnectionPeerIsNullError from './errors/PeerConnectionPeerIsNullError';
|
||||
import PeerConnectionUIHandler from './PeerConnectionUIHandler';
|
||||
import {
|
||||
prepareDataMessageToChangeQuality,
|
||||
prepareDataMessageToGetSharingSourceType,
|
||||
} from './simplePeerDataMessages';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('../../utils/crypto.ts');
|
||||
jest.mock('simple-peer', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
const listeners = new Map<string, any[]>();
|
||||
return {
|
||||
...jest.requireActual('simple-peer'),
|
||||
on: jest.fn().mockImplementation((s: string, callback: any) => {
|
||||
if (!listeners.has(s)) {
|
||||
listeners.set(s, []);
|
||||
}
|
||||
listeners.get(s)?.push(callback);
|
||||
}),
|
||||
emit: jest.fn().mockImplementation((s: string, any: any) => {
|
||||
listeners.forEach((callbacks, key) => {
|
||||
callbacks.forEach((callback) => {
|
||||
if (key === s) {
|
||||
callback(any);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
send: jest.fn().mockImplementation((_: string) => {}),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const screen_sharing_source_type_JSON_DATA =
|
||||
' \
|
||||
{ \
|
||||
"type": "screen_sharing_source_type", \
|
||||
"payload": { \
|
||||
"value": "screen" \
|
||||
} \
|
||||
} \
|
||||
';
|
||||
|
||||
const window_sharing_source_type_JSON_DATA =
|
||||
' \
|
||||
{ \
|
||||
"type": "screen_sharing_source_type", \
|
||||
"payload": { \
|
||||
"value": "window" \
|
||||
} \
|
||||
} \
|
||||
';
|
||||
|
||||
describe('peerConnectionHandlePeer callback', () => {
|
||||
let peerConnection: PeerConnection;
|
||||
let videoQualityOptimizer: VideoAutoQualityOptimizer;
|
||||
|
||||
const setURLCallbackMock = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
videoQualityOptimizer = new VideoAutoQualityOptimizer();
|
||||
jest.spyOn(videoQualityOptimizer, 'startOptimizationLoop');
|
||||
jest.spyOn(videoQualityOptimizer, 'goodQualityCallback');
|
||||
jest.spyOn(videoQualityOptimizer, 'halfQualityCallbak');
|
||||
peerConnection = new PeerConnection(
|
||||
'123',
|
||||
setURLCallbackMock,
|
||||
new Crypto(),
|
||||
videoQualityOptimizer,
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn()
|
||||
)
|
||||
);
|
||||
jest.spyOn(peerConnection, 'sendEncryptedMessage');
|
||||
peerConnection.peer = new SimplePeer();
|
||||
|
||||
peerConnection.user = {
|
||||
username: 'af',
|
||||
privateKey: 'af',
|
||||
publicKey: 'af',
|
||||
};
|
||||
peerConnection.partner = {
|
||||
username: 'af',
|
||||
publicKey: 'af',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when peerConnection with peer null passed as parameter', () => {
|
||||
function callPeerConnectionHandlePeerWithPeerNull() {
|
||||
peerConnection.peer = null;
|
||||
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
}
|
||||
it('should throw an error', () => {
|
||||
peerConnection.peer = null;
|
||||
|
||||
expect(callPeerConnectionHandlePeerWithPeerNull).toThrow(
|
||||
new PeerConnectionPeerIsNullError()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when peerConnectionHandlePeer() is called properly', () => {
|
||||
it('should have set .on("stream", () => {}) callback on PeerConnection.peer', () => {
|
||||
jest.requireMock('simple-peer');
|
||||
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
expect(peerConnection.peer?.on).toBeCalledWith(
|
||||
'stream',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should have set .on("data", () => {}) callback on PeerConnection.peer', () => {
|
||||
jest.requireMock('simple-peer');
|
||||
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
expect(peerConnection.peer?.on).toBeCalledWith('data', expect.anything());
|
||||
});
|
||||
|
||||
it('should have set .on("signal", () => {}) callback on PeerConnection.peer', () => {
|
||||
jest.requireMock('simple-peer');
|
||||
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
expect(peerConnection.peer?.on).toBeCalledWith(
|
||||
'signal',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
describe('when "stream" event occured', () => {
|
||||
it('should start video quality optimization loop', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
peerConnection.peer?.emit('stream');
|
||||
|
||||
expect(
|
||||
peerConnection.videoAutoQualityOptimizer.startOptimizationLoop
|
||||
).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call getSharingShourceType function to get sharing source type from host', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
peerConnection.peer?.emit('stream');
|
||||
|
||||
expect(setTimeout).toHaveBeenLastCalledWith(
|
||||
getSharingShourceType,
|
||||
1000,
|
||||
peerConnection
|
||||
);
|
||||
});
|
||||
|
||||
describe('when quality is AUTO and when video quality optimizer requiests GOOD quality', () => {
|
||||
it('should call .send with proper data message', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
peerConnection.peer?.emit('stream');
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.goodQualityCallback();
|
||||
|
||||
expect(peerConnection.videoQuality).toBe(VideoQuality.Q_AUTO);
|
||||
expect(peerConnection.peer?.send).toBeCalledWith(
|
||||
prepareDataMessageToChangeQuality(1)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when quality is NOT AUTO and when video quality optimizer requiests GOOD quality', () => {
|
||||
it('should call NOT .send with proper data message', () => {
|
||||
peerConnection.videoQuality = VideoQuality.Q_25_PERCENT;
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
peerConnection.peer?.emit('stream');
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.goodQualityCallback();
|
||||
|
||||
expect(peerConnection.peer?.send).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when quality is AUTO and when video quality optimizer requiests HALF quality', () => {
|
||||
it('should call .send with proper data message', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
peerConnection.peer?.emit('stream');
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.halfQualityCallbak();
|
||||
|
||||
expect(peerConnection.videoQuality).toBe(VideoQuality.Q_AUTO);
|
||||
expect(peerConnection.peer?.send).toBeCalledWith(
|
||||
prepareDataMessageToChangeQuality(0.5)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when quality is NOT AUTO and when video quality optimizer requiests GOOD quality', () => {
|
||||
it('should call NOT .send with proper data message', () => {
|
||||
peerConnection.videoQuality = VideoQuality.Q_25_PERCENT;
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
peerConnection.peer?.emit('stream');
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.halfQualityCallbak();
|
||||
|
||||
expect(peerConnection.peer?.send).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "signal" event occured', () => {
|
||||
it('should call sendEncryptedMessage on peer connection', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
peerConnection.peer?.emit('signal');
|
||||
|
||||
expect(peerConnection.sendEncryptedMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "data" event occured', () => {
|
||||
describe('when data.type is "screen_sharing_source_type"', () => {
|
||||
describe('when dataJSON.payload.value === screen', () => {
|
||||
it('should call UIHandler.setScreenSharingSourceTypeCallback with "screen" string as parameter', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
peerConnection.peer?.emit(
|
||||
'data',
|
||||
screen_sharing_source_type_JSON_DATA
|
||||
);
|
||||
|
||||
expect(
|
||||
peerConnection.UIHandler.setScreenSharingSourceTypeCallback
|
||||
).toBeCalledWith('screen');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when dataJSON.payload.value === window', () => {
|
||||
it('should call UIHandler.setScreenSharingSourceTypeCallback with "window" string as parameter', () => {
|
||||
peerConnectionHandlePeer(peerConnection);
|
||||
|
||||
peerConnection.peer?.emit(
|
||||
'data',
|
||||
window_sharing_source_type_JSON_DATA
|
||||
);
|
||||
|
||||
expect(
|
||||
peerConnection.UIHandler.setScreenSharingSourceTypeCallback
|
||||
).toBeCalledWith('window');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when dataJSON.payload.value is NOT "screen" or "window"', () => {
|
||||
it('should do nothing', () => {
|
||||
expect(
|
||||
peerConnection.UIHandler.setScreenSharingSourceTypeCallback
|
||||
).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when data.type is NOT "screen_sharing_source_type"', () => {
|
||||
it('should do nothing', () => {
|
||||
expect(
|
||||
peerConnection.UIHandler.setScreenSharingSourceTypeCallback
|
||||
).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getSharingSourceType is called properly', () => {
|
||||
it('should call .send method with proper payload to get sharing source type from host', () => {
|
||||
getSharingShourceType(peerConnection);
|
||||
|
||||
expect(peerConnection.peer?.send).toBeCalledWith(
|
||||
prepareDataMessageToGetSharingSourceType()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
import PeerConnection from '.';
|
||||
import {
|
||||
prepareDataMessageToChangeQuality,
|
||||
prepareDataMessageToGetSharingSourceType,
|
||||
} from './simplePeerDataMessages';
|
||||
import { VideoQuality } from '../VideoAutoQualityOptimizer/VideoQualityEnum';
|
||||
import PeerConnectionPeerIsNullError from './errors/PeerConnectionPeerIsNullError';
|
||||
|
||||
export function getSharingShourceType(peerConnection: PeerConnection) {
|
||||
try {
|
||||
peerConnection.peer?.send(prepareDataMessageToGetSharingSourceType());
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export default (peerConnection: PeerConnection) => {
|
||||
if (peerConnection.peer === null) {
|
||||
throw new PeerConnectionPeerIsNullError();
|
||||
}
|
||||
peerConnection.peer.on('stream', (stream) => {
|
||||
peerConnection.setUrlCallback(stream);
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.setGoodQualityCallback(() => {
|
||||
if (peerConnection.videoQuality === VideoQuality.Q_AUTO) {
|
||||
try {
|
||||
peerConnection.peer?.send(prepareDataMessageToChangeQuality(1));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.setHalfQualityCallbak(() => {
|
||||
if (peerConnection.videoQuality === VideoQuality.Q_AUTO) {
|
||||
try {
|
||||
peerConnection.peer?.send(prepareDataMessageToChangeQuality(0.5));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
peerConnection.videoAutoQualityOptimizer.startOptimizationLoop();
|
||||
|
||||
setTimeout(getSharingShourceType, 1000, peerConnection);
|
||||
|
||||
peerConnection.isStreamStarted = true;
|
||||
});
|
||||
|
||||
peerConnection.peer.on('signal', (data) => {
|
||||
// fired when webrtc done preparation to start call on peerConnection machine
|
||||
peerConnection.sendEncryptedMessage({
|
||||
type: 'CALL_ACCEPTED',
|
||||
payload: {
|
||||
signalData: data,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
peerConnection.peer.on('data', (data) => {
|
||||
const dataJSON = JSON.parse(data);
|
||||
|
||||
if (dataJSON.type === 'screen_sharing_source_type') {
|
||||
peerConnection.screenSharingSourceType = dataJSON.payload.value;
|
||||
if (
|
||||
peerConnection.screenSharingSourceType === 'screen' ||
|
||||
peerConnection.screenSharingSourceType === 'window'
|
||||
) {
|
||||
peerConnection.UIHandler.setScreenSharingSourceTypeCallback(
|
||||
peerConnection.screenSharingSourceType
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,241 @@
|
||||
import { OUTPUTDeviceDetailsFromUAParsed } from './mocks/OUTPUTDeviceDetailsFromUAParsed';
|
||||
import { INPUTtestWindowNavigatorUserAgent } from './mocks/INPUTtestWindowNavigatorUserAgent';
|
||||
jest.mock('./setAndShowErrorDialogMessage');
|
||||
// import SimplePeer from 'simple-peer';
|
||||
import PeerConnection from '.';
|
||||
// import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import PeerConnectionUIHandler from './PeerConnectionUIHandler';
|
||||
import peerConnectionHandleSocket, {
|
||||
getMyIPCallback,
|
||||
} from './peerConnectionHandleSocket';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
import PeerConnectionSocketNotDefined from './errors/PeerConnectionSocketNotDefined';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
// jest.mock('.');
|
||||
jest.mock('../../utils/crypto.ts');
|
||||
|
||||
const TEST_IP = '123.123.123.123';
|
||||
|
||||
describe('peerConnectionHandleSocket callback', () => {
|
||||
let peerConnection: PeerConnection;
|
||||
|
||||
beforeEach(() => {
|
||||
peerConnection = new PeerConnection(
|
||||
'123',
|
||||
jest.fn(),
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn()
|
||||
)
|
||||
);
|
||||
|
||||
peerConnection.initApp = jest.fn();
|
||||
|
||||
const listeners = new Map<string, any[]>();
|
||||
|
||||
peerConnection.socket = {
|
||||
on: jest.fn().mockImplementation((s: string, callback: any) => {
|
||||
if (!listeners.has(s)) {
|
||||
listeners.set(s, []);
|
||||
}
|
||||
listeners.get(s)?.push(callback);
|
||||
}),
|
||||
emit: jest.fn().mockImplementation((s: string, any: any) => {
|
||||
listeners.forEach((callbacks, key) => {
|
||||
callbacks.forEach((callback) => {
|
||||
if (key === s) {
|
||||
callback(any);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
io: {
|
||||
engine: {
|
||||
id: '434241'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
peerConnection.receiveEncryptedMessage = jest.fn();
|
||||
peerConnection.sendEncryptedMessage = jest.fn();
|
||||
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('when peerConnectionHandleSocket is called with .socket undefined', () => {
|
||||
it('should throw an error', () => {
|
||||
peerConnection.socket = undefined;
|
||||
|
||||
try {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
fail('PeerConnectionSocketNotDefined should be thrown here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new PeerConnectionSocketNotDefined());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when peerConnectionHandleSocket is called properly', () => {
|
||||
describe('when socket.on("disconnect") occured', () => {
|
||||
it('should call setAndShowErrorDialogMessage to show user that they are disconnected', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('disconnect');
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when socket.on("connect") occured', () => {
|
||||
it('should call socket.emit("GET_MY_IP")', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('connect');
|
||||
|
||||
expect(peerConnection.socket.emit).toBeCalledWith(
|
||||
'GET_MY_IP',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when socket.on("ENCRYPTED_MESSAGE") occured', () => {
|
||||
it('should call peerConnection receiveEncryptedMessage', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('ENCRYPTED_MESSAGE');
|
||||
|
||||
expect(peerConnection.receiveEncryptedMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when socket.on("NOT_ALLOWED") occured', () => {
|
||||
it('should change UI accordingly', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('NOT_ALLOWED');
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when socket.on("ROOM_LOCKED") occured', () => {
|
||||
it('should change UI accordingly', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('ROOM_LOCKED');
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when socket.on("USER_ENTER") occured', () => {
|
||||
it('should call setMyDeviceDetails on UIHandler', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('USER_ENTER', {
|
||||
users: [{ username: 'asdf', publicKey: '1234' }],
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(peerConnection.UIHandler.setMyDeviceDetails).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call sendEncryptedMessage with ADD_USER type', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('USER_ENTER', {
|
||||
users: [{ username: 'asdf', publicKey: '1234' }],
|
||||
});
|
||||
|
||||
expect(peerConnection.sendEncryptedMessage).toBeCalledWith({
|
||||
type: 'ADD_USER',
|
||||
payload: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should call sendEncryptedMessage with DEVICE_DETAILS type', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('USER_ENTER', {
|
||||
users: [{ username: 'asdf', publicKey: '1234' }],
|
||||
});
|
||||
|
||||
expect(peerConnection.sendEncryptedMessage).toBeCalledWith({
|
||||
type: 'DEVICE_DETAILS',
|
||||
payload: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should call sendEncryptedMessage with GET_APP_THEME type', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('USER_ENTER', {
|
||||
users: [{ username: 'asdf', publicKey: '1234' }],
|
||||
});
|
||||
|
||||
expect(peerConnection.sendEncryptedMessage).toBeCalledWith({
|
||||
type: 'GET_APP_THEME',
|
||||
payload: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should call sendEncryptedMessage with GET_APP_LANGUAGE type', () => {
|
||||
peerConnectionHandleSocket(peerConnection);
|
||||
|
||||
peerConnection.socket.emit('USER_ENTER', {
|
||||
users: [{ username: 'asdf', publicKey: '1234' }],
|
||||
});
|
||||
|
||||
expect(peerConnection.sendEncryptedMessage).toBeCalledWith({
|
||||
type: 'GET_APP_LANGUAGE',
|
||||
payload: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getMyIPCallback is called properly', () => {
|
||||
it('should call initApp on peerConnection', () => {
|
||||
getMyIPCallback(
|
||||
peerConnection,
|
||||
TEST_IP,
|
||||
INPUTtestWindowNavigatorUserAgent
|
||||
);
|
||||
|
||||
expect(peerConnection.initApp).toBeCalled();
|
||||
});
|
||||
|
||||
it('should make peerConnection.myDeviceDetails with proper parameters', () => {
|
||||
getMyIPCallback(
|
||||
peerConnection,
|
||||
TEST_IP,
|
||||
INPUTtestWindowNavigatorUserAgent
|
||||
);
|
||||
|
||||
expect(peerConnection.myDeviceDetails).toEqual(
|
||||
OUTPUTDeviceDetailsFromUAParsed
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,118 @@
|
||||
import PeerConnection from '.';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import {
|
||||
getBrowserFromUAParser,
|
||||
getDeviceTypeFromUAParser,
|
||||
getOSFromUAParser,
|
||||
} from '../../utils/userAgentParserHelpers';
|
||||
import PeerConnectionSocketNotDefined from './errors/PeerConnectionSocketNotDefined';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
|
||||
export function getMyIPCallback(
|
||||
peerConnection: PeerConnection,
|
||||
ip: string,
|
||||
userAgent: string
|
||||
) {
|
||||
peerConnection.myDeviceDetails.myIP = ip;
|
||||
|
||||
peerConnection.uaParser.setUA(userAgent);
|
||||
peerConnection.myDeviceDetails.myOS = getOSFromUAParser(
|
||||
peerConnection.uaParser
|
||||
);
|
||||
peerConnection.myDeviceDetails.myDeviceType = getDeviceTypeFromUAParser(
|
||||
peerConnection.uaParser
|
||||
);
|
||||
peerConnection.myDeviceDetails.myBrowser = getBrowserFromUAParser(
|
||||
peerConnection.uaParser
|
||||
);
|
||||
|
||||
peerConnection.initApp(peerConnection.user, ip);
|
||||
}
|
||||
|
||||
export default (peerConnection: PeerConnection) => {
|
||||
if (!peerConnection.socket) {
|
||||
throw new PeerConnectionSocketNotDefined();
|
||||
}
|
||||
|
||||
peerConnection.socket.on('disconnect', () => {
|
||||
setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DISCONNECTED);
|
||||
});
|
||||
|
||||
peerConnection.socket.on('connect', () => {
|
||||
peerConnection.socket.emit('GET_MY_IP', (ip: string) => {
|
||||
getMyIPCallback(peerConnection, ip, window.navigator.userAgent);
|
||||
});
|
||||
});
|
||||
|
||||
peerConnection.socket.on('NOT_ALLOWED', () => {
|
||||
setAndShowErrorDialogMessage(peerConnection, ErrorMessage.NOT_ALLOWED);
|
||||
});
|
||||
|
||||
peerConnection.socket.on(
|
||||
'USER_ENTER',
|
||||
(payload: { users: PartnerPeerUser[] }) => {
|
||||
const filteredPartner = payload.users.filter((v) => {
|
||||
return peerConnection.user.publicKey !== v.publicKey;
|
||||
});
|
||||
|
||||
peerConnection.partner = filteredPartner[0];
|
||||
|
||||
if (!peerConnection.partner) return;
|
||||
|
||||
peerConnection.sendEncryptedMessage({
|
||||
type: 'ADD_USER',
|
||||
payload: {
|
||||
username: peerConnection.user.username,
|
||||
publicKey: peerConnection.user.publicKey,
|
||||
isOwner: true,
|
||||
id: peerConnection.user.username,
|
||||
},
|
||||
});
|
||||
|
||||
peerConnection.sendEncryptedMessage({
|
||||
type: 'DEVICE_DETAILS',
|
||||
payload: {
|
||||
socketID: peerConnection.socket.io.engine.id,
|
||||
os: peerConnection.myDeviceDetails.myOS,
|
||||
deviceType: peerConnection.myDeviceDetails.myDeviceType,
|
||||
browser: peerConnection.myDeviceDetails.myBrowser,
|
||||
deviceScreenWidth: window.screen.width,
|
||||
deviceScreenHeight: window.screen.height,
|
||||
},
|
||||
});
|
||||
|
||||
peerConnection.sendEncryptedMessage({
|
||||
type: 'GET_APP_THEME',
|
||||
payload: {},
|
||||
});
|
||||
peerConnection.sendEncryptedMessage({
|
||||
type: 'GET_APP_LANGUAGE',
|
||||
payload: {},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
peerConnection.UIHandler.setMyDeviceDetails({
|
||||
myIP: peerConnection.myDeviceDetails.myIP,
|
||||
myOS: peerConnection.myDeviceDetails.myOS,
|
||||
myBrowser: peerConnection.myDeviceDetails.myBrowser,
|
||||
myDeviceType: peerConnection.myDeviceDetails.myDeviceType,
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
);
|
||||
|
||||
// peerConnection.socket.on('USER_EXIT', (payload: any) => {
|
||||
// // peerConnection.props.receiveUnencryptedMessage('USER_EXIT', payload);
|
||||
// });
|
||||
|
||||
peerConnection.socket.on(
|
||||
'ENCRYPTED_MESSAGE',
|
||||
(payload: ReceiveEncryptedMessagePayload) => {
|
||||
peerConnection.receiveEncryptedMessage(payload);
|
||||
}
|
||||
);
|
||||
|
||||
peerConnection.socket.on('ROOM_LOCKED', () => {
|
||||
setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DENY_TO_CONNECT);
|
||||
});
|
||||
};
|
@ -0,0 +1,284 @@
|
||||
jest.mock('../../utils/message');
|
||||
jest.mock('./setAndShowErrorDialogMessage');
|
||||
|
||||
import PeerConnection from '.';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import PeerConnectionUIHandler from './PeerConnectionUIHandler';
|
||||
import peerConnectionReceiveEncryptedMessage from './peerConnectionReceiveEncryptedMessage';
|
||||
import NullUser from './NullUser';
|
||||
import PeerConnectionUserIsNotDefinedError from './errors/PeerConnectionUserIsNotDefinedError';
|
||||
import SimplePeer from 'simple-peer';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const TEST_USER = { username: 'asdf', publicKey: 'ff', privateKey: 'sss' };
|
||||
|
||||
const DUMMY_PAYLOAD = {
|
||||
payload: '',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const CALL_USER_PAYLOAD = {
|
||||
payload: 'CALL_USER',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const DENY_TO_CONNECT_PAYLOAD = {
|
||||
payload: 'DENY_TO_CONNECT',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const DISCONNECT_BY_HOST_MACHINE_USER = {
|
||||
payload: 'DISCONNECT_BY_HOST_MACHINE_USER',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const ALLOWED_TO_CONNECT_PAYLOAD = {
|
||||
payload: 'ALLOWED_TO_CONNECT',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const APP_THEME_PAYLOAD = {
|
||||
payload: 'APP_THEME',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const APP_LANGUAGE_PAYLOAD = {
|
||||
payload: 'APP_LANGUAGE',
|
||||
signature: '',
|
||||
iv: '',
|
||||
keys: [],
|
||||
};
|
||||
|
||||
jest.mock('../../utils/message', () => {
|
||||
return {
|
||||
process: (payload: any) => {
|
||||
return new Promise<ProcessedMessage>((resolve) => {
|
||||
if (payload.payload === 'CALL_USER') {
|
||||
resolve({
|
||||
type: 'CALL_USER',
|
||||
payload: {
|
||||
signalData: '1signal',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (payload.payload === 'DENY_TO_CONNECT') {
|
||||
resolve({
|
||||
type: 'DENY_TO_CONNECT',
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
if (payload.payload === 'DISCONNECT_BY_HOST_MACHINE_USER') {
|
||||
resolve({
|
||||
type: 'DISCONNECT_BY_HOST_MACHINE_USER',
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
if (payload.payload === 'ALLOWED_TO_CONNECT') {
|
||||
resolve({
|
||||
type: 'ALLOWED_TO_CONNECT',
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
if (payload.payload === 'APP_THEME') {
|
||||
resolve({
|
||||
type: 'APP_THEME',
|
||||
payload: {
|
||||
value: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (payload.payload === 'APP_LANGUAGE') {
|
||||
resolve({
|
||||
type: 'APP_LANGUAGE',
|
||||
payload: {
|
||||
value: 'latin',
|
||||
},
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('simple-peer', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
const listeners = new Map<string, any[]>();
|
||||
return {
|
||||
...jest.requireActual('simple-peer'),
|
||||
on: jest.fn().mockImplementation((s: string, callback: any) => {
|
||||
if (!listeners.has(s)) {
|
||||
listeners.set(s, []);
|
||||
}
|
||||
listeners.get(s)?.push(callback);
|
||||
}),
|
||||
emit: jest.fn().mockImplementation((s: string, any: any) => {
|
||||
listeners.forEach((callbacks, key) => {
|
||||
callbacks.forEach((callback) => {
|
||||
if (key === s) {
|
||||
callback(any);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
send: jest.fn().mockImplementation((_: string) => {}),
|
||||
signal: jest.fn().mockImplementation((_: string) => {}),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('peerConnectionReceiveEncryptedMessage', () => {
|
||||
let peerConnection: PeerConnection;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
peerConnection = new PeerConnection(
|
||||
'123',
|
||||
jest.fn(),
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn()
|
||||
)
|
||||
);
|
||||
peerConnection.user = TEST_USER;
|
||||
peerConnection.peer = new SimplePeer();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when peerConnectionReceiveEncryptedMessage is called', () => {
|
||||
describe('when peerConnection.user is NullUser', () => {
|
||||
it('should throw and error', () => {
|
||||
peerConnection.user = NullUser;
|
||||
|
||||
peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
DUMMY_PAYLOAD
|
||||
).catch((e) => expect(e).toEqual(new PeerConnectionUserIsNotDefinedError()));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "CALL_USER"', () => {
|
||||
it('should call .signal on simple-peer', async () => {
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
CALL_USER_PAYLOAD
|
||||
);
|
||||
|
||||
expect(peerConnection.peer?.signal).toBeCalledWith('1signal');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "DENY_TO_CONNECT"', () => {
|
||||
it('should call setAndShowErrorDialogMessage with DENY_TO_CONNECT error', async () => {
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
DENY_TO_CONNECT_PAYLOAD
|
||||
);
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalledWith(
|
||||
peerConnection,
|
||||
ErrorMessage.DENY_TO_CONNECT
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "DISCONNECT_BY_HOST_MACHINE_USER"', () => {
|
||||
it('should call setAndShowErrorDialogMessage with DISCONNECT_BY_HOST_MACHINE_USER error', async () => {
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
DISCONNECT_BY_HOST_MACHINE_USER
|
||||
);
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalledWith(
|
||||
peerConnection,
|
||||
ErrorMessage.DISCONNECTED
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "ALLOWED_TO_CONNECT"', () => {
|
||||
it('should call setAndShowErrorDialogMessage with ALLOWED_TO_CONNECT error', async () => {
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
ALLOWED_TO_CONNECT_PAYLOAD
|
||||
);
|
||||
|
||||
expect(
|
||||
peerConnection.UIHandler.hostAllowedToConnectCallback
|
||||
).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "APP_THEME"', () => {
|
||||
it('should call setAndShowErrorDialogMessage with APP_THEME error', async () => {
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
APP_THEME_PAYLOAD
|
||||
);
|
||||
|
||||
expect(peerConnection.UIHandler.setIsDarkThemeCallback).toBeCalledWith(
|
||||
false
|
||||
);
|
||||
expect(peerConnection.UIHandler.isDarkTheme).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "APP_THEME" and current theme is the same as received', () => {
|
||||
it('should call setAndShowErrorDialogMessage with APP_LANGUAGE error', async () => {
|
||||
peerConnection.UIHandler.isDarkTheme = false;
|
||||
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
APP_LANGUAGE_PAYLOAD
|
||||
);
|
||||
|
||||
expect(peerConnection.UIHandler.setIsDarkThemeCallback).not.toBeCalledWith(
|
||||
false
|
||||
);
|
||||
expect(peerConnection.UIHandler.isDarkTheme).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when processedMessageType is "APP_LANGUAGE"', () => {
|
||||
it('should call setAndShowErrorDialogMessage with APP_LANGUAGE error', async () => {
|
||||
await peerConnectionReceiveEncryptedMessage(
|
||||
peerConnection,
|
||||
APP_LANGUAGE_PAYLOAD
|
||||
);
|
||||
|
||||
expect(peerConnection.UIHandler.setAppLanguageCallback).toBeCalledWith(
|
||||
'latin'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import PeerConnection from '.';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import { process as processMessage } from '../../utils/message';
|
||||
import NullUser from './NullUser';
|
||||
import PeerConnectionUserIsNotDefinedError from './errors/PeerConnectionUserIsNotDefinedError';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
|
||||
export default async (
|
||||
peerConnection: PeerConnection,
|
||||
payload: ReceiveEncryptedMessagePayload
|
||||
) => {
|
||||
if (peerConnection.user === NullUser) {
|
||||
throw new PeerConnectionUserIsNotDefinedError();
|
||||
}
|
||||
const message = (await processMessage(
|
||||
payload,
|
||||
peerConnection.user.privateKey
|
||||
)) as any;
|
||||
if (message.type === 'CALL_USER') {
|
||||
peerConnection.peer?.signal(message.payload.signalData);
|
||||
}
|
||||
if (message.type === 'DENY_TO_CONNECT') {
|
||||
setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DENY_TO_CONNECT);
|
||||
}
|
||||
if (message.type === 'DISCONNECT_BY_HOST_MACHINE_USER') {
|
||||
setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DISCONNECTED);
|
||||
}
|
||||
if (message.type === 'ALLOWED_TO_CONNECT') {
|
||||
peerConnection.UIHandler.hostAllowedToConnectCallback();
|
||||
}
|
||||
if (message.type === 'APP_THEME') {
|
||||
if (peerConnection.UIHandler.isDarkTheme !== message.payload.value) {
|
||||
peerConnection.UIHandler.setIsDarkThemeCallback(message.payload.value);
|
||||
peerConnection.UIHandler.isDarkTheme = message.payload.value;
|
||||
}
|
||||
}
|
||||
if (message.type === 'APP_LANGUAGE') {
|
||||
peerConnection.UIHandler.setAppLanguageCallback(message.payload.value);
|
||||
}
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
export default (q: number) => {
|
||||
return `
|
||||
{
|
||||
"type": "set_video_quality",
|
||||
"payload": {
|
||||
"value": ${q}
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
export default () => {
|
||||
return `
|
||||
{
|
||||
"type": "get_sharing_source_type",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
@ -0,0 +1,112 @@
|
||||
import SimplePeer from 'simple-peer';
|
||||
import PeerConnection from '.';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import Crypto from '../../utils/crypto';
|
||||
import VideoAutoQualityOptimizer from '../VideoAutoQualityOptimizer';
|
||||
import PeerConnectionUIHandler from './PeerConnectionUIHandler';
|
||||
import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('../../utils/crypto.ts');
|
||||
jest.mock('simple-peer', () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
const listeners = new Map<string, any[]>();
|
||||
return {
|
||||
...jest.requireActual('simple-peer'),
|
||||
on: jest.fn().mockImplementation((s: string, callback: any) => {
|
||||
if (!listeners.has(s)) {
|
||||
listeners.set(s, []);
|
||||
}
|
||||
listeners.get(s)?.push(callback);
|
||||
}),
|
||||
emit: jest.fn().mockImplementation((s: string, any: any) => {
|
||||
listeners.forEach((callbacks, key) => {
|
||||
callbacks.forEach((callback) => {
|
||||
if (key === s) {
|
||||
callback(any);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
send: jest.fn().mockImplementation((_: string) => {}),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('peerConnectionHandlePeer callback', () => {
|
||||
let peerConnection: PeerConnection;
|
||||
|
||||
const setIsErrorDialogOpen = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
peerConnection = new PeerConnection(
|
||||
'123',
|
||||
jest.fn(),
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
setIsErrorDialogOpen
|
||||
)
|
||||
);
|
||||
peerConnection.peer = new SimplePeer();
|
||||
peerConnection.UIHandler.errorDialogMessage = ErrorMessage.UNKNOWN_ERROR;
|
||||
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('when setAndShowErrorDialogMessage is called properly', () => {
|
||||
describe('when error message in ui handler is UNKNOWN_ERROR', () => {
|
||||
it('whould show error dialog with given error message', () => {
|
||||
expect(peerConnection.UIHandler.errorDialogMessage).toEqual(
|
||||
ErrorMessage.UNKNOWN_ERROR
|
||||
);
|
||||
|
||||
const errorMessage = ErrorMessage.DENY_TO_CONNECT;
|
||||
setAndShowErrorDialogMessage(peerConnection, errorMessage);
|
||||
|
||||
expect(
|
||||
peerConnection.UIHandler.setDialogErrorMessageCallback
|
||||
).toBeCalledWith(errorMessage);
|
||||
expect(peerConnection.UIHandler.setIsErrorDialogOpen).toBeCalledWith(
|
||||
true
|
||||
);
|
||||
expect(peerConnection.UIHandler.errorDialogMessage).toEqual(
|
||||
errorMessage
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when error message in ui handler is NOT UNKNOWN_ERROR', () => {
|
||||
it('whould not show anything in UI and do nothing to UIHandler', () => {
|
||||
const originalErrorMessage = ErrorMessage.DISCONNECTED;
|
||||
peerConnection.UIHandler.errorDialogMessage = originalErrorMessage;
|
||||
|
||||
const errorMessage = ErrorMessage.UNKNOWN_ERROR;
|
||||
|
||||
setAndShowErrorDialogMessage(peerConnection, errorMessage);
|
||||
|
||||
expect(
|
||||
peerConnection.UIHandler.setDialogErrorMessageCallback
|
||||
).not.toBeCalled();
|
||||
expect(peerConnection.UIHandler.setIsErrorDialogOpen).not.toBeCalled();
|
||||
expect(peerConnection.UIHandler.errorDialogMessage).toEqual(
|
||||
originalErrorMessage
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
import PeerConnection from '.';
|
||||
import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
|
||||
|
||||
export default (peerConnection: PeerConnection, errorMessage: ErrorMessage) => {
|
||||
if (
|
||||
peerConnection.UIHandler.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR
|
||||
) {
|
||||
peerConnection.UIHandler.setDialogErrorMessageCallback(errorMessage);
|
||||
peerConnection.UIHandler.setIsErrorDialogOpen(true);
|
||||
peerConnection.UIHandler.errorDialogMessage = errorMessage;
|
||||
}
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
import { INPUTtestSdpMediaBitrate } from './mocks/INPUTvideo500000testSdpMediaBitrate';
|
||||
import { OUTPUTtestSdpMediaBitrate } from './mocks/OUTPUTvideo500000testSdpMediaBitrate';
|
||||
import setSdpMediaBitrate from './setSdpMediaBitrate';
|
||||
|
||||
describe('setSdpMediaBitrate', () => {
|
||||
describe('when setSdpMediaBitrate is called with sdp input', () => {
|
||||
it('should produce a result that should match with test sdp string', () => {
|
||||
const result = setSdpMediaBitrate(
|
||||
INPUTtestSdpMediaBitrate,
|
||||
'video',
|
||||
500000
|
||||
);
|
||||
|
||||
expect(result).toMatch(OUTPUTtestSdpMediaBitrate);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
export function prepareDataMessageToChangeQuality(q: number) {
|
||||
return `
|
||||
{
|
||||
"type": "set_video_quality",
|
||||
"payload": {
|
||||
"value": ${q}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
export function prepareDataMessageToGetSharingSourceType(){
|
||||
return `
|
||||
{
|
||||
"type": "get_sharing_source_type",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import PeerConnection from '..';
|
||||
import VideoAutoQualityOptimizer from '../../VideoAutoQualityOptimizer';
|
||||
import Crypto from '../../../utils/crypto';
|
||||
import startSocketConnectedCheckingLoop from '.';
|
||||
import setAndShowErrorDialogMessage from '../setAndShowErrorDialogMessage';
|
||||
import PeerConnectionUIHandler from '../PeerConnectionUIHandler';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('../setAndShowErrorDialogMessage');
|
||||
|
||||
describe('startSocketConnectedCheckingLoop', () => {
|
||||
let peerConnection: PeerConnection;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
peerConnection = new PeerConnection(
|
||||
'123',
|
||||
jest.fn(),
|
||||
new Crypto(),
|
||||
new VideoAutoQualityOptimizer(),
|
||||
new PeerConnectionUIHandler(
|
||||
true,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
jest.fn()
|
||||
)
|
||||
);
|
||||
|
||||
peerConnection.socket = { connected: true };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('when interval passed and socket is connected', () => {
|
||||
it('should NOT call setAndShowErrorDialogMessage', () => {
|
||||
startSocketConnectedCheckingLoop(peerConnection);
|
||||
jest.advanceTimersByTime(3000);
|
||||
|
||||
expect(setAndShowErrorDialogMessage).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when interval passed and socket is NOT connected', () => {
|
||||
it('should call setAndShowErrorDialogMessage', () => {
|
||||
peerConnection.socket.connected = false;
|
||||
|
||||
startSocketConnectedCheckingLoop(peerConnection);
|
||||
jest.advanceTimersByTime(3000);
|
||||
|
||||
expect(setAndShowErrorDialogMessage).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
import PeerConnection from '..';
|
||||
import { ErrorMessage } from '../../../components/ErrorDialog/ErrorMessageEnum';
|
||||
import setAndShowErrorDialogMessage from '../setAndShowErrorDialogMessage';
|
||||
|
||||
export default (peerConnection: PeerConnection) => {
|
||||
setInterval(() => {
|
||||
if (!peerConnection.socket.connected) {
|
||||
setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DENY_TO_CONNECT);
|
||||
}
|
||||
}, 2000);
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
export default class CanvasNotDefinedError extends Error {
|
||||
constructor() {
|
||||
super('internal variable of canvas DOM elemenent should be defined!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, CanvasNotDefinedError.prototype);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export default class ImageDataIsUndefinedError extends Error {
|
||||
constructor() {
|
||||
super('imageData retreived is undefined!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, ImageDataIsUndefinedError.prototype);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
export default class VideoDimensionsAreWrongError extends Error {
|
||||
constructor() {
|
||||
super(
|
||||
'video dimensions are wrong, neither width nor height can be zero!'
|
||||
);
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, VideoDimensionsAreWrongError.prototype);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
export default class VideoNotDefinedError extends Error {
|
||||
constructor() {
|
||||
super('internal variable of video DOM elemenent should be defined!');
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, VideoNotDefinedError.prototype);
|
||||
}
|
||||
}
|
381
app/client/src/features/VideoAutoQualityOptimizer/index.spec.ts
Normal file
381
app/client/src/features/VideoAutoQualityOptimizer/index.spec.ts
Normal file
@ -0,0 +1,381 @@
|
||||
import VideoAutoQualityOptimizer from '.';
|
||||
import {
|
||||
COMPARISON_CANVAS_ID,
|
||||
REACT_PLAYER_WRAPPER_ID,
|
||||
} from '../../constants/appConstants';
|
||||
import CanvasNotDefinedError from './errors/CanvasNotDefinedError';
|
||||
import ImageDataIsUndefinedError from './errors/ImageDataIsUndefinedError';
|
||||
import VideoDimensionsAreWrongError from './errors/VideoDimensionsAreWrongError';
|
||||
import VideoNotDefinedError from './errors/VideoNotDefinedError';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const TEST_IMAGE_DATA = {
|
||||
data: new Uint8ClampedArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]),
|
||||
height: 1,
|
||||
width: 10,
|
||||
} as ImageData;
|
||||
|
||||
const PREV_FRAME_IMAGE_DATA = {
|
||||
data: new Uint8ClampedArray([2, 2, 3, 4, 5, 6, 7, 8, 9, 0]),
|
||||
height: 1,
|
||||
width: 10,
|
||||
} as ImageData;
|
||||
|
||||
const LOW_MISMATCH = 0.09;
|
||||
const HIGH_MISMATCH = 0.2;
|
||||
|
||||
describe('VideoAutoQualityOptimizer', () => {
|
||||
let optimizer: VideoAutoQualityOptimizer;
|
||||
|
||||
beforeEach(() => {
|
||||
optimizer = new VideoAutoQualityOptimizer();
|
||||
|
||||
optimizer.video = {
|
||||
...optimizer.video,
|
||||
videoWidth: 1142,
|
||||
videoHeight: 1142,
|
||||
} as HTMLVideoElement;
|
||||
// @ts-ignore
|
||||
optimizer.canvas = {
|
||||
width: 123,
|
||||
height: 123,
|
||||
getContext: () => {
|
||||
return {
|
||||
clearRect: () => {},
|
||||
drawImage: () => {},
|
||||
};
|
||||
},
|
||||
} as HTMLCanvasElement;
|
||||
});
|
||||
|
||||
describe('when VideoAutoQualityOptimizer is created properly', () => {
|
||||
describe('whev optimization loop is running', () => {
|
||||
it('should call goodQualityCallback when there is small frames mismatch and halfQualityCallback when there is large frames mismatch', () => {
|
||||
optimizer.prepareCanvasAndVideo = () => {};
|
||||
optimizer.validateBeforeCalculations = () => {};
|
||||
optimizer.clearCanvas = () => {};
|
||||
optimizer.scaleCanvas = () => {};
|
||||
optimizer.drawVideoFrameToCanvas = () => {};
|
||||
optimizer.getImageDataFromCanvas = () => ({} as ImageData);
|
||||
optimizer.prevFrame = PREV_FRAME_IMAGE_DATA;
|
||||
|
||||
optimizer.halfQualityCallbak = jest.fn();
|
||||
optimizer.goodQualityCallback = jest.fn();
|
||||
|
||||
// 1. STEP 1 simulate high percent of frames mismatch
|
||||
optimizer.getPreviousAndCurrentFrameMismatchInPercent = () => 0.5
|
||||
optimizer.largeMismatchFramesCount = 5;
|
||||
optimizer.startOptimizationLoop();
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
expect(optimizer.halfQualityCallbak).toBeCalled();
|
||||
|
||||
// 2. STEP 2 simulate low percent of frames mismatch
|
||||
optimizer.getPreviousAndCurrentFrameMismatchInPercent = () => 0.03
|
||||
optimizer.largeMismatchFramesCount = 0;
|
||||
optimizer.startOptimizationLoop();
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
expect(optimizer.goodQualityCallback).toBeCalled();
|
||||
|
||||
// 3. repeat step 1
|
||||
optimizer.getPreviousAndCurrentFrameMismatchInPercent = () => 0.5
|
||||
optimizer.largeMismatchFramesCount = 5;
|
||||
optimizer.startOptimizationLoop();
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
expect(optimizer.halfQualityCallbak).toBeCalled();
|
||||
|
||||
// 4. repeat step 2
|
||||
optimizer.getPreviousAndCurrentFrameMismatchInPercent = () => 0.03
|
||||
optimizer.largeMismatchFramesCount = 0;
|
||||
optimizer.startOptimizationLoop();
|
||||
jest.advanceTimersByTime(5000);
|
||||
|
||||
expect(optimizer.goodQualityCallback).toBeCalled();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('when validateVideoIsDefined is called', () => {
|
||||
describe('when video local variable is undefined', () => {
|
||||
it('should throw an error', () => {
|
||||
optimizer.video = undefined;
|
||||
|
||||
try {
|
||||
optimizer.validateVideoIsDefined();
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new VideoNotDefinedError());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when validateVideoIsDefined is called', () => {
|
||||
describe('when canvas local variable is undefined', () => {
|
||||
it('should throw an error', () => {
|
||||
optimizer.canvas = undefined;
|
||||
|
||||
try {
|
||||
optimizer.validateCanvasIsDefined();
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new CanvasNotDefinedError());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when startOptimizationLoop of VideoAutoQualityOptimizer is called', () => {
|
||||
it('should define canvas and video internal variables', () => {
|
||||
const originalDocumentQuerySelector = document.querySelector;
|
||||
const valueForVideo = 'a';
|
||||
const valueForCanvas = 'b';
|
||||
|
||||
try {
|
||||
document.querySelector = (s: string) => {
|
||||
if (s === `#${REACT_PLAYER_WRAPPER_ID} > video`) {
|
||||
return valueForVideo;
|
||||
}
|
||||
if (s === `#${COMPARISON_CANVAS_ID}`) {
|
||||
return valueForCanvas;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
optimizer.startOptimizationLoop();
|
||||
jest.advanceTimersByTime(2000);
|
||||
} catch (e) {
|
||||
expect(optimizer.video).toBe(valueForVideo);
|
||||
expect(optimizer.canvas).toBe(valueForCanvas);
|
||||
}
|
||||
|
||||
document.querySelector = originalDocumentQuerySelector;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when doFrameComparisonAndQualityOptimization of VideoAutoQualityOptimizer is called', () => {
|
||||
describe('when width of video is 0', () => {
|
||||
it('should throw an error', () => {
|
||||
optimizer.video = {
|
||||
...optimizer.video,
|
||||
videoWidth: 0,
|
||||
videoHeight: 1142,
|
||||
} as HTMLVideoElement;
|
||||
try {
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
fail('should have thrown error here!');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new VideoDimensionsAreWrongError());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when height of video is 0', () => {
|
||||
it('should throw an error', () => {
|
||||
optimizer.video = {
|
||||
...optimizer.video,
|
||||
videoWidth: 1142,
|
||||
videoHeight: 0,
|
||||
} as HTMLVideoElement;
|
||||
try {
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
fail('should have thrown error here!');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new VideoDimensionsAreWrongError());
|
||||
}
|
||||
});
|
||||
|
||||
it('should call scaleCanvas()', () => {
|
||||
const spy = jest.spyOn(optimizer, 'scaleCanvas');
|
||||
|
||||
try {
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
} catch (e) {}
|
||||
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call drawVideoFrameToCanvas()', () => {
|
||||
const spy = jest.spyOn(optimizer, 'drawVideoFrameToCanvas');
|
||||
|
||||
try {
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
} catch (e) {}
|
||||
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
|
||||
describe('when getImageDataFromCanvas returns undefined', () => {
|
||||
it('should throw an error', () => {
|
||||
jest
|
||||
.spyOn(optimizer, 'getImageDataFromCanvas')
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
try {
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
fail('it should have thrown an error here');
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new ImageDataIsUndefinedError());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when prevFrame is undefined', () => {
|
||||
it('should set prevFrame to be the same as test image data', () => {
|
||||
jest
|
||||
.spyOn(optimizer, 'getImageDataFromCanvas')
|
||||
.mockImplementation(() => TEST_IMAGE_DATA);
|
||||
|
||||
try {
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
} catch (e) {}
|
||||
|
||||
expect(optimizer.prevFrame).toEqual(TEST_IMAGE_DATA);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getPreviousAndCurrentFrameMismatchInPercent returns a number', () => {
|
||||
it('should call handleFramesMismatch()', () => {
|
||||
const TEST_MISMATCH = 0.1;
|
||||
optimizer.prevFrame = TEST_IMAGE_DATA;
|
||||
jest
|
||||
.spyOn(optimizer, 'getImageDataFromCanvas')
|
||||
.mockImplementation(() => TEST_IMAGE_DATA);
|
||||
jest
|
||||
.spyOn(optimizer, 'getPreviousAndCurrentFrameMismatchInPercent')
|
||||
.mockImplementation(() => 0.1);
|
||||
const spyOfHandleFramesMismatch = jest.spyOn(
|
||||
optimizer,
|
||||
'handleFramesMismatch'
|
||||
);
|
||||
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
|
||||
expect(spyOfHandleFramesMismatch).toBeCalledWith(TEST_MISMATCH);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when doFrameComparisonAndQualityOptimization run was successful', () => {
|
||||
it('should set prevFrame to test image data', () => {
|
||||
optimizer.prevFrame = PREV_FRAME_IMAGE_DATA;
|
||||
jest
|
||||
.spyOn(optimizer, 'getImageDataFromCanvas')
|
||||
.mockImplementation(() => TEST_IMAGE_DATA);
|
||||
jest
|
||||
.spyOn(optimizer, 'getPreviousAndCurrentFrameMismatchInPercent')
|
||||
.mockImplementation(() => 0.1);
|
||||
|
||||
optimizer.doFrameComparisonAndQualityOptimization();
|
||||
|
||||
expect(optimizer.prevFrame).toEqual(TEST_IMAGE_DATA);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getPreviousAndCurrentFrameMismatchInPercent run was successful', () => {});
|
||||
});
|
||||
|
||||
describe('when getPreviousAndCurrentFrameMismatchInPercent was called', () => {
|
||||
describe('when canvas is undefined', () => {
|
||||
it('should return 0', () => {
|
||||
optimizer.canvas = undefined;
|
||||
|
||||
const res = optimizer.getPreviousAndCurrentFrameMismatchInPercent(
|
||||
TEST_IMAGE_DATA
|
||||
);
|
||||
|
||||
expect(res).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getPreviousAndCurrentFrameMismatchInPercent ran properly', () => {
|
||||
it('should return proper mismatch in percent', () => {
|
||||
const MISMATCH_TO_RETURN = 44;
|
||||
jest
|
||||
.spyOn(optimizer, 'getNumberOfMismatchedPixels')
|
||||
.mockImplementation(() => MISMATCH_TO_RETURN);
|
||||
// @ts-ignore
|
||||
optimizer.canvas = {
|
||||
width: 123,
|
||||
height: 123,
|
||||
};
|
||||
const width = optimizer.canvas?.width as number;
|
||||
const height = optimizer.canvas?.height as number;
|
||||
const expected = MISMATCH_TO_RETURN / (width * height);
|
||||
|
||||
const res = optimizer.getPreviousAndCurrentFrameMismatchInPercent(
|
||||
TEST_IMAGE_DATA
|
||||
);
|
||||
|
||||
expect(res).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when handleFramesMismatch was called', () => {
|
||||
describe('when it received low percent mismatch and largeMismatchFramesCount is more than zero', () => {
|
||||
it('should decrease largeMismatchFramesCount by one', () => {
|
||||
const MISMATCH_FRAMES_COUNT = 2;
|
||||
optimizer.largeMismatchFramesCount = MISMATCH_FRAMES_COUNT;
|
||||
optimizer.handleFramesMismatch(LOW_MISMATCH);
|
||||
const expected = MISMATCH_FRAMES_COUNT - 1;
|
||||
|
||||
expect(optimizer.largeMismatchFramesCount).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it received LOW percent mismatch and isRequestedHalfQuality is true', () => {
|
||||
it('should call goodQualityCallback and set isRequestedHalfQuality to false', () => {
|
||||
const MISMATCH_FRAMES_COUNT = 0;
|
||||
optimizer.largeMismatchFramesCount = MISMATCH_FRAMES_COUNT;
|
||||
optimizer.isRequestedHalfQuality = true;
|
||||
optimizer.goodQualityCallback = jest.fn();
|
||||
optimizer.handleFramesMismatch(LOW_MISMATCH);
|
||||
|
||||
expect(optimizer.goodQualityCallback).toBeCalled();
|
||||
expect(optimizer.isRequestedHalfQuality).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it received LOW percent mismatch and isRequestedHalfQuality is false', () => {
|
||||
it('should call NOT goodQualityCallback', () => {
|
||||
const MISMATCH_FRAMES_COUNT = 0;
|
||||
optimizer.largeMismatchFramesCount = MISMATCH_FRAMES_COUNT;
|
||||
optimizer.isRequestedHalfQuality = false;
|
||||
optimizer.goodQualityCallback = jest.fn();
|
||||
optimizer.handleFramesMismatch(LOW_MISMATCH);
|
||||
|
||||
expect(optimizer.goodQualityCallback).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it received HIGH percent mismatch', () => {
|
||||
describe('when isRequestedHalfQuality is false and there were MORE than consecutive 3 frames mismatch', () => {
|
||||
it('should call halfQualityCallbak and set isRequestedHalfQuality to true', () => {
|
||||
const MISMATCH_FRAMES_COUNT = 3;
|
||||
optimizer.largeMismatchFramesCount = MISMATCH_FRAMES_COUNT;
|
||||
optimizer.isRequestedHalfQuality = false;
|
||||
optimizer.halfQualityCallbak = jest.fn();
|
||||
optimizer.handleFramesMismatch(HIGH_MISMATCH);
|
||||
|
||||
expect(optimizer.halfQualityCallbak).toBeCalled();
|
||||
expect(optimizer.isRequestedHalfQuality).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when isRequestedHalfQuality is false and there were LESS than consecutive 3 frames mismatch', () => {
|
||||
it('should increase largeMismatchFramesCount by one', () => {
|
||||
const MISMATCH_FRAMES_COUNT = 1;
|
||||
optimizer.largeMismatchFramesCount = MISMATCH_FRAMES_COUNT;
|
||||
optimizer.isRequestedHalfQuality = false;
|
||||
optimizer.handleFramesMismatch(HIGH_MISMATCH);
|
||||
const expected = MISMATCH_FRAMES_COUNT + 1;
|
||||
|
||||
expect(optimizer.largeMismatchFramesCount).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,12 +1,20 @@
|
||||
import { COMPARISON_CANVAS_ID } from './../../constants/appConstants';
|
||||
import pixelmatch from 'pixelmatch';
|
||||
import { REACT_PLAYER_WRAPPER_ID } from '../../constants/appConstants';
|
||||
import CanvasNotDefinedError from './errors/CanvasNotDefinedError';
|
||||
import VideoDimensionsAreWrongError from './errors/VideoDimensionsAreWrongError';
|
||||
import VideoNotDefinedError from './errors/VideoNotDefinedError';
|
||||
import ImageDataIsUndefinedError from './errors/ImageDataIsUndefinedError';
|
||||
|
||||
export const CANVAS_SCALE_MULTIPLIER = 0.125; // 1/8 of original canvas size, to speed up calculations
|
||||
export const MISMATCH_PERCENT_THRESHOLD = 0.1;
|
||||
|
||||
export default class VideoAutoQualityOptimizer {
|
||||
video: any;
|
||||
video: undefined | HTMLVideoElement;
|
||||
|
||||
canvas: any;
|
||||
canvas: undefined | HTMLCanvasElement;
|
||||
|
||||
prevFrame: any;
|
||||
prevFrame: undefined | ImageData;
|
||||
|
||||
largeMismatchFramesCount = 0;
|
||||
|
||||
@ -25,66 +33,158 @@ export default class VideoAutoQualityOptimizer {
|
||||
}
|
||||
|
||||
startOptimizationLoop() {
|
||||
this.prepareCanvasAndVideo();
|
||||
setInterval(() => {
|
||||
this.frameComparisonQualityOptimization();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
frameComparisonQualityOptimization() {
|
||||
if (this.video && this.canvas) {
|
||||
if (this.video.videoWidth === 0 || this.video.videoHeight === 0) return;
|
||||
// scale the canvas accordingly
|
||||
this.canvas
|
||||
.getContext('2d')
|
||||
.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.canvas.width = this.video.videoWidth / 8;
|
||||
this.canvas.height = this.video.videoHeight / 8;
|
||||
// draw the video at that frame
|
||||
this.canvas
|
||||
.getContext('2d')
|
||||
.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
||||
// convert it to a usable data URL
|
||||
let imageData = this.canvas
|
||||
.getContext('2d')
|
||||
.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
if (this.prevFrame) {
|
||||
try {
|
||||
const numMismatchedPixels = pixelmatch(
|
||||
this.prevFrame.data,
|
||||
imageData.data,
|
||||
null,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
const mismatchInPercent =
|
||||
numMismatchedPixels / (this.canvas.width * this.canvas.height);
|
||||
if (mismatchInPercent < 0.1 && this.largeMismatchFramesCount > 0) {
|
||||
this.largeMismatchFramesCount -= 1;
|
||||
} else if (mismatchInPercent < 0.1 && this.isRequestedHalfQuality) {
|
||||
this.largeMismatchFramesCount = 0;
|
||||
this.isRequestedHalfQuality = false;
|
||||
this.goodQualityCallback();
|
||||
} else if (mismatchInPercent >= 0.1 && !this.isRequestedHalfQuality) {
|
||||
if (this.largeMismatchFramesCount < 3) {
|
||||
this.largeMismatchFramesCount += 1;
|
||||
} else {
|
||||
this.halfQualityCallbak();
|
||||
this.isRequestedHalfQuality = true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
try {
|
||||
this.doFrameComparisonAndQualityOptimization();
|
||||
} catch (e) {
|
||||
// some errors may be thrown here, better ignore them in production
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
doFrameComparisonAndQualityOptimization() {
|
||||
this.validateBeforeCalculations();
|
||||
this.clearCanvas();
|
||||
this.scaleCanvas();
|
||||
this.drawVideoFrameToCanvas();
|
||||
let imageData = this.getImageDataFromCanvas();
|
||||
|
||||
if (!imageData) {
|
||||
throw new ImageDataIsUndefinedError();
|
||||
}
|
||||
if (!this.prevFrame) {
|
||||
this.prevFrame = imageData;
|
||||
imageData = null;
|
||||
} else {
|
||||
this.video = document.querySelector(
|
||||
`#${REACT_PLAYER_WRAPPER_ID} > video`
|
||||
);
|
||||
this.canvas = document.getElementById('comparison-canvas');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const mismatchInPercent = this.getPreviousAndCurrentFrameMismatchInPercent(imageData);
|
||||
this.handleFramesMismatch(mismatchInPercent);
|
||||
} catch (e) {
|
||||
// usually frames size mismatch thrown here, so can be ignored as it happens
|
||||
// often when changing sharing window size
|
||||
// so logging this error may be not necessary
|
||||
}
|
||||
|
||||
this.prevFrame = imageData;
|
||||
}
|
||||
|
||||
findAndSetVideoInternalVariable(document: Document) {
|
||||
this.video = document.querySelector(
|
||||
`#${REACT_PLAYER_WRAPPER_ID} > video`
|
||||
) as HTMLVideoElement;
|
||||
}
|
||||
|
||||
findAndSetCanvasInternalVariable(document: Document) {
|
||||
this.canvas = document.querySelector(
|
||||
`#${COMPARISON_CANVAS_ID}`
|
||||
) as HTMLCanvasElement;
|
||||
}
|
||||
|
||||
prepareCanvasAndVideo() {
|
||||
setTimeout(() => {
|
||||
this.findAndSetVideoInternalVariable(document);
|
||||
this.findAndSetCanvasInternalVariable(document);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
clearCanvas() {
|
||||
this.canvas
|
||||
?.getContext('2d')
|
||||
?.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
validateVideoWidthAndHeight() {
|
||||
if (this.video?.videoWidth === 0 || this.video?.videoHeight === 0) {
|
||||
throw new VideoDimensionsAreWrongError();
|
||||
}
|
||||
}
|
||||
|
||||
validateBeforeCalculations() {
|
||||
this.validateVideoWidthAndHeight();
|
||||
this.validateVideoIsDefined();
|
||||
this.validateCanvasIsDefined();
|
||||
}
|
||||
|
||||
validateVideoIsDefined() {
|
||||
if (!this.video) {
|
||||
throw new VideoNotDefinedError();
|
||||
}
|
||||
}
|
||||
|
||||
validateCanvasIsDefined() {
|
||||
if (!this.canvas) {
|
||||
throw new CanvasNotDefinedError();
|
||||
}
|
||||
}
|
||||
|
||||
scaleCanvas() {
|
||||
if (
|
||||
!this.canvas ||
|
||||
!this.video
|
||||
)
|
||||
return;
|
||||
this.canvas.width = this.video.videoWidth * CANVAS_SCALE_MULTIPLIER;
|
||||
this.canvas.height = this.video.videoHeight * CANVAS_SCALE_MULTIPLIER;
|
||||
}
|
||||
|
||||
drawVideoFrameToCanvas() {
|
||||
if (!this.video) return;
|
||||
this.canvas
|
||||
?.getContext('2d')
|
||||
?.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
getImageDataFromCanvas() {
|
||||
return this.canvas
|
||||
?.getContext('2d')
|
||||
?.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
getNumberOfMismatchedPixels(imageData: ImageData) {
|
||||
if (!this.canvas || !this.canvas.width || !this.prevFrame) return 0;
|
||||
return pixelmatch(
|
||||
this.prevFrame.data,
|
||||
imageData.data,
|
||||
null,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
}
|
||||
|
||||
getPreviousAndCurrentFrameMismatchInPercent(imageData: ImageData) {
|
||||
if (!this.canvas) return 0;
|
||||
return this.getNumberOfMismatchedPixels(imageData) / (this.canvas.width * this.canvas.height);
|
||||
}
|
||||
|
||||
handleFramesMismatch(mismatchInPercent: number) {
|
||||
if (mismatchInPercent < 0.1 && this.largeMismatchFramesCount > 0) {
|
||||
this.largeMismatchFramesCount -= 1;
|
||||
} else if (mismatchInPercent < 0.1 && this.isRequestedHalfQuality) {
|
||||
this.largeMismatchFramesCount = 0;
|
||||
this.isRequestedHalfQuality = false;
|
||||
this.goodQualityCallback();
|
||||
} else if (mismatchInPercent >= 0.1 && !this.isRequestedHalfQuality) {
|
||||
if (this.largeMismatchFramesCount < 3) {
|
||||
this.largeMismatchFramesCount += 1;
|
||||
} else {
|
||||
this.halfQualityCallbak();
|
||||
this.isRequestedHalfQuality = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isLowMismatchPercent(mismatchInPercent: number) {
|
||||
return mismatchInPercent < MISMATCH_PERCENT_THRESHOLD;
|
||||
}
|
||||
|
||||
isHighMismatchPercent(mismatchInPercent: number) {
|
||||
return mismatchInPercent >= MISMATCH_PERCENT_THRESHOLD;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should match exact snapshot 1`] = `null`;
|
14
app/client/src/providers/AppContextProvider/index.spec.tsx
Normal file
14
app/client/src/providers/AppContextProvider/index.spec.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { AppContextProvider } from '.';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
it('should match exact snapshot', () => {
|
||||
const subject = renderer.create(
|
||||
<>
|
||||
<AppContextProvider />
|
||||
</>
|
||||
);
|
||||
expect(subject).toMatchSnapshot();
|
||||
});
|
26
app/client/src/react-app-env.d.ts
vendored
26
app/client/src/react-app-env.d.ts
vendored
@ -1 +1,27 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
type ConnectionIconType = 'feed' | 'feed-subscribed';
|
||||
type LoadingSharingIconType = 'desktop' | 'application';
|
||||
type ScreenSharingSourceType = 'screen' | 'window';
|
||||
type CreatePeerConnectionUseEffectParams = {
|
||||
isDarkTheme: boolean;
|
||||
peer: undefined | PeerConnection;
|
||||
setIsDarkThemeHook: (_: boolean) => void;
|
||||
setMyDeviceDetails: (_: DeviceDetails) => void;
|
||||
setConnectionIconType: (_: ConnectionIconType) => void;
|
||||
setIsShownTextPrompt: (_: boolean) => void;
|
||||
setPromptStep: (_: number) => void;
|
||||
setScreenSharingSourceType: (_: ScreenSharingSourceType) => void;
|
||||
setDialogErrorMessage: (_: ErrorMessage) => void;
|
||||
setIsErrorDialogOpen: (_: boolean) => void;
|
||||
setUrl: (_: MediaStream) => void;
|
||||
setPeer: (_: PeerConnection) => void;
|
||||
};
|
||||
type handleDisplayingLoadingSharingIconLoopParams = {
|
||||
promptStep: number;
|
||||
url: undefined | MediaStream;
|
||||
setIsShownLoadingSharingIcon: (_: boolean) => void;
|
||||
loadingSharingIconType: LoadingSharingIconType;
|
||||
isShownLoadingSharingIcon: boolean;
|
||||
setLoadingSharingIconType: (_: LoadingSharingIconType) => void;
|
||||
};
|
||||
|
43
app/client/src/utils/ProcessedMessage.d.ts
vendored
43
app/client/src/utils/ProcessedMessage.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
type CallAcceptedMessageWithPayload = {
|
||||
type: 'CALL_ACCEPTED';
|
||||
type CallUserMessageWithPayload = {
|
||||
type: 'CALL_USER';
|
||||
payload: {
|
||||
signalData: string;
|
||||
};
|
||||
@ -17,6 +17,41 @@ type DeviceDetailsMessageWithPayload = {
|
||||
};
|
||||
};
|
||||
|
||||
type DenyToConnectMessageWithPayload = {
|
||||
type: 'DENY_TO_CONNECT';
|
||||
payload: {};
|
||||
};
|
||||
|
||||
type DisconnectByHostMachineUserMessageWithPayload = {
|
||||
type: 'DISCONNECT_BY_HOST_MACHINE_USER';
|
||||
payload: {};
|
||||
};
|
||||
|
||||
type AllowedToConnectMessageWithPayload = {
|
||||
type: 'ALLOWED_TO_CONNECT';
|
||||
payload: {};
|
||||
};
|
||||
|
||||
type AppThemeMessageWithPayload = {
|
||||
type: 'APP_THEME';
|
||||
payload: {
|
||||
value: boolean,
|
||||
};
|
||||
};
|
||||
|
||||
type AppLanguageMessageWithPayload = {
|
||||
type: 'APP_LANGUAGE';
|
||||
payload: {
|
||||
value: string,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
type ProcessedMessage =
|
||||
| CallAcceptedMessageWithPayload
|
||||
| DeviceDetailsMessageWithPayload;
|
||||
| CallUserMessageWithPayload
|
||||
| DeviceDetailsMessageWithPayload
|
||||
| DenyToConnectMessageWithPayload
|
||||
| DisconnectByHostMachineUserMessageWithPayload
|
||||
| AllowedToConnectMessageWithPayload
|
||||
| AppThemeMessageWithPayload
|
||||
| AppLanguageMessageWithPayload;
|
||||
|
3
app/client/src/utils/areWeTestingWithJest.ts
Normal file
3
app/client/src/utils/areWeTestingWithJest.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default () => {
|
||||
return process.env.JEST_WORKER_ID !== undefined;
|
||||
};
|
3
app/client/src/utils/getRoomIDOfCurrentBrowserWindow.ts
Normal file
3
app/client/src/utils/getRoomIDOfCurrentBrowserWindow.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default () => {
|
||||
return encodeURI(window.location.pathname.replace('/', ''));
|
||||
}
|
53
app/client/src/utils/message.spec.ts
Normal file
53
app/client/src/utils/message.spec.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { prepare, process } from './message';
|
||||
import getTestPublickKeyPem from './mocks/getTestPublicKeyPem';
|
||||
import getTestPrivateKeyPem from './mocks/getTestPrivateKeyPem';
|
||||
|
||||
interface TestPayload {
|
||||
text: string;
|
||||
}
|
||||
|
||||
describe('message.ts tests for proper encryption and decryption functionality', () => {
|
||||
const TEST_TEXT = 'some test text here';
|
||||
const TEST_TEXT_AS_URL = 'some%20test%20text%20here';
|
||||
const testPayloadToEncrypt = {
|
||||
payload: {
|
||||
text: TEST_TEXT,
|
||||
},
|
||||
};
|
||||
|
||||
const testUser = {
|
||||
username: 'testUsername',
|
||||
id: 'testId',
|
||||
};
|
||||
|
||||
const testPartner = {
|
||||
publicKey: getTestPublickKeyPem(),
|
||||
};
|
||||
it('should create encrypted payload with prepare() method', async () => {
|
||||
const encryptedPayload = await prepare(
|
||||
testPayloadToEncrypt,
|
||||
testUser,
|
||||
testPartner
|
||||
);
|
||||
|
||||
expect(encryptedPayload.toSend.payload).not.toContain(TEST_TEXT);
|
||||
expect(encryptedPayload.toSend.payload).not.toContain(TEST_TEXT_AS_URL);
|
||||
});
|
||||
|
||||
it('should decrypt encrypted payload with process() method', async () => {
|
||||
const encryptedPayload = await prepare(
|
||||
testPayloadToEncrypt,
|
||||
testUser,
|
||||
testPartner
|
||||
);
|
||||
|
||||
const decryptedPayload = await process(
|
||||
encryptedPayload.toSend,
|
||||
getTestPrivateKeyPem()
|
||||
);
|
||||
|
||||
expect(
|
||||
((decryptedPayload.payload as unknown) as TestPayload).text
|
||||
).toContain(TEST_TEXT_AS_URL);
|
||||
});
|
||||
});
|
@ -42,7 +42,7 @@ export const process = (payload: any, privateKeyString: string) =>
|
||||
key.sessionKey
|
||||
);
|
||||
signingHMACKey = crypto.unwrapKey(privateKey, key.signingKey);
|
||||
resolvePayload();
|
||||
resolvePayload(undefined);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
29
app/client/src/utils/mocks/getTestPrivateKeyPem.ts
Normal file
29
app/client/src/utils/mocks/getTestPrivateKeyPem.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export default function getTestPrivateKeyPem() {
|
||||
return `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAoQ8+vdVAerEyaxGaffzrTTv+sgpQ3xToBFYkxrT6f+zP4MqV
|
||||
nTIy2+UMlEGGryMFgJWyurqv+NyoVf7vIFOxQcxJko1BL/oIt/e5YZ/fMIw9AhgP
|
||||
0A0oZyFKNbGesCY3zMdpZqPE0brzfhjr/lu5VzioZI9vxocOSSc3+S8w1EXqujgO
|
||||
X3PWXgZrD6Y//Oo+8BgNQta/e5PUyc9yNchU/W3ddzBdE0iUXTdQt7yFvzy4vTbS
|
||||
ywUxoNNJnD6dUJ6dZ4c24VGvrvhJ5mzNqQjibAhtkPFbvhn0e0BHl0BZ876fLHGg
|
||||
zpHgmmiWbKwBYl1ydv8fW9W3r5nQoW0RThYJjwIDAQABAoIBAHqOrUGrGsvCNwl+
|
||||
db9VTICTHLbCXtPChuN14bpLUSszOuRlhAAAiO8HltDiI+j1j2RPhZfOI8YNsxLt
|
||||
UW2aAhJ9r6aTUn19mFDVcv20uBOrQ2lqge3hdVM049GD/asxCdkMDUqLaGPoDQ1x
|
||||
TXNavOiANrN+6qF5eAd2joNRw6hi7osyWqfpgM9y58kiYYazKHKlI/er19JsD2t5
|
||||
hgRiFti+oN9nqhhVzJI/GYU9JugXnB/Z0uFvqOyt/3YDHPpOC07WYpfA3yzshBkW
|
||||
TiiMp1eJNX/sRh4JHzRI9/MQe9ajlAS7T74HwCkclzaS8ZY6t+dfVZ097/ug77eQ
|
||||
NwY4DAECgYEA0hWv7hpookKKcl0srfxbx+FbtBhzXqz+Tfy3H5do6uXRc0aUwQ25
|
||||
PZLs7UAT3zjt+4aYu1xgkp3cUx5FNr/FAPYLtjXw1/6fmt3HX8820SpCMjIzS1bU
|
||||
UwJvmAut70YA+n2eKwO1qJNeLcpNCDebbIXwj0lKDh5HQDyoswuz7I8CgYEAxEKV
|
||||
N4Ma6GqZ0hgdQlO71oSuftHUI80Iz+Riorrp4AA8hp5A3V6Fd9QDy8PYLz1kD9pM
|
||||
uWdCLqaxdOVDtLVnjNVZH1SI+wSBDA/0OtdvPaONL1JKRA21kol049f0M8wkulAw
|
||||
6lfi2aQwZ3KWyEAfDy5iPdVROnQWqUcLmxO0kwECgYAGpSr4dBtlLoekkG/uXPIm
|
||||
Q2mcK73Se9RbcSf1tttZusVCSTRBWwbF/NTDuGgogmt8rkg8fPKNELM8adO0pKI9
|
||||
oorCS7h/jI1N38ADttE8EoMfhVj8BBYZPhV7kLsCu4siYUDUiXyAhZDQD/sZzHB9
|
||||
IUt3rNDL24dTb9fCOheJ3wKBgGRgpY7Z2DZM51VkDfrxdp3WCKVGTljtMfeaGLSg
|
||||
IqP1mv9DC2vtPxg1cKeUCArJPFc7UIh2/ot7qEFgTQusyERojgePJew0tofj1QcP
|
||||
To7ZConMbb12wYosEYPC3NxtKc+82ffRcW3dIwCVw/axjPEnyQlVBBGAdGKpuo7b
|
||||
Oj0BAoGAMs2zuAJfJB4xE1UZdLXk9uHWPAzgUNDzHuS5mMWuloGvKX+19yck6o2J
|
||||
ZA/iq6GwOgvD2y9MC0mV4WkmwZpVXwPVpZD8GVQXZf2xZgh/q2e3IObAd80NLnaG
|
||||
v86qN98DRTh9L+47Nsaf1J5vDDaAfH2Ir8UgAQ5ZMEFDm7P0hUQ=
|
||||
-----END RSA PRIVATE KEY-----`;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
// @ts-ignore
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
|
||||
export function getOSFromUAParser(uaParser: UAParser) {
|
||||
|
@ -17,7 +17,7 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
@ -1648,13 +1648,6 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/cheerio@^0.22.22":
|
||||
version "0.22.23"
|
||||
resolved "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.23.tgz#74bcfee9c5ee53f619711dca953a89fe5cfa4eb4"
|
||||
integrity sha512-QfHLujVMlGqcS/ePSf3Oe5hK3H8wi/yN2JYuxSB1U10VvW1fO3K8C+mURQesFYS1Hn7lspOsTT75SKq/XtydQg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/dom4@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/@types/dom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033"
|
||||
@ -2129,21 +2122,6 @@ aggregate-error@^3.0.0:
|
||||
clean-stack "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
|
||||
airbnb-prop-types@^2.16.0:
|
||||
version "2.16.0"
|
||||
resolved "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2"
|
||||
integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==
|
||||
dependencies:
|
||||
array.prototype.find "^2.1.1"
|
||||
function.prototype.name "^1.1.2"
|
||||
is-regex "^1.1.0"
|
||||
object-is "^1.1.2"
|
||||
object.assign "^4.1.0"
|
||||
object.entries "^1.1.2"
|
||||
prop-types "^15.7.2"
|
||||
prop-types-exact "^1.2.0"
|
||||
react-is "^16.13.1"
|
||||
|
||||
ajv-errors@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
|
||||
@ -2299,11 +2277,6 @@ array-equal@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
|
||||
integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
|
||||
|
||||
array-filter@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
|
||||
integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
|
||||
|
||||
array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
@ -2342,15 +2315,7 @@ array-unique@^0.3.2:
|
||||
resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||
|
||||
array.prototype.find@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c"
|
||||
integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.17.4"
|
||||
|
||||
array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3:
|
||||
array.prototype.flat@^1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123"
|
||||
integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==
|
||||
@ -3136,30 +3101,6 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
cheerio-select-tmp@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646"
|
||||
integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==
|
||||
dependencies:
|
||||
css-select "^3.1.2"
|
||||
css-what "^4.0.0"
|
||||
domelementtype "^2.1.0"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.4"
|
||||
|
||||
cheerio@^1.0.0-rc.3:
|
||||
version "1.0.0-rc.5"
|
||||
resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f"
|
||||
integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==
|
||||
dependencies:
|
||||
cheerio-select-tmp "^0.1.0"
|
||||
dom-serializer "~1.2.0"
|
||||
domhandler "^4.0.0"
|
||||
entities "~2.1.0"
|
||||
htmlparser2 "^6.0.0"
|
||||
parse5 "^6.0.0"
|
||||
parse5-htmlparser2-tree-adapter "^6.0.0"
|
||||
|
||||
chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
@ -3366,7 +3307,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.11.0, commander@^2.19.0, commander@^2.20.0:
|
||||
commander@^2.11.0, commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
@ -3722,17 +3663,6 @@ css-select@^2.0.0:
|
||||
domutils "^1.7.0"
|
||||
nth-check "^1.0.2"
|
||||
|
||||
css-select@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8"
|
||||
integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^4.0.0"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.3"
|
||||
nth-check "^2.0.0"
|
||||
|
||||
css-tree@1.0.0-alpha.37:
|
||||
version "1.0.0-alpha.37"
|
||||
resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
|
||||
@ -3759,11 +3689,6 @@ css-what@^3.2.1:
|
||||
resolved "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
|
||||
integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
|
||||
|
||||
css-what@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233"
|
||||
integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==
|
||||
|
||||
css.escape@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
@ -4098,11 +4023,6 @@ dir-glob@2.0.0:
|
||||
arrify "^1.0.1"
|
||||
path-type "^3.0.0"
|
||||
|
||||
discontinuous-range@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
|
||||
integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
|
||||
|
||||
dns-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
|
||||
@ -4177,15 +4097,6 @@ dom-serializer@0:
|
||||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-serializer@^1.0.1, dom-serializer@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
|
||||
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom4@^2.1.5:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.npmjs.org/dom4/-/dom4-2.1.6.tgz#c90df07134aa0dbd81ed4d6ba1237b36fc164770"
|
||||
@ -4201,7 +4112,7 @@ domelementtype@1, domelementtype@^1.3.1:
|
||||
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||
|
||||
domelementtype@^2.0.1, domelementtype@^2.1.0:
|
||||
domelementtype@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
|
||||
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
|
||||
@ -4220,13 +4131,6 @@ domhandler@^2.3.0:
|
||||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
domhandler@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e"
|
||||
integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==
|
||||
dependencies:
|
||||
domelementtype "^2.1.0"
|
||||
|
||||
domutils@1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
|
||||
@ -4243,15 +4147,6 @@ domutils@^1.5.1, domutils@^1.7.0:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
domutils@^2.4.3, domutils@^2.4.4:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
|
||||
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
|
||||
dependencies:
|
||||
dom-serializer "^1.0.1"
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
|
||||
dot-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
||||
@ -4397,84 +4292,11 @@ entities@^1.1.1:
|
||||
resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||
|
||||
entities@^2.0.0, entities@~2.1.0:
|
||||
entities@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
enzyme-adapter-react-16@^1.15.5:
|
||||
version "1.15.5"
|
||||
resolved "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz#7a6f0093d3edd2f7025b36e7fbf290695473ee04"
|
||||
integrity sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw==
|
||||
dependencies:
|
||||
enzyme-adapter-utils "^1.13.1"
|
||||
enzyme-shallow-equal "^1.0.4"
|
||||
has "^1.0.3"
|
||||
object.assign "^4.1.0"
|
||||
object.values "^1.1.1"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.13.1"
|
||||
react-test-renderer "^16.0.0-0"
|
||||
semver "^5.7.0"
|
||||
|
||||
enzyme-adapter-utils@^1.13.1:
|
||||
version "1.14.0"
|
||||
resolved "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz#afbb0485e8033aa50c744efb5f5711e64fbf1ad0"
|
||||
integrity sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg==
|
||||
dependencies:
|
||||
airbnb-prop-types "^2.16.0"
|
||||
function.prototype.name "^1.1.3"
|
||||
has "^1.0.3"
|
||||
object.assign "^4.1.2"
|
||||
object.fromentries "^2.0.3"
|
||||
prop-types "^15.7.2"
|
||||
semver "^5.7.1"
|
||||
|
||||
enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e"
|
||||
integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
object-is "^1.1.2"
|
||||
|
||||
enzyme-to-json@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc"
|
||||
integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg==
|
||||
dependencies:
|
||||
"@types/cheerio" "^0.22.22"
|
||||
lodash "^4.17.15"
|
||||
react-is "^16.12.0"
|
||||
|
||||
enzyme@^3.11.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28"
|
||||
integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==
|
||||
dependencies:
|
||||
array.prototype.flat "^1.2.3"
|
||||
cheerio "^1.0.0-rc.3"
|
||||
enzyme-shallow-equal "^1.0.1"
|
||||
function.prototype.name "^1.1.2"
|
||||
has "^1.0.3"
|
||||
html-element-map "^1.2.0"
|
||||
is-boolean-object "^1.0.1"
|
||||
is-callable "^1.1.5"
|
||||
is-number-object "^1.0.4"
|
||||
is-regex "^1.0.5"
|
||||
is-string "^1.0.5"
|
||||
is-subset "^0.1.1"
|
||||
lodash.escape "^4.0.1"
|
||||
lodash.isequal "^4.5.0"
|
||||
object-inspect "^1.7.0"
|
||||
object-is "^1.0.2"
|
||||
object.assign "^4.1.0"
|
||||
object.entries "^1.1.1"
|
||||
object.values "^1.1.1"
|
||||
raf "^3.4.1"
|
||||
rst-selector-parser "^2.2.3"
|
||||
string.prototype.trim "^1.2.1"
|
||||
|
||||
errno@^0.1.3, errno@~0.1.7:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
|
||||
@ -4489,7 +4311,7 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4:
|
||||
es-abstract@^1.17.0-next.1, es-abstract@^1.17.2:
|
||||
version "1.17.7"
|
||||
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
|
||||
integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
|
||||
@ -5341,26 +5163,11 @@ function-bind@^1.1.1:
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
function.prototype.name@^1.1.2, function.prototype.name@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.3.tgz#0bb034bb308e7682826f215eb6b2ae64918847fe"
|
||||
integrity sha512-H51qkbNSp8mtkJt+nyW1gyStBiKZxfRqySNUR99ylq6BPXHKI4SEvIlTKp4odLfjRKJV04DFWMU3G/YRlQOsag==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.18.0-next.1"
|
||||
functions-have-names "^1.2.1"
|
||||
|
||||
functional-red-black-tree@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
functions-have-names@^1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21"
|
||||
integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==
|
||||
|
||||
gensync@^1.0.0-beta.1:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
@ -5672,13 +5479,6 @@ html-comment-regex@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
|
||||
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
|
||||
|
||||
html-element-map@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22"
|
||||
integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==
|
||||
dependencies:
|
||||
array-filter "^1.0.0"
|
||||
|
||||
html-encoding-sniffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
|
||||
@ -5740,16 +5540,6 @@ htmlparser2@^3.3.0:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
htmlparser2@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.0.tgz#c2da005030390908ca4c91e5629e418e0665ac01"
|
||||
integrity sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.4"
|
||||
entities "^2.0.0"
|
||||
|
||||
http-deceiver@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
||||
@ -6108,19 +5898,12 @@ is-binary-path@~2.1.0:
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-boolean-object@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0"
|
||||
integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
|
||||
is-buffer@^1.0.2, is-buffer@^1.1.5:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||
|
||||
is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2:
|
||||
is-callable@^1.1.4, is-callable@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
|
||||
integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
|
||||
@ -6249,11 +6032,6 @@ is-negative-zero@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
|
||||
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
|
||||
|
||||
is-number-object@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
|
||||
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
|
||||
|
||||
is-number@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
|
||||
@ -6307,7 +6085,7 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1:
|
||||
is-regex@^1.0.4, is-regex@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
|
||||
integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
|
||||
@ -6339,11 +6117,6 @@ is-string@^1.0.5:
|
||||
resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
|
||||
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
|
||||
|
||||
is-subset@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
|
||||
integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=
|
||||
|
||||
is-svg@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75"
|
||||
@ -7207,21 +6980,6 @@ lodash._reinterpolate@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
||||
|
||||
lodash.escape@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
|
||||
integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
|
||||
|
||||
lodash.flattendeep@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
@ -7576,11 +7334,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
moo@^0.5.0:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
|
||||
integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
@ -7663,16 +7416,6 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||
|
||||
nearley@^2.7.10:
|
||||
version "2.20.1"
|
||||
resolved "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474"
|
||||
integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==
|
||||
dependencies:
|
||||
commander "^2.19.0"
|
||||
moo "^0.5.0"
|
||||
railroad-diagrams "^1.0.0"
|
||||
randexp "0.4.6"
|
||||
|
||||
negotiator@0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
@ -7837,13 +7580,6 @@ nth-check@^1.0.2, nth-check@~1.0.1:
|
||||
dependencies:
|
||||
boolbase "~1.0.0"
|
||||
|
||||
nth-check@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125"
|
||||
integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
|
||||
num2fraction@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
|
||||
@ -7878,12 +7614,12 @@ object-hash@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea"
|
||||
integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==
|
||||
|
||||
object-inspect@^1.7.0, object-inspect@^1.8.0:
|
||||
object-inspect@^1.8.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
|
||||
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
|
||||
|
||||
object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2:
|
||||
object-is@^1.0.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068"
|
||||
integrity sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==
|
||||
@ -7908,7 +7644,7 @@ object-visit@^1.0.0:
|
||||
dependencies:
|
||||
isobject "^3.0.0"
|
||||
|
||||
object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2:
|
||||
object.assign@^4.1.0, object.assign@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
|
||||
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
|
||||
@ -7918,7 +7654,7 @@ object.assign@^4.1.0, object.assign@^4.1.1, object.assign@^4.1.2:
|
||||
has-symbols "^1.0.1"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
object.entries@^1.1.0, object.entries@^1.1.1, object.entries@^1.1.2:
|
||||
object.entries@^1.1.0, object.entries@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6"
|
||||
integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==
|
||||
@ -7928,7 +7664,7 @@ object.entries@^1.1.0, object.entries@^1.1.1, object.entries@^1.1.2:
|
||||
es-abstract "^1.18.0-next.1"
|
||||
has "^1.0.3"
|
||||
|
||||
object.fromentries@^2.0.2, object.fromentries@^2.0.3:
|
||||
object.fromentries@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.3.tgz#13cefcffa702dc67750314a3305e8cb3fad1d072"
|
||||
integrity sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==
|
||||
@ -8193,13 +7929,6 @@ parse-json@^5.0.0:
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
|
||||
dependencies:
|
||||
parse5 "^6.0.1"
|
||||
|
||||
parse5@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
|
||||
@ -8210,11 +7939,6 @@ parse5@5.1.0:
|
||||
resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
|
||||
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
|
||||
|
||||
parse5@^6.0.0, parse5@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
|
||||
parseqs@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
|
||||
@ -9202,15 +8926,6 @@ prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types-exact@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869"
|
||||
integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
object.assign "^4.1.0"
|
||||
reflect.ownkeys "^0.2.0"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
@ -9335,19 +9050,6 @@ raf@^3.4.1:
|
||||
dependencies:
|
||||
performance-now "^2.1.0"
|
||||
|
||||
railroad-diagrams@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
|
||||
integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=
|
||||
|
||||
randexp@0.4.6:
|
||||
version "0.4.6"
|
||||
resolved "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
|
||||
integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
|
||||
dependencies:
|
||||
discontinuous-range "1.0.0"
|
||||
ret "~0.1.10"
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
@ -9456,12 +9158,12 @@ react-i18next@^11.7.4:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
html-parse-stringify2 "2.0.1"
|
||||
|
||||
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
|
||||
react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.1:
|
||||
"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1:
|
||||
version "17.0.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
|
||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||
@ -9562,6 +9264,14 @@ react-scripts@3.4.3:
|
||||
optionalDependencies:
|
||||
fsevents "2.1.2"
|
||||
|
||||
react-shallow-renderer@^16.13.1:
|
||||
version "16.14.1"
|
||||
resolved "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
|
||||
integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.12.0 || ^17.0.0"
|
||||
|
||||
react-spinners@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.npmjs.org/react-spinners/-/react-spinners-0.9.0.tgz#b22c38acbfce580cd6f1b04a4649e812370b1fb8"
|
||||
@ -9569,15 +9279,15 @@ react-spinners@^0.9.0:
|
||||
dependencies:
|
||||
"@emotion/core" "^10.0.15"
|
||||
|
||||
react-test-renderer@^16.0.0-0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
|
||||
integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==
|
||||
react-test-renderer@^17.0.1:
|
||||
version "17.0.1"
|
||||
resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3187e636c3063e6ae498aedf21ecf972721574c7"
|
||||
integrity sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.8.6"
|
||||
scheduler "^0.19.1"
|
||||
react-is "^17.0.1"
|
||||
react-shallow-renderer "^16.13.1"
|
||||
scheduler "^0.20.1"
|
||||
|
||||
react-transition-group@^2.9.0:
|
||||
version "2.9.0"
|
||||
@ -9692,11 +9402,6 @@ redent@^3.0.0:
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
reflect.ownkeys@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
|
||||
integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
|
||||
|
||||
regenerate-unicode-properties@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
|
||||
@ -9995,14 +9700,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rst-selector-parser@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
|
||||
integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=
|
||||
dependencies:
|
||||
lodash.flattendeep "^4.4.0"
|
||||
nearley "^2.7.10"
|
||||
|
||||
rsvp@^4.8.4:
|
||||
version "4.8.5"
|
||||
resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
@ -10100,6 +9797,14 @@ scheduler@^0.19.1:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
scheduler@^0.20.1:
|
||||
version "0.20.1"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c"
|
||||
integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
schema-utils@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
||||
@ -10135,7 +9840,7 @@ selfsigned@^1.10.7:
|
||||
dependencies:
|
||||
node-forge "^0.10.0"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
@ -10679,15 +10384,6 @@ string.prototype.matchall@^4.0.2:
|
||||
regexp.prototype.flags "^1.3.0"
|
||||
side-channel "^1.0.3"
|
||||
|
||||
string.prototype.trim@^1.2.1:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.3.tgz#d23a22fde01c1e6571a7fadcb9be11decd8061a7"
|
||||
integrity sha512-16IL9pIBA5asNOSukPfxX2W68BaBvxyiRK16H3RA/lWW9BDosh+w7f+LhomPHpXJ82QEe7w7/rY/S1CV97raLg==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.18.0-next.1"
|
||||
|
||||
string.prototype.trimend@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Intent, Alert, H4 } from '@blueprintjs/core';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import DeviceInfoCallout from './DeviceInfoCallout';
|
||||
import isWithReactRevealAnimations from '../utils/isWithReactRevealAnimations';
|
||||
|
||||
interface AllowConnectionForDeviceAlertProps {
|
||||
device: Device | null;
|
||||
@ -25,7 +25,10 @@ export default function AllowConnectionForDeviceAlert(
|
||||
isOpen={isOpen}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
transitionDuration={isWithReactRevealAnimations() ? 700 : 0}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
usePortal={false}
|
||||
>
|
||||
<H4>Device is trying to connect, do you allow?</H4>
|
||||
<DeviceInfoCallout
|
||||
|
@ -15,11 +15,12 @@ import {
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles';
|
||||
import CloseOverlayButton from './CloseOverlayButton';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import ConnectedDevicesService from '../features/ConnectedDevicesService';
|
||||
import SharingSessionsService from '../features/SharingSessionsService';
|
||||
import DeviceInfoCallout from './DeviceInfoCallout';
|
||||
import SharingSourcePreviewCard from './SharingSourcePreviewCard';
|
||||
import isWithReactRevealAnimations from '../utils/isWithReactRevealAnimations';
|
||||
import isProduction from '../utils/isProduction';
|
||||
|
||||
const sharingSessionsService = remote.getGlobal(
|
||||
'sharingSessionService'
|
||||
@ -149,7 +150,7 @@ export default function ConnectedDevicesListDrawer(
|
||||
size={Drawer.SIZE_LARGE}
|
||||
isOpen={props.isOpen}
|
||||
onClose={props.handleToggle}
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
transitionDuration={isWithReactRevealAnimations() ? 700 : 0}
|
||||
// transitionDuration={0}
|
||||
>
|
||||
<Row between="xs" middle="xs" className={classes.drawerInnerTopPanel}>
|
||||
@ -176,7 +177,11 @@ export default function ConnectedDevicesListDrawer(
|
||||
</Row>
|
||||
<Row className={classes.connectedDevicesRoot}>
|
||||
<Col xs={12}>
|
||||
<Fade bottom cascade duration={isProduction() ? 700 : 0}>
|
||||
<Fade
|
||||
bottom
|
||||
cascade
|
||||
duration={isWithReactRevealAnimations() ? 700 : 0}
|
||||
>
|
||||
<div className={classes.zoomFullWidth}>
|
||||
{connectedDevicesService.getDevices().map((device) => {
|
||||
return (
|
||||
@ -245,7 +250,7 @@ export default function ConnectedDevicesListDrawer(
|
||||
setIsAlertDisconectAllOpen(false);
|
||||
}}
|
||||
onConfirm={handleDisconnectAndHideAllDevices}
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
transitionDuration={isWithReactRevealAnimations() ? 700 : 0}
|
||||
>
|
||||
<H4>
|
||||
Are you sure you want to disconnect all connected viewing devices?
|
||||
|
@ -29,8 +29,8 @@ import i18n_client, {
|
||||
getLangFullNameToLangISOKeyMap,
|
||||
getLangISOKeyToLangFullNameMap,
|
||||
} from '../../configs/i18next.config.client';
|
||||
import isProduction from '../../utils/isProduction';
|
||||
import SettingRowLabelAndInput from './SettingRowLabelAndInput';
|
||||
import isWithReactRevealAnimations from '../../utils/isWithReactRevealAnimations';
|
||||
|
||||
const Fade = require('react-reveal/Fade');
|
||||
|
||||
@ -259,7 +259,7 @@ export default function SettingsOverlay(props: SettingsOverlayProps) {
|
||||
transitionDuration={0}
|
||||
>
|
||||
<div className={getClassesCallback().overlayInnerRoot}>
|
||||
<Fade duration={isProduction() ? 700 : 0}>
|
||||
<Fade duration={isWithReactRevealAnimations() ? 700 : 0}>
|
||||
<div
|
||||
id="settings-overlay-inner"
|
||||
className={`${getClassesCallback().overlayInsideFade} ${
|
||||
|
@ -1035,6 +1035,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<CSSTransition
|
||||
addEndListener={[Function]}
|
||||
classNames="bp3-overlay"
|
||||
in={true}
|
||||
key=".$__backdrop"
|
||||
@ -1042,6 +1043,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
timeout={0}
|
||||
>
|
||||
<Transition
|
||||
addEndListener={[Function]}
|
||||
appear={false}
|
||||
enter={true}
|
||||
exit={true}
|
||||
@ -1064,6 +1066,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
</Transition>
|
||||
</CSSTransition>
|
||||
<CSSTransition
|
||||
addEndListener={[Function]}
|
||||
classNames="bp3-overlay"
|
||||
in={true}
|
||||
key=".$.0"
|
||||
@ -1071,6 +1074,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
timeout={0}
|
||||
>
|
||||
<Transition
|
||||
addEndListener={[Function]}
|
||||
appear={false}
|
||||
enter={true}
|
||||
exit={true}
|
||||
@ -1144,6 +1148,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
<button
|
||||
className="bp3-button makeStyles-closeButton-13 makeStyles-absoluteCloseButton-23"
|
||||
id="close-overlay-button"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
@ -1275,6 +1280,8 @@ exports[`should match exact snapshot 1`] = `
|
||||
</Blueprint3.Icon>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-large"
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
@ -1342,6 +1349,8 @@ exports[`should match exact snapshot 1`] = `
|
||||
</Blueprint3.Icon>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-large"
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
@ -1407,6 +1416,8 @@ exports[`should match exact snapshot 1`] = `
|
||||
</Blueprint3.Icon>
|
||||
<Blueprint3.Text
|
||||
className="bp3-text-large"
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className="bp3-text-large"
|
||||
@ -1531,7 +1542,10 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
@ -1569,6 +1583,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
>
|
||||
<button
|
||||
className="bp3-button bp3-active"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
@ -1618,6 +1633,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
>
|
||||
<button
|
||||
className="bp3-button"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
@ -1740,7 +1756,10 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
@ -1899,7 +1918,10 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
|
@ -3,7 +3,7 @@ import { Button, Icon, ControlGroup, Text } from '@blueprintjs/core';
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles';
|
||||
import ChooseAppOrScreenOverlay from './StepsOfStepper/ChooseAppOrScreenOverlay/ChooseAppOrScreenOverlay';
|
||||
|
||||
interface ShareEntireScreenOrAppWindowProps {
|
||||
interface ShareAppOrScreenControlGroupProps {
|
||||
handleNextEntireScreen: () => void;
|
||||
handleNextApplicationWindow: () => void;
|
||||
}
|
||||
@ -50,8 +50,8 @@ const useStyles = makeStyles(() =>
|
||||
})
|
||||
);
|
||||
|
||||
export default function ShareEntireScreenOrAppWindowControlGroup(
|
||||
props: ShareEntireScreenOrAppWindowProps
|
||||
export default function ShareAppOrScreenControlGroup(
|
||||
props: ShareAppOrScreenControlGroupProps
|
||||
) {
|
||||
const { handleNextEntireScreen, handleNextApplicationWindow } = props;
|
||||
const classes = useStyles();
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { Icon, Text, Button, Popover, Tooltip } from '@blueprintjs/core';
|
||||
import isProduction from '../../utils/isProduction';
|
||||
import DeviceInfoCallout from '../DeviceInfoCallout';
|
||||
import isWithReactRevealAnimations from '../../utils/isWithReactRevealAnimations';
|
||||
|
||||
interface DeviceConnectedInfoButtonProps {
|
||||
device: Device;
|
||||
@ -55,7 +55,7 @@ export default function DeviceConnectedInfoButton(
|
||||
content={getDeviceConnectedPopoverContent(device, onDisconnect)}
|
||||
position="bottom"
|
||||
inheritDarkTheme={false}
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
transitionDuration={isWithReactRevealAnimations() ? 700 : 0}
|
||||
>
|
||||
<Tooltip
|
||||
content={<Text>Click to manage</Text>}
|
||||
|
@ -110,13 +110,17 @@ exports[`should match exact snapshot 1`] = `
|
||||
<Blueprint3.Tooltip
|
||||
className=""
|
||||
content={
|
||||
<Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
Click to manage
|
||||
</Blueprint3.Text>
|
||||
}
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={400}
|
||||
key=".0"
|
||||
minimal={false}
|
||||
position="right"
|
||||
transitionDuration={100}
|
||||
>
|
||||
@ -127,7 +131,10 @@ exports[`should match exact snapshot 1`] = `
|
||||
captureDismiss={false}
|
||||
className=""
|
||||
content={
|
||||
<Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
Click to manage
|
||||
</Blueprint3.Text>
|
||||
}
|
||||
@ -142,7 +149,13 @@ exports[`should match exact snapshot 1`] = `
|
||||
interactionKind="hover-target"
|
||||
lazy={true}
|
||||
minimal={false}
|
||||
modifiers={Object {}}
|
||||
modifiers={
|
||||
Object {
|
||||
"arrow": Object {
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
openOnTargetFocus={true}
|
||||
popoverClassName="bp3-tooltip"
|
||||
position="right"
|
||||
@ -191,6 +204,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
<button
|
||||
className="bp3-button bp3-intent-success"
|
||||
id="connected-device-info-stepper-button"
|
||||
onBlur={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
style={
|
||||
@ -254,7 +268,10 @@ exports[`should match exact snapshot 1`] = `
|
||||
<div
|
||||
className="col-xs"
|
||||
>
|
||||
<Blueprint3.Text>
|
||||
<Blueprint3.Text
|
||||
ellipsize={false}
|
||||
tagName="div"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
|
@ -4,9 +4,9 @@ import { H3, Card, Dialog } from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-flexbox-grid';
|
||||
import { createStyles, makeStyles } from '@material-ui/core/styles';
|
||||
import CloseOverlayButton from '../../CloseOverlayButton';
|
||||
import isProduction from '../../../utils/isProduction';
|
||||
import PreviewGridList from './PreviewGridList';
|
||||
import DesktopCapturerSources from '../../../features/DesktopCapturerSourcesService';
|
||||
import isWithReactRevealAnimations from '../../../utils/isWithReactRevealAnimations';
|
||||
|
||||
const desktopCapturerSourcesService = remote.getGlobal(
|
||||
'desktopCapturerSourcesService'
|
||||
@ -104,15 +104,15 @@ export default function ChooseAppOrScreenOverlay(
|
||||
hasBackdrop
|
||||
isOpen={isChooseAppOrScreenOverlayOpen}
|
||||
usePortal
|
||||
transitionDuration={isProduction() ? 750 : 0}
|
||||
transitionDuration={isWithReactRevealAnimations() ? 750 : 0}
|
||||
>
|
||||
<div
|
||||
id="choose-app-or-screen-overlay-container"
|
||||
style={{ minHeight: '95%' }}
|
||||
>
|
||||
<Fade
|
||||
duration={isProduction() ? 1000 : 0}
|
||||
delay={isProduction() ? 1000 : 0}
|
||||
duration={isWithReactRevealAnimations() ? 1000 : 0}
|
||||
delay={isWithReactRevealAnimations() ? 1000 : 0}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@ -172,7 +172,7 @@ export default function ChooseAppOrScreenOverlay(
|
||||
</Fade>
|
||||
|
||||
<Zoom
|
||||
duration={isProduction() ? 750 : 0}
|
||||
duration={isWithReactRevealAnimations() ? 750 : 0}
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: '1',
|
||||
|
@ -298,6 +298,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<CSSTransition
|
||||
addEndListener={[Function]}
|
||||
appear={true}
|
||||
classNames="bp3-overlay"
|
||||
in={true}
|
||||
@ -306,6 +307,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
timeout={0}
|
||||
>
|
||||
<Transition
|
||||
addEndListener={[Function]}
|
||||
appear={true}
|
||||
enter={true}
|
||||
exit={true}
|
||||
@ -328,6 +330,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
</Transition>
|
||||
</CSSTransition>
|
||||
<CSSTransition
|
||||
addEndListener={[Function]}
|
||||
appear={true}
|
||||
classNames="bp3-overlay"
|
||||
in={true}
|
||||
@ -336,6 +339,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
timeout={0}
|
||||
>
|
||||
<Transition
|
||||
addEndListener={[Function]}
|
||||
appear={true}
|
||||
enter={true}
|
||||
exit={true}
|
||||
@ -512,6 +516,7 @@ exports[`should match exact snapshot 1`] = `
|
||||
<button
|
||||
className="bp3-button"
|
||||
id="close-overlay-button"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
|
@ -90,27 +90,13 @@ export default function IntermediateStep(props: IntermediateStepProps) {
|
||||
)}
|
||||
|
||||
{
|
||||
// TODO: uncomment this for TRUE production use ! no Connect Test Device buttons in production!
|
||||
// // eslint-disable-next-line no-nested-ternary
|
||||
// process.env.NODE_ENV === 'production' &&
|
||||
// process.env.RUN_MODE !== 'dev' &&
|
||||
// process.env.RUN_MODE !== 'test' ? (
|
||||
// <></>
|
||||
// ) : activeStep === 0 ? (
|
||||
// // eslint-disable-next-line react/jsx-indent
|
||||
// <Button
|
||||
// onClick={() => {
|
||||
// connectDevice(
|
||||
// DEVICES[Math.floor(Math.random() * DEVICES.length)]
|
||||
// );
|
||||
// }}
|
||||
// >
|
||||
// Connect Test Device
|
||||
// </Button>
|
||||
// ) : (
|
||||
// <></>
|
||||
// )
|
||||
activeStep === 0 ? (
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
process.env.NODE_ENV === 'production' &&
|
||||
process.env.RUN_MODE !== 'dev' &&
|
||||
process.env.RUN_MODE !== 'test' ? (
|
||||
<></>
|
||||
) : activeStep === 0 ? (
|
||||
// eslint-disable-next-line react/jsx-indent
|
||||
<Button
|
||||
onClick={() => {
|
||||
connectDevice(
|
||||
@ -125,6 +111,21 @@ export default function IntermediateStep(props: IntermediateStepProps) {
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
// activeStep === 0 ? (
|
||||
// <Button
|
||||
// onClick={() => {
|
||||
// connectDevice(
|
||||
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// // @ts-ignore
|
||||
// DEVICES[Math.floor(Math.random() * DEVICES.length)]
|
||||
// );
|
||||
// }}
|
||||
// >
|
||||
// Connect Test Device
|
||||
// </Button>
|
||||
// ) : (
|
||||
// <></>
|
||||
// )
|
||||
}
|
||||
{
|
||||
/**/
|
||||
|
@ -155,6 +155,7 @@ const ScanQRStep: React.FC = () => {
|
||||
canOutsideClickClose
|
||||
transitionDuration={isProduction() ? 700 : 0}
|
||||
style={{ position: 'relative', top: '0px' }}
|
||||
usePortal={false}
|
||||
>
|
||||
<Row
|
||||
id="qr-code-dialog-inner"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user