├── .codecov.yml
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── 1-Bug_report.md
│ └── 3-Feature_request.md
├── config.yml
├── stale.yml
└── workflows
│ ├── build-and-test.yml
│ ├── codecov.yml
│ ├── post-release-publish.yml
│ ├── publish-to-wiki.yml
│ └── release.yml
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── app
├── .testcafe-electron-rc
├── Routes.tsx
├── __snapshots__
│ └── menu.spec.ts.snap
├── api
│ ├── config.ts
│ └── urlGenerator.ts
├── app.global.css
├── app.html
├── app.icns
├── client
│ ├── .codecov.yml
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── locales
│ │ │ ├── da
│ │ │ │ └── translation.json
│ │ │ ├── de
│ │ │ │ └── translation.json
│ │ │ ├── en
│ │ │ │ └── translation.json
│ │ │ ├── es
│ │ │ │ └── translation.json
│ │ │ ├── fi
│ │ │ │ └── translation.json
│ │ │ ├── fr
│ │ │ │ └── translation.json
│ │ │ ├── it
│ │ │ │ └── translation.json
│ │ │ ├── ja
│ │ │ │ └── translation.json
│ │ │ ├── ko
│ │ │ │ └── translation.json
│ │ │ ├── nl
│ │ │ │ └── translation.json
│ │ │ ├── ru
│ │ │ │ └── translation.json
│ │ │ ├── sv
│ │ │ │ └── translation.json
│ │ │ ├── ua
│ │ │ │ └── translation.json
│ │ │ ├── zh_CN
│ │ │ │ └── translation.json
│ │ │ └── zh_TW
│ │ │ │ └── translation.json
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── sonar-project.properties
│ ├── src
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── api
│ │ │ ├── config.ts
│ │ │ ├── generator.spec.ts
│ │ │ ├── generator.ts
│ │ │ └── mocks
│ │ │ │ └── generatorTestVariables.ts
│ │ ├── components
│ │ │ ├── ConnectingIndicator
│ │ │ │ ├── ConnectingIndicatorIcon.tsx
│ │ │ │ ├── LoadingSharingIcon.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── ErrorDialog
│ │ │ │ ├── ErrorMessageEnum.ts
│ │ │ │ ├── index.css
│ │ │ │ └── index.tsx
│ │ │ ├── MyDeviceInfoCard
│ │ │ │ ├── DeviceDetails.d.ts
│ │ │ │ └── index.tsx
│ │ │ ├── PlayerControlPanel
│ │ │ │ ├── handlePlayerToggleFullscreen.ts
│ │ │ │ ├── index.css
│ │ │ │ ├── index.tsx
│ │ │ │ └── initScreenfullOnChange.ts
│ │ │ └── ToggleDarkModeSwitch
│ │ │ │ └── index.tsx
│ │ ├── config
│ │ │ └── i18n.ts
│ │ ├── constants
│ │ │ ├── appConstants.ts
│ │ │ └── styleConstants.ts
│ │ ├── containers
│ │ │ ├── ConnectionPrompts
│ │ │ │ └── index.tsx
│ │ │ ├── MainView
│ │ │ │ ├── ConnectionIconEnum.ts
│ │ │ │ ├── LoadingSharingIconEnum.ts
│ │ │ │ ├── changeLanguage.ts
│ │ │ │ ├── handleCreatePeerConnection.ts
│ │ │ │ ├── handleDisplayingLoadingSharingIconLoop.spec.ts
│ │ │ │ ├── handleDisplayingLoadingSharingIconLoop.ts
│ │ │ │ ├── handleNoConnectionTimeout.ts
│ │ │ │ ├── handleRemoveDanglingReactRevealContainer.ts
│ │ │ │ ├── handleSetVideoQuality.ts
│ │ │ │ ├── index.css
│ │ │ │ └── index.tsx
│ │ │ └── PlayerView
│ │ │ │ └── index.tsx
│ │ ├── features
│ │ │ ├── PeerConnection
│ │ │ │ ├── NullUser.ts
│ │ │ │ ├── PartnerPeerUser.d.ts
│ │ │ │ ├── PeerConnection.d.ts
│ │ │ │ ├── PeerConnectionUIHandler.ts
│ │ │ │ ├── ReceiveEncryptedMessagePayload.d.ts
│ │ │ │ ├── ScreenSharingSourceEnum.ts
│ │ │ │ ├── errors
│ │ │ │ │ ├── PeerConnectionPartnerIsNotDefinedError.ts
│ │ │ │ │ ├── PeerConnectionPeerIsNullError.ts
│ │ │ │ │ ├── PeerConnectionSocketNotDefined.ts
│ │ │ │ │ └── PeerConnectionUserIsNotDefinedError.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mocks
│ │ │ │ │ ├── INPUTtestWindowNavigatorUserAgent.ts
│ │ │ │ │ ├── INPUTvideo500000testSdpMediaBitrate.ts
│ │ │ │ │ ├── OUTPUTDeviceDetailsFromUAParsed.ts
│ │ │ │ │ └── OUTPUTvideo500000testSdpMediaBitrate.ts
│ │ │ │ ├── peerConnectionHandlePeer.spec.ts
│ │ │ │ ├── peerConnectionHandlePeer.ts
│ │ │ │ ├── peerConnectionHandleSocket.spec.ts
│ │ │ │ ├── peerConnectionHandleSocket.ts
│ │ │ │ ├── peerConnectionReceiveEncryptedMessage.spec.ts
│ │ │ │ ├── peerConnectionReceiveEncryptedMessage.ts
│ │ │ │ ├── setAndShowErrorDialogMessage.spec.ts
│ │ │ │ ├── setAndShowErrorDialogMessage.ts
│ │ │ │ ├── setSdpMediaBitrate.spec.ts
│ │ │ │ ├── setSdpMediaBitrate.ts
│ │ │ │ ├── simplePeerDataMessages.ts
│ │ │ │ └── startSocketConnectedCheckingLoop
│ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ └── index.ts
│ │ │ └── VideoAutoQualityOptimizer
│ │ │ │ ├── VideoQualityEnum.ts
│ │ │ │ ├── errors
│ │ │ │ ├── CanvasNotDefinedError.ts
│ │ │ │ ├── ImageDataIsUndefinedError.ts
│ │ │ │ ├── VideoDimensionsAreWrongError.ts
│ │ │ │ └── VideoNotDefinedError.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ └── index.ts
│ │ ├── images
│ │ │ ├── deskreen_logo_128x128.png
│ │ │ ├── fullscreen_24px.svg
│ │ │ ├── fullscreen_exit-24px.svg
│ │ │ └── red_heart_2764_twemoji_120x120.png
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── logo.svg
│ │ ├── polyfills.tsx
│ │ ├── providers
│ │ │ └── AppContextProvider
│ │ │ │ └── index.tsx
│ │ ├── react-app-env.d.ts
│ │ ├── serviceWorker.ts
│ │ ├── setupTests.ts
│ │ └── utils
│ │ │ ├── ProcessedMessage.d.ts
│ │ │ ├── areWeTestingWithJest.ts
│ │ │ ├── crypto.ts
│ │ │ ├── getRoomIDOfCurrentBrowserWindow.ts
│ │ │ ├── message.spec.ts
│ │ │ ├── message.ts
│ │ │ ├── mocks
│ │ │ ├── getTestPrivateKeyPem.ts
│ │ │ └── getTestPublicKeyPem.ts
│ │ │ ├── socket.ts
│ │ │ └── userAgentParserHelpers.ts
│ ├── tsconfig.json
│ └── yarn.lock
├── components
│ ├── AllowConnectionForDeviceAlert.tsx
│ ├── CloseOverlayButton.tsx
│ ├── ConnectedDevicesListDrawer.tsx
│ ├── DeviceInfoCallout
│ │ └── index.tsx
│ ├── LanguageSelector
│ │ └── index.tsx
│ ├── SettingsOverlay
│ │ ├── SettingRowLabelAndInput.tsx
│ │ └── SettingsOverlay.tsx
│ ├── ShareAppOrScreenControlGroup.tsx
│ ├── SharingSourcePreviewCard
│ │ └── index.tsx
│ ├── StepperPanel
│ │ ├── ColorlibConnector.tsx
│ │ ├── ColorlibStepIcon.tsx
│ │ └── DeviceConnectedInfoButton.tsx
│ ├── StepsOfStepper
│ │ ├── ChooseAppOrScreeenStep.tsx
│ │ ├── ChooseAppOrScreenOverlay
│ │ │ ├── ChooseAppOrScreenOverlay.tsx
│ │ │ ├── PreviewGridList.tsx
│ │ │ └── ViewSharingObject.d.ts
│ │ ├── ConfirmStep.tsx
│ │ ├── IntermediateStep.spec.tsx
│ │ ├── IntermediateStep.tsx
│ │ ├── ScanQRStep.spec.tsx
│ │ ├── ScanQRStep.tsx
│ │ ├── SuccessStep.tsx
│ │ └── __snapshots__
│ │ │ ├── IntermediateStep.spec.tsx.snap
│ │ │ └── ScanQRStep.spec.tsx.snap
│ ├── ToggleThemeBtnGroup
│ │ └── index.tsx
│ ├── TopPanel.spec.tsx
│ ├── TopPanel.tsx
│ └── css.d.ts
├── configs
│ ├── app.lang.config.ts
│ ├── i18next.config.client.spec.ts
│ ├── i18next.config.client.ts
│ └── i18next.config.ts
├── constants
│ ├── routes.json
│ └── test-devices.json
├── containers
│ ├── App.tsx
│ ├── DeskreenStepper.tsx
│ ├── HomePage.tsx
│ ├── Root.tsx
│ ├── SettingsProvider.tsx
│ └── __mocks__
│ │ ├── electron-settings.ts
│ │ ├── electron.ts
│ │ └── react-i18next.ts
├── deskreen-electron-store.ts
├── enums
│ └── ElectronStoreKeys.enum.ts
├── features
│ ├── ConnectedDevicesService
│ │ ├── Device.d.ts
│ │ ├── index.spec.ts
│ │ └── index.ts
│ ├── DesktopCapturerSourcesService
│ │ ├── DesktopCapturerSourceType.ts
│ │ ├── DesktopCapturerSourceWithType.d.ts
│ │ ├── index.spec.ts
│ │ └── index.ts
│ ├── PeerConnection
│ │ ├── NullSimplePeer.ts
│ │ ├── NullUser.ts
│ │ ├── PartnerPeerUser.d.ts
│ │ ├── PeerConnection.d.ts
│ │ ├── ReceiveEncryptedMessagePayload.d.ts
│ │ ├── SendEncryptedMessagePayload.d.ts
│ │ ├── createDesktopCapturerStream.spec.ts
│ │ ├── createDesktopCapturerStream.ts
│ │ ├── getDesktopSourceStreamBySourceID.spec.ts
│ │ ├── getDesktopSourceStreamBySourceID.ts
│ │ ├── handleCreatePeer.spec.ts
│ │ ├── handleCreatePeer.ts
│ │ ├── handlePeerOnData.spec.ts
│ │ ├── handlePeerOnData.ts
│ │ ├── handleRecieveEncryptedMessage.spec.ts
│ │ ├── handleRecieveEncryptedMessage.ts
│ │ ├── handleSelfDestroy.spec.ts
│ │ ├── handleSelfDestroy.ts
│ │ ├── handleSetDisplaySizeFromLocalStream.spec.ts
│ │ ├── handleSetDisplaySizeFromLocalStream.ts
│ │ ├── handleSocket.spec.ts
│ │ ├── handleSocket.ts
│ │ ├── handleSocketUserEnter.spec.ts
│ │ ├── handleSocketUserEnter.ts
│ │ ├── handleSocketUserExit.spec.ts
│ │ ├── handleSocketUserExit.ts
│ │ ├── index.spec.ts
│ │ ├── index.ts
│ │ ├── mocks
│ │ │ ├── INPUTvideo500000testSdpMediaBitrate.ts
│ │ │ ├── OUTPUTvideo500000testSdpMediaBitrate.ts
│ │ │ └── testVars.ts
│ │ ├── prepareDataMessageToSendScreenSourceType.ts
│ │ ├── setSdpMediaBitrate.spec.ts
│ │ ├── setSdpMediaBitrate.ts
│ │ ├── simplePeerHandleSdpTransform.spec.ts
│ │ └── simplePeerHandleSdpTransform.ts
│ ├── PeerConnectionHelperRendererService
│ │ ├── index.spec.ts
│ │ └── index.ts
│ └── SharingSessionService
│ │ ├── LocalPeerUser.d.ts
│ │ ├── SharingSession.spec.ts
│ │ ├── SharingSession.ts
│ │ ├── SharingSessionStatusChangeListener.d.ts
│ │ ├── SharingSessionStatusEnum.ts
│ │ ├── SharingTypeEnum.ts
│ │ ├── __mocks__
│ │ ├── PeerConnection.ts
│ │ └── shortid.ts
│ │ ├── index.spec.ts
│ │ └── index.ts
├── images
│ └── red_heart_2764_twemoji_120x120.png
├── index.tsx
├── locales
│ ├── da
│ │ └── translation.json
│ ├── de
│ │ └── translation.json
│ ├── en
│ │ └── translation.json
│ ├── es
│ │ └── translation.json
│ ├── fi
│ │ └── translation.json
│ ├── fr
│ │ └── translation.json
│ ├── it
│ │ └── translation.json
│ ├── ja
│ │ └── translation.json
│ ├── ko
│ │ └── translation.json
│ ├── nl
│ │ └── translation.json
│ ├── ru
│ │ └── translation.json
│ ├── sv
│ │ └── translation.json
│ ├── ua
│ │ └── translation.json
│ ├── zh_CN
│ │ └── translation.json
│ └── zh_TW
│ │ └── translation.json
├── main.dev.spec.ts
├── main.dev.ts
├── main.prod.js.LICENSE
├── main.prod.js.LICENSE.txt
├── main
│ ├── IpcEvents.enum.ts
│ └── ipcMainHandlers.ts
├── menu.spec.ts
├── menu.ts
├── package-lock.json
├── package.json
├── peerConnectionHelperRendererWindow.html
├── peerConnectionHelperRendererWindowIndex.spec.ts
├── peerConnectionHelperRendererWindowIndex.tsx
├── rootReducer.ts
├── server
│ ├── Room.d.ts
│ ├── RoomIDService
│ │ ├── RoomIDService.spec.ts
│ │ └── index.ts
│ ├── connectSocket.ts
│ ├── darkwireSocket.spec.ts
│ ├── darkwireSocket.ts
│ ├── index.ts
│ ├── pollForInactiveRooms.spec.ts
│ ├── pollForInactiveRooms.ts
│ ├── socketsIPService.ts
│ └── store
│ │ ├── MemoryStore.ts
│ │ ├── index.ts
│ │ └── socketIOServerStore.ts
├── store.ts
├── utils
│ ├── AppUpdater.ts
│ ├── LoggerWithFilePrefix.spec.ts
│ ├── LoggerWithFilePrefix.ts
│ ├── ProcessedMessage.d.ts
│ ├── crypto.ts
│ ├── getAppLanguage.ts
│ ├── getAppTheme.ts
│ ├── getNewVersionTag.ts
│ ├── installExtensions.ts
│ ├── isProduction.ts
│ ├── isWithReactRevealAnimations.ts
│ ├── mainProcessHelpers
│ │ ├── DeskreenGlobal.d.ts
│ │ ├── getDeskreenGlobal.ts
│ │ └── initGlobals.ts
│ ├── message.spec.ts
│ ├── message.ts
│ └── mocks
│ │ ├── getTestPrivateKeyPem.ts
│ │ └── getTestPublicKeyPem.ts
└── yarn.lock
├── babel.config.js
├── configs
├── .eslintrc
├── webpack.config.base.js
├── webpack.config.eslint.js
├── webpack.config.main.prod.babel.js
├── webpack.config.renderer.dev.babel.js
├── webpack.config.renderer.dev.dll.babel.js
└── webpack.config.renderer.prod.babel.js
├── doc
├── architecture
│ ├── deskreen-arch-pavlobu-21012021.html
│ └── deskreen-arch-pavlobu-21012021.svg
├── benchmarks
│ └── 1-IPad-pro-2017-12.9-inch+Macbook-Pro-mid-2015.md
├── init-sharing-session
│ ├── deskreen-webrtc-screen-sharing-session-initiation-pavlobu-22012021.html
│ └── deskreen-webrtc-screen-sharing-session-initiation-pavlobu-22012021.svg
└── translations-docs
│ ├── fix-a-typo-in-existing-translation-of-Deskreen.md
│ └── how-to-add-new-language-translation-to-Deskreen.md
├── drivers
├── README.md
├── linux
│ └── git-dummy
├── mac
│ └── git-dummy
└── win
│ └── git-dummy
├── internals
├── img
│ ├── erb-banner.png
│ ├── erb-logo.png
│ ├── eslint-padded-90.png
│ ├── eslint-padded.png
│ ├── eslint.png
│ ├── jest-padded-90.png
│ ├── jest-padded.png
│ ├── jest.png
│ ├── js-padded.png
│ ├── js.png
│ ├── npm.png
│ ├── react-padded-90.png
│ ├── react-padded.png
│ ├── react-router-padded-90.png
│ ├── react-router-padded.png
│ ├── react-router.png
│ ├── react.png
│ ├── redux-padded-90.png
│ ├── redux-padded.png
│ ├── redux.png
│ ├── webpack-padded-90.png
│ ├── webpack-padded.png
│ ├── webpack.png
│ ├── yarn-padded-90.png
│ ├── yarn-padded.png
│ └── yarn.png
├── jest
│ └── setEnvVars.js
├── mocks
│ └── fileMock.js
└── scripts
│ ├── .eslintrc
│ ├── BabelRegister.js
│ ├── CheckBuildsExist.js
│ ├── CheckNativeDep.js
│ ├── CheckNodeEnv.js
│ ├── CheckPortInUse.js
│ ├── CheckYarn.js
│ ├── DeleteSourceMaps.js
│ └── ElectronRebuild.js
├── package.json
├── resources
├── icon.icns
├── icon.ico
├── icon.png
└── icons
│ ├── icon_1024x1024.png
│ ├── icon_128x128.png
│ ├── icon_16x16.png
│ ├── icon_24x24.png
│ ├── icon_256x256.png
│ ├── icon_32x32.png
│ ├── icon_48x48.png
│ ├── icon_512x512.png
│ ├── icon_64x64.png
│ └── icon_96x96.png
├── sonar-project.properties
├── test
├── .eslintrc.json
├── integration
│ └── app
│ │ └── server
│ │ └── index.spec.ts
└── ux
│ └── Stepper.ux.ts
├── tsconfig.json
├── wiki
└── Home.md
└── yarn.lock
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | patch:
4 | default:
5 | enabled: no
6 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # OSX
31 | .DS_Store
32 |
33 | # App packaged
34 | release
35 | app/main.prod.js
36 | app/main.prod.js.map
37 | app/renderer.prod.js
38 | app/renderer.prod.js.map
39 | app/style.css
40 | app/style.css.map
41 | dist
42 | dll
43 | main.js
44 | main.js.map
45 |
46 | .idea
47 | npm-debug.log.*
48 | .*.dockerfile
49 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # OSX
31 | .DS_Store
32 |
33 | # App packaged
34 | release
35 | app/*.main.prod.js
36 | app/main.prod.js
37 | app/main.prod.js.map
38 | app/renderer.prod.js
39 | app/renderer.prod.js.map
40 | app/style.css
41 | app/style.css.map
42 | dist
43 | dll
44 | main.js
45 | main.js.map
46 |
47 | .idea
48 | npm-debug.log.*
49 | __snapshots__
50 |
51 | # Package.json
52 | package.json
53 | .travis.yml
54 | *.css.d.ts
55 | *.sass.d.ts
56 | *.scss.d.ts
57 |
58 |
59 | # Entire app/client directory
60 | app/client
61 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'erb/typescript',
3 | rules: {
4 | // A temporary hack related to IDE not resolving correct package.json
5 | 'import/no-extraneous-dependencies': 'off',
6 | },
7 | parserOptions: {
8 | ecmaVersion: 2020,
9 | sourceType: 'module',
10 | project: './tsconfig.json',
11 | tsconfigRootDir: __dirname,
12 | createDefaultProgram: true,
13 | },
14 | settings: {
15 | 'import/resolver': {
16 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
17 | node: {},
18 | webpack: {
19 | config: require.resolve('./configs/webpack.config.eslint.js'),
20 | },
21 | },
22 | 'import/parsers': {
23 | '@typescript-eslint/parser': ['.ts', '.tsx'],
24 | },
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.png binary
3 | *.jpg binary
4 | *.jpeg binary
5 | *.ico binary
6 | *.icns binary
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # TODO: edit funding when ready
2 |
3 | # These are supported funding model platforms
4 |
5 | # github: [deskreen, pavlobu]
6 | patreon: deskreen
7 | open_collective: deskreen
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: You're having technical issues. 🐞 And willing to share more details. If you don't know details, write here https://github.com/pavlobu/deskreen/discussions/68
4 | labels: 'bug'
5 | ---
6 |
7 |
8 |
9 | ## Prerequisites
10 |
11 |
12 |
13 | - [ ] Using yarn
14 | - [ ] Using an up-to-date [`master` branch](https://github.com/pavlobu/deskreen/tree/master)
15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/)
16 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true yarn build && yarn start`
17 |
18 | ## Expected Behavior
19 |
20 |
21 |
22 | ## Current Behavior
23 |
24 |
25 |
26 | ## Steps to Reproduce
27 |
28 |
29 |
30 |
31 | 1.
32 |
33 | 2.
34 |
35 | 3.
36 |
37 | 4.
38 |
39 | ## Possible Solution (Not obligatory)
40 |
41 |
42 |
43 | ## Context
44 |
45 |
46 |
47 |
48 |
49 | ## Your Environment
50 |
51 |
52 |
53 | - Node version :
54 | - Deskreen version or branch :
55 | - Operating System and version :
56 | - Link to your project :
57 |
58 |
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3-Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You want something small added to Deskreen and with concrete code and solution. 🎉 If it is a big enhancement drop it here https://github.com/pavlobu/deskreen/discussions/50
4 | labels: 'enhancement'
5 | ---
6 |
7 | # Here you post only concrete examples of enhancements with code and solutions that you have. Other BIG enhancements and general ideas of features you would like to see in Deskreen, you post here: https://github.com/pavlobu/deskreen/discussions/50
8 |
9 | # Otherwise this issue will be closed.
10 |
11 |
20 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | requiredHeaders:
2 | - Prerequisites
3 | - Expected Behavior
4 | - Current Behavior
5 | - Possible Solution
6 | - Your Environment
7 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pr
8 | - discussion
9 | - e2e
10 | - enhancement
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/codecov.yml:
--------------------------------------------------------------------------------
1 | #name: codecov
2 | #
3 | #on:
4 | # push:
5 | # branches:
6 | # - master
7 | # pull_request:
8 | # branches:
9 | # - master
10 | #
11 | #jobs:
12 | # run:
13 | # runs-on: ubuntu-18.04 # IMPORTANT!!! this LINUX os should be the same as in build-and-test and release workflows! this is for making sure caches are used in most efficient way
14 | #
15 | # steps:
16 | # - name: Check out Git repository
17 | # uses: actions/checkout@v2.3.4
18 | #
19 | # - name: Setup Node.js environment
20 | # uses: actions/setup-node@v2.1.4
21 | # with:
22 | # node-version: '12'
23 | #
24 | # - name: Get yarn cache directory path
25 | # id: yarn-cache-dir-path
26 | # run: echo "::set-output name=dir::$(yarn cache dir)"
27 | #
28 | # - uses: actions/cache@v2.1.3
29 | # id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
30 | # with:
31 | # path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
32 | # key: ubuntu-18.04-yarn-${{ hashFiles('**/yarn.lock') }}
33 | # restore-keys: |
34 | # ubuntu-18.04-yarn-
35 | #
36 | # - name: yarn install in ./app/client
37 | # run: |
38 | # cd ./app/client
39 | # yarn install --frozen-lockfile
40 | #
41 | # - name: yarn install in ./
42 | # run: yarn install --frozen-lockfile
43 | #
44 | # - name: yarn install in ./app
45 | # run: |
46 | # cd ./app
47 | # yarn install --frozen-lockfile
48 | #
49 | # - name: yarn build
50 | # env:
51 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52 | # run: yarn build
53 | #
54 | # - name: yarn test
55 | # run: yarn test
56 | #
57 | # - name: yarn coverage
58 | # run: yarn coverage
59 | #
60 | # - name: Upload app coverage to Codecov
61 | # uses: codecov/codecov-action@v1.0.12
62 | # with:
63 | # token: ${{ secrets.CODECOV_TOKEN }}
64 | # file: ./coverage/clover.xml
65 | # flags: unittests
66 | # name: codecov-umbrella
67 | # fail_ci_if_error: true
68 | #
69 | # - name: Upload app/client coverage to Codecov
70 | # uses: codecov/codecov-action@v1.0.12
71 | # with:
72 | # token: ${{ secrets.CODECOV_TOKEN }}
73 | # file: ./app/client/coverage/clover.xml
74 | # flags: unittests
75 | # name: codecov-umbrella
76 | # fail_ci_if_error: true
77 |
--------------------------------------------------------------------------------
/.github/workflows/publish-to-wiki.yml:
--------------------------------------------------------------------------------
1 | name: Publish wiki
2 | on:
3 | push:
4 | branches: [master]
5 | paths:
6 | - wiki/**
7 | - .github/workflows/publish-wiki.yml
8 | concurrency:
9 | group: publish-wiki
10 | cancel-in-progress: true
11 | permissions:
12 | contents: write
13 | jobs:
14 | publish-wiki:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 | - uses: Andrew-Chen-Wang/github-wiki-action@v4
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .nyc_output
2 | .scannerwork/
3 | test-reports/
4 | test-reporter.xml
5 |
6 | app/client/node_modules
7 | app/client/build
8 |
9 | # Logs
10 | logs
11 | *.log
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # node-waf configuration
28 | .lock-wscript
29 |
30 | # Compiled binary addons (http://nodejs.org/api/addons.html)
31 | build/Release
32 | .eslintcache
33 |
34 | # Dependency directory
35 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
36 | node_modules
37 |
38 | # OSX
39 | .DS_Store
40 |
41 | # App packaged
42 | release
43 | app/main.prod.js
44 | app/main.prod.js.map
45 | app/renderer.prod.js
46 | app/renderer.prod.js.map
47 | app/style.css
48 | app/style.css.map
49 | dist
50 | dll
51 | main.js
52 | main.js.map
53 |
54 | .idea
55 | npm-debug.log.*
56 | *.css.d.ts
57 | *.sass.d.ts
58 | *.scss.d.ts
59 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "EditorConfig.EditorConfig",
5 | "msjsdiag.debugger-for-chrome"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "node",
6 | "request": "launch",
7 | "name": "Deskreen: Main",
8 | "protocol": "inspector",
9 | "runtimeExecutable": "yarn",
10 | "runtimeArgs": ["start-main-debug"],
11 | "preLaunchTask": "Start Webpack Dev"
12 | },
13 | {
14 | "name": "Deskreen: Renderer",
15 | "type": "chrome",
16 | "request": "attach",
17 | "port": 9223,
18 | "webRoot": "${workspaceFolder}",
19 | "timeout": 15000
20 | }
21 | ],
22 | "compounds": [
23 | {
24 | "name": "Deskreen: All",
25 | "configurations": ["Deskreen: Main", "Deskreen: Renderer"]
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | ".babelrc": "jsonc",
4 | ".eslintrc": "jsonc",
5 | ".prettierrc": "jsonc",
6 | ".stylelintrc": "json",
7 | ".dockerignore": "ignore",
8 | ".eslintignore": "ignore"
9 | },
10 |
11 | "javascript.validate.enable": false,
12 | "javascript.format.enable": false,
13 | "typescript.format.enable": false,
14 |
15 | "search.exclude": {
16 | ".git": true,
17 | ".eslintcache": true,
18 | "app/dist": true,
19 | "app/main.prod.js": true,
20 | "app/main.prod.js.map": true,
21 | "bower_components": true,
22 | "dll": true,
23 | "release": true,
24 | "node_modules": true,
25 | "npm-debug.log.*": true,
26 | "test/**/__snapshots__": true,
27 | "yarn.lock": true,
28 | "*.{css,sass,scss}.d.ts": true
29 | },
30 | "editor.codeActionsOnSave": {
31 | "source.fixAll.eslint": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "label": "Start Webpack Dev",
7 | "script": "start-renderer-dev",
8 | "options": {
9 | "cwd": "${workspaceFolder}"
10 | },
11 | "isBackground": true,
12 | "problemMatcher": {
13 | "owner": "custom",
14 | "pattern": {
15 | "regexp": "____________"
16 | },
17 | "background": {
18 | "activeOnStart": true,
19 | "beginsPattern": "Compiling\\.\\.\\.$",
20 | "endsPattern": "(Compiled successfully|Failed to compile)\\.$"
21 | }
22 | }
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/app/.testcafe-electron-rc:
--------------------------------------------------------------------------------
1 | {
2 | "mainWindowUrl": "./app.html",
3 | "appPath": "."
4 | }
5 |
--------------------------------------------------------------------------------
/app/Routes.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | /* eslint react/jsx-props-no-spreading: off */
3 | import React from 'react';
4 | import { Switch, Route } from 'react-router-dom';
5 | import routes from './constants/routes.json';
6 | import App from './containers/App';
7 | import HomePage from './containers/HomePage';
8 |
9 | export default function Routes() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/app/api/config.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | let host;
4 | let protocol;
5 | let port;
6 |
7 | if (!host && !protocol && !port) {
8 | host = '127.0.0.1';
9 | protocol = 'http';
10 | port = 3131; // TODO: read port from signaling server api
11 | }
12 |
13 | export default {
14 | host,
15 | port,
16 | protocol,
17 | };
18 |
--------------------------------------------------------------------------------
/app/api/urlGenerator.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | import config from './config';
4 |
5 | export default (resourceName = '') => {
6 | const { port, protocol, host } = config;
7 |
8 | const resourcePath = resourceName;
9 |
10 | if (!host) {
11 | return `/localhost`;
12 | }
13 |
14 | return `${protocol}://${host}:${port}/${resourcePath}`;
15 | };
16 |
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Deskreen
6 |
20 |
21 |
22 |
23 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/app.icns
--------------------------------------------------------------------------------
/app/client/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | patch:
4 | default:
5 | enabled: no
6 |
--------------------------------------------------------------------------------
/app/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/app/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskreen-client-viewer",
3 | "version": "2.0.3",
4 | "private": true,
5 | "dependencies": {
6 | "@blueprintjs/core": "^3.35.0",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.3.2",
9 | "@testing-library/user-event": "^7.1.2",
10 | "@types/jest": "^24.0.0",
11 | "@types/node": "^12.0.0",
12 | "@types/react": "^16.9.0",
13 | "@types/react-dom": "^16.9.0",
14 | "@types/ua-parser-js": "^0.7.33",
15 | "i18next": "^19.8.4",
16 | "i18next-http-backend": "^1.0.21",
17 | "jest-sonar-reporter": "^2.0.0",
18 | "node-forge": "^0.9.1",
19 | "pixelmatch": "^5.2.1",
20 | "react": "^16.13.1",
21 | "react-app-polyfill": "^2.0.0",
22 | "react-dom": "^16.13.1",
23 | "react-flexbox-grid": "^2.1.2",
24 | "react-i18next": "^11.7.4",
25 | "react-player": "^2.6.2",
26 | "react-reveal": "^1.2.2",
27 | "react-scripts": "3.4.3",
28 | "react-spinners": "^0.9.0",
29 | "screenfull": "^5.0.2",
30 | "shortid": "^2.2.15",
31 | "socket.io-client": "^2.3.0",
32 | "typescript": "~3.7.2",
33 | "ua-parser-js": "^0.7.22"
34 | },
35 | "scripts": {
36 | "start": "react-scripts start",
37 | "build": "react-scripts build",
38 | "test": "react-scripts test",
39 | "eject": "react-scripts eject",
40 | "test:nowatch": "react-scripts test --watchAll=false",
41 | "coverage": "yarn test -- --coverage --watchAll=false"
42 | },
43 | "eslintConfig": {
44 | "extends": "react-app"
45 | },
46 | "browserslist": {
47 | "production": [
48 | ">0.2%",
49 | "not dead",
50 | "not op_mini all"
51 | ],
52 | "development": [
53 | "last 1 chrome version",
54 | "last 1 firefox version",
55 | "last 1 safari version"
56 | ]
57 | },
58 | "devDependencies": {
59 | "@types/node-forge": "^0.9.5",
60 | "@types/pixelmatch": "^5.2.2",
61 | "@types/resemblejs": "^1.3.29",
62 | "@types/simple-peer": "^9.6.0",
63 | "@types/socket.io-client": "^1.4.33"
64 | },
65 | "jest": {
66 | "collectCoverageFrom": [
67 | "src/**/*.{js,jsx,ts,tsx}",
68 | "!/node_modules/",
69 | "!/src/serviceWorker.ts"
70 | ],
71 | "coverageThreshold": {
72 | "global": {
73 | "branches": 0,
74 | "functions": 0,
75 | "lines": 0,
76 | "statements": 0
77 | }
78 | },
79 | "coverageReporters": [
80 | "json",
81 | "lcov",
82 | "text",
83 | "clover"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/client/public/favicon.ico
--------------------------------------------------------------------------------
/app/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Deskreen Viewer
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/client/public/locales/zh_CN/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "Waiting for user to click ALLOW button on screen sharing device...": "正在等待用户单击屏幕共享设备上的允许按钮...",
3 | "Waiting for user to select source to share from screen sharing device...": "正在等待用户从屏幕共享设备选择要共享的源...",
4 | "My Device Info": "我的设备信息",
5 | "Device Type": "设备类型",
6 | "Your Device IP should match with Device IP in alert popup appeared on your computer, where Deskreen is running": "您的设备 IP 应该与运行 Deskreen 的计算机上出现的警报弹出窗口中的 '设备 IP' 相匹配。",
7 | "Device IP": "设备 IP",
8 | "Device Browser": "设备浏览器",
9 | "Device OS": "设备操作系统",
10 | "These details should match with the ones that you see in alert popup on computer screen, where Deskreen is running": "这些详细信息应与您在屏幕共享设备上的警报弹出窗口中看到的信息相匹配。",
11 | "Deskreen Screen Viewer": "Deskreen 屏幕查看器",
12 | "Connected!": "已连接!",
13 | "Error occurred": "出现错误",
14 | "Deskreen Error Dialog": "Deskreen 错误对话框",
15 | "Something went wrong": "出问题了",
16 | "You may close this browser window then try to connect again": "您可以关闭此浏览器窗口,然后尝试重新连接",
17 | "An unknown error occurred": "出现未知错误",
18 | "You were not allowed to connect": "您不能连接",
19 | "You were disconnected": "您将断开连接",
20 | "WebRTC error occurred": "出现 WebRTC 错误",
21 | "If you like Deskreen consider contributing financially Deskreen is open-source Your donations keep us motivated to make Deskreen even better": "如果你喜欢 Deskreen,可以考虑出钱。Deskreen 是开源的。您的捐赠使我们有动力让 Deskreen 变得更好。",
22 | "Donate": "捐赠",
23 | "Video stream is paused": "视频流暂停",
24 | "Video stream is playing": "正在播放视频流",
25 | "Pause": "暂停",
26 | "Play": "播放",
27 | "Video Settings": "视频设置",
28 | "Flip": "翻转",
29 | "Video quality has been changed to": "视频质量已更改为",
30 | "Click to Open Video Settings": "单击以打开视频设置",
31 | "Click to Enter Full Screen Mode": "单击以进入全屏模式",
32 | "Default video player has been turned OFF": "默认视频播放器已关闭",
33 | "Default video player has been turned ON": "默认视频播放器已开启",
34 | "ON": "开启",
35 | "OFF": "关闭",
36 | "Default Video Player": "默认视频播放器",
37 | "Click to visit our website": "点击访问我们的网站",
38 | "Video is flipped horizontally": "视频水平翻转",
39 | "TRANSLATIONS BELOW ARE NOT ADDED TO UI YET, BUT YOUR TRANSLATIONS ARE WELCOME! THE FEATURES WILL BE ADDED SOON SO YOUR TRANSLATIONS ARE NEEDED": "以下翻译尚未添加到 UI,但欢迎您的翻译!功能将很快添加,因此需要您的翻译",
40 | "Click to see connection info": "单击以查看连接信息",
41 | "Pair ID": "配对 ID",
42 | "Unpair": "取消配对",
43 | "Session ID": "会话 ID",
44 | "Click to boost video stream if it is lagging": "如果视频流滞后,请单击以提高视频流"
45 | }
46 |
--------------------------------------------------------------------------------
/app/client/public/locales/zh_TW/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "Waiting for user to click ALLOW button on screen sharing device...": "正在等待使用者單擊螢幕共享裝置上的允許按鈕...",
3 | "Waiting for user to select source to share from screen sharing device...": "正在等待使用者從螢幕共享裝置選擇要共享的源...",
4 | "My Device Info": "我的裝置資訊",
5 | "Device Type": "裝置型別",
6 | "Your Device IP should match with Device IP in alert popup appeared on your computer, where Deskreen is running": "您的裝置 IP 應該與執行 Deskreen 的計算機上出現的警報彈出視窗中的 '裝置 IP' 相匹配。",
7 | "Device IP": "裝置 IP",
8 | "Device Browser": "裝置瀏覽器",
9 | "Device OS": "裝置作業系統",
10 | "These details should match with the ones that you see in alert popup on computer screen, where Deskreen is running": "這些詳細資訊應與您在螢幕共享裝置上的警報彈出視窗中看到的資訊相匹配。",
11 | "Deskreen Screen Viewer": "Deskreen 螢幕檢視器",
12 | "Connected!": "已連線!",
13 | "Error occurred": "出現錯誤",
14 | "Deskreen Error Dialog": "Deskreen 錯誤對話方塊",
15 | "Something went wrong": "出問題了",
16 | "You may close this browser window then try to connect again": "您可以關閉此瀏覽器視窗,然後嘗試重新連線",
17 | "An unknown error occurred": "出現未知錯誤",
18 | "You were not allowed to connect": "您不能連線",
19 | "You were disconnected": "您將斷開連線",
20 | "WebRTC error occurred": "出現 WebRTC 錯誤",
21 | "If you like Deskreen consider contributing financially Deskreen is open-source Your donations keep us motivated to make Deskreen even better": "如果你喜歡 Deskreen,可以考慮出錢。Deskreen 是開源的。您的捐贈使我們有動力讓 Deskreen 變得更好。",
22 | "Donate": "捐贈",
23 | "Video stream is paused": "影片流暫停",
24 | "Video stream is playing": "正在播放影片流",
25 | "Pause": "暫停",
26 | "Play": "播放",
27 | "Video Settings": "影片設定",
28 | "Flip": "翻轉",
29 | "Video quality has been changed to": "影片質量已更改為",
30 | "Click to Open Video Settings": "單擊以開啟影片設定",
31 | "Click to Enter Full Screen Mode": "單擊以進入全屏模式",
32 | "Default video player has been turned OFF": "預設影片播放器已關閉",
33 | "Default video player has been turned ON": "預設影片播放器已開啟",
34 | "ON": "開啟",
35 | "OFF": "關閉",
36 | "Default Video Player": "預設影片播放器",
37 | "Click to visit our website": "點選訪問我們的網站",
38 | "Video is flipped horizontally": "影片水平翻轉",
39 | "TRANSLATIONS BELOW ARE NOT ADDED TO UI YET, BUT YOUR TRANSLATIONS ARE WELCOME! THE FEATURES WILL BE ADDED SOON SO YOUR TRANSLATIONS ARE NEEDED": "以下翻譯尚未新增到 UI,但歡迎您的翻譯!功能將很快新增,因此需要您的翻譯",
40 | "Click to see connection info": "單擊以檢視連線資訊",
41 | "Pair ID": "配對 ID",
42 | "Unpair": "取消配對",
43 | "Session ID": "會話 ID",
44 | "Click to boost video stream if it is lagging": "如果影片流滯後,請單擊以提高影片流"
45 | }
46 |
--------------------------------------------------------------------------------
/app/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/client/public/logo192.png
--------------------------------------------------------------------------------
/app/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/client/public/logo512.png
--------------------------------------------------------------------------------
/app/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Deskreen",
3 | "name": "Deskreen Makes Any Device a Second Screen For Your Computer",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/app/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/app/client/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=deskreen-viewer
2 | sonar.typescript.lcov.reportPaths=coverage/lcov.info
3 | sonar.sources=src
4 | sonar.cpd.exclusions=src/config/*,src/**/__mocks__/*,src/**/mocks/*,src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
5 | sonar.coverage.exclusions=src/config/*,src/**/__mocks__/*,src/**/mocks/*,src/**/*.spec.ts,src/**/*.spec.tsx,src/**/*.test.ts,src/**/*.test.tsx,src/serviceWorker.ts,src/index.tsx
6 | sonar.host.url=http://localhost:9000
7 | sonar.login=e3b5f73b8778290f7074c40a4159c32b7f15a8e6
8 | sonar.exclusions=src/serviceWorker.ts,node_modules/**
9 |
--------------------------------------------------------------------------------
/app/client/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | // const { getByText } = render();
7 | // const linkElement = getByText(/learn react/i);
8 | // expect(linkElement).toBeInTheDocument();
9 | expect(true).toBe(true);
10 | });
11 |
--------------------------------------------------------------------------------
/app/client/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MainView from './containers/MainView';
3 |
4 | function App() {
5 | return (
6 |
7 | );
8 | }
9 |
10 | export default App;
11 |
--------------------------------------------------------------------------------
/app/client/src/api/config.ts:
--------------------------------------------------------------------------------
1 | let host;
2 | let protocol;
3 | let port;
4 |
5 | if (!host && !protocol && !port) {
6 | host = window.location.host.split(':')[0];
7 | protocol = 'http';
8 | port = 3131;
9 | }
10 |
11 | export default {
12 | host,
13 | port,
14 | protocol,
15 | };
16 |
--------------------------------------------------------------------------------
/app/client/src/api/generator.spec.ts:
--------------------------------------------------------------------------------
1 | import generator from './generator';
2 | import { TEST_PORT, TEST_HOST, TEST_PROTOCOL } from './mocks/generatorTestVariables';
3 |
4 | // how to use local variables in jest mock to get rid of hoisting mocks to top most code block:
5 | //stackoverflow.com/questions/44649699/service-mocked-with-jest-causes-the-module-factory-of-jest-mock-is-not-allowe
6 | jest.mock('./config', () => {
7 | const generatorTestVariables = require('./mocks/generatorTestVariables');
8 |
9 | return {
10 | host: generatorTestVariables.TEST_HOST,
11 | protocol: generatorTestVariables.TEST_PROTOCOL,
12 | port: generatorTestVariables.TEST_PORT,
13 | };
14 | });
15 |
16 |
17 | describe('generator.ts', () => {
18 | afterEach(() => {
19 | jest.clearAllMocks();
20 | jest.restoreAllMocks();
21 | });
22 |
23 | describe('when generator() is called properly', () => {
24 | it('should produce correct string', () => {
25 | const roomID = '333';
26 |
27 | const result = generator(roomID);
28 |
29 | expect(result).toMatch(
30 | `${TEST_PROTOCOL}://${TEST_HOST}:${TEST_PORT}/${roomID}`
31 | );
32 | });
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/app/client/src/api/generator.ts:
--------------------------------------------------------------------------------
1 | import config from './config';
2 |
3 | export default (resourceName = '') => {
4 | const { port, protocol, host } = config;
5 |
6 | const resourcePath = resourceName;
7 |
8 | if (!host) {
9 | return `/localhost`;
10 | }
11 |
12 | return `${protocol}://${host}:${port}/${resourcePath}`;
13 | };
14 |
--------------------------------------------------------------------------------
/app/client/src/api/mocks/generatorTestVariables.ts:
--------------------------------------------------------------------------------
1 | export const TEST_PORT = '3232';
2 | export const TEST_HOST = '123.123.123.123';
3 | export const TEST_PROTOCOL = 'http';
4 |
--------------------------------------------------------------------------------
/app/client/src/components/ConnectingIndicator/ConnectingIndicatorIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon } from '@blueprintjs/core';
3 | import { Col, Row } from 'react-flexbox-grid';
4 |
5 | interface ConnectingIndicatorIconProps {
6 | connectionIconType: "feed" | "feed-subscribed";
7 | }
8 |
9 | function ConnectingIndicatorIcon(props: ConnectingIndicatorIconProps) {
10 | const { connectionIconType } = props;
11 |
12 | return (
13 |
21 |
22 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default ConnectingIndicatorIcon;
36 |
--------------------------------------------------------------------------------
/app/client/src/components/ConnectingIndicator/LoadingSharingIcon.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Icon } from '@blueprintjs/core';
3 | import { Col, Row } from 'react-flexbox-grid';
4 | import PropagateLoader from 'react-spinners/PropagateLoader';
5 | import { AppContext } from '../../providers/AppContextProvider';
6 |
7 | const Fade = require('react-reveal/Fade');
8 |
9 | interface SelectSharingIconProps {
10 | loadingSharingIconType: LoadingSharingIconType;
11 | isShownLoadingSharingIcon: boolean;
12 | }
13 |
14 | function LoadingSharingIcon(props: SelectSharingIconProps) {
15 | const { isDarkTheme } = useContext(AppContext);
16 |
17 | const {
18 | loadingSharingIconType: selectingSharingIconType,
19 | isShownLoadingSharingIcon: isShownSelectingSharingIcon,
20 | } = props;
21 |
22 | return (
23 |
33 |
34 |
41 |
42 |
47 |
48 |
49 |
50 |
51 |
52 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 | }
64 |
65 | export default LoadingSharingIcon;
66 |
--------------------------------------------------------------------------------
/app/client/src/components/ErrorDialog/ErrorMessageEnum.ts:
--------------------------------------------------------------------------------
1 | export enum ErrorMessage {
2 | UNKNOWN_ERROR = 'An unknown error occurred',
3 | DENY_TO_CONNECT = 'You were not allowed to connect',
4 | DISCONNECTED = 'You were disconnected',
5 | NOT_ALLOWED = 'You were not allowed to connect',
6 | WEBRTC_ERROR = 'WebRTC error occurred'
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/components/ErrorDialog/index.css:
--------------------------------------------------------------------------------
1 | .error-dialog-backdrop {
2 | backdrop-filter: blur(2px);
3 | }
4 |
--------------------------------------------------------------------------------
/app/client/src/components/ErrorDialog/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Classes,
4 | Dialog,
5 | Divider,
6 | H1,
7 | H2,
8 | H3,
9 | Icon,
10 | } from '@blueprintjs/core';
11 | import { Col, Row } from 'react-flexbox-grid';
12 | import { useTranslation } from 'react-i18next';
13 | import './index.css';
14 | import { ErrorMessage } from './ErrorMessageEnum';
15 |
16 | interface ErrorDialogProps {
17 | isOpen: boolean;
18 | errorMessage: ErrorMessage;
19 | }
20 |
21 | function ErrorDialog(props: ErrorDialogProps) {
22 | const { t } = useTranslation();
23 | const { errorMessage, isOpen } = props;
24 |
25 | return (
26 |
69 | );
70 | }
71 |
72 | export default ErrorDialog;
73 |
--------------------------------------------------------------------------------
/app/client/src/components/MyDeviceInfoCard/DeviceDetails.d.ts:
--------------------------------------------------------------------------------
1 | interface DeviceDetails {
2 | myIP: string,
3 | myOS: string,
4 | myDeviceType: string,
5 | myBrowser: string,
6 | }
7 |
--------------------------------------------------------------------------------
/app/client/src/components/MyDeviceInfoCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Callout, Card, H3, Text, Tooltip, Position } from '@blueprintjs/core';
3 | import { useTranslation } from 'react-i18next';
4 | import { AppContext } from '../../providers/AppContextProvider';
5 | import {
6 | DARK_UI_BACKGROUND,
7 | LIGHT_UI_BACKGROUND,
8 | } from '../../constants/styleConstants';
9 |
10 | interface MyDeviceDetailsCardProps {
11 | deviceDetails: DeviceDetails;
12 | }
13 |
14 | function MyDeviceInfoCard(props: MyDeviceDetailsCardProps) {
15 | const { t } = useTranslation();
16 | const { isDarkTheme } = useContext(AppContext);
17 |
18 | const { deviceDetails } = props;
19 | const { myIP, myOS, myDeviceType, myBrowser } = deviceDetails;
20 |
21 | return (
22 |
29 | {`${t('My Device Info')}:`}
30 |
31 | {`${t('Device Type')}: ${myDeviceType}`}
32 |
36 |
45 | {`${t('Device IP')}: ${myIP}`}
46 |
47 |
48 | {`${t('Device Browser')}: ${myBrowser}`}
49 | {`${t('Device OS')}: ${myOS}`}
50 |
51 |
52 | {t('These details should match with the ones that you see in alert popup on computer screen, where Deskreen is running')}
53 |
54 |
55 | );
56 | }
57 |
58 | export default MyDeviceInfoCard;
59 |
--------------------------------------------------------------------------------
/app/client/src/components/PlayerControlPanel/handlePlayerToggleFullscreen.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | // IMPORTANT! leave upper blank line so this file is ignored for coverage!!! More on this issue here
4 | // https://github.com/facebook/create-react-app/issues/6106#issuecomment-550076629
5 | import { REACT_PLAYER_WRAPPER_ID } from "../../constants/appConstants";
6 |
7 | export default () => {
8 | const player = document.querySelector(
9 | `#${REACT_PLAYER_WRAPPER_ID} > video`
10 | );
11 | if (!player) return;
12 | // @ts-ignore
13 | if (player.requestFullScreen) {
14 | // @ts-ignore
15 | player.requestFullScreen();
16 | // @ts-ignore
17 | } else if (player.webkitRequestFullScreen) {
18 | // @ts-ignore
19 | player.webkitRequestFullScreen();
20 | // @ts-ignore
21 | } else if (player.mozRequestFullScreen) {
22 | // @ts-ignore
23 | player.mozRequestFullScreen();
24 | // @ts-ignore
25 | } else if (player.msRequestFullscreen) {
26 | // @ts-ignore
27 | player.msRequestFullscreen();
28 | // @ts-ignore
29 | } else if (player.webkitEnterFullscreen) {
30 | // @ts-ignore
31 | player.webkitEnterFullscreen(); //for iphone this code worked
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/app/client/src/components/PlayerControlPanel/index.css:
--------------------------------------------------------------------------------
1 | .play-pause-text {
2 | width: max-content;
3 | }
4 |
--------------------------------------------------------------------------------
/app/client/src/components/PlayerControlPanel/initScreenfullOnChange.ts:
--------------------------------------------------------------------------------
1 | import screenfull from "screenfull";
2 |
3 | export default (setIsFullScreenOn: (_: boolean) => void) => {
4 | if (!screenfull.isEnabled) return;
5 | // @ts-ignore
6 | screenfull.on('change', () => {
7 | // @ts-ignore
8 | setIsFullScreenOn(screenfull.isFullscreen);
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/app/client/src/components/ToggleDarkModeSwitch/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Switch, Classes, Alignment } from '@blueprintjs/core';
3 | import { AppContext } from '../../providers/AppContextProvider';
4 |
5 | function ToggleDarkModeSwitch() {
6 | const { isDarkTheme, setIsDarkThemeHook } = useContext(AppContext)
7 |
8 | return (
9 | {
11 | document.body.classList.toggle(Classes.DARK);
12 | setIsDarkThemeHook(!isDarkTheme)
13 | }}
14 | innerLabel={isDarkTheme ? 'ON' : 'OFF'}
15 | inline
16 | large
17 | label={`Dark Theme is`}
18 | alignIndicator={Alignment.RIGHT}
19 | checked={isDarkTheme}
20 | style={{ marginTop: '25px', marginLeft: '15px' }}
21 | />
22 | );
23 | }
24 |
25 | export default ToggleDarkModeSwitch;
26 |
--------------------------------------------------------------------------------
/app/client/src/config/i18n.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | import i18n from 'i18next';
4 | import { initReactI18next } from 'react-i18next';
5 | import Backend from 'i18next-http-backend';
6 |
7 | // don't want to use this?
8 | // have a look at the Quick start guide
9 | // for passing in lng and translations on init
10 |
11 | i18n
12 | // load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
13 | // learn more: https://github.com/i18next/i18next-http-backend
14 | .use(Backend)
15 | // pass the i18n instance to react-i18next.
16 | .use(initReactI18next)
17 | // init i18next
18 | // for all options read: https://www.i18next.com/overview/configuration-options
19 | .init({
20 | lng: 'en',
21 | saveMissing: true,
22 | saveMissingTo: 'all',
23 | fallbackLng: 'en', // TODO: to generate missing keys use false as value here, will be useful when custom nodejs server is created to store missing values
24 | debug: false, // change to true to see debug message logs in browser console
25 | whitelist: ['en', 'es', 'ru', 'ua', 'zh_CN', 'zh_TW', 'da', 'de', 'fi', 'ko', 'it', 'ja', 'nl', 'fr', 'sv'],
26 | backend: {
27 | // path where resources get loaded from
28 | loadPath: '/locales/{{lng}}/{{ns}}.json',
29 | // TODO: in future implement custom nodejs server that accepts missing translations POST requests and updates .missing.json files accordingly. Here is how to do so: https://www.robinwieruch.de/react-internationalization . it can be simple nodejs server that can be started when 'yarn dev' is running, need to ckagne package.json file then
30 | // path to post missing resources
31 | addPath: '/locales/{{lng}}/{{ns}}.json',
32 | // jsonIndent to use when storing json files
33 | jsonIndent: 2,
34 | },
35 |
36 | keySeparator: false, // we do not use keys in form messages.welcome
37 |
38 | interpolation: {
39 | escapeValue: false, // react already safes from xss
40 | },
41 | });
42 |
43 | export default i18n;
44 |
--------------------------------------------------------------------------------
/app/client/src/constants/appConstants.ts:
--------------------------------------------------------------------------------
1 | export const REACT_PLAYER_WRAPPER_ID = 'react-player-wrapper-id';
2 | export const VIDEO_QUALITY_TO_DECIMAL = {
3 | '25%': 0.25, // Q_25_PERCENT
4 | '40%': 0.40, // Q_40_PERCENT
5 | '60%': 0.60, // Q_60_PERCENT
6 | '80%': 0.80, // Q_80_PERCENT
7 | '100%': 1, // Q_100_PERCENT
8 | }
9 | export const COMPARISON_CANVAS_ID = 'comparison-canvas';
10 | export const DUMMY_MY_DEVICE_DETAILS = {
11 | myIP: '',
12 | myOS: '',
13 | myDeviceType: '',
14 | myBrowser: '',
15 | };
16 |
--------------------------------------------------------------------------------
/app/client/src/constants/styleConstants.ts:
--------------------------------------------------------------------------------
1 | export const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
2 | export const DARK_UI_BACKGROUND = '#293742';
3 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/ConnectionIconEnum.ts:
--------------------------------------------------------------------------------
1 | enum ConnectionIcon {
2 | FEED = 'feed',
3 | FEED_SUBSCRIBED = 'feed-subscribed'
4 | }
5 |
6 | export default ConnectionIcon;
7 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/LoadingSharingIconEnum.ts:
--------------------------------------------------------------------------------
1 | enum LoadingSharingIconEnum {
2 | DESKTOP = 'desktop',
3 | APPLICATION = 'application'
4 | }
5 |
6 | export default LoadingSharingIconEnum;
7 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/changeLanguage.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../config/i18n";
2 |
3 | export default (lng: string) => {
4 | i18n.changeLanguage(lng);
5 | };
6 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/handleCreatePeerConnection.ts:
--------------------------------------------------------------------------------
1 | import PeerConnection from '../../features/PeerConnection';
2 | import PeerConnectionUIHandler from '../../features/PeerConnection/PeerConnectionUIHandler';
3 | import getRoomIDOfCurrentBrowserWindow from '../../utils/getRoomIDOfCurrentBrowserWindow';
4 | import Crypto from '../../utils/crypto';
5 | import VideoAutoQualityOptimizer from '../../features/VideoAutoQualityOptimizer';
6 | import changeLanguage from './changeLanguage';
7 | import ConnectionIcon from './ConnectionIconEnum';
8 |
9 | export default (params: CreatePeerConnectionUseEffectParams) => {
10 | const {
11 | isDarkTheme,
12 | peer,
13 | setIsDarkThemeHook,
14 | setMyDeviceDetails,
15 | setConnectionIconType,
16 | setIsShownTextPrompt,
17 | setPromptStep,
18 | setScreenSharingSourceType,
19 | setDialogErrorMessage,
20 | setIsErrorDialogOpen,
21 | setUrl,
22 | setPeer,
23 | } = params;
24 |
25 | return () => {
26 | if (!peer) {
27 | const UIHandler = new PeerConnectionUIHandler(
28 | isDarkTheme,
29 | setMyDeviceDetails,
30 | () => {
31 | setConnectionIconType(ConnectionIcon.FEED_SUBSCRIBED);
32 |
33 | setIsShownTextPrompt(false);
34 | setIsShownTextPrompt(true);
35 | setPromptStep(2);
36 |
37 | setTimeout(() => {
38 | setIsShownTextPrompt(false);
39 | setIsShownTextPrompt(true);
40 | setPromptStep(3);
41 | }, 2000);
42 | },
43 | setScreenSharingSourceType,
44 | setIsDarkThemeHook,
45 | changeLanguage,
46 | setDialogErrorMessage,
47 | setIsErrorDialogOpen
48 | );
49 |
50 | const _peer = new PeerConnection(
51 | getRoomIDOfCurrentBrowserWindow(),
52 | setUrl,
53 | new Crypto(),
54 | new VideoAutoQualityOptimizer(),
55 | UIHandler
56 | );
57 |
58 | setPeer(_peer);
59 |
60 | setTimeout(() => {
61 | setIsShownTextPrompt(true);
62 | }, 100);
63 | }
64 | };
65 | };
66 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/handleDisplayingLoadingSharingIconLoop.spec.ts:
--------------------------------------------------------------------------------
1 | import handleDisplayingLoadingSharingIconLoop from './handleDisplayingLoadingSharingIconLoop';
2 | import LoadingSharingIconEnum from './LoadingSharingIconEnum';
3 |
4 | jest.useFakeTimers();
5 |
6 | describe('handleDisplayingLoadingSharingIconLoop callback', () => {
7 | let testParams: handleDisplayingLoadingSharingIconLoopParams;
8 |
9 | beforeEach(() => {
10 | testParams = {
11 | promptStep: 3,
12 | url: undefined,
13 | setIsShownLoadingSharingIcon: jest.fn(),
14 | setLoadingSharingIconType: jest.fn(),
15 | loadingSharingIconType: LoadingSharingIconEnum.DESKTOP,
16 | isShownLoadingSharingIcon: false,
17 | } as handleDisplayingLoadingSharingIconLoopParams
18 | });
19 |
20 | afterEach(() => {
21 | jest.clearAllMocks();
22 | jest.restoreAllMocks();
23 | });
24 |
25 | describe('when it was called with promptStep 3 and url undefined', () => {
26 | it('it should start loop, the ui should change with interval, showing and hiding icons', () => {
27 | const callback = handleDisplayingLoadingSharingIconLoop(testParams);
28 | callback();
29 |
30 | // imitation of infinite loop
31 | jest.advanceTimersByTime(2000);
32 |
33 | expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(true);
34 | jest.clearAllMocks();
35 |
36 | jest.advanceTimersByTime(2000);
37 |
38 | expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(false);
39 | expect(testParams.setLoadingSharingIconType).toBeCalledWith(LoadingSharingIconEnum.APPLICATION);
40 | jest.clearAllMocks();
41 |
42 | jest.advanceTimersByTime(2000);
43 |
44 | expect(testParams.setIsShownLoadingSharingIcon).toBeCalledWith(true);
45 | expect(testParams.setLoadingSharingIconType).toBeCalledWith(LoadingSharingIconEnum.DESKTOP);
46 | jest.clearAllMocks();
47 |
48 | // ... by this time we are sure, this function works!
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/handleDisplayingLoadingSharingIconLoop.ts:
--------------------------------------------------------------------------------
1 | import LoadingSharingIconEnum from './LoadingSharingIconEnum';
2 |
3 | export default (params: handleDisplayingLoadingSharingIconLoopParams) => {
4 | const {
5 | promptStep,
6 | url,
7 | setIsShownLoadingSharingIcon,
8 | loadingSharingIconType,
9 | isShownLoadingSharingIcon,
10 | setLoadingSharingIconType,
11 | } = params;
12 | return () => {
13 | let interval: NodeJS.Timeout;
14 | if (promptStep === 3 && url === undefined) {
15 | setIsShownLoadingSharingIcon(true);
16 |
17 | let currentIcon = loadingSharingIconType;
18 | let isShownIcon = isShownLoadingSharingIcon;
19 | let isShownWithFadingUIEffect = false;
20 | interval = setInterval(() => {
21 | isShownIcon = !isShownIcon;
22 | setIsShownLoadingSharingIcon(isShownIcon);
23 | if (isShownWithFadingUIEffect) {
24 | currentIcon =
25 | currentIcon === LoadingSharingIconEnum.DESKTOP
26 | ? LoadingSharingIconEnum.APPLICATION
27 | : LoadingSharingIconEnum.DESKTOP;
28 | setLoadingSharingIconType(currentIcon);
29 | isShownWithFadingUIEffect = false;
30 | } else {
31 | isShownWithFadingUIEffect = true;
32 | }
33 | }, 1500);
34 | }
35 |
36 | return () => {
37 | clearInterval(interval);
38 | };
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/handleNoConnectionTimeout.ts:
--------------------------------------------------------------------------------
1 | import { DUMMY_MY_DEVICE_DETAILS } from "../../constants/appConstants";
2 |
3 | export default (
4 | myDeviceDetails: DeviceDetails,
5 | setIsErrorDialogOpen: (_: boolean) => void
6 | ) => {
7 | return () => {
8 | const timeout = setTimeout(() => {
9 | if (myDeviceDetails === DUMMY_MY_DEVICE_DETAILS) {
10 | setIsErrorDialogOpen(true);
11 | }
12 | }, 10000);
13 | return () => {
14 | clearTimeout(timeout);
15 | };
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/handleRemoveDanglingReactRevealContainer.ts:
--------------------------------------------------------------------------------
1 | export default (
2 | url: undefined | MediaStream
3 | ) => {
4 | return () => {
5 | if (url !== undefined) {
6 | setTimeout(() => {
7 | // @ts-ignore
8 | document.querySelector('.container > .react-reveal').style.display =
9 | 'none';
10 | }, 1000);
11 | }
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/handleSetVideoQuality.ts:
--------------------------------------------------------------------------------
1 | import PeerConnection from "../../features/PeerConnection";
2 | import { VideoQuality } from "../../features/VideoAutoQualityOptimizer/VideoQualityEnum";
3 |
4 | export default (
5 | videoQuality: VideoQuality,
6 | peer: PeerConnection | undefined
7 | ) => {
8 | return () => {
9 | if (!peer) return;
10 | if (!peer.isStreamStarted) return;
11 | peer.setVideoQuality(videoQuality);
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/app/client/src/containers/MainView/index.css:
--------------------------------------------------------------------------------
1 | @media (prefers-reduced-motion: no-preference) {
2 | .App-logo {
3 | animation: App-logo-spin infinite 20s linear;
4 | }
5 |
6 | #pulsing-circle-1 {
7 | animation: pulse1 infinite 6s linear;
8 | }
9 |
10 | #pulsing-circle-2 {
11 | animation: pulse2 infinite 6s linear;
12 | }
13 |
14 | .pulse-3-once {
15 | animation: pulse3twice 2 750ms linear;
16 | }
17 | }
18 |
19 | @keyframes pulse1 {
20 | 0% {
21 | transform: scale(0.95);
22 | box-shadow: 0 0 0 0 rgba(72, 175, 240, 0.7);
23 | }
24 |
25 | 80% {
26 | transform: scale(1);
27 | box-shadow: 0 0 0 15px rgba(72, 175, 240, 0.4);
28 | }
29 |
30 | 100% {
31 | transform: scale(0.95);
32 | box-shadow: 0 0 0 0 rgba(72, 175, 240, 0);
33 | }
34 | }
35 |
36 | @keyframes pulse2 {
37 | 100% {
38 | transform: scale(0.95);
39 | box-shadow: 0 0 0 0 rgba(72, 175, 240, 0);
40 | }
41 |
42 | 80% {
43 | transform: scale(1);
44 | box-shadow: 0 0 0 33px rgba(72, 175, 240, 0.3);
45 | }
46 |
47 | 0% {
48 | transform: scale(0.95);
49 | box-shadow: 0 0 0 0 rgba(72, 175, 240, 1);
50 | }
51 | }
52 |
53 | @keyframes pulse3twice {
54 | 0% {
55 | box-shadow: 0 0 0 0 rgba(21, 179, 113, 0.7);
56 | }
57 |
58 | 50% {
59 | box-shadow: 0 0 0 30px rgba(21, 179, 113, 0.3);
60 | }
61 |
62 | 100% {
63 | box-shadow: 0 0 0 0 rgba(21, 179, 113, 0);
64 | }
65 | }
66 |
67 |
68 | .container > .react-reveal {
69 | overflow: hidden;
70 | }
71 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/NullUser.ts:
--------------------------------------------------------------------------------
1 | export default { username: '', publicKey: '', privateKey: '' };
2 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/PartnerPeerUser.d.ts:
--------------------------------------------------------------------------------
1 | interface PartnerPeerUser {
2 | username: string;
3 | publicKey: string;
4 | }
5 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/PeerConnection.d.ts:
--------------------------------------------------------------------------------
1 | type PeerConnection = import('.').default;
2 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/PeerConnectionUIHandler.ts:
--------------------------------------------------------------------------------
1 | import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
2 |
3 | export default class PeerConnectionUIHandler {
4 | isDarkTheme: boolean;
5 |
6 | setMyDeviceDetails: (details: DeviceDetails) => void;
7 |
8 | hostAllowedToConnectCallback: () => void;
9 |
10 | setScreenSharingSourceTypeCallback: (s: ScreenSharingSourceType) => void;
11 |
12 | setIsDarkThemeCallback: (val: boolean) => void;
13 |
14 | setAppLanguageCallback: (newLang: string) => void;
15 |
16 | setDialogErrorMessageCallback: (message: ErrorMessage) => void;
17 |
18 | setIsErrorDialogOpen: (val: boolean) => void;
19 |
20 | errorDialogMessage = ErrorMessage.UNKNOWN_ERROR;
21 |
22 | constructor(
23 | isDarkTheme: boolean,
24 | setMyDeviceDetails: (details: DeviceDetails) => void,
25 | hostAllowedToConnectCallback: () => void,
26 | setScreenSharingSourceTypeCallback: (s: ScreenSharingSourceType) => void,
27 | setIsDarkThemeCallback: (val: boolean) => void,
28 | setAppLanguageCallback: (newLang: string) => void,
29 | setDialogErrorMessageCallback: (message: ErrorMessage) => void,
30 | setIsErrorDialogOpen: (val: boolean) => void
31 | ) {
32 | this.isDarkTheme = isDarkTheme;
33 | this.hostAllowedToConnectCallback = hostAllowedToConnectCallback;
34 | this.setMyDeviceDetails = setMyDeviceDetails;
35 | this.setScreenSharingSourceTypeCallback = setScreenSharingSourceTypeCallback;
36 | this.setIsDarkThemeCallback = setIsDarkThemeCallback;
37 | this.setAppLanguageCallback = setAppLanguageCallback;
38 | this.setDialogErrorMessageCallback = setDialogErrorMessageCallback;
39 | this.setIsErrorDialogOpen = setIsErrorDialogOpen;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/ReceiveEncryptedMessagePayload.d.ts:
--------------------------------------------------------------------------------
1 | interface ReceiveEncryptedMessagePayload {
2 | fromSocketID: string;
3 | payload: string;
4 | signature: string;
5 | iv: string;
6 | keys: { sessionKey: string; signingKey: string }[];
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/ScreenSharingSourceEnum.ts:
--------------------------------------------------------------------------------
1 | enum ScreenSharingSource {
2 | WINDOW = 'window',
3 | SCREEN = 'screen',
4 | }
5 |
6 | export default ScreenSharingSource;
7 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/errors/PeerConnectionPartnerIsNotDefinedError.ts:
--------------------------------------------------------------------------------
1 | export default class PeerConnectionPartnerIsNotDefinedError extends Error {
2 | constructor() {
3 | super('partner should be defined!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, PeerConnectionPartnerIsNotDefinedError.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/errors/PeerConnectionPeerIsNullError.ts:
--------------------------------------------------------------------------------
1 | export default class PeerConnectionPeerIsNullError extends Error {
2 | constructor() {
3 | super('peer of PeerConnection should not be null!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, PeerConnectionPeerIsNullError.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/errors/PeerConnectionSocketNotDefined.ts:
--------------------------------------------------------------------------------
1 | export default class PeerConnectionSocketNotDefined extends Error {
2 | constructor() {
3 | super('socket should be defined!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, PeerConnectionSocketNotDefined.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/errors/PeerConnectionUserIsNotDefinedError.ts:
--------------------------------------------------------------------------------
1 | export default class PeerConnectionUserIsNotDefinedError extends Error {
2 | constructor() {
3 | super('user should be defined!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, PeerConnectionUserIsNotDefinedError.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/mocks/INPUTtestWindowNavigatorUserAgent.ts:
--------------------------------------------------------------------------------
1 | export const INPUTtestWindowNavigatorUserAgent =
2 | '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';
3 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/mocks/OUTPUTDeviceDetailsFromUAParsed.ts:
--------------------------------------------------------------------------------
1 | export const OUTPUTDeviceDetailsFromUAParsed: DeviceDetails = {
2 | myBrowser: 'Chrome 87.0.4280.88',
3 | myDeviceType: 'computer',
4 | myIP: '123.123.123.123',
5 | myOS: 'Mac OS 10.15.6',
6 | };
7 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/peerConnectionReceiveEncryptedMessage.ts:
--------------------------------------------------------------------------------
1 | import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
2 | import { process as processMessage } from '../../utils/message';
3 | import NullUser from './NullUser';
4 | import PeerConnectionUserIsNotDefinedError from './errors/PeerConnectionUserIsNotDefinedError';
5 | import setAndShowErrorDialogMessage from './setAndShowErrorDialogMessage';
6 |
7 | export default async (
8 | peerConnection: PeerConnection,
9 | payload: ReceiveEncryptedMessagePayload
10 | ) => {
11 | if (peerConnection.user === NullUser) {
12 | throw new PeerConnectionUserIsNotDefinedError();
13 | }
14 | const message = (await processMessage(
15 | payload,
16 | peerConnection.user.privateKey
17 | )) as any;
18 | if (message.type === 'CALL_USER') {
19 | peerConnection.peer?.signal(message.payload.signalData);
20 | }
21 | if (message.type === 'DENY_TO_CONNECT') {
22 | setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DENY_TO_CONNECT);
23 | }
24 | if (message.type === 'DISCONNECT_BY_HOST_MACHINE_USER') {
25 | setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DISCONNECTED);
26 | }
27 | if (message.type === 'ALLOWED_TO_CONNECT') {
28 | peerConnection.UIHandler.hostAllowedToConnectCallback();
29 | }
30 | if (message.type === 'APP_THEME') {
31 | if (peerConnection.UIHandler.isDarkTheme !== message.payload.value) {
32 | peerConnection.UIHandler.setIsDarkThemeCallback(message.payload.value);
33 | peerConnection.UIHandler.isDarkTheme = message.payload.value;
34 | }
35 | }
36 | if (message.type === 'APP_LANGUAGE') {
37 | peerConnection.UIHandler.setAppLanguageCallback(message.payload.value);
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/setAndShowErrorDialogMessage.ts:
--------------------------------------------------------------------------------
1 | import { ErrorMessage } from '../../components/ErrorDialog/ErrorMessageEnum';
2 |
3 | export default (peerConnection: PeerConnection, errorMessage: ErrorMessage) => {
4 | if (
5 | peerConnection.UIHandler.errorDialogMessage === ErrorMessage.UNKNOWN_ERROR
6 | ) {
7 | peerConnection.UIHandler.setDialogErrorMessageCallback(errorMessage);
8 | peerConnection.UIHandler.setIsErrorDialogOpen(true);
9 | peerConnection.UIHandler.errorDialogMessage = errorMessage;
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/setSdpMediaBitrate.spec.ts:
--------------------------------------------------------------------------------
1 | import { INPUTtestSdpMediaBitrate } from './mocks/INPUTvideo500000testSdpMediaBitrate';
2 | import { OUTPUTtestSdpMediaBitrate } from './mocks/OUTPUTvideo500000testSdpMediaBitrate';
3 | import setSdpMediaBitrate from './setSdpMediaBitrate';
4 |
5 | describe('setSdpMediaBitrate', () => {
6 | describe('when setSdpMediaBitrate is called with sdp input', () => {
7 | it('should produce a result that should match with test sdp string', () => {
8 | const result = setSdpMediaBitrate(
9 | INPUTtestSdpMediaBitrate,
10 | 'video',
11 | 500000
12 | );
13 |
14 | expect(result).toMatch(OUTPUTtestSdpMediaBitrate);
15 | });
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/setSdpMediaBitrate.ts:
--------------------------------------------------------------------------------
1 | export default (sdp: string, mediaType: string, bitrate: number) => {
2 | const sdpLines = sdp.split('\n');
3 | let mediaLineIndex = -1;
4 | const mediaLine = `m=${mediaType}`;
5 | let bitrateLineIndex = -1;
6 | const bitrateLine = `b=AS:${bitrate}`;
7 | mediaLineIndex = sdpLines.findIndex((line) => line.startsWith(mediaLine));
8 |
9 | // If we find a line matching “m={mediaType}”
10 | if (mediaLineIndex && mediaLineIndex < sdpLines.length) {
11 | // Skip the media line
12 | bitrateLineIndex = mediaLineIndex + 1;
13 |
14 | // Skip both i=* and c=* lines (bandwidths limiters have to come afterwards)
15 | while (
16 | sdpLines[bitrateLineIndex].startsWith('i=') ||
17 | sdpLines[bitrateLineIndex].startsWith('c=')
18 | ) {
19 | bitrateLineIndex += 1;
20 | }
21 |
22 | if (sdpLines[bitrateLineIndex].startsWith('b=')) {
23 | // If the next line is a b=* line, replace it with our new bandwidth
24 | sdpLines[bitrateLineIndex] = bitrateLine;
25 | } else {
26 | // Otherwise insert a new bitrate line.
27 | sdpLines.splice(bitrateLineIndex, 0, bitrateLine);
28 | }
29 | }
30 |
31 | // Then return the updated sdp content as a string
32 | return sdpLines.join('\n');
33 | };
34 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/simplePeerDataMessages.ts:
--------------------------------------------------------------------------------
1 | export function prepareDataMessageToChangeQuality(q: number) {
2 | return `
3 | {
4 | "type": "set_video_quality",
5 | "payload": {
6 | "value": ${q}
7 | }
8 | }
9 | `;
10 | }
11 |
12 | export function prepareDataMessageToGetSharingSourceType(){
13 | return `
14 | {
15 | "type": "get_sharing_source_type",
16 | "payload": {
17 | }
18 | }
19 | `;
20 | }
21 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/startSocketConnectedCheckingLoop/index.spec.ts:
--------------------------------------------------------------------------------
1 | import PeerConnection from '..';
2 | import VideoAutoQualityOptimizer from '../../VideoAutoQualityOptimizer';
3 | import Crypto from '../../../utils/crypto';
4 | import startSocketConnectedCheckingLoop from '.';
5 | import setAndShowErrorDialogMessage from '../setAndShowErrorDialogMessage';
6 | import PeerConnectionUIHandler from '../PeerConnectionUIHandler';
7 |
8 | jest.useFakeTimers();
9 |
10 | jest.mock('../setAndShowErrorDialogMessage');
11 |
12 | describe('startSocketConnectedCheckingLoop', () => {
13 | let peerConnection: PeerConnection;
14 |
15 | beforeEach(() => {
16 | jest.clearAllMocks();
17 |
18 | peerConnection = new PeerConnection(
19 | '123',
20 | jest.fn(),
21 | new Crypto(),
22 | new VideoAutoQualityOptimizer(),
23 | new PeerConnectionUIHandler(
24 | true,
25 | jest.fn(),
26 | jest.fn(),
27 | jest.fn(),
28 | jest.fn(),
29 | jest.fn(),
30 | jest.fn(),
31 | jest.fn()
32 | )
33 | );
34 |
35 | peerConnection.socket = { connected: true };
36 | });
37 |
38 | afterEach(() => {
39 | jest.restoreAllMocks();
40 | jest.clearAllMocks();
41 | });
42 |
43 | describe('when interval passed and socket is connected', () => {
44 | it('should NOT call setAndShowErrorDialogMessage', () => {
45 | startSocketConnectedCheckingLoop(peerConnection);
46 | jest.advanceTimersByTime(3000);
47 |
48 | expect(setAndShowErrorDialogMessage).not.toBeCalled();
49 | });
50 | });
51 |
52 | describe('when interval passed and socket is NOT connected', () => {
53 | it('should call setAndShowErrorDialogMessage', () => {
54 | peerConnection.socket.connected = false;
55 |
56 | startSocketConnectedCheckingLoop(peerConnection);
57 | jest.advanceTimersByTime(3000);
58 |
59 | expect(setAndShowErrorDialogMessage).toBeCalled();
60 | });
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/app/client/src/features/PeerConnection/startSocketConnectedCheckingLoop/index.ts:
--------------------------------------------------------------------------------
1 | import PeerConnection from '..';
2 | import { ErrorMessage } from '../../../components/ErrorDialog/ErrorMessageEnum';
3 | import setAndShowErrorDialogMessage from '../setAndShowErrorDialogMessage';
4 |
5 | export default (peerConnection: PeerConnection) => {
6 | setInterval(() => {
7 | if (!peerConnection.socket.connected) {
8 | setAndShowErrorDialogMessage(peerConnection, ErrorMessage.DENY_TO_CONNECT);
9 | }
10 | }, 2000);
11 | };
12 |
--------------------------------------------------------------------------------
/app/client/src/features/VideoAutoQualityOptimizer/VideoQualityEnum.ts:
--------------------------------------------------------------------------------
1 | export enum VideoQuality {
2 | Q_AUTO = 'Auto',
3 | Q_25_PERCENT = '25%',
4 | Q_40_PERCENT = '40%',
5 | Q_60_PERCENT = '60%',
6 | Q_80_PERCENT = '80%',
7 | Q_100_PERCENT = '100%',
8 | }
9 |
--------------------------------------------------------------------------------
/app/client/src/features/VideoAutoQualityOptimizer/errors/CanvasNotDefinedError.ts:
--------------------------------------------------------------------------------
1 | export default class CanvasNotDefinedError extends Error {
2 | constructor() {
3 | super('internal variable of canvas DOM element should be defined!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, CanvasNotDefinedError.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/VideoAutoQualityOptimizer/errors/ImageDataIsUndefinedError.ts:
--------------------------------------------------------------------------------
1 | export default class ImageDataIsUndefinedError extends Error {
2 | constructor() {
3 | super('imageData retrieved is undefined!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, ImageDataIsUndefinedError.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/features/VideoAutoQualityOptimizer/errors/VideoDimensionsAreWrongError.ts:
--------------------------------------------------------------------------------
1 | export default class VideoDimensionsAreWrongError extends Error {
2 | constructor() {
3 | super(
4 | 'video dimensions are wrong, neither width nor height can be zero!'
5 | );
6 | // Set the prototype explicitly.
7 | Object.setPrototypeOf(this, VideoDimensionsAreWrongError.prototype);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/client/src/features/VideoAutoQualityOptimizer/errors/VideoNotDefinedError.ts:
--------------------------------------------------------------------------------
1 | export default class VideoNotDefinedError extends Error {
2 | constructor() {
3 | super('internal variable of video DOM element should be defined!');
4 | // Set the prototype explicitly.
5 | Object.setPrototypeOf(this, VideoNotDefinedError.prototype);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/client/src/images/deskreen_logo_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/client/src/images/deskreen_logo_128x128.png
--------------------------------------------------------------------------------
/app/client/src/images/fullscreen_24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/client/src/images/fullscreen_exit-24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/client/src/images/red_heart_2764_twemoji_120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/client/src/images/red_heart_2764_twemoji_120x120.png
--------------------------------------------------------------------------------
/app/client/src/index.css:
--------------------------------------------------------------------------------
1 | @import "~normalize.css";
2 | @import "~@blueprintjs/core/lib/css/blueprint.css";
3 | @import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | code {
15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
16 | monospace;
17 | }
18 |
--------------------------------------------------------------------------------
/app/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import './polyfills';
2 | import React, { Suspense } from 'react';
3 | import './config/i18n';
4 | import ReactDOM from 'react-dom';
5 | import './index.css';
6 | import App from './App';
7 | import * as serviceWorker from './serviceWorker';
8 | import { AppContextProvider } from './providers/AppContextProvider';
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 | ,
18 | document.getElementById('root')
19 | );
20 |
21 | // If you want your app to work offline and load faster, you can change
22 | // unregister() to register() below. Note this comes with some pitfalls.
23 | // Learn more about service workers: https://bit.ly/CRA-PWA
24 | serviceWorker.unregister();
25 |
--------------------------------------------------------------------------------
/app/client/src/polyfills.tsx:
--------------------------------------------------------------------------------
1 | // recommended by create-react-app
2 | import 'react-app-polyfill/ie9';
3 | import 'react-app-polyfill/ie11';
4 | import 'react-app-polyfill/stable';
5 | // probably is better to load polyfills on demand,
6 | // browser tests are required to detect which polyfills are needed
7 | import 'core-js';
8 |
--------------------------------------------------------------------------------
/app/client/src/providers/AppContextProvider/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { Classes } from '@blueprintjs/core';
3 | import React, { useState } from 'react';
4 | import {
5 | DARK_UI_BACKGROUND,
6 | LIGHT_UI_BACKGROUND,
7 | } from '../../constants/styleConstants';
8 |
9 | interface AppContextInterface {
10 | isDarkTheme: boolean;
11 | setIsDarkThemeHook: (val: boolean) => void;
12 | }
13 |
14 | const defaultAppContextValue = {
15 | isDarkTheme: false,
16 | setIsDarkThemeHook: () => {},
17 | };
18 |
19 | export const AppContext = React.createContext(
20 | defaultAppContextValue
21 | );
22 |
23 | export const AppContextProvider: React.FC = ({ children }) => {
24 | const [isDarkTheme, setIsDarkTheme] = useState(false);
25 | const [appLanguage, setAppLanguage] = useState('en');
26 |
27 | const setIsDarkThemeHook = (val: boolean) => {
28 | document.body.classList.toggle(Classes.DARK);
29 |
30 | document.body.style.backgroundColor = val
31 | ? DARK_UI_BACKGROUND
32 | : LIGHT_UI_BACKGROUND;
33 | setIsDarkTheme(val);
34 | };
35 |
36 | const setAppLanguageHook = (newLang: string) => {
37 | setAppLanguage(newLang);
38 | };
39 |
40 | const value = {
41 | isDarkTheme,
42 | setIsDarkThemeHook,
43 | appLanguage,
44 | setAppLanguageHook,
45 | };
46 |
47 | return {children};
48 | };
49 |
--------------------------------------------------------------------------------
/app/client/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | const ConnectionIconEnum = import('./containers/MainView/ConnectionIconEnum').default;
4 | const LoadingSharingIconEnum = import('./containers/MainView/LoadingSharingIconEnum').default;
5 | const ScreenSharingSourceEnum = import('./features/PeerConnection/ScreenSharingSourceEnum').default;
6 |
7 | type ConnectionIconType = ConnectionIconEnum.FEED | ConnectionIconEnum.FEED_SUBSCRIBED;
8 | type LoadingSharingIconType = LoadingSharingIconEnum.DESKTOP | LoadingSharingIconEnum.APPLICATION;
9 | type ScreenSharingSourceType =
10 | | ScreenSharingSourceEnum.SCREEN
11 | | ScreenSharingSourceEnum.WINDOW;
12 | type CreatePeerConnectionUseEffectParams = {
13 | isDarkTheme: boolean;
14 | peer: undefined | PeerConnection;
15 | setIsDarkThemeHook: (_: boolean) => void;
16 | setMyDeviceDetails: (_: DeviceDetails) => void;
17 | setConnectionIconType: (_: ConnectionIconType) => void;
18 | setIsShownTextPrompt: (_: boolean) => void;
19 | setPromptStep: (_: number) => void;
20 | setScreenSharingSourceType: (_: ScreenSharingSourceType) => void;
21 | setDialogErrorMessage: (_: ErrorMessage) => void;
22 | setIsErrorDialogOpen: (_: boolean) => void;
23 | setUrl: (_: MediaStream) => void;
24 | setPeer: (_: PeerConnection) => void;
25 | };
26 | type handleDisplayingLoadingSharingIconLoopParams = {
27 | promptStep: number;
28 | url: undefined | MediaStream;
29 | setIsShownLoadingSharingIcon: (_: boolean) => void;
30 | loadingSharingIconType: LoadingSharingIconType;
31 | isShownLoadingSharingIcon: boolean;
32 | setLoadingSharingIconType: (_: LoadingSharingIconType) => void;
33 | };
34 |
--------------------------------------------------------------------------------
/app/client/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/app/client/src/utils/ProcessedMessage.d.ts:
--------------------------------------------------------------------------------
1 | type CallUserMessageWithPayload = {
2 | type: 'CALL_USER';
3 | payload: {
4 | signalData: string;
5 | };
6 | };
7 |
8 | type DeviceDetailsMessageWithPayload = {
9 | type: 'DEVICE_DETAILS';
10 | payload: {
11 | socketID: string;
12 | deviceType: { type: string };
13 | os: { name: string; version: string };
14 | browser: { name: string; version: string; major: string };
15 | deviceScreenWidth: number;
16 | deviceScreenHeight: number;
17 | };
18 | };
19 |
20 | type DenyToConnectMessageWithPayload = {
21 | type: 'DENY_TO_CONNECT';
22 | payload: {};
23 | };
24 |
25 | type DisconnectByHostMachineUserMessageWithPayload = {
26 | type: 'DISCONNECT_BY_HOST_MACHINE_USER';
27 | payload: {};
28 | };
29 |
30 | type AllowedToConnectMessageWithPayload = {
31 | type: 'ALLOWED_TO_CONNECT';
32 | payload: {};
33 | };
34 |
35 | type AppThemeMessageWithPayload = {
36 | type: 'APP_THEME';
37 | payload: {
38 | value: boolean,
39 | };
40 | };
41 |
42 | type AppLanguageMessageWithPayload = {
43 | type: 'APP_LANGUAGE';
44 | payload: {
45 | value: string,
46 | };
47 | };
48 |
49 |
50 | type ProcessedMessage =
51 | | CallUserMessageWithPayload
52 | | DeviceDetailsMessageWithPayload
53 | | DenyToConnectMessageWithPayload
54 | | DisconnectByHostMachineUserMessageWithPayload
55 | | AllowedToConnectMessageWithPayload
56 | | AppThemeMessageWithPayload
57 | | AppLanguageMessageWithPayload;
58 |
--------------------------------------------------------------------------------
/app/client/src/utils/areWeTestingWithJest.ts:
--------------------------------------------------------------------------------
1 | export default () => {
2 | return process.env.JEST_WORKER_ID !== undefined;
3 | };
4 |
--------------------------------------------------------------------------------
/app/client/src/utils/getRoomIDOfCurrentBrowserWindow.ts:
--------------------------------------------------------------------------------
1 | export default () => {
2 | return encodeURI(window.location.pathname.replace('/', ''));
3 | }
4 |
--------------------------------------------------------------------------------
/app/client/src/utils/message.spec.ts:
--------------------------------------------------------------------------------
1 | import { prepare, process } from './message';
2 | import getTestPublickKeyPem from './mocks/getTestPublicKeyPem';
3 | import getTestPrivateKeyPem from './mocks/getTestPrivateKeyPem';
4 |
5 | interface TestPayload {
6 | text: string;
7 | }
8 |
9 | describe('message.ts tests for proper encryption and decryption functionality', () => {
10 | const TEST_TEXT = 'some test text here';
11 | const TEST_TEXT_AS_URL = 'some%20test%20text%20here';
12 | const testPayloadToEncrypt = {
13 | payload: {
14 | text: TEST_TEXT,
15 | },
16 | };
17 |
18 | const testUser = {
19 | username: 'testUsername',
20 | id: 'testId',
21 | };
22 |
23 | const testPartner = {
24 | publicKey: getTestPublickKeyPem(),
25 | };
26 | it('should create encrypted payload with prepare() method', async () => {
27 | const encryptedPayload = await prepare(
28 | testPayloadToEncrypt,
29 | testUser,
30 | testPartner
31 | );
32 |
33 | expect(encryptedPayload.toSend.payload).not.toContain(TEST_TEXT);
34 | expect(encryptedPayload.toSend.payload).not.toContain(TEST_TEXT_AS_URL);
35 | });
36 |
37 | it('should decrypt encrypted payload with process() method', async () => {
38 | const encryptedPayload = await prepare(
39 | testPayloadToEncrypt,
40 | testUser,
41 | testPartner
42 | );
43 |
44 | const decryptedPayload = await process(
45 | encryptedPayload.toSend,
46 | getTestPrivateKeyPem()
47 | );
48 |
49 | expect(
50 | ((decryptedPayload.payload as unknown) as TestPayload).text
51 | ).toContain(TEST_TEXT_AS_URL);
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/app/client/src/utils/mocks/getTestPrivateKeyPem.ts:
--------------------------------------------------------------------------------
1 | export default function getTestPrivateKeyPem() {
2 | return `-----BEGIN RSA PRIVATE KEY-----
3 | MIIEogIBAAKCAQEAoQ8+vdVAerEyaxGaffzrTTv+sgpQ3xToBFYkxrT6f+zP4MqV
4 | nTIy2+UMlEGGryMFgJWyurqv+NyoVf7vIFOxQcxJko1BL/oIt/e5YZ/fMIw9AhgP
5 | 0A0oZyFKNbGesCY3zMdpZqPE0brzfhjr/lu5VzioZI9vxocOSSc3+S8w1EXqujgO
6 | X3PWXgZrD6Y//Oo+8BgNQta/e5PUyc9yNchU/W3ddzBdE0iUXTdQt7yFvzy4vTbS
7 | ywUxoNNJnD6dUJ6dZ4c24VGvrvhJ5mzNqQjibAhtkPFbvhn0e0BHl0BZ876fLHGg
8 | zpHgmmiWbKwBYl1ydv8fW9W3r5nQoW0RThYJjwIDAQABAoIBAHqOrUGrGsvCNwl+
9 | db9VTICTHLbCXtPChuN14bpLUSszOuRlhAAAiO8HltDiI+j1j2RPhZfOI8YNsxLt
10 | UW2aAhJ9r6aTUn19mFDVcv20uBOrQ2lqge3hdVM049GD/asxCdkMDUqLaGPoDQ1x
11 | TXNavOiANrN+6qF5eAd2joNRw6hi7osyWqfpgM9y58kiYYazKHKlI/er19JsD2t5
12 | hgRiFti+oN9nqhhVzJI/GYU9JugXnB/Z0uFvqOyt/3YDHPpOC07WYpfA3yzshBkW
13 | TiiMp1eJNX/sRh4JHzRI9/MQe9ajlAS7T74HwCkclzaS8ZY6t+dfVZ097/ug77eQ
14 | NwY4DAECgYEA0hWv7hpookKKcl0srfxbx+FbtBhzXqz+Tfy3H5do6uXRc0aUwQ25
15 | PZLs7UAT3zjt+4aYu1xgkp3cUx5FNr/FAPYLtjXw1/6fmt3HX8820SpCMjIzS1bU
16 | UwJvmAut70YA+n2eKwO1qJNeLcpNCDebbIXwj0lKDh5HQDyoswuz7I8CgYEAxEKV
17 | N4Ma6GqZ0hgdQlO71oSuftHUI80Iz+Riorrp4AA8hp5A3V6Fd9QDy8PYLz1kD9pM
18 | uWdCLqaxdOVDtLVnjNVZH1SI+wSBDA/0OtdvPaONL1JKRA21kol049f0M8wkulAw
19 | 6lfi2aQwZ3KWyEAfDy5iPdVROnQWqUcLmxO0kwECgYAGpSr4dBtlLoekkG/uXPIm
20 | Q2mcK73Se9RbcSf1tttZusVCSTRBWwbF/NTDuGgogmt8rkg8fPKNELM8adO0pKI9
21 | oorCS7h/jI1N38ADttE8EoMfhVj8BBYZPhV7kLsCu4siYUDUiXyAhZDQD/sZzHB9
22 | IUt3rNDL24dTb9fCOheJ3wKBgGRgpY7Z2DZM51VkDfrxdp3WCKVGTljtMfeaGLSg
23 | IqP1mv9DC2vtPxg1cKeUCArJPFc7UIh2/ot7qEFgTQusyERojgePJew0tofj1QcP
24 | To7ZConMbb12wYosEYPC3NxtKc+82ffRcW3dIwCVw/axjPEnyQlVBBGAdGKpuo7b
25 | Oj0BAoGAMs2zuAJfJB4xE1UZdLXk9uHWPAzgUNDzHuS5mMWuloGvKX+19yck6o2J
26 | ZA/iq6GwOgvD2y9MC0mV4WkmwZpVXwPVpZD8GVQXZf2xZgh/q2e3IObAd80NLnaG
27 | v86qN98DRTh9L+47Nsaf1J5vDDaAfH2Ir8UgAQ5ZMEFDm7P0hUQ=
28 | -----END RSA PRIVATE KEY-----`;
29 | }
30 |
--------------------------------------------------------------------------------
/app/client/src/utils/mocks/getTestPublicKeyPem.ts:
--------------------------------------------------------------------------------
1 | export default function getTestPublicKeyPem() {
2 | return `-----BEGIN PUBLIC KEY-----
3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoQ8+vdVAerEyaxGaffzr
4 | TTv+sgpQ3xToBFYkxrT6f+zP4MqVnTIy2+UMlEGGryMFgJWyurqv+NyoVf7vIFOx
5 | QcxJko1BL/oIt/e5YZ/fMIw9AhgP0A0oZyFKNbGesCY3zMdpZqPE0brzfhjr/lu5
6 | VzioZI9vxocOSSc3+S8w1EXqujgOX3PWXgZrD6Y//Oo+8BgNQta/e5PUyc9yNchU
7 | /W3ddzBdE0iUXTdQt7yFvzy4vTbSywUxoNNJnD6dUJ6dZ4c24VGvrvhJ5mzNqQji
8 | bAhtkPFbvhn0e0BHl0BZ876fLHGgzpHgmmiWbKwBYl1ydv8fW9W3r5nQoW0RThYJ
9 | jwIDAQAB
10 | -----END PUBLIC KEY-----`;
11 | }
12 |
--------------------------------------------------------------------------------
/app/client/src/utils/socket.ts:
--------------------------------------------------------------------------------
1 | import socketIO from 'socket.io-client';
2 | import generateUrl from '../api/generator';
3 |
4 | let socket: SocketIOClient.Socket;
5 |
6 | export const connect = (roomId: string) => {
7 | socket = socketIO(generateUrl(), {
8 | query: {
9 | roomId,
10 | },
11 | forceNew: true,
12 | });
13 | return socket;
14 | };
15 |
16 | export const getSocket = () => socket;
17 |
--------------------------------------------------------------------------------
/app/client/src/utils/userAgentParserHelpers.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { UAParser } from 'ua-parser-js';
3 |
4 | export function getOSFromUAParser(uaParser: UAParser) {
5 | const osFromUAParser = uaParser.getResult().os;
6 |
7 | return `${osFromUAParser.name ? osFromUAParser.name : ''} ${
8 | osFromUAParser.version ? osFromUAParser.version : ''
9 | }`;
10 | }
11 |
12 | export function getDeviceTypeFromUAParser(uaParser: UAParser) {
13 | const deviceTypeFromUAParser = uaParser.getResult().device;
14 |
15 | return deviceTypeFromUAParser.type
16 | ? deviceTypeFromUAParser.type.toString()
17 | : 'computer'
18 | }
19 |
20 | export function getBrowserFromUAParser(uaParser: UAParser) {
21 | const browserFromUAParser = uaParser.getResult().browser;
22 |
23 | return `${browserFromUAParser.name ? browserFromUAParser.name : ''} ${
24 | browserFromUAParser.version ? browserFromUAParser.version : ''
25 | }`;
26 | }
27 |
--------------------------------------------------------------------------------
/app/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "react",
20 | "strict": true,
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/app/components/AllowConnectionForDeviceAlert.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Intent, Alert, H4 } from '@blueprintjs/core';
3 | import { useTranslation } from 'react-i18next';
4 | import DeviceInfoCallout from './DeviceInfoCallout';
5 | import isWithReactRevealAnimations from '../utils/isWithReactRevealAnimations';
6 |
7 | interface AllowConnectionForDeviceAlertProps {
8 | device: Device | null;
9 | isOpen: boolean;
10 | onCancel: () => void;
11 | onConfirm: () => void;
12 | }
13 |
14 | export default function AllowConnectionForDeviceAlert(
15 | props: AllowConnectionForDeviceAlertProps
16 | ) {
17 | const { t } = useTranslation();
18 | const { device, isOpen, onCancel, onConfirm } = props;
19 | const denyText = t('Deny');
20 | const allowText = t('Allow');
21 |
22 | return (
23 |
37 | {t('Someone is trying to connect, do you allow?')}
38 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/app/components/CloseOverlayButton.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/destructuring-assignment */
2 | import React from 'react';
3 | import { createStyles, makeStyles } from '@material-ui/core/styles';
4 | import { Button, Icon } from '@blueprintjs/core';
5 |
6 | class CloseOverlayButtonProps {
7 | onClick = () => {};
8 |
9 | style? = {};
10 |
11 | isDefaultStyles? = false;
12 |
13 | className? = '';
14 | }
15 |
16 | const useStyles = makeStyles(() =>
17 | createStyles({
18 | closeButton: {
19 | position: 'relative',
20 | width: '40px',
21 | height: '40px',
22 | left: 'calc(100% - 55px)',
23 | borderRadius: '100px',
24 | zIndex: 9999,
25 | },
26 | })
27 | );
28 |
29 | const CloseOverlayButton: React.FC = (
30 | props: CloseOverlayButtonProps
31 | ) => {
32 | const { className, isDefaultStyles, style, onClick } = props;
33 | const classes = useStyles();
34 | return (
35 |
43 | );
44 | };
45 |
46 | export default CloseOverlayButton;
47 |
--------------------------------------------------------------------------------
/app/components/LanguageSelector/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react';
2 | import { ipcRenderer } from 'electron';
3 | import { HTMLSelect } from '@blueprintjs/core';
4 | import i18n from 'i18next';
5 | import { SettingsContext } from '../../containers/SettingsProvider';
6 | import i18n_client, {
7 | getLangFullNameToLangISOKeyMap,
8 | getLangISOKeyToLangFullNameMap,
9 | } from '../../configs/i18next.config.client';
10 | import { IpcEvents } from '../../main/IpcEvents.enum';
11 |
12 | export default function LanguageSelector() {
13 | const { setCurrentLanguageHook } = useContext(SettingsContext);
14 |
15 | const [languagesList, setLanguagesList] = useState([] as string[]);
16 |
17 | useEffect(() => {
18 | const tmp: string[] = [];
19 | getLangFullNameToLangISOKeyMap().forEach((_, key) => {
20 | tmp.push(key);
21 | });
22 | setLanguagesList(tmp);
23 | setCurrentLanguageHook(i18n_client.language);
24 | }, [setCurrentLanguageHook]);
25 |
26 | const onChangeLanguageHTMLSelectHandler = (
27 | event: React.ChangeEvent
28 | ) => {
29 | if (
30 | event.currentTarget &&
31 | getLangFullNameToLangISOKeyMap().has(event.currentTarget.value)
32 | ) {
33 | const newLang =
34 | getLangFullNameToLangISOKeyMap().get(event.currentTarget.value) ||
35 | 'English';
36 | i18n.changeLanguage(newLang);
37 | ipcRenderer.invoke(IpcEvents.AppLanguageChanged, newLang);
38 | }
39 | };
40 |
41 | return (
42 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/app/components/SettingsOverlay/SettingRowLabelAndInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Row, Col } from 'react-flexbox-grid';
3 | import { Icon, Text } from '@blueprintjs/core';
4 | import { createStyles, makeStyles } from '@material-ui/core/styles';
5 | import { SettingsContext } from '../../containers/SettingsProvider';
6 |
7 | const useStylesWithTheme = (isDarkTheme: boolean) =>
8 | makeStyles(() =>
9 | createStyles({
10 | oneSettingRow: {
11 | color: isDarkTheme ? '#CED9E0 !important' : '#5C7080 !important',
12 | fontSize: '18px',
13 | fontWeight: 900,
14 | },
15 | settingRowIcon: {
16 | margin: '10px',
17 | color: isDarkTheme ? '#BFCCD6' : '#8A9BA8',
18 | },
19 | })
20 | );
21 |
22 | interface SettingRowLabelAndInput {
23 | icon: string;
24 | label: string;
25 | input: React.ReactNode;
26 | }
27 |
28 | export default function SettingRowLabelAndInput(
29 | props: SettingRowLabelAndInput
30 | ) {
31 | const { icon, label, input } = props;
32 | const { isDarkTheme } = useContext(SettingsContext);
33 |
34 | const getClassesCallback = useStylesWithTheme(isDarkTheme);
35 |
36 | return (
37 |
38 |
39 |
40 |
41 |
48 |
49 |
50 | {label}
51 |
52 |
53 |
54 |
55 | {input}
56 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/app/components/StepperPanel/ColorlibConnector.tsx:
--------------------------------------------------------------------------------
1 | import { withStyles } from '@material-ui/core/styles';
2 | import StepConnector from '@material-ui/core/StepConnector';
3 |
4 | const ColorlibConnector = withStyles({
5 | alternativeLabel: {
6 | top: 43,
7 | },
8 | active: {
9 | '& $line': {
10 | backgroundImage:
11 | 'linear-gradient( 95deg, #3DCC91 0%, #15B371 50%, #FFB366 100%)',
12 | },
13 | },
14 | completed: {
15 | '& $line': {
16 | backgroundImage:
17 | 'linear-gradient( 95deg, #3DCC91 0%, #15B371 50%, #3DCC91 100%)',
18 | },
19 | },
20 | line: {
21 | height: 2,
22 | border: 0,
23 | backgroundColor: '#CED9E0',
24 | borderRadius: 1,
25 | },
26 | })(StepConnector);
27 |
28 | export default ColorlibConnector;
29 |
--------------------------------------------------------------------------------
/app/components/StepperPanel/ColorlibStepIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import { StepIconProps } from '@material-ui/core/StepIcon';
5 | import { Icon } from '@blueprintjs/core';
6 |
7 | export interface StepIconPropsDeskreen extends StepIconProps {
8 | isEntireScreenSelected: boolean;
9 | isApplicationWindowSelected: boolean;
10 | }
11 |
12 | const useColorlibStepIconStyles = makeStyles({
13 | root: {
14 | backgroundColor: '#BFCCD6',
15 | zIndex: 1,
16 | color: '#5C7080',
17 | width: 65,
18 | height: 65,
19 | display: 'flex',
20 | borderRadius: '50%',
21 | justifyContent: 'center',
22 | alignItems: 'center',
23 | },
24 | active: {
25 | backgroundImage:
26 | 'linear-gradient( 136deg, #FFB366 0%, #F29D49 50%, #A66321 100%)',
27 | boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)',
28 | },
29 | completed: {
30 | backgroundImage:
31 | 'linear-gradient( 136deg, #3DCC91 0%, #15B371 50%, #0E5A8A 100%)',
32 | },
33 | stepContent: {},
34 | });
35 |
36 | const getDesktopOrAppIcon = (isDesktop: boolean, color: string) => {
37 | if (isDesktop) {
38 | return ;
39 | }
40 | return ;
41 | };
42 |
43 | export default function ColorlibStepIcon(props: StepIconPropsDeskreen) {
44 | const { icon } = props;
45 | const classes = useColorlibStepIconStyles();
46 | const { active, completed, isEntireScreenSelected } = props;
47 |
48 | const color = active || completed ? '#fff' : '#5C7080';
49 |
50 | const icons: { [index: string]: React.ReactElement } = {
51 | 1: completed ? (
52 |
53 | ) : (
54 |
55 | ),
56 | 2: completed ? (
57 | getDesktopOrAppIcon(isEntireScreenSelected, color)
58 | ) : (
59 |
60 | ),
61 | 3: completed ? (
62 |
63 | ) : (
64 |
65 | ),
66 | };
67 |
68 | return (
69 |
75 | {icons[String(icon)]}
76 |
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/app/components/StepsOfStepper/ChooseAppOrScreeenStep.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row, Col } from 'react-flexbox-grid';
3 | import ShareEntireScreenOrAppWindowControlGroup from '../ShareAppOrScreenControlGroup';
4 |
5 | interface ChooseAppOrScreeenStepProps {
6 | handleNextEntireScreen: () => void;
7 | handleNextApplicationWindow: () => void;
8 | }
9 |
10 | const ChooseAppOrScreeenStep: React.FC = (
11 | props: ChooseAppOrScreeenStepProps
12 | ) => {
13 | const { handleNextEntireScreen, handleNextApplicationWindow } = props;
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default ChooseAppOrScreeenStep;
36 |
--------------------------------------------------------------------------------
/app/components/StepsOfStepper/ChooseAppOrScreenOverlay/PreviewGridList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ipcRenderer } from 'electron';
3 | import { Row, Col } from 'react-flexbox-grid';
4 | import SharingSourcePreviewCard from '../../SharingSourcePreviewCard';
5 | import { IpcEvents } from '../../../main/IpcEvents.enum';
6 |
7 | class PreviewGridListProps {
8 | viewSharingIds: string[] = [];
9 |
10 | isEntireScreen = true;
11 |
12 | handleNextEntireScreen = () => {};
13 |
14 | handleNextApplicationWindow = () => {};
15 | }
16 |
17 | export default function PreviewGridList(props: PreviewGridListProps) {
18 | const {
19 | viewSharingIds,
20 | isEntireScreen,
21 | handleNextEntireScreen,
22 | handleNextApplicationWindow,
23 | } = props;
24 |
25 | return (
26 |
33 | {viewSharingIds.map((id) => {
34 | return (
35 |
36 | {
40 | ipcRenderer.invoke(IpcEvents.SetDesktopCapturerSourceId, id);
41 | if (isEntireScreen) {
42 | handleNextEntireScreen();
43 | } else {
44 | handleNextApplicationWindow();
45 | }
46 | }}
47 | />
48 |
49 | );
50 | })}
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/app/components/StepsOfStepper/ChooseAppOrScreenOverlay/ViewSharingObject.d.ts:
--------------------------------------------------------------------------------
1 | type ViewSharingObject = { thumbnailUrl: string; name: string };
2 |
--------------------------------------------------------------------------------
/app/components/StepsOfStepper/ConfirmStep.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { ipcRenderer } from 'electron';
3 | import { Text } from '@blueprintjs/core';
4 | import { Row, Col } from 'react-flexbox-grid';
5 | import { useTranslation } from 'react-i18next';
6 | import SharingSourcePreviewCard from '../SharingSourcePreviewCard';
7 | import DeviceInfoCallout from '../DeviceInfoCallout';
8 | import { IpcEvents } from '../../main/IpcEvents.enum';
9 |
10 | interface ConfirmStepProps {
11 | device: Device | null;
12 | }
13 |
14 | export default function ConfirmStep(props: ConfirmStepProps) {
15 | const { t } = useTranslation();
16 | const { device } = props;
17 | const [
18 | waitingForConnectionSharingSessionSourceId,
19 | setWaitingForConnectionSharingSessionSourceId,
20 | ] = useState();
21 |
22 | useEffect(() => {
23 | ipcRenderer
24 | .invoke(IpcEvents.GetWaitingForConnectionSharingSessionSourceId)
25 | // eslint-disable-next-line promise/always-return
26 | .then((id) => {
27 | setWaitingForConnectionSharingSessionSourceId(id);
28 | })
29 | .catch((e) => console.error(e));
30 | }, []);
31 |
32 | return (
33 |
34 |
35 |
36 | {t('Check if all is OK and click Confirm')}
37 |
38 |
39 |
40 |
41 |
48 |
49 |
50 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/app/components/StepsOfStepper/ScanQRStep.spec.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | /* eslint-disable @typescript-eslint/ban-types */
3 | /* eslint-disable @typescript-eslint/ban-ts-comment */
4 | import React from 'react';
5 | import Enzyme from 'enzyme';
6 | import Adapter from 'enzyme-adapter-react-16';
7 | import ScanQRStep from './ScanQRStep';
8 |
9 | Enzyme.configure({ adapter: new Adapter() });
10 | jest.useFakeTimers();
11 |
12 | const bp3QRCodeDialogRootSelector = '#bp3-qr-code-dialog-root';
13 | const magnifyQRCodeButtonSelector = '#magnify-qr-code-button';
14 | const qrCodeDialogInnerSelector = '#qr-code-dialog-inner';
15 |
16 | describe('', () => {
17 | let wrapper = Enzyme.shallow();
18 |
19 | beforeEach(() => {
20 | wrapper = Enzyme.shallow();
21 | });
22 |
23 | afterEach(() => {
24 | jest.clearAllMocks();
25 | });
26 |
27 | describe('when user clicks on magnify QR code button', () => {
28 | it('should set "QR Code Dialog Root" isOpen property to "true"', () => {
29 | // @ts-ignore
30 | wrapper.find(magnifyQRCodeButtonSelector).props().onClick();
31 | // @ts-ignore
32 | expect(wrapper.find(bp3QRCodeDialogRootSelector).props().isOpen).toBe(
33 | true
34 | );
35 | });
36 | });
37 |
38 | describe(`when magnified QR code dialog is opened,
39 | and when user closes magnified QR code dialog`, () => {
40 | it('should set "QR Code Dialog Root" isOpen property to "false"', () => {
41 | // @ts-ignore
42 | wrapper.find(magnifyQRCodeButtonSelector).props().onClick();
43 | // @ts-ignore
44 | wrapper.find(bp3QRCodeDialogRootSelector).props().onClose();
45 | // @ts-ignore
46 | expect(wrapper.find(bp3QRCodeDialogRootSelector).props().isOpen).toBe(
47 | false
48 | );
49 | });
50 | });
51 |
52 | describe(`when magnified QR code dialog is opened,
53 | and when user clicks on qr code dialog inner`, () => {
54 | it('should set "QR Code Dialog Root" isOpen property to "false"', () => {
55 | // @ts-ignore
56 | wrapper.find(magnifyQRCodeButtonSelector).props().onClick();
57 | // @ts-ignore
58 | wrapper.find(qrCodeDialogInnerSelector).props().onClick();
59 | // @ts-ignore
60 | expect(wrapper.find(bp3QRCodeDialogRootSelector).props().isOpen).toBe(
61 | false
62 | );
63 | });
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/app/components/ToggleThemeBtnGroup/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useCallback } from 'react';
2 | import { Button, Classes, ControlGroup } from '@blueprintjs/core';
3 | import { ipcRenderer } from 'electron';
4 | import { SettingsContext } from '../../containers/SettingsProvider';
5 | import { IpcEvents } from '../../main/IpcEvents.enum';
6 |
7 | export default function ToggleThemeBtnGroup() {
8 | const { isDarkTheme, setIsDarkThemeHook } = useContext(SettingsContext);
9 |
10 | const handleToggleDarkTheme = useCallback(() => {
11 | if (!isDarkTheme) {
12 | document.body.classList.toggle(Classes.DARK);
13 | setIsDarkThemeHook(true);
14 | }
15 | ipcRenderer.invoke(IpcEvents.NotifyAllSessionsWithAppThemeChanged);
16 | }, [isDarkTheme, setIsDarkThemeHook]);
17 |
18 | const handleToggleLightTheme = useCallback(() => {
19 | if (isDarkTheme) {
20 | document.body.classList.toggle(Classes.DARK);
21 | setIsDarkThemeHook(false);
22 | }
23 | ipcRenderer.invoke(IpcEvents.NotifyAllSessionsWithAppThemeChanged);
24 | }, [isDarkTheme, setIsDarkThemeHook]);
25 |
26 | return (
27 |
28 |
35 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/app/components/css.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.scss' {
2 | const content: { [className: string]: string };
3 | export default content;
4 | }
5 |
6 | declare module '*.css' {
7 | const content: { [className: string]: string };
8 | export default content;
9 | }
10 |
--------------------------------------------------------------------------------
/app/configs/app.lang.config.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | export default {
4 | fallbackLng: 'en',
5 | namespace: 'translation',
6 | languages: [
7 | 'ru',
8 | 'en',
9 | 'es',
10 | 'ua',
11 | 'zh_CN',
12 | 'zh_TW',
13 | 'da',
14 | 'de',
15 | 'fi',
16 | 'ko',
17 | 'it',
18 | 'ja',
19 | 'nl',
20 | 'fr',
21 | 'sv',
22 | ],
23 | langISOKeyToLangFullNameMap: {
24 | en: 'English',
25 | es: 'Español',
26 | ru: 'Русский',
27 | ua: 'Українська',
28 | da: 'Dansk',
29 | de: 'Deutsch',
30 | fi: 'Suomi',
31 | it: 'Italiano',
32 | nl: 'Nederlands',
33 | fr: 'Français',
34 | sv: 'Svenska',
35 | ko: '한국어',
36 | zh_CN: '简体中文',
37 | zh_TW: '繁體中文',
38 | ja: '日本語',
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/app/configs/i18next.config.client.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 |
3 | import {
4 | getLangFullNameToLangISOKeyMap,
5 | getLangISOKeyToLangFullNameMap,
6 | } from './i18next.config.client';
7 |
8 | jest.useFakeTimers();
9 |
10 | jest.mock('electron', () => {
11 | return {
12 | ipcRenderer: {
13 | invoke: jest.fn(),
14 | },
15 | };
16 | });
17 |
18 | describe('i18next.config.client tests', () => {
19 | beforeEach(() => {});
20 |
21 | afterEach(() => {
22 | jest.clearAllMocks();
23 | jest.restoreAllMocks();
24 | });
25 |
26 | describe('when getLangFullNameToLangISOKeyMap called', () => {
27 | it('should return proper key map', () => {
28 | // TODO: add more languages here manually when adding new languages in app!
29 | const expectedMap = new Map();
30 | expectedMap.set('English', 'en');
31 | expectedMap.set('Español', 'es');
32 | expectedMap.set('한국어', 'ko');
33 | expectedMap.set('Русский', 'ru');
34 | expectedMap.set('Українська', 'ua');
35 | expectedMap.set('简体中文', 'zh_CN');
36 | expectedMap.set('繁體中文', 'zh_TW');
37 | expectedMap.set('Dansk', 'da');
38 | expectedMap.set('Deutsch', 'de');
39 | expectedMap.set('Nederlands', 'nl');
40 | expectedMap.set('Svenska', 'sv');
41 |
42 | const res = getLangFullNameToLangISOKeyMap();
43 |
44 | expect(res).toEqual(expectedMap);
45 | });
46 | });
47 |
48 | describe('when getLangISOKeyToLangFullNameMap called', () => {
49 | it('should return proper key map', () => {
50 | // TODO: add more languages here manually when adding new languages in app!
51 | const expectedMap = new Map();
52 | expectedMap.set('en', 'English');
53 | expectedMap.set('es', 'Español');
54 | expectedMap.set('한국어', 'ko');
55 | expectedMap.set('ru', 'Русский');
56 | expectedMap.set('ua', 'Українська');
57 | expectedMap.set('zh_CN', '简体中文');
58 | expectedMap.set('zh_TW', '繁體中文');
59 | expectedMap.set('da', 'Dansk');
60 | expectedMap.set('de', 'Deutsch');
61 | expectedMap.set('nl', 'Nederlands');
62 | expectedMap.set('sv', 'Svenska');
63 |
64 | const res = getLangISOKeyToLangFullNameMap();
65 |
66 | expect(res).toEqual(expectedMap);
67 | });
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/app/configs/i18next.config.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 |
3 | import i18n from 'i18next';
4 | import i18nextBackend from 'i18next-node-fs-backend';
5 | import { join } from 'path';
6 | import config from './app.lang.config';
7 | import isProduction from '../utils/isProduction';
8 | import store from '../deskreen-electron-store';
9 | import { ElectronStoreKeys } from '../enums/ElectronStoreKeys.enum';
10 |
11 | const i18nextOptions = {
12 | fallbackLng: config.fallbackLng,
13 | lng: store.has(ElectronStoreKeys.AppLanguage)
14 | ? String(store.get(ElectronStoreKeys.AppLanguage))
15 | : 'en',
16 | ns: 'translation',
17 | defaultNS: 'translation',
18 | backend: {
19 | // path where resources get loaded from
20 | loadPath: isProduction()
21 | ? join(__dirname, 'locales/{{lng}}/{{ns}}.json')
22 | : join(__dirname, '../locales/{{lng}}/{{ns}}.json'),
23 | // path to post missing resources
24 | addPath: isProduction()
25 | ? join(__dirname, 'locales/{{lng}}/{{ns}}.json')
26 | : join(__dirname, '../locales/{{lng}}/{{ns}}.missing.json'),
27 | // jsonIndent to use when storing json files
28 | jsonIndent: 2,
29 | },
30 | interpolation: {
31 | escapeValue: false,
32 | },
33 | saveMissing: true,
34 | whitelist: config.languages,
35 | react: {
36 | wait: false,
37 | },
38 | };
39 | i18n.use(i18nextBackend);
40 |
41 | if (!i18n.isInitialized) {
42 | i18n.init(i18nextOptions);
43 | }
44 |
45 | export default i18n;
46 |
--------------------------------------------------------------------------------
/app/constants/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "HOME": "/",
3 | "COUNTER": "/counter"
4 | }
5 |
--------------------------------------------------------------------------------
/app/constants/test-devices.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "123414",
4 | "sharingSessionID": "14422424",
5 | "deviceOS": "Android",
6 | "deviceType": "Mobile",
7 | "deviceIP": "123.123.123.123"
8 | },
9 | {
10 | "id": "1123",
11 | "sharingSessionID": "43",
12 | "deviceOS": "IOS",
13 | "deviceType": "Mobile",
14 | "deviceIP": "124.124.124.124"
15 | },
16 | {
17 | "id": "2",
18 | "sharingSessionID": "22",
19 | "deviceOS": "Windows",
20 | "deviceType": "PS",
21 | "deviceIP": "255.255.124.124"
22 | },
23 | {
24 | "id": "33",
25 | "sharingSessionID": "22",
26 | "deviceOS": "Ubuntu",
27 | "deviceType": "PC",
28 | "deviceIP": "4.4.4.124"
29 | },
30 | {
31 | "id": "414",
32 | "sharingSessionID": "423",
33 | "deviceOS": "Blackberry",
34 | "deviceType": "Mobile",
35 | "deviceIP": "224.224.224.124"
36 | },
37 | {
38 | "id": "1133223",
39 | "sharingSessionID": "4133",
40 | "deviceOS": "IOS",
41 | "deviceType": "Mobile",
42 | "deviceIP": "24.24.24.24"
43 | },
44 | {
45 | "id": "1321123",
46 | "sharingSessionID": "44133",
47 | "deviceOS": "Android",
48 | "deviceType": "Mobile",
49 | "deviceIP": "14.14.14.14"
50 | },
51 | {
52 | "id": "993",
53 | "sharingSessionID": "91322",
54 | "deviceOS": "Debian",
55 | "deviceType": "PC",
56 | "deviceIP": "1.1.14.14"
57 | },
58 | {
59 | "id": "7757",
60 | "sharingSessionID": "1111",
61 | "deviceOS": "MacOS",
62 | "deviceType": "Mac",
63 | "deviceIP": "12.12.14.14"
64 | },
65 | {
66 | "id": "123332",
67 | "sharingSessionID": "323231",
68 | "deviceOS": "MacOS",
69 | "deviceType": "Mac",
70 | "deviceIP": "11.11.14.14"
71 | }
72 | ]
73 |
--------------------------------------------------------------------------------
/app/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | export default function App(props: Props) {
8 | const { children } = props;
9 | return <>{children}>;
10 | }
11 |
--------------------------------------------------------------------------------
/app/containers/HomePage.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/destructuring-assignment */
2 | /* eslint-disable react/jsx-props-no-spreading */
3 | /* eslint-disable @typescript-eslint/ban-ts-comment */
4 |
5 | import React, { useRef } from 'react';
6 | import { Classes } from '@blueprintjs/core';
7 | import { ToastProvider, DefaultToast } from 'react-toast-notifications';
8 |
9 | import TopPanel from '../components/TopPanel';
10 | import { LIGHT_UI_BACKGROUND } from './SettingsProvider';
11 | import DeskreenStepper from './DeskreenStepper';
12 |
13 | // @ts-ignore: it is ok here, be like js it is fine
14 | // eslint-disable-next-line react/prop-types
15 | export const CustomToastWithTheme = ({ children, ...props }) => {
16 | return (
17 |
29 | <>{children}>
30 |
31 | );
32 | };
33 |
34 | export default function HomePage() {
35 | const stepperRef = useRef();
36 |
37 | return (
38 |
43 |
44 |
45 |
46 | {/* */}
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/app/containers/Root.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { FocusStyleManager } from '@blueprintjs/core';
3 | import { Provider } from 'react-redux';
4 |
5 | import { ConnectedRouter } from 'connected-react-router';
6 | import { hot } from 'react-hot-loader/root';
7 | import { History } from 'history';
8 | import { Store } from '../store';
9 | import Routes from '../Routes';
10 | import i18n from '../configs/i18next.config.client';
11 | import { SettingsProvider } from './SettingsProvider';
12 |
13 | FocusStyleManager.onlyShowFocusOnTabs();
14 |
15 | type Props = {
16 | store: Store;
17 | history: History;
18 | };
19 |
20 | const Root = ({ store, history }: Props) => {
21 | const [, setAppLanguage] = useState('');
22 |
23 | useEffect(() => {
24 | i18n.on('languageChanged', (lng) => {
25 | setAppLanguage(lng);
26 | });
27 | }, []);
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default hot(Root);
41 |
--------------------------------------------------------------------------------
/app/containers/SettingsProvider.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import React, { useState, useEffect } from 'react';
3 | import { Classes } from '@blueprintjs/core';
4 | import { ipcRenderer } from 'electron';
5 | import { IpcEvents } from '../main/IpcEvents.enum';
6 |
7 | // TODO: move to 'constants' tsx file ?
8 | export const LIGHT_UI_BACKGROUND = 'rgba(240, 248, 250, 1)';
9 | export const DARK_UI_BACKGROUND = '#293742';
10 |
11 | interface SettingsContextInterface {
12 | isDarkTheme: boolean;
13 | currentLanguage: string;
14 | setIsDarkThemeHook: (val: boolean) => void;
15 | setCurrentLanguageHook: (newLang: string) => void;
16 | }
17 |
18 | const defaultSettingsContextValue = {
19 | isDarkTheme: false,
20 | setIsDarkThemeHook: () => {},
21 | setCurrentLanguageHook: () => {},
22 | currentLanguage: 'en',
23 | };
24 |
25 | export const SettingsContext = React.createContext(
26 | defaultSettingsContextValue
27 | );
28 |
29 | export const SettingsProvider: React.FC = ({ children }) => {
30 | const [isDarkTheme, setIsDarkTheme] = useState(false);
31 | const [currentLanguage, setCurrentLanguage] = useState('en');
32 |
33 | const loadDarkThemeFromSettings = async () => {
34 | const isDarkAppTheme = await ipcRenderer.invoke(
35 | IpcEvents.GetIsAppDarkTheme
36 | );
37 |
38 | if (isDarkAppTheme) {
39 | document.body.classList.toggle(Classes.DARK);
40 | document.body.style.backgroundColor = LIGHT_UI_BACKGROUND;
41 | }
42 |
43 | setIsDarkTheme(isDarkAppTheme);
44 | };
45 |
46 | useEffect(() => {
47 | loadDarkThemeFromSettings();
48 | }, []);
49 |
50 | const setIsDarkThemeHook = (isAppDarkTheme: boolean) => {
51 | ipcRenderer.invoke(IpcEvents.SetIsAppDarkTheme, isAppDarkTheme);
52 | setIsDarkTheme(isAppDarkTheme);
53 | };
54 |
55 | const setCurrentLanguageHook = (newLang: string) => {
56 | setCurrentLanguage(newLang);
57 | };
58 |
59 | const value = {
60 | isDarkTheme,
61 | setIsDarkThemeHook,
62 | currentLanguage,
63 | setCurrentLanguageHook,
64 | };
65 |
66 | return (
67 |
68 | {children}
69 |
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/app/containers/__mocks__/electron-settings.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | hasSync: (name: string) => {
3 | if (name === 'appLanguage') {
4 | return true;
5 | }
6 | return false;
7 | },
8 | getSync: (name: string) => {
9 | if (name === 'appLanguage') {
10 | return 'en';
11 | }
12 | return '';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/app/containers/__mocks__/electron.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 |
3 | export const remote = {
4 | getGlobal: (name: string) => {
5 | if (name === 'appPath') {
6 | return __dirname;
7 | }
8 | return '';
9 | },
10 | };
11 | export const ipcRenderer = jest.fn();
12 |
--------------------------------------------------------------------------------
/app/containers/__mocks__/react-i18next.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 |
3 | import { ThirdPartyModule } from 'i18next';
4 |
5 | export const useTranslation = () => {
6 | return {
7 | t: (key: string) => {
8 | if (key === 'Language') {
9 | return 'Language';
10 | }
11 | return '';
12 | },
13 | };
14 | };
15 |
16 | export const initReactI18next: ThirdPartyModule = {
17 | type: '3rdParty',
18 | init: () => {},
19 | };
20 |
--------------------------------------------------------------------------------
/app/deskreen-electron-store.ts:
--------------------------------------------------------------------------------
1 | import ElectronStore from 'electron-store';
2 |
3 | const store = new ElectronStore();
4 |
5 | export default store;
6 |
--------------------------------------------------------------------------------
/app/enums/ElectronStoreKeys.enum.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export enum ElectronStoreKeys {
3 | AppLanguage = 'appLanguage',
4 | IsNotFirstTimeAppStart = 'isNotFirstTimeAppStart',
5 | IsAppDarkTheme = 'isAppDarkTheme',
6 | }
7 |
--------------------------------------------------------------------------------
/app/features/ConnectedDevicesService/Device.d.ts:
--------------------------------------------------------------------------------
1 | interface Device {
2 | id: string;
3 | sharingSessionID: string;
4 | deviceOS: string;
5 | deviceType: string;
6 | deviceIP: string;
7 | deviceBrowser: string;
8 | deviceScreenWidth: number;
9 | deviceScreenHeight: number;
10 | }
11 |
--------------------------------------------------------------------------------
/app/features/ConnectedDevicesService/index.ts:
--------------------------------------------------------------------------------
1 | export const nullDevice: Device = {
2 | id: '',
3 | sharingSessionID: '',
4 | deviceOS: '',
5 | deviceType: '',
6 | deviceIP: '',
7 | deviceBrowser: '',
8 | deviceScreenWidth: -1,
9 | deviceScreenHeight: -1,
10 | };
11 |
12 | class ConnectedDevices {
13 | devices: Device[] = [];
14 |
15 | pendingConnectionDevice: Device = nullDevice;
16 |
17 | resetPendingConnectionDevice() {
18 | this.pendingConnectionDevice = nullDevice;
19 | }
20 |
21 | getDevices() {
22 | return this.devices;
23 | }
24 |
25 | disconnectAllDevices() {
26 | this.devices = [] as Device[];
27 | }
28 |
29 | disconnectDeviceByID(deviceIDToRemove: string) {
30 | return new Promise((resolve) => {
31 | this.devices = this.devices.filter((d) => {
32 | return d.id !== deviceIDToRemove;
33 | });
34 | resolve(undefined);
35 | });
36 | }
37 |
38 | addDevice(device: Device) {
39 | this.devices.push(device);
40 | }
41 |
42 | setPendingConnectionDevice(device: Device) {
43 | this.pendingConnectionDevice = device;
44 | }
45 | }
46 |
47 | export default ConnectedDevices;
48 |
--------------------------------------------------------------------------------
/app/features/DesktopCapturerSourcesService/DesktopCapturerSourceType.ts:
--------------------------------------------------------------------------------
1 | enum DesktopCapturerSourceType {
2 | WINDOW = 'window',
3 | SCREEN = 'screen',
4 | }
5 |
6 | export default DesktopCapturerSourceType;
7 |
--------------------------------------------------------------------------------
/app/features/DesktopCapturerSourcesService/DesktopCapturerSourceWithType.d.ts:
--------------------------------------------------------------------------------
1 | interface DesktopCapturerSourceWithType {
2 | source: import('electron').DesktopCapturerSource;
3 | type: import('./DesktopCapturerSourceType').default;
4 | }
5 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/NullSimplePeer.ts:
--------------------------------------------------------------------------------
1 | import SimplePeer from 'simple-peer';
2 |
3 | const NullSimplePeer = new SimplePeer();
4 |
5 | export default NullSimplePeer;
6 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/NullUser.ts:
--------------------------------------------------------------------------------
1 | export default { username: '', publicKey: '', privateKey: '' };
2 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/PartnerPeerUser.d.ts:
--------------------------------------------------------------------------------
1 | interface PartnerPeerUser {
2 | username: string;
3 | publicKey: string;
4 | }
5 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/PeerConnection.d.ts:
--------------------------------------------------------------------------------
1 | // use import() to prevent cycle import!
2 | // From here: https://stackoverflow.com/questions/39040108/import-class-in-definition-file-d-ts
3 | type PeerConnection = import('.').default;
4 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/ReceiveEncryptedMessagePayload.d.ts:
--------------------------------------------------------------------------------
1 | interface ReceiveEncryptedMessagePayload {
2 | payload: string;
3 | fromSocketID: string;
4 | signature: string;
5 | iv: string;
6 | keys: { sessionKey: string; signingKey: string }[];
7 | }
8 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/SendEncryptedMessagePayload.d.ts:
--------------------------------------------------------------------------------
1 | interface SendEncryptedMessagePayload {
2 | type: string;
3 | payload: Record;
4 | }
5 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/createDesktopCapturerStream.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable promise/catch-or-return */
2 | import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
3 | import Logger from '../../utils/LoggerWithFilePrefix';
4 | import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
5 |
6 | const log = new Logger(__filename);
7 |
8 | export default async function createDesktopCapturerStream(
9 | peerConnection: PeerConnection,
10 | sourceID: string
11 | ) {
12 | try {
13 | if (process.env.RUN_MODE === 'test') return;
14 |
15 | if (sourceID.includes(DesktopCapturerSourceType.SCREEN)) {
16 | const stream = await getDesktopSourceStreamBySourceID(
17 | sourceID,
18 | peerConnection.sourceDisplaySize?.width,
19 | peerConnection.sourceDisplaySize?.height,
20 | 0.5,
21 | 1
22 | );
23 | peerConnection.localStream = stream;
24 | } else {
25 | // when source is app window
26 | const stream = await getDesktopSourceStreamBySourceID(sourceID);
27 | peerConnection.localStream = stream;
28 | }
29 | } catch (e) {
30 | log.error(e);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/getDesktopSourceStreamBySourceID.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
3 |
4 | jest.useFakeTimers();
5 |
6 | const TEST_SCREEN_SHARING_SOURCE_ID = 'screen:1234ad';
7 |
8 | describe('getDesktopSourceStreamBySourceID callback', () => {
9 | beforeEach(() => {
10 | // eslint-disable-next-line no-global-assign
11 | // @ts-ignore
12 | global.navigator.mediaDevices = { getUserMedia: jest.fn() };
13 | });
14 |
15 | afterEach(() => {
16 | jest.clearAllMocks();
17 | jest.restoreAllMocks();
18 | });
19 |
20 | describe('when getDesktopSourceStreamBySourceID called with default parameters', () => {
21 | it('should handle getUserMedia without width and height (APPLICATION WINDOW SHARING CASE)', () => {
22 | getDesktopSourceStreamBySourceID(TEST_SCREEN_SHARING_SOURCE_ID);
23 |
24 | expect(navigator.mediaDevices.getUserMedia).toBeCalledWith({
25 | audio: false,
26 | video: {
27 | mandatory: {
28 | chromeMediaSource: 'desktop',
29 | chromeMediaSourceId: TEST_SCREEN_SHARING_SOURCE_ID,
30 | minFrameRate: 15,
31 | maxFrameRate: 60,
32 | },
33 | },
34 | });
35 | });
36 | });
37 |
38 | describe('when getDesktopSourceStreamBySourceID called with width and height parameters (SCREEN SHARING CASE)', () => {
39 | it('should handle getUserMedia with width and height', () => {
40 | const TEST_WIDTH = 640;
41 | const TEST_HEIGHT = 480;
42 | getDesktopSourceStreamBySourceID(TEST_SCREEN_SHARING_SOURCE_ID, 640, 480);
43 |
44 | expect(navigator.mediaDevices.getUserMedia).toBeCalledWith({
45 | audio: false,
46 | video: {
47 | mandatory: {
48 | chromeMediaSource: 'desktop',
49 | chromeMediaSourceId: TEST_SCREEN_SHARING_SOURCE_ID,
50 |
51 | minWidth: TEST_WIDTH,
52 | maxWidth: TEST_WIDTH,
53 | minHeight: TEST_HEIGHT,
54 | maxHeight: TEST_HEIGHT,
55 |
56 | minFrameRate: 15,
57 | maxFrameRate: 60,
58 | },
59 | },
60 | });
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/getDesktopSourceStreamBySourceID.ts:
--------------------------------------------------------------------------------
1 | export default async (
2 | sourceID: string,
3 | width: number | null | undefined = undefined,
4 | height: number | null | undefined = undefined,
5 | minSizeMultiplier = 1,
6 | maxSizeMultiplier = 1,
7 | minFrameRate = 15,
8 | maxFrameRate = 60
9 | ) => {
10 | if (width && height) {
11 | return navigator.mediaDevices.getUserMedia({
12 | audio: false,
13 | video: {
14 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
15 | // @ts-ignore: fine here, mandatory does not exist, it's a problem with types
16 | mandatory: {
17 | chromeMediaSource: 'desktop',
18 | chromeMediaSourceId: sourceID,
19 |
20 | minWidth: width * minSizeMultiplier,
21 | maxWidth: width * maxSizeMultiplier,
22 | minHeight: height * minSizeMultiplier,
23 | maxHeight: height * maxSizeMultiplier,
24 |
25 | minFrameRate,
26 | maxFrameRate,
27 | },
28 | },
29 | });
30 | }
31 |
32 | return navigator.mediaDevices.getUserMedia({
33 | audio: false,
34 | video: {
35 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
36 | // @ts-ignore: fine here, mandatory does not exist, it's a problem with types
37 | mandatory: {
38 | chromeMediaSource: 'desktop',
39 | chromeMediaSourceId: sourceID,
40 | minFrameRate,
41 | maxFrameRate,
42 | },
43 | },
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleCreatePeer.ts:
--------------------------------------------------------------------------------
1 | import SimplePeer from 'simple-peer';
2 | import createDesktopCapturerStream from './createDesktopCapturerStream';
3 | import handlePeerOnData from './handlePeerOnData';
4 | // import setSdpMediaBitrate from './setSdpMediaBitrate';
5 | import Logger from '../../utils/LoggerWithFilePrefix';
6 | import NullSimplePeer from './NullSimplePeer';
7 | import simplePeerHandleSdpTransform from './simplePeerHandleSdpTransform';
8 |
9 | const log = new Logger(__filename);
10 |
11 | export default function handleCreatePeer(peerConnection: PeerConnection) {
12 | return new Promise((resolve, reject) => {
13 | createDesktopCapturerStream(
14 | peerConnection,
15 | peerConnection.desktopCapturerSourceID
16 | )
17 | .then(() => {
18 | // eslint-disable-next-line promise/always-return
19 | if (peerConnection.peer === NullSimplePeer) {
20 | peerConnection.peer = new SimplePeer({
21 | initiator: true,
22 | config: { iceServers: [] },
23 | sdpTransform: simplePeerHandleSdpTransform,
24 | });
25 | }
26 |
27 | // eslint-disable-next-line promise/always-return
28 | if (peerConnection.localStream !== null) {
29 | peerConnection.peer.addStream(peerConnection.localStream);
30 | }
31 |
32 | peerConnection.peer.on('signal', (data: string) => {
33 | // fired when simple peer and webrtc done preparation to start call on peerConnection machine
34 | peerConnection.signalsDataToCallUser.push(data);
35 | });
36 |
37 | peerConnection.peer.on('data', (data) => {
38 | handlePeerOnData(peerConnection, data);
39 | });
40 | resolve(undefined);
41 | })
42 | .catch((e) => {
43 | log.error(e);
44 | reject();
45 | });
46 | });
47 | }
48 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handlePeerOnData.ts:
--------------------------------------------------------------------------------
1 | import DesktopCapturerSourceType from '../DesktopCapturerSourcesService/DesktopCapturerSourceType';
2 | import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
3 | import prepareDataMessageToSendScreenSourceType from './prepareDataMessageToSendScreenSourceType';
4 |
5 | export default async function handlePeerOnData(
6 | peerConnection: PeerConnection,
7 | data: string
8 | ) {
9 | const dataJSON = JSON.parse(data);
10 |
11 | if (dataJSON.type === 'set_video_quality') {
12 | const maxVideoQualityMultiplier = dataJSON.payload.value;
13 | const minVideoQualityMultiplier =
14 | maxVideoQualityMultiplier === 1 ? 0.5 : maxVideoQualityMultiplier;
15 |
16 | if (
17 | !peerConnection.desktopCapturerSourceID.includes(
18 | DesktopCapturerSourceType.SCREEN
19 | )
20 | )
21 | return;
22 |
23 | const newStream = await getDesktopSourceStreamBySourceID(
24 | peerConnection.desktopCapturerSourceID,
25 | peerConnection.sourceDisplaySize?.width,
26 | peerConnection.sourceDisplaySize?.height,
27 | minVideoQualityMultiplier,
28 | maxVideoQualityMultiplier
29 | );
30 | const newVideoTrack = newStream.getVideoTracks()[0];
31 | const oldTrack = peerConnection.localStream?.getVideoTracks()[0];
32 |
33 | if (oldTrack && peerConnection.localStream) {
34 | peerConnection.peer.replaceTrack(
35 | oldTrack,
36 | newVideoTrack,
37 | peerConnection.localStream
38 | );
39 | oldTrack.stop();
40 | }
41 | }
42 |
43 | if (dataJSON.type === 'get_sharing_source_type') {
44 | const sourceType = peerConnection.desktopCapturerSourceID.includes(
45 | DesktopCapturerSourceType.SCREEN
46 | )
47 | ? DesktopCapturerSourceType.SCREEN
48 | : DesktopCapturerSourceType.WINDOW;
49 |
50 | peerConnection.peer.send(
51 | prepareDataMessageToSendScreenSourceType(sourceType)
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleRecieveEncryptedMessage.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import uuid from 'uuid';
3 | import { process as processMessage } from '../../utils/message';
4 | import { IpcEvents } from '../../main/IpcEvents.enum';
5 |
6 | export function handleDeviceIPMessage(
7 | deviceIP: string,
8 | peerConnection: PeerConnection,
9 | message: ProcessedMessage
10 | ) {
11 | if (message.type !== 'DEVICE_DETAILS') return;
12 | const device = {
13 | id: uuid.v4(),
14 | deviceIP,
15 | deviceType: message.payload.deviceType,
16 | deviceOS: message.payload.os,
17 | deviceBrowser: message.payload.browser,
18 | deviceScreenWidth: message.payload.deviceScreenWidth,
19 | deviceScreenHeight: message.payload.deviceScreenHeight,
20 | sharingSessionID: peerConnection.sharingSessionID,
21 | };
22 | peerConnection.partnerDeviceDetails = device;
23 | peerConnection.onDeviceConnectedCallback(device);
24 | }
25 |
26 | export default async function handleRecieveEncryptedMessage(
27 | peerConnection: PeerConnection,
28 | payload: ReceiveEncryptedMessagePayload
29 | ) {
30 | const message = await processMessage(payload, peerConnection.user.privateKey);
31 | if (message.type === 'CALL_ACCEPTED') {
32 | peerConnection.peer.signal(message.payload.signalData);
33 | }
34 | if (message.type === 'DEVICE_DETAILS') {
35 | peerConnection.socket.emit(
36 | 'GET_IP_BY_SOCKET_ID',
37 | payload.fromSocketID,
38 | (deviceIP: string) => {
39 | // TODO: need to add myIP in client message.payload.myIP, then if retrieved deviceIP and myIP from client don't match, we were spoofed, then we can interrupt connection immediately!
40 | handleDeviceIPMessage(deviceIP, peerConnection, message);
41 | }
42 | );
43 | }
44 | if (message.type === 'GET_APP_THEME') {
45 | const isDarkAppTheme = await ipcRenderer.invoke(
46 | IpcEvents.GetIsAppDarkTheme
47 | );
48 | peerConnection.sendEncryptedMessage({
49 | type: 'APP_THEME',
50 | payload: {
51 | value: isDarkAppTheme,
52 | },
53 | });
54 | }
55 | if (message.type === 'GET_APP_LANGUAGE') {
56 | const appLanguage = await ipcRenderer.invoke(IpcEvents.GetAppLanguage);
57 | peerConnection.sendEncryptedMessage({
58 | type: 'APP_LANGUAGE',
59 | payload: {
60 | value: appLanguage,
61 | },
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleSelfDestroy.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import { IpcEvents } from '../../main/IpcEvents.enum';
3 | import NullSimplePeer from './NullSimplePeer';
4 | import NullUser from './NullUser';
5 |
6 | export default function handleSelfDestroy(peerConnection: PeerConnection) {
7 | peerConnection.partner = NullUser;
8 | ipcRenderer.invoke(
9 | IpcEvents.DisconnectDeviceById,
10 | peerConnection.partnerDeviceDetails.id
11 | );
12 | if (peerConnection.peer !== NullSimplePeer) {
13 | peerConnection.peer.destroy();
14 | }
15 | if (peerConnection.localStream) {
16 | peerConnection.localStream.getTracks().forEach((track) => {
17 | track.stop();
18 | });
19 | peerConnection.localStream = null;
20 | }
21 | ipcRenderer.invoke(
22 | IpcEvents.DestroySharingSessionById,
23 | peerConnection.sharingSessionID
24 | );
25 | peerConnection.onDeviceConnectedCallback = () => {};
26 | peerConnection.isCallStarted = false;
27 | peerConnection.socket.disconnect();
28 |
29 | ipcRenderer.invoke(IpcEvents.UnmarkRoomIDAsTaken, peerConnection.roomID);
30 | }
31 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleSetDisplaySizeFromLocalStream.ts:
--------------------------------------------------------------------------------
1 | export default function setDisplaySizeFromLocalStream(
2 | peerConnection: PeerConnection
3 | ) {
4 | if (
5 | !peerConnection.localStream ||
6 | !peerConnection.localStream.getVideoTracks()[0]
7 | )
8 | return;
9 | if (!peerConnection.localStream.getVideoTracks()[0].getSettings().width)
10 | return;
11 | if (!peerConnection.localStream.getVideoTracks()[0].getSettings().height)
12 | return;
13 | peerConnection.sourceDisplaySize = {
14 | width: peerConnection.localStream.getVideoTracks()[0].getSettings().width
15 | ? (peerConnection.localStream.getVideoTracks()[0].getSettings()
16 | .width as number)
17 | : 640,
18 | height: peerConnection.localStream.getVideoTracks()[0].getSettings().height
19 | ? (peerConnection.localStream.getVideoTracks()[0].getSettings()
20 | .height as number)
21 | : 480,
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleSocket.ts:
--------------------------------------------------------------------------------
1 | import handleSocketUserEnter from './handleSocketUserEnter';
2 | import handleSocketUserExit from './handleSocketUserExit';
3 |
4 | export default function handleSocket(peerConnection: PeerConnection) {
5 | peerConnection.socket.removeAllListeners();
6 |
7 | peerConnection.socket.on('disconnect', () => {
8 | peerConnection.selfDestroy();
9 | });
10 |
11 | peerConnection.socket.on('connect', () => {
12 | // peerConnection.emitUserEnter();
13 | });
14 |
15 | peerConnection.socket.on(
16 | 'USER_ENTER',
17 | (payload: { users: PartnerPeerUser[] }) => {
18 | handleSocketUserEnter(peerConnection, payload);
19 | }
20 | );
21 |
22 | peerConnection.socket.on('USER_EXIT', () => {
23 | handleSocketUserExit(peerConnection);
24 | });
25 |
26 | peerConnection.socket.on(
27 | 'ENCRYPTED_MESSAGE',
28 | (payload: ReceiveEncryptedMessagePayload) => {
29 | peerConnection.receiveEncryptedMessage(payload);
30 | }
31 | );
32 |
33 | peerConnection.socket.on('USER_DISCONNECT', () => {
34 | peerConnection.toggleLockRoom(false);
35 | });
36 |
37 | // socketConnection.on('TOGGLE_LOCK_ROOM', payload => {
38 | // peerConnection.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload);
39 | // });
40 |
41 | // socketConnection.on('ROOM_LOCKED', payload => {
42 | // peerConnection.props.openModal('Room Locked');
43 | // });
44 | }
45 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleSocketUserEnter.ts:
--------------------------------------------------------------------------------
1 | export default (
2 | peerConnection: PeerConnection,
3 | payload: { users: PartnerPeerUser[] }
4 | ) => {
5 | const filteredPartner = payload.users.filter((user: PartnerPeerUser) => {
6 | return peerConnection.user.publicKey !== user.publicKey;
7 | });
8 |
9 | if (filteredPartner[0] === undefined) return;
10 |
11 | [peerConnection.partner] = filteredPartner;
12 |
13 | if (peerConnection.partner.publicKey !== '') {
14 | // peerConnection.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
15 | // peerConnection.isSocketRoomLocked = true;
16 | peerConnection.toggleLockRoom(true);
17 | peerConnection.emitUserEnter();
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleSocketUserExit.spec.ts:
--------------------------------------------------------------------------------
1 | // /* eslint-disable @typescript-eslint/no-explicit-any */
2 | // /* eslint-disable @typescript-eslint/ban-ts-comment */
3 | // import {
4 | // TEST_ROOM_ID,
5 | // TEST_SHARING_SESSION_ID,
6 | // TEST_USER,
7 | // } from './mocks/testVars';
8 | // import PeerConnection from '.';
9 | // import RoomIDService from '../../server/RoomIDService';
10 | // import ConnectedDevicesService from '../ConnectedDevicesService';
11 | // import SharingSessionService from '../SharingSessionService';
12 | // import handleSocketUserExit from './handleSocketUserExit';
13 | // import DesktopCapturerSourcesService from '../DesktopCapturerSourcesService';
14 |
15 | // jest.useFakeTimers();
16 |
17 | // jest.mock('simple-peer');
18 |
19 | // describe('handleSocketUserExit callback', () => {
20 | // let peerConnection: PeerConnection;
21 |
22 | // beforeEach(() => {
23 | // // @ts-ignore
24 | // peerConnection = new PeerConnection(
25 | // TEST_ROOM_ID,
26 | // TEST_SHARING_SESSION_ID,
27 | // TEST_USER,
28 | // {} as RoomIDService,
29 | // {} as ConnectedDevicesService,
30 | // {} as SharingSessionService,
31 | // {} as DesktopCapturerSourcesService
32 | // );
33 | // peerConnection.socket = ({
34 | // on: jest.fn(),
35 | // removeAllListeners: jest.fn(),
36 | // } as unknown) as SocketIOClient.Socket;
37 | // });
38 |
39 | // afterEach(() => {
40 | // jest.clearAllMocks();
41 | // jest.restoreAllMocks();
42 | // });
43 |
44 | // describe('when handleSocketUserExit called properly', () => {
45 | // it('should call toggleLockRoom and selfDestroy', () => {
46 | // peerConnection.isSocketRoomLocked = true;
47 | // peerConnection.isCallStarted = true;
48 | // peerConnection.toggleLockRoom = jest.fn();
49 | // peerConnection.selfDestroy = jest.fn();
50 |
51 | // handleSocketUserExit(peerConnection);
52 |
53 | // expect(peerConnection.toggleLockRoom).toBeCalledWith(false);
54 | // expect(peerConnection.selfDestroy).toBeCalled();
55 | // });
56 | // });
57 | // });
58 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/handleSocketUserExit.ts:
--------------------------------------------------------------------------------
1 | export default (peerConnection: PeerConnection) => {
2 | if (peerConnection.isSocketRoomLocked) {
3 | peerConnection.toggleLockRoom(false);
4 | if (peerConnection.isCallStarted) {
5 | // TODO: display toast device is gone ....
6 | peerConnection.selfDestroy();
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/mocks/testVars.ts:
--------------------------------------------------------------------------------
1 | export const TEST_ROOM_ID = '1';
2 | export const TEST_SHARING_SESSION_ID = '123';
3 | export const TEST_USER = {
4 | username: 'asd',
5 | publicKey: 'nvxm,zv',
6 | privateKey: '14234',
7 | };
8 | export const TEST_APP_THEME = false;
9 | export const TEST_APP_LANGUAGE = 'en';
10 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/prepareDataMessageToSendScreenSourceType.ts:
--------------------------------------------------------------------------------
1 | export default (s: string) => {
2 | return `
3 | {
4 | "type": "screen_sharing_source_type",
5 | "payload": {
6 | "value": "${s}"
7 | }
8 | }
9 | `;
10 | };
11 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/setSdpMediaBitrate.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { INPUTtestSdpMediaBitrate } from './mocks/INPUTvideo500000testSdpMediaBitrate';
3 | import { OUTPUTtestSdpMediaBitrate } from './mocks/OUTPUTvideo500000testSdpMediaBitrate';
4 | import setSdpMediaBitrate from './setSdpMediaBitrate';
5 |
6 | describe('when setSdpMediaBitrate is called', () => {
7 | it('should return proper sdp media bitrate', () => {
8 | const res = setSdpMediaBitrate(INPUTtestSdpMediaBitrate, 'video', 500000);
9 |
10 | expect(res).toEqual(OUTPUTtestSdpMediaBitrate);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/setSdpMediaBitrate.ts:
--------------------------------------------------------------------------------
1 | export default (sdp: string, mediaType: string, bitrate: number) => {
2 | const sdpLines = sdp.split('\n');
3 | let mediaLineIndex = -1;
4 | const mediaLine = `m=${mediaType}`;
5 | let bitrateLineIndex = -1;
6 | const bitrateLine = `b=AS:${bitrate}`;
7 | mediaLineIndex = sdpLines.findIndex((line) => line.startsWith(mediaLine));
8 |
9 | // If we find a line matching “m={mediaType}”
10 | if (mediaLineIndex && mediaLineIndex < sdpLines.length) {
11 | // Skip the media line
12 | bitrateLineIndex = mediaLineIndex + 1;
13 |
14 | // Skip both i=* and c=* lines (bandwidths limiters have to come afterwards)
15 | while (
16 | sdpLines[bitrateLineIndex].startsWith('i=') ||
17 | sdpLines[bitrateLineIndex].startsWith('c=')
18 | ) {
19 | bitrateLineIndex += 1;
20 | }
21 |
22 | if (sdpLines[bitrateLineIndex].startsWith('b=')) {
23 | // If the next line is a b=* line, replace it with our new bandwidth
24 | sdpLines[bitrateLineIndex] = bitrateLine;
25 | } else {
26 | // Otherwise insert a new bitrate line.
27 | sdpLines.splice(bitrateLineIndex, 0, bitrateLine);
28 | }
29 | }
30 |
31 | // Then return the updated sdp content as a string
32 | return sdpLines.join('\n');
33 | };
34 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/simplePeerHandleSdpTransform.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { INPUTtestSdpMediaBitrate } from './mocks/INPUTvideo500000testSdpMediaBitrate';
3 | import { OUTPUTtestSdpMediaBitrate } from './mocks/OUTPUTvideo500000testSdpMediaBitrate';
4 | import simplePeerHandleSdpTransform from './simplePeerHandleSdpTransform';
5 | import setSdpMediaBitrate from './setSdpMediaBitrate';
6 |
7 | jest.useFakeTimers();
8 | jest.mock('./setSdpMediaBitrate', () => {
9 | return jest.fn();
10 | });
11 |
12 | describe('when simplePeerHandleSdpTransform is called', () => {
13 | afterEach(() => {
14 | jest.clearAllMocks();
15 | jest.restoreAllMocks();
16 | });
17 | it('should call setSdpMediaBitrate', () => {
18 | simplePeerHandleSdpTransform(INPUTtestSdpMediaBitrate);
19 |
20 | expect(setSdpMediaBitrate).toBeCalled();
21 | });
22 |
23 | it('should return proper sdp media bitrate', () => {
24 | // @ts-ignore
25 | setSdpMediaBitrate.mockImplementation(
26 | jest.requireActual('./setSdpMediaBitrate').default
27 | );
28 |
29 | const res = simplePeerHandleSdpTransform(INPUTtestSdpMediaBitrate);
30 |
31 | expect(res).toEqual(OUTPUTtestSdpMediaBitrate);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/app/features/PeerConnection/simplePeerHandleSdpTransform.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import setSdpMediaBitrate from './setSdpMediaBitrate';
3 |
4 | export default (sdp: any) => {
5 | let newSDP = sdp;
6 | newSDP = setSdpMediaBitrate(newSDP as string, 'video', 500000) as typeof sdp;
7 | return newSDP;
8 | };
9 |
--------------------------------------------------------------------------------
/app/features/PeerConnectionHelperRendererService/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/lines-between-class-members */
2 | import path from 'path';
3 | import { BrowserWindow } from 'electron';
4 |
5 | type RendererHelperWebcontentsID = number;
6 |
7 | export default class RendererWebrtcHelpersService {
8 | helpers: Map;
9 | appPath: string;
10 |
11 | constructor(_appPath: string) {
12 | this.helpers = new Map();
13 | this.appPath = _appPath;
14 | }
15 |
16 | createPeerConnectionHelperRenderer() {
17 | let helperRendererWindow: BrowserWindow | null = null;
18 |
19 | helperRendererWindow = new BrowserWindow({
20 | show: false,
21 | webPreferences:
22 | (process.env.NODE_ENV === 'development' ||
23 | process.env.E2E_BUILD === 'true') &&
24 | process.env.ERB_SECURE !== 'true'
25 | ? {
26 | contextIsolation: true,
27 | nodeIntegration: true,
28 | }
29 | : {
30 | preload: path.join(
31 | this.appPath,
32 | 'dist/peerConnectionHelperRendererWindow.renderer.prod.js'
33 | ),
34 | },
35 | });
36 |
37 | helperRendererWindow.loadURL(
38 | `file://${this.appPath}/peerConnectionHelperRendererWindow.html`
39 | );
40 |
41 | helperRendererWindow.webContents.on('did-finish-load', () => {
42 | if (!helperRendererWindow) {
43 | throw new Error('"helperRendererWindow" is not defined');
44 | }
45 | helperRendererWindow.webContents.send('start-peer-connection');
46 | });
47 |
48 | helperRendererWindow.on('closed', () => {
49 | helperRendererWindow = null;
50 | });
51 |
52 | this.helpers.set(helperRendererWindow.webContents.id, helperRendererWindow);
53 |
54 | if (process.env.NODE_ENV === 'dev') {
55 | helperRendererWindow.webContents.toggleDevTools();
56 | }
57 | // helperRendererWindow.webContents.toggleDevTools();
58 |
59 | return helperRendererWindow;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/features/SharingSessionService/LocalPeerUser.d.ts:
--------------------------------------------------------------------------------
1 | interface LocalPeerUser {
2 | username: string;
3 | privateKey: string;
4 | publicKey: string;
5 | }
6 |
--------------------------------------------------------------------------------
/app/features/SharingSessionService/SharingSessionStatusChangeListener.d.ts:
--------------------------------------------------------------------------------
1 | type SharingSessionStatusChangeListener = (sharingSessionID: string) => void;
2 |
--------------------------------------------------------------------------------
/app/features/SharingSessionService/SharingSessionStatusEnum.ts:
--------------------------------------------------------------------------------
1 | enum SharingSessionStatusEnum {
2 | NOT_CONNECTED,
3 | CONNECTED,
4 | SHARING,
5 | ERROR,
6 | DESTROYED,
7 | }
8 |
9 | export default SharingSessionStatusEnum;
10 |
--------------------------------------------------------------------------------
/app/features/SharingSessionService/SharingTypeEnum.ts:
--------------------------------------------------------------------------------
1 | enum SharingTypeEnum {
2 | NOT_SET,
3 | SCREEN,
4 | APP,
5 | }
6 |
7 | export default SharingTypeEnum;
8 |
--------------------------------------------------------------------------------
/app/features/SharingSessionService/__mocks__/PeerConnection.ts:
--------------------------------------------------------------------------------
1 | export default class PeerConnection {
2 | roomID: string;
3 |
4 | constructor(roomID: string) {
5 | this.roomID = roomID;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/features/SharingSessionService/__mocks__/shortid.ts:
--------------------------------------------------------------------------------
1 | export default () => {
2 | return '1234';
3 | };
4 |
--------------------------------------------------------------------------------
/app/images/red_heart_2764_twemoji_120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/app/images/red_heart_2764_twemoji_120x120.png
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { ipcRenderer } from 'electron';
3 | import React, { Fragment, Suspense } from 'react';
4 | import { render } from 'react-dom';
5 | import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
6 | import './configs/i18next.config.client';
7 | import { history, configuredStore } from './store';
8 | import './app.global.css';
9 |
10 | const store = configuredStore();
11 |
12 | const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer;
13 |
14 | document.addEventListener('DOMContentLoaded', () => {
15 | if (process.platform === 'darwin') {
16 | const windowTopBar = document.createElement('div');
17 | windowTopBar.style.width = '100%';
18 | windowTopBar.style.height = '50px';
19 | windowTopBar.style.position = 'absolute';
20 | windowTopBar.style.top = '0';
21 | windowTopBar.style.left = '0';
22 | // @ts-ignore: all good here
23 | windowTopBar.style.webkitAppRegion = 'drag';
24 | windowTopBar.style.pointerEvents = 'none';
25 | document.body.appendChild(windowTopBar);
26 | }
27 |
28 | // eslint-disable-next-line global-require
29 | const Root = require('./containers/Root').default;
30 | render(
31 |
32 |
33 |
34 |
35 | ,
36 | document.getElementById('root')
37 | );
38 | });
39 |
40 | window.onbeforeunload = () => {
41 | ipcRenderer.invoke('main-window-onbeforeunload');
42 | };
43 |
--------------------------------------------------------------------------------
/app/main.prod.js.LICENSE:
--------------------------------------------------------------------------------
1 | /*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
2 |
--------------------------------------------------------------------------------
/app/main/IpcEvents.enum.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export enum IpcEvents {
3 | CreateWaitingForConnectionSharingSession = 'create-waiting-for-connection-sharing-session',
4 | SetPendingConnectionDevice = 'set-pending-connection-device',
5 | UnmarkRoomIDAsTaken = 'unmark-room-id-as-taken',
6 | GetAppPath = 'get-app-path',
7 | ResetWaitingForConnectionSharingSession = 'reset-waiting-for-connection-sharing-session',
8 | SetDeviceConnectedStatus = 'set-device-connected-status',
9 | GetSourceDisplayIDByDesktopCapturerSourceID = 'get-source-display-id-by-desktop-capturer-source-id',
10 | DisconnectPeerAndDestroySharingSessionBySessionID = 'disconnect-peer-and-destroy-sharing-session-by-session-id',
11 | GetDesktopCapturerSourceIdBySharingSessionId = 'get-desktop-capturer-source-id-by-sharing-session-id',
12 | GetConnectedDevices = 'get-connected-devices-list',
13 | DisconnectDeviceById = 'disconnect-device-by-id',
14 | DisconnectAllDevices = 'disconnect-all-devices',
15 | AppLanguageChanged = 'app-language-changed',
16 | GetDesktopCapturerServiceSourcesMap = 'get-desktop-capturer-service-sources-map',
17 | GetWaitingForConnectionSharingSessionSourceId = 'get-waiting-for-connection-sharing-session-source-id',
18 | StartSharingOnWaitingForConnectionSharingSession = 'start-sharing-on-waiting-for-connection-sharing-session',
19 | GetPendingConnectionDevice = 'get-pending-connection-device',
20 | GetWaitingForConnectionSharingSessionRoomId = 'get-waiting-for-connection-sharing-session-room-id',
21 | GetDesktopSharingSourceIds = 'get-desktop-sharing-source-ids',
22 | SetDesktopCapturerSourceId = 'set-desktop-capturer-source-id',
23 | NotifyAllSessionsWithAppThemeChanged = 'notify-all-sessions-with-app-theme-changed',
24 | GetAppLanguage = 'get-app-language',
25 | GetIsFirstTimeAppStart = 'get-is-not-first-time-app-start',
26 | SetAppStartedOnce = 'set-app-started-once',
27 | GetIsAppDarkTheme = 'get-is-app-dark-theme',
28 | SetIsAppDarkTheme = 'set-is-app-dark-theme',
29 | DestroySharingSessionById = 'destroy-sharing-session-by-id',
30 | }
31 |
--------------------------------------------------------------------------------
/app/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Deskreen",
3 | "version": "2.0.4",
4 | "lockfileVersion": 1
5 | }
6 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskreen",
3 | "productName": "Deskreen",
4 | "version": "2.0.4",
5 | "description": "Deskreen turns any device into a secondary screen for your computer",
6 | "main": "./main.prod.js",
7 | "author": {
8 | "name": "Pavlo (Paul) Buidenkov",
9 | "email": "pavlobu@gmail.com",
10 | "url": "https://github.com/pavlobu"
11 | },
12 | "scripts": {
13 | "electron-rebuild": "node -r ../internals/scripts/BabelRegister.js ../internals/scripts/ElectronRebuild.js",
14 | "postinstall": "yarn electron-rebuild"
15 | },
16 | "license": "MIT",
17 | "dependencies": {
18 | "sass": "^1.30.0",
19 | "socket.io": "^2.3.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/peerConnectionHelperRendererWindow.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | Mouse Pointer Renderer
11 |
25 |
26 |
27 |
28 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/peerConnectionHelperRendererWindowIndex.tsx:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import PeerConnection from './features/PeerConnection';
3 |
4 | // eslint-disable-next-line import/prefer-default-export
5 | export function handleIpcRenderer() {
6 | ipcRenderer.on('start-peer-connection', () => {
7 | let peerConnection: PeerConnection;
8 |
9 | ipcRenderer.on('create-peer-connection-with-data', (_, data) => {
10 | peerConnection = new PeerConnection(
11 | data.roomID,
12 | data.sharingSessionID,
13 | data.user
14 | );
15 |
16 | peerConnection.setOnDeviceConnectedCallback((deviceData) => {
17 | ipcRenderer.send('peer-connected', deviceData);
18 | });
19 | });
20 |
21 | ipcRenderer.on('set-desktop-capturer-source-id', (_, id) => {
22 | peerConnection.setDesktopCapturerSourceID(id);
23 | });
24 |
25 | ipcRenderer.on('call-peer', () => {
26 | peerConnection.callPeer();
27 | });
28 |
29 | ipcRenderer.on('disconnect-by-host-machine-user', (_, deviceId: string) => {
30 | peerConnection.disconnectByHostMachineUser(deviceId);
31 | });
32 |
33 | ipcRenderer.on('deny-connection-for-partner', () => {
34 | peerConnection.denyConnectionForPartner();
35 | });
36 |
37 | ipcRenderer.on('send-user-allowed-to-connect', () => {
38 | peerConnection.sendUserAllowedToConnect();
39 | });
40 |
41 | ipcRenderer.on('app-color-theme-changed', () => {
42 | peerConnection.notifyClientWithNewColorTheme();
43 | });
44 |
45 | ipcRenderer.on('app-language-changed', () => {
46 | peerConnection.notifyClientWithNewLanguage();
47 | });
48 | });
49 | }
50 |
51 | handleIpcRenderer();
52 |
--------------------------------------------------------------------------------
/app/rootReducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { connectRouter } from 'connected-react-router';
3 | import { History } from 'history';
4 | // eslint-disable-next-line import/no-cycle
5 | // import counterReducer from './features/counter/counterSlice';
6 |
7 | export default function createRootReducer(history: History) {
8 | return combineReducers({
9 | router: connectRouter(history),
10 | // counter: counterReducer,
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/app/server/Room.d.ts:
--------------------------------------------------------------------------------
1 | interface Room {
2 | id: string;
3 | users: User[];
4 | isLocked: boolean;
5 | createdAt: number;
6 | }
7 |
--------------------------------------------------------------------------------
/app/server/RoomIDService/RoomIDService.spec.ts:
--------------------------------------------------------------------------------
1 | import RoomIDService from '.';
2 |
3 | jest.useFakeTimers();
4 |
5 | describe('SharingSessionService unit tests', () => {
6 | let roomIDService: RoomIDService;
7 |
8 | beforeEach(() => {
9 | roomIDService = new RoomIDService();
10 | });
11 |
12 | afterEach(() => {
13 | jest.clearAllMocks();
14 | });
15 |
16 | describe('when new RoomIDService() is created', () => {
17 | it('should have empty takenRoomIDs Set', () => {
18 | expect(roomIDService.takenRoomIDs.size).toBe(0);
19 | });
20 | });
21 |
22 | describe('when getShortIDStringOfAvailableRoom() is called', () => {
23 | it('should resolve with non empty string', async () => {
24 | const gotShortStringID = await roomIDService.getShortIDStringOfAvailableRoom();
25 | expect(gotShortStringID).toBeTruthy();
26 | });
27 | });
28 |
29 | describe('when getSimpleAvailableRoomID() is called', () => {
30 | it('should resolve with non empty string of nextAvailableRoomIDNumber property', async () => {
31 | const gotRoomID = await roomIDService.getSimpleAvailableRoomID();
32 | expect(gotRoomID).not.toBe('');
33 | });
34 | });
35 |
36 | describe('when markRoomIDAsTaken(id: string) is called', () => {
37 | it('should add passed id: string argument to takenRoomIDs Set', () => {
38 | const testRoomID = '1';
39 | roomIDService.markRoomIDAsTaken(testRoomID);
40 |
41 | expect(roomIDService.takenRoomIDs.has(testRoomID)).toBe(true);
42 | });
43 | });
44 |
45 | describe('when unmarkRoomIDAsTaken(id: string) is called', () => {
46 | it('should remove passed id: string argument to takenRoomIDs Set', () => {
47 | const testRoomID = '1';
48 | roomIDService.markRoomIDAsTaken(testRoomID);
49 |
50 | roomIDService.unmarkRoomIDAsTaken(testRoomID);
51 |
52 | expect(roomIDService.takenRoomIDs.has(testRoomID)).toBe(false);
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/app/server/RoomIDService/index.ts:
--------------------------------------------------------------------------------
1 | import shortID from 'shortid';
2 | import crypto from 'crypto';
3 |
4 | export default class RoomIDService {
5 | takenRoomIDs: Set;
6 |
7 | nextSimpleRoomID: number;
8 |
9 | constructor() {
10 | this.takenRoomIDs = new Set();
11 | this.nextSimpleRoomID = 1;
12 | // TODO: load saved taken room ids from local storage, will be useful for saved devices feature in FUTURE
13 | }
14 |
15 | getSimpleAvailableRoomID(): Promise {
16 | this.nextSimpleRoomID += 1;
17 | return new Promise((resolve) => {
18 | crypto.randomBytes(3, (_, buffer) => {
19 | resolve(parseInt(buffer.toString('hex'), 16).toString().substr(0, 6));
20 | });
21 | });
22 | }
23 |
24 | getShortIDStringOfAvailableRoom(): Promise {
25 | return new Promise((resolve) => {
26 | let newID = shortID();
27 | while (this.takenRoomIDs.has(newID)) {
28 | newID = shortID();
29 | }
30 | resolve(newID);
31 | });
32 | }
33 |
34 | markRoomIDAsTaken(id: string) {
35 | this.takenRoomIDs.add(id);
36 | }
37 |
38 | unmarkRoomIDAsTaken(id: string) {
39 | this.takenRoomIDs.delete(id);
40 | }
41 |
42 | isRoomIDTaken(id: string) {
43 | return this.takenRoomIDs.has(id);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/server/connectSocket.ts:
--------------------------------------------------------------------------------
1 | import socketIO from 'socket.io-client';
2 | import generateUrl from '../api/urlGenerator';
3 |
4 | export default (roomId: string) => {
5 | return socketIO(generateUrl(), {
6 | query: {
7 | roomId,
8 | },
9 | forceNew: true,
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/app/server/pollForInactiveRooms.spec.ts:
--------------------------------------------------------------------------------
1 | import pollForInactiveRooms from './pollForInactiveRooms';
2 | import getStore from './store';
3 |
4 | const TEN_DAYS_PERIOD_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 10;
5 | describe('Inactive rooms auto removed from store', () => {
6 | it('should remove inactive rooms from store that are updatedAt about 10 days ago', async () => {
7 | const store = getStore();
8 | store.set(
9 | 'rooms',
10 | 'roomId1',
11 | JSON.stringify({
12 | updatedAt: Date.now() - TEN_DAYS_PERIOD_IN_MILLISECONDS,
13 | })
14 | );
15 |
16 | pollForInactiveRooms();
17 |
18 | const rooms = (await store.getAll('rooms')) || {};
19 | expect(Object.keys(rooms).length).toBe(0);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/app/server/pollForInactiveRooms.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * original JS code from darkwire.io
3 | * translated to typescript for Deskreen app
4 | * */
5 | import getStore from './store';
6 |
7 | export default async function pollForInactiveRooms() {
8 | const store = getStore();
9 | const rooms = (await store.getAll('rooms')) || {};
10 |
11 | Object.keys(rooms).forEach(async (roomId) => {
12 | const room = JSON.parse(rooms[roomId]);
13 | const timeSinceUpdatedInSeconds = (Date.now() - room.updatedAt) / 1000;
14 | const timeSinceUpdatedInDays = Math.round(
15 | timeSinceUpdatedInSeconds / 60 / 60 / 24
16 | );
17 | if (timeSinceUpdatedInDays > 7) {
18 | await store.del('rooms', roomId);
19 | }
20 | });
21 |
22 | setTimeout(pollForInactiveRooms, 1000 * 60 * 60); // every hour
23 | }
24 |
--------------------------------------------------------------------------------
/app/server/socketsIPService.ts:
--------------------------------------------------------------------------------
1 | class SocketsIPService {
2 | private static instance: SocketsIPService;
3 |
4 | idToIpMap: Map;
5 |
6 | ipToIdMap: Map;
7 |
8 | constructor() {
9 | this.idToIpMap = new Map();
10 | this.ipToIdMap = new Map();
11 | }
12 |
13 | setIPOfSocketID(id: string, ip: string) {
14 | this.idToIpMap.set(id, ip);
15 | this.ipToIdMap.set(ip, id);
16 | }
17 |
18 | setSocketIDOfIP(ip: string, id: string) {
19 | this.idToIpMap.set(id, ip);
20 | this.ipToIdMap.set(ip, id);
21 | }
22 |
23 | getSocketIPByID(id: string): string | undefined {
24 | return this.idToIpMap.get(id);
25 | }
26 |
27 | getSocketIDByIP(ip: string): string | undefined {
28 | return this.ipToIdMap.get(ip);
29 | }
30 |
31 | isIPExists(ip: string) {
32 | return this.ipToIdMap.has(ip);
33 | }
34 |
35 | static getInstance() {
36 | if (!this.instance) {
37 | this.instance = new SocketsIPService();
38 | }
39 | return this.instance;
40 | }
41 | }
42 |
43 | export default SocketsIPService.getInstance();
44 |
--------------------------------------------------------------------------------
/app/server/store/MemoryStore.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 |
3 | /*
4 | * original JS code from darkwire.io
5 | * translated to typescript for Deskreen app
6 | * */
7 |
8 | interface MemoryStoreParams {
9 | store: unknown;
10 | }
11 |
12 | class MemoryStore implements MemoryStoreParams {
13 | store: any;
14 |
15 | constructor() {
16 | this.store = {};
17 | }
18 |
19 | async get(key: string, field: any) {
20 | if (this.store[key] === undefined || this.store[key][field] === undefined) {
21 | return null;
22 | }
23 | return this.store[key][field];
24 | }
25 |
26 | async getAll(key: string) {
27 | if (this.store[key] === undefined) {
28 | return [];
29 | }
30 |
31 | return this.store[key];
32 | }
33 |
34 | async set(key: string, field: string, value: any) {
35 | if (this.store[key] === undefined) {
36 | this.store[key] = {};
37 | }
38 | this.store[key][field] = value;
39 | return 1;
40 | }
41 |
42 | async del(key: string, field: string) {
43 | if (this.store[key] === undefined || this.store[key][field] === undefined) {
44 | return 0;
45 | }
46 | delete this.store[key][field];
47 | return 1;
48 | }
49 |
50 | async inc(key: string, field: string, inc = 1) {
51 | this.store[key][field] += inc;
52 | return this.store[key][field];
53 | }
54 | }
55 |
56 | export default MemoryStore;
57 |
--------------------------------------------------------------------------------
/app/server/store/index.ts:
--------------------------------------------------------------------------------
1 | import MemoryStore from './MemoryStore';
2 |
3 | let store: MemoryStore;
4 |
5 | const getStore = () => {
6 | if (store === undefined) {
7 | store = new MemoryStore();
8 | }
9 | return store;
10 | };
11 |
12 | export default getStore;
13 |
--------------------------------------------------------------------------------
/app/server/store/socketIOServerStore.ts:
--------------------------------------------------------------------------------
1 | import Io from 'socket.io';
2 |
3 | class SocketIOServerStore {
4 | ioServer = ({} as unknown) as Io.Server;
5 |
6 | setServer(server: Io.Server) {
7 | this.ioServer = server;
8 | }
9 |
10 | getServer() {
11 | return this.ioServer;
12 | }
13 | }
14 |
15 | const store = new SocketIOServerStore();
16 |
17 | export default store;
18 |
--------------------------------------------------------------------------------
/app/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, getDefaultMiddleware, Action } from '@reduxjs/toolkit';
2 | import { createHashHistory } from 'history';
3 | import { routerMiddleware } from 'connected-react-router';
4 | import { createLogger } from 'redux-logger';
5 | import { ThunkAction } from 'redux-thunk';
6 | // eslint-disable-next-line import/no-cycle
7 | import createRootReducer from './rootReducer';
8 |
9 | export const history = createHashHistory();
10 | const rootReducer = createRootReducer(history);
11 | export type RootState = ReturnType;
12 |
13 | const router = routerMiddleware(history);
14 | const middleware = [...getDefaultMiddleware(), router];
15 |
16 | const excludeLoggerEnvs = ['test', 'production'];
17 | const shouldIncludeLogger = !excludeLoggerEnvs.includes(
18 | process.env.NODE_ENV || ''
19 | );
20 |
21 | if (shouldIncludeLogger) {
22 | const logger = createLogger({
23 | level: 'info',
24 | collapsed: true,
25 | });
26 | middleware.push(logger);
27 | }
28 |
29 | export const configuredStore = (initialState?: RootState) => {
30 | // Create Store
31 | const store = configureStore({
32 | reducer: rootReducer,
33 | middleware,
34 | preloadedState: initialState,
35 | });
36 |
37 | if (process.env.NODE_ENV === 'development' && module.hot) {
38 | module.hot.accept(
39 | './rootReducer',
40 | // eslint-disable-next-line global-require
41 | () => store.replaceReducer(require('./rootReducer').default)
42 | );
43 | }
44 | return store;
45 | };
46 | export type Store = ReturnType;
47 | export type AppThunk = ThunkAction>;
48 |
--------------------------------------------------------------------------------
/app/utils/AppUpdater.ts:
--------------------------------------------------------------------------------
1 | import { autoUpdater } from 'electron-updater';
2 | import log from 'electron-log';
3 |
4 | export default class AppUpdater {
5 | constructor() {
6 | log.transports.file.level = 'info';
7 | autoUpdater.logger = log;
8 | autoUpdater.checkForUpdatesAndNotify();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/utils/LoggerWithFilePrefix.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | const log = require('electron-log');
3 |
4 | log.transports.file.level = 'warn';
5 |
6 | if (process.env.NODE_ENV !== 'production') {
7 | log.transports.console.level = 'silly';
8 | } else {
9 | log.transports.console.level = 'debug'; // TODO: make false when doing release
10 | }
11 |
12 | export default class LoggerWithFilePrefix {
13 | filenamePath: string;
14 |
15 | electronLog: typeof log;
16 |
17 | constructor(_filenamePath: string) {
18 | this.filenamePath = _filenamePath;
19 | this.electronLog = log;
20 | }
21 |
22 | error(...args: any[]) {
23 | this.electronLog.error(this.filenamePath, ':', ...args);
24 | }
25 |
26 | warn(...args: any[]) {
27 | this.electronLog.warn(this.filenamePath, ':', ...args);
28 | }
29 |
30 | info(...args: any[]) {
31 | this.electronLog.info(this.filenamePath, ':', ...args);
32 | }
33 |
34 | verbose(...args: any[]) {
35 | this.electronLog.verbose(this.filenamePath, ':', ...args);
36 | }
37 |
38 | debug(...args: any[]) {
39 | this.electronLog.debug(this.filenamePath, ':', ...args);
40 | }
41 |
42 | silly(...args: any[]) {
43 | this.electronLog.silly(this.filenamePath, ':', ...args);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/utils/ProcessedMessage.d.ts:
--------------------------------------------------------------------------------
1 | type CallAcceptedMessageWithPayload = {
2 | type: 'CALL_ACCEPTED';
3 | payload: {
4 | signalData: string;
5 | };
6 | };
7 |
8 | type DeviceDetailsMessageWithPayload = {
9 | type: 'DEVICE_DETAILS';
10 | payload: {
11 | deviceType: string;
12 | os: string;
13 | browser: string;
14 | deviceScreenWidth: number;
15 | deviceScreenHeight: number;
16 | };
17 | };
18 |
19 | type GetAppThemeMessageWithPayload = {
20 | type: 'GET_APP_THEME';
21 | payload: Record;
22 | };
23 |
24 | type GetAppLanguageMessageWithPayload = {
25 | type: 'GET_APP_LANGUAGE';
26 | payload: Record;
27 | };
28 |
29 | type ProcessedMessage =
30 | | CallAcceptedMessageWithPayload
31 | | DeviceDetailsMessageWithPayload
32 | | GetAppThemeMessageWithPayload
33 | | GetAppLanguageMessageWithPayload;
34 |
--------------------------------------------------------------------------------
/app/utils/getAppLanguage.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import { IpcEvents } from '../main/IpcEvents.enum';
3 |
4 | export default async function getAppLanguage(): Promise {
5 | const appLanguage = await ipcRenderer.invoke(IpcEvents.GetAppLanguage);
6 | return appLanguage;
7 | }
8 |
--------------------------------------------------------------------------------
/app/utils/getAppTheme.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import { IpcEvents } from '../main/IpcEvents.enum';
3 |
4 | export default async function getAppTheme(): Promise {
5 | const isAppDarkTheme = await ipcRenderer.invoke(IpcEvents.GetIsAppDarkTheme);
6 | return isAppDarkTheme;
7 | }
8 |
--------------------------------------------------------------------------------
/app/utils/getNewVersionTag.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import axios from 'axios';
3 |
4 | const githubApiRepoTagsUrl =
5 | 'https://api.github.com/repos/pavlobu/deskreen/releases/latest';
6 |
7 | export default async function getNewVersionTag() {
8 | let latestVersionTag = '';
9 |
10 | const response = await axios({
11 | url: githubApiRepoTagsUrl,
12 | method: 'get',
13 | headers: { 'User-Agent': 'node.js' },
14 | });
15 |
16 | const tagName = response.data.tag_name;
17 |
18 | latestVersionTag = tagName.slice(1);
19 |
20 | return latestVersionTag;
21 | }
22 |
--------------------------------------------------------------------------------
/app/utils/installExtensions.ts:
--------------------------------------------------------------------------------
1 | export default async function installExtensions() {
2 | // eslint-disable-next-line global-require
3 | const installer = require('electron-devtools-installer');
4 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
5 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
6 |
7 | return Promise.all(
8 | extensions.map((name) => installer.default(installer[name], forceDownload))
9 | // eslint-disable-next-line no-console
10 | ).catch(console.log);
11 | }
12 |
--------------------------------------------------------------------------------
/app/utils/isProduction.ts:
--------------------------------------------------------------------------------
1 | export default function isProduction() {
2 | return (
3 | process.env.NODE_ENV === 'production' &&
4 | process.env.RUN_MODE !== 'dev' &&
5 | process.env.RUN_MODE !== 'test'
6 | );
7 | // return true; // for animations and other things debugging as in production mode
8 | // return false;
9 | }
10 |
--------------------------------------------------------------------------------
/app/utils/isWithReactRevealAnimations.ts:
--------------------------------------------------------------------------------
1 | export default function isWithReactRevealAnimations() {
2 | // return isProduction() && getIsAnimationsFromSettings();
3 | return false;
4 | }
5 |
--------------------------------------------------------------------------------
/app/utils/mainProcessHelpers/DeskreenGlobal.d.ts:
--------------------------------------------------------------------------------
1 | import ConnectedDevicesService from '../../features/ConnectedDevicesService';
2 | import SharingSessionService from '../../features/SharingSessionService';
3 | import RendererWebrtcHelpersService from '../../features/PeerConnectionHelperRendererService';
4 | import RoomIDService from '../../server/RoomIDService';
5 | import DesktopCapturerSourcesService from '../../features/DesktopCapturerSourcesService';
6 |
7 | interface DeskreenGlobal {
8 | appPath: string;
9 | rendererWebrtcHelpersService: RendererWebrtcHelpersService;
10 | roomIDService: RoomIDService;
11 | connectedDevicesService: ConnectedDevicesService;
12 | sharingSessionService: SharingSessionService;
13 | desktopCapturerSourcesService: DesktopCapturerSourcesService;
14 | latestAppVersion: string;
15 | currentAppVersion: string;
16 | }
17 |
--------------------------------------------------------------------------------
/app/utils/mainProcessHelpers/getDeskreenGlobal.ts:
--------------------------------------------------------------------------------
1 | import { DeskreenGlobal } from './DeskreenGlobal';
2 |
3 | export default () => {
4 | return (global as unknown) as DeskreenGlobal;
5 | };
6 |
--------------------------------------------------------------------------------
/app/utils/mainProcessHelpers/initGlobals.ts:
--------------------------------------------------------------------------------
1 | import { app } from 'electron';
2 | import ConnectedDevicesService from '../../features/ConnectedDevicesService';
3 | import SharingSessionService from '../../features/SharingSessionService';
4 | import RendererWebrtcHelpersService from '../../features/PeerConnectionHelperRendererService';
5 | import RoomIDService from '../../server/RoomIDService';
6 | import DesktopCapturerSources from '../../features/DesktopCapturerSourcesService';
7 | import { DeskreenGlobal } from './DeskreenGlobal';
8 |
9 | export default (appPath: string) => {
10 | const deskreenGlobal: DeskreenGlobal = (global as unknown) as DeskreenGlobal;
11 |
12 | deskreenGlobal.appPath = appPath;
13 |
14 | deskreenGlobal.rendererWebrtcHelpersService = new RendererWebrtcHelpersService(
15 | appPath
16 | );
17 | deskreenGlobal.roomIDService = new RoomIDService();
18 | deskreenGlobal.connectedDevicesService = new ConnectedDevicesService();
19 | deskreenGlobal.sharingSessionService = new SharingSessionService(
20 | deskreenGlobal.roomIDService,
21 | deskreenGlobal.connectedDevicesService,
22 | deskreenGlobal.rendererWebrtcHelpersService
23 | );
24 | deskreenGlobal.desktopCapturerSourcesService = new DesktopCapturerSources();
25 | deskreenGlobal.latestAppVersion = '';
26 | deskreenGlobal.currentAppVersion = app.getVersion();
27 | };
28 |
--------------------------------------------------------------------------------
/app/utils/message.spec.ts:
--------------------------------------------------------------------------------
1 | import { prepare, process } from './message';
2 | import getTestPublickKeyPem from './mocks/getTestPublicKeyPem';
3 | import getTestPrivateKeyPem from './mocks/getTestPrivateKeyPem';
4 |
5 | interface TestPayload {
6 | text: string;
7 | }
8 |
9 | describe('message.ts tests for proper encryption and decryption functionality', () => {
10 | const TEST_TEXT = 'some test text here';
11 | const TEST_TEXT_AS_URL = 'some%20test%20text%20here';
12 | const testPayloadToEncrypt = {
13 | payload: {
14 | text: TEST_TEXT,
15 | },
16 | };
17 |
18 | const testUser = {
19 | username: 'testUsername',
20 | id: 'testId',
21 | };
22 |
23 | const testPartner = {
24 | publicKey: getTestPublickKeyPem(),
25 | };
26 | it('should create encrypted payload with prepare() method', async () => {
27 | const encryptedPayload = await prepare(
28 | testPayloadToEncrypt,
29 | testUser,
30 | testPartner
31 | );
32 |
33 | expect(encryptedPayload.toSend.payload).not.toContain(TEST_TEXT);
34 | expect(encryptedPayload.toSend.payload).not.toContain(TEST_TEXT_AS_URL);
35 | });
36 |
37 | it('should decrypt encrypted payload with process() method', async () => {
38 | const encryptedPayload = await prepare(
39 | testPayloadToEncrypt,
40 | testUser,
41 | testPartner
42 | );
43 |
44 | const decryptedPayload = await process(
45 | encryptedPayload.toSend,
46 | getTestPrivateKeyPem()
47 | );
48 |
49 | expect(
50 | ((decryptedPayload.payload as unknown) as TestPayload).text
51 | ).toContain(TEST_TEXT_AS_URL);
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/app/utils/mocks/getTestPrivateKeyPem.ts:
--------------------------------------------------------------------------------
1 | export default function getTestPrivateKeyPem() {
2 | return `-----BEGIN RSA PRIVATE KEY-----
3 | MIIEogIBAAKCAQEAoQ8+vdVAerEyaxGaffzrTTv+sgpQ3xToBFYkxrT6f+zP4MqV
4 | nTIy2+UMlEGGryMFgJWyurqv+NyoVf7vIFOxQcxJko1BL/oIt/e5YZ/fMIw9AhgP
5 | 0A0oZyFKNbGesCY3zMdpZqPE0brzfhjr/lu5VzioZI9vxocOSSc3+S8w1EXqujgO
6 | X3PWXgZrD6Y//Oo+8BgNQta/e5PUyc9yNchU/W3ddzBdE0iUXTdQt7yFvzy4vTbS
7 | ywUxoNNJnD6dUJ6dZ4c24VGvrvhJ5mzNqQjibAhtkPFbvhn0e0BHl0BZ876fLHGg
8 | zpHgmmiWbKwBYl1ydv8fW9W3r5nQoW0RThYJjwIDAQABAoIBAHqOrUGrGsvCNwl+
9 | db9VTICTHLbCXtPChuN14bpLUSszOuRlhAAAiO8HltDiI+j1j2RPhZfOI8YNsxLt
10 | UW2aAhJ9r6aTUn19mFDVcv20uBOrQ2lqge3hdVM049GD/asxCdkMDUqLaGPoDQ1x
11 | TXNavOiANrN+6qF5eAd2joNRw6hi7osyWqfpgM9y58kiYYazKHKlI/er19JsD2t5
12 | hgRiFti+oN9nqhhVzJI/GYU9JugXnB/Z0uFvqOyt/3YDHPpOC07WYpfA3yzshBkW
13 | TiiMp1eJNX/sRh4JHzRI9/MQe9ajlAS7T74HwCkclzaS8ZY6t+dfVZ097/ug77eQ
14 | NwY4DAECgYEA0hWv7hpookKKcl0srfxbx+FbtBhzXqz+Tfy3H5do6uXRc0aUwQ25
15 | PZLs7UAT3zjt+4aYu1xgkp3cUx5FNr/FAPYLtjXw1/6fmt3HX8820SpCMjIzS1bU
16 | UwJvmAut70YA+n2eKwO1qJNeLcpNCDebbIXwj0lKDh5HQDyoswuz7I8CgYEAxEKV
17 | N4Ma6GqZ0hgdQlO71oSuftHUI80Iz+Riorrp4AA8hp5A3V6Fd9QDy8PYLz1kD9pM
18 | uWdCLqaxdOVDtLVnjNVZH1SI+wSBDA/0OtdvPaONL1JKRA21kol049f0M8wkulAw
19 | 6lfi2aQwZ3KWyEAfDy5iPdVROnQWqUcLmxO0kwECgYAGpSr4dBtlLoekkG/uXPIm
20 | Q2mcK73Se9RbcSf1tttZusVCSTRBWwbF/NTDuGgogmt8rkg8fPKNELM8adO0pKI9
21 | oorCS7h/jI1N38ADttE8EoMfhVj8BBYZPhV7kLsCu4siYUDUiXyAhZDQD/sZzHB9
22 | IUt3rNDL24dTb9fCOheJ3wKBgGRgpY7Z2DZM51VkDfrxdp3WCKVGTljtMfeaGLSg
23 | IqP1mv9DC2vtPxg1cKeUCArJPFc7UIh2/ot7qEFgTQusyERojgePJew0tofj1QcP
24 | To7ZConMbb12wYosEYPC3NxtKc+82ffRcW3dIwCVw/axjPEnyQlVBBGAdGKpuo7b
25 | Oj0BAoGAMs2zuAJfJB4xE1UZdLXk9uHWPAzgUNDzHuS5mMWuloGvKX+19yck6o2J
26 | ZA/iq6GwOgvD2y9MC0mV4WkmwZpVXwPVpZD8GVQXZf2xZgh/q2e3IObAd80NLnaG
27 | v86qN98DRTh9L+47Nsaf1J5vDDaAfH2Ir8UgAQ5ZMEFDm7P0hUQ=
28 | -----END RSA PRIVATE KEY-----`;
29 | }
30 |
--------------------------------------------------------------------------------
/app/utils/mocks/getTestPublicKeyPem.ts:
--------------------------------------------------------------------------------
1 | export default function getTestPublicKeyPem() {
2 | return `-----BEGIN PUBLIC KEY-----
3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoQ8+vdVAerEyaxGaffzr
4 | TTv+sgpQ3xToBFYkxrT6f+zP4MqVnTIy2+UMlEGGryMFgJWyurqv+NyoVf7vIFOx
5 | QcxJko1BL/oIt/e5YZ/fMIw9AhgP0A0oZyFKNbGesCY3zMdpZqPE0brzfhjr/lu5
6 | VzioZI9vxocOSSc3+S8w1EXqujgOX3PWXgZrD6Y//Oo+8BgNQta/e5PUyc9yNchU
7 | /W3ddzBdE0iUXTdQt7yFvzy4vTbSywUxoNNJnD6dUJ6dZ4c24VGvrvhJ5mzNqQji
8 | bAhtkPFbvhn0e0BHl0BZ876fLHGgzpHgmmiWbKwBYl1ydv8fW9W3r5nQoW0RThYJ
9 | jwIDAQAB
10 | -----END PUBLIC KEY-----`;
11 | }
12 |
--------------------------------------------------------------------------------
/configs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/configs/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { dependencies as externals } from '../app/package.json';
8 |
9 | export default {
10 | externals: [...Object.keys(externals || {})],
11 |
12 | module: {
13 | rules: [
14 | {
15 | test: /\.tsx?$/,
16 | exclude: /node_modules/,
17 | use: {
18 | loader: 'babel-loader',
19 | options: {
20 | cacheDirectory: true,
21 | },
22 | },
23 | },
24 | ],
25 | },
26 |
27 | output: {
28 | path: path.join(__dirname, '..', 'app'),
29 | // https://github.com/webpack/webpack/issues/1114
30 | libraryTarget: 'commonjs2',
31 | },
32 |
33 | /**
34 | * Determine the array of extensions that should be used to resolve modules.
35 | */
36 | resolve: {
37 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
38 | modules: [path.join(__dirname, '..', 'app'), 'node_modules'],
39 | },
40 |
41 | plugins: [
42 | new webpack.EnvironmentPlugin({
43 | NODE_ENV: 'production',
44 | }),
45 |
46 | new webpack.NamedModulesPlugin(),
47 | ],
48 | };
49 |
--------------------------------------------------------------------------------
/configs/webpack.config.eslint.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 | require('@babel/register');
3 |
4 | module.exports = require('./webpack.config.renderer.dev.babel').default;
5 |
--------------------------------------------------------------------------------
/configs/webpack.config.main.prod.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { merge } from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import baseConfig from './webpack.config.base';
11 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
12 | import DeleteSourceMaps from '../internals/scripts/DeleteSourceMaps';
13 |
14 | CheckNodeEnv('production');
15 | DeleteSourceMaps();
16 |
17 | export default merge(baseConfig, {
18 | devtool: process.env.DEBUG_PROD === 'true' ? 'source-map' : 'none',
19 |
20 | mode: 'production',
21 |
22 | target: 'electron-main',
23 |
24 | entry: './app/main.dev.ts',
25 |
26 | output: {
27 | path: path.join(__dirname, '..'),
28 | filename: './app/main.prod.js',
29 | },
30 |
31 | optimization: {
32 | minimizer: process.env.E2E_BUILD
33 | ? []
34 | : [
35 | new TerserPlugin({
36 | parallel: true,
37 | sourceMap: true,
38 | cache: true,
39 | }),
40 | ],
41 | },
42 |
43 | plugins: [
44 | new BundleAnalyzerPlugin({
45 | analyzerMode:
46 | process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
47 | openAnalyzer: process.env.OPEN_ANALYZER === 'true',
48 | }),
49 |
50 | /**
51 | * Create global constants which can be configured at compile time.
52 | *
53 | * Useful for allowing different behaviour between development builds and
54 | * release builds
55 | *
56 | * NODE_ENV should be production so that modules do not perform certain
57 | * development checks
58 | */
59 | new webpack.EnvironmentPlugin({
60 | NODE_ENV: 'production',
61 | DEBUG_PROD: false,
62 | START_MINIMIZED: false,
63 | E2E_BUILD: false,
64 | }),
65 | ],
66 |
67 | /**
68 | * Disables webpack processing of __dirname and __filename.
69 | * If you run the bundle in node.js it falls back to these values of node.js.
70 | * https://github.com/webpack/webpack/issues/2010
71 | */
72 | node: {
73 | __dirname: false,
74 | __filename: false,
75 | },
76 | });
77 |
--------------------------------------------------------------------------------
/configs/webpack.config.renderer.dev.dll.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import path from 'path';
7 | import { merge } from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 | import { dependencies } from '../package.json';
10 | import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
11 |
12 | CheckNodeEnv('development');
13 |
14 | const dist = path.join(__dirname, '..', 'dll');
15 |
16 | export default merge(baseConfig, {
17 | context: path.join(__dirname, '..'),
18 |
19 | devtool: 'eval',
20 |
21 | mode: 'development',
22 |
23 | target: 'electron-renderer',
24 |
25 | externals: ['fsevents', 'crypto-browserify'],
26 |
27 | /**
28 | * Use `module` from `webpack.config.renderer.dev.js`
29 | */
30 | module: require('./webpack.config.renderer.dev.babel').default.module,
31 |
32 | entry: {
33 | renderer: Object.keys(dependencies || {}),
34 | },
35 |
36 | output: {
37 | library: 'renderer',
38 | path: dist,
39 | filename: '[name].dev.dll.js',
40 | libraryTarget: 'var',
41 | },
42 |
43 | plugins: [
44 | new webpack.DllPlugin({
45 | path: path.join(dist, '[name].json'),
46 | name: '[name]',
47 | }),
48 |
49 | /**
50 | * Create global constants which can be configured at compile time.
51 | *
52 | * Useful for allowing different behaviour between development builds and
53 | * release builds
54 | *
55 | * NODE_ENV should be production so that modules do not perform certain
56 | * development checks
57 | */
58 | new webpack.EnvironmentPlugin({
59 | NODE_ENV: 'development',
60 | }),
61 |
62 | new webpack.LoaderOptionsPlugin({
63 | debug: true,
64 | options: {
65 | context: path.join(__dirname, '..', 'app'),
66 | output: {
67 | path: path.join(__dirname, '..', 'dll'),
68 | },
69 | },
70 | }),
71 | ],
72 | });
73 |
--------------------------------------------------------------------------------
/drivers/linux/git-dummy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/drivers/linux/git-dummy
--------------------------------------------------------------------------------
/drivers/mac/git-dummy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/drivers/mac/git-dummy
--------------------------------------------------------------------------------
/drivers/win/git-dummy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/drivers/win/git-dummy
--------------------------------------------------------------------------------
/internals/img/erb-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/erb-banner.png
--------------------------------------------------------------------------------
/internals/img/erb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/erb-logo.png
--------------------------------------------------------------------------------
/internals/img/eslint-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/eslint-padded-90.png
--------------------------------------------------------------------------------
/internals/img/eslint-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/eslint-padded.png
--------------------------------------------------------------------------------
/internals/img/eslint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/eslint.png
--------------------------------------------------------------------------------
/internals/img/jest-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/jest-padded-90.png
--------------------------------------------------------------------------------
/internals/img/jest-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/jest-padded.png
--------------------------------------------------------------------------------
/internals/img/jest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/jest.png
--------------------------------------------------------------------------------
/internals/img/js-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/js-padded.png
--------------------------------------------------------------------------------
/internals/img/js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/js.png
--------------------------------------------------------------------------------
/internals/img/npm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/npm.png
--------------------------------------------------------------------------------
/internals/img/react-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/react-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/react-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/react-router-padded-90.png
--------------------------------------------------------------------------------
/internals/img/react-router-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/react-router-padded.png
--------------------------------------------------------------------------------
/internals/img/react-router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/react-router.png
--------------------------------------------------------------------------------
/internals/img/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/react.png
--------------------------------------------------------------------------------
/internals/img/redux-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/redux-padded-90.png
--------------------------------------------------------------------------------
/internals/img/redux-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/redux-padded.png
--------------------------------------------------------------------------------
/internals/img/redux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/redux.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/webpack-padded-90.png
--------------------------------------------------------------------------------
/internals/img/webpack-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/webpack-padded.png
--------------------------------------------------------------------------------
/internals/img/webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/webpack.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded-90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/yarn-padded-90.png
--------------------------------------------------------------------------------
/internals/img/yarn-padded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/yarn-padded.png
--------------------------------------------------------------------------------
/internals/img/yarn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/internals/img/yarn.png
--------------------------------------------------------------------------------
/internals/jest/setEnvVars.js:
--------------------------------------------------------------------------------
1 | process.env.RUN_MODE = 'test';
2 |
--------------------------------------------------------------------------------
/internals/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/internals/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off",
6 | "import/no-extraneous-dependencies": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/internals/scripts/BabelRegister.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | require('@babel/register')({
4 | extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'],
5 | cwd: path.join(__dirname, '..', '..'),
6 | });
7 |
--------------------------------------------------------------------------------
/internals/scripts/CheckBuildsExist.js:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import fs from 'fs';
5 |
6 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js');
7 | const mainWindowRendererPath = path.join(
8 | __dirname,
9 | '..',
10 | '..',
11 | 'app',
12 | 'dist',
13 | 'mainWindow.renderer.prod.js'
14 | );
15 |
16 | const peerConnectionHelperRendererWindowPath = path.join(
17 | __dirname,
18 | '..',
19 | '..',
20 | 'app',
21 | 'dist',
22 | 'peerConnectionHelperRendererWindow.renderer.prod.js'
23 | );
24 |
25 | if (!fs.existsSync(mainPath)) {
26 | throw new Error(
27 | chalk.whiteBright.bgRed.bold(
28 | 'The main process is not built yet. Build it by running "yarn build-main"'
29 | )
30 | );
31 | }
32 |
33 | if (!fs.existsSync(mainWindowRendererPath)) {
34 | throw new Error(
35 | chalk.whiteBright.bgRed.bold(
36 | 'The renderer process is not built yet. Build it by running "yarn build-renderer"'
37 | )
38 | );
39 | }
40 |
41 | if (!fs.existsSync(peerConnectionHelperRendererWindowPath)) {
42 | throw new Error(
43 | chalk.whiteBright.bgRed.bold(
44 | 'The mousePointer renderer process is not built yet. Build it by running "yarn build-renderer"'
45 | )
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNativeDep.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import chalk from 'chalk';
3 | import { execSync } from 'child_process';
4 | import { dependencies } from '../../package.json';
5 |
6 | if (dependencies) {
7 | const dependenciesKeys = Object.keys(dependencies);
8 | const nativeDeps = fs
9 | .readdirSync('node_modules')
10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
11 | try {
12 | // Find the reason for why the dependency is installed. If it is installed
13 | // because of a devDependency then that is okay. Warn when it is installed
14 | // because of a dependency
15 | const { dependencies: dependenciesObject } = JSON.parse(
16 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
17 | );
18 | const rootDependencies = Object.keys(dependenciesObject);
19 | const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
20 | dependenciesKeys.includes(rootDependency)
21 | );
22 | if (filteredRootDependencies.length > 0) {
23 | const plural = filteredRootDependencies.length > 1;
24 | console.log(`
25 | ${chalk.whiteBright.bgYellow.bold(
26 | 'Webpack does not work with native dependencies.'
27 | )}
28 | ${chalk.bold(filteredRootDependencies.join(', '))} ${
29 | plural ? 'are native dependencies' : 'is a native dependency'
30 | } and should be installed inside of the "./app" folder.
31 | First, uninstall the packages from "./package.json":
32 | ${chalk.whiteBright.bgGreen.bold('yarn remove your-package')}
33 | ${chalk.bold(
34 | 'Then, instead of installing the package to the root "./package.json":'
35 | )}
36 | ${chalk.whiteBright.bgRed.bold('yarn add your-package')}
37 | ${chalk.bold('Install the package to "./app/package.json"')}
38 | ${chalk.whiteBright.bgGreen.bold('cd ./app && yarn add your-package')}
39 | Read more about native dependencies at:
40 | ${chalk.bold(
41 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure'
42 | )}
43 | `);
44 | process.exit(1);
45 | }
46 | } catch (e) {
47 | console.log('Native dependencies could not be checked');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/internals/scripts/CheckNodeEnv.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | export default function CheckNodeEnv(expectedEnv) {
4 | if (!expectedEnv) {
5 | throw new Error('"expectedEnv" not set');
6 | }
7 |
8 | if (process.env.NODE_ENV !== expectedEnv) {
9 | console.log(
10 | chalk.whiteBright.bgRed.bold(
11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
12 | )
13 | );
14 | process.exit(2);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/internals/scripts/CheckPortInUse.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import detectPort from 'detect-port';
3 |
4 | const port = process.env.PORT || '1212';
5 |
6 | detectPort(port, (err, availablePort) => {
7 | if (port !== String(availablePort)) {
8 | throw new Error(
9 | chalk.whiteBright.bgRed.bold(
10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 yarn dev`
11 | )
12 | );
13 | } else {
14 | process.exit(0);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/internals/scripts/CheckYarn.js:
--------------------------------------------------------------------------------
1 | if (!/yarn\.js$/.test(process.env.npm_execpath || '')) {
2 | console.warn(
3 | "\u001b[33mYou don't seem to be using yarn. This could produce unexpected results.\u001b[39m"
4 | );
5 | }
6 |
--------------------------------------------------------------------------------
/internals/scripts/DeleteSourceMaps.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import rimraf from 'rimraf';
3 |
4 | export default function deleteSourceMaps() {
5 | rimraf.sync(path.join(__dirname, '../../app/dist/*.js.map'));
6 | rimraf.sync(path.join(__dirname, '../../app/*.js.map'));
7 | }
8 |
--------------------------------------------------------------------------------
/internals/scripts/ElectronRebuild.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { execSync } from 'child_process';
3 | import fs from 'fs';
4 | import { dependencies } from '../../app/package.json';
5 |
6 | const nodeModulesPath = path.join(__dirname, '..', '..', 'app', 'node_modules');
7 |
8 | if (
9 | Object.keys(dependencies || {}).length > 0 &&
10 | fs.existsSync(nodeModulesPath)
11 | ) {
12 | const electronRebuildCmd =
13 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .';
14 | const cmd =
15 | process.platform === 'win32'
16 | ? electronRebuildCmd.replace(/\//g, '\\')
17 | : electronRebuildCmd;
18 | execSync(cmd, {
19 | cwd: path.join(__dirname, '..', '..', 'app'),
20 | stdio: 'inherit',
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icon.icns
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icon.ico
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icon.png
--------------------------------------------------------------------------------
/resources/icons/icon_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_1024x1024.png
--------------------------------------------------------------------------------
/resources/icons/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_128x128.png
--------------------------------------------------------------------------------
/resources/icons/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_16x16.png
--------------------------------------------------------------------------------
/resources/icons/icon_24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_24x24.png
--------------------------------------------------------------------------------
/resources/icons/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_256x256.png
--------------------------------------------------------------------------------
/resources/icons/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_32x32.png
--------------------------------------------------------------------------------
/resources/icons/icon_48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_48x48.png
--------------------------------------------------------------------------------
/resources/icons/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_512x512.png
--------------------------------------------------------------------------------
/resources/icons/icon_64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_64x64.png
--------------------------------------------------------------------------------
/resources/icons/icon_96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavlobu/deskreen/4a87fc16d4c448cf9936a63fd84c1712bb5016ee/resources/icons/icon_96x96.png
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=deskreen-main
2 | # sonar.testExecutionReportPaths=test-reporter.xml
3 | sonar.typescript.lcov.reportPaths=coverage/lcov.info
4 | sonar.cpd.exclusions=app/configs/*,app/**/__mocks__/*,app/**/mocks/*,app/**/*.spec.ts,app/**/*.spec.tsx,app/**/*.test.ts,app/**/*.test.tsx,app/serviceWorker.ts,app/index.tsx
5 | sonar.coverage.exclusions=app/configs/*,app/**/__mocks__/*,app/**/mocks/*,app/**/*.spec.ts,app/**/*.spec.tsx,app/**/*.test.ts,app/**/*.test.tsx,app/serviceWorker.ts,app/index.tsx
6 | sonar.sources=app
7 | sonar.tests=test
8 | sonar.host.url=http://localhost:9000
9 | sonar.login=7cdf1971934d910e71b44b78d54c154975c40199
10 | sonar.exclusions=app/client/**
11 | # sonar.login=039884f95817f7b26d781d7cdd47430cb3734a0a
12 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "plugin:testcafe/recommended",
3 | "env": {
4 | "jest/globals": true
5 | },
6 | "plugins": ["jest", "testcafe"],
7 | "rules": {
8 | "jest/no-disabled-tests": "warn",
9 | "jest/no-focused-tests": "error",
10 | "jest/no-identical-title": "error",
11 | "no-console": "off"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/integration/app/server/index.spec.ts:
--------------------------------------------------------------------------------
1 | import http from 'http';
2 | import SignalingServer from '../../../../app/server/index';
3 |
4 | describe('signaling server', () => {
5 | let server: typeof SignalingServer;
6 | beforeEach(() => {
7 | server = SignalingServer;
8 | });
9 |
10 | afterEach(() => {
11 | server.stop();
12 | });
13 |
14 | it('start() should return http.Server', async () => {
15 | const res = await server.start();
16 | expect(res instanceof http.Server).toBe(true);
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "CommonJS",
5 | "lib": ["dom", "esnext"],
6 | "declaration": true,
7 | "declarationMap": true,
8 | "noEmit": true,
9 | "jsx": "react",
10 | "strict": true,
11 | // "strictNullChecks": false,
12 | "pretty": true,
13 | "sourceMap": true,
14 | /* Additional Checks */
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": true,
19 | /* Module Resolution Options */
20 | "moduleResolution": "node",
21 | "esModuleInterop": true,
22 | "allowSyntheticDefaultImports": true,
23 | "resolveJsonModule": true,
24 | "allowJs": true,
25 | "skipLibCheck": true
26 | },
27 | "exclude": [
28 | "test",
29 | "release",
30 | "app/main.prod.js",
31 | "app/renderer.prod.js",
32 | "app/dist",
33 | "dll",
34 | "app/client"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/wiki/Home.md:
--------------------------------------------------------------------------------
1 | Welcome to the deskreen wiki!
2 |
3 | To edit [Deskreen wiki](https://github.com/pavlobu/deskreen/wiki) please open your PR to [Deskreen Repo](https://github.com/pavlobu/deskreen) with changes in `wiki/` folder.
4 |
--------------------------------------------------------------------------------