From 99dd6433a6c5334cd3e00aa8a24fbb3354f7d239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 10 Nov 2019 23:57:28 +0100 Subject: [PATCH] Merged electron into main code. One unified codebase for both web and native version of client. --- app/Procfile | 2 + app/package.json | 11 ++- app/public/config/config.example.js | 8 ++- app/src/ScreenShare.js | 106 +++++++++++++++++++++++----- app/src/components/App.js | 4 +- app/src/components/ChooseRoom.js | 15 ++-- app/src/components/JoinDialog.js | 15 ++-- app/src/components/Room.js | 15 ++-- app/src/electron-starter.js | 50 +++++++++++++ app/src/electron-wait-react.js | 39 ++++++++++ app/src/index.js | 2 +- app/src/urlFactory.js | 8 ++- 12 files changed, 230 insertions(+), 45 deletions(-) create mode 100644 app/Procfile create mode 100644 app/src/electron-starter.js create mode 100644 app/src/electron-wait-react.js diff --git a/app/Procfile b/app/Procfile new file mode 100644 index 0000000..a25f475 --- /dev/null +++ b/app/Procfile @@ -0,0 +1,2 @@ +react: npm start +electron: node src/electron-wait-react \ No newline at end of file diff --git a/app/package.json b/app/package.json index 4716292..1c669b7 100644 --- a/app/package.json +++ b/app/package.json @@ -5,6 +5,8 @@ "description": "multiparty meeting service", "author": "Håvar Aambø Fosstveit ", "license": "MIT", + "homepage": "./", + "main": "src/electron-starter.js", "dependencies": { "@material-ui/core": "^4.5.1", "@material-ui/icons": "^4.5.1", @@ -13,6 +15,7 @@ "domready": "^1.0.8", "file-saver": "^2.0.2", "hark": "^1.2.3", + "is-electron": "^2.2.0", "marked": "^0.7.0", "mediasoup-client": "^3.2.7", "notistack": "^0.9.5", @@ -41,7 +44,9 @@ "start": "HTTPS=true PORT=4443 react-scripts start", "build": "react-scripts build && mkdir -p ../server/public && rm -rf ../server/public/* && cp -r build/* ../server/public/", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "electron": "electron .", + "dev": "nf start -p 3000" }, "browserslist": [ ">0.2%", @@ -50,8 +55,10 @@ "not op_mini all" ], "devDependencies": { + "electron": "^7.1.1", "eslint": "^6.5.1", "eslint-plugin-import": "^2.18.2", - "eslint-plugin-react": "^7.16.0" + "eslint-plugin-react": "^7.16.0", + "foreman": "^3.0.1" } } diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index 43dab12..8739850 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -1,9 +1,11 @@ // eslint-disable-next-line var config = { - loginEnabled : false, - developmentPort : 3443, - turnServers : [ + loginEnabled : false, + developmentPort : 3443, + productionPort : 443, + multipartyServer : 'letsmeet.no', + turnServers : [ { urls : [ 'turn:turn.example.com:443?transport=tcp' diff --git a/app/src/ScreenShare.js b/app/src/ScreenShare.js index c9336d1..c819fb2 100644 --- a/app/src/ScreenShare.js +++ b/app/src/ScreenShare.js @@ -1,3 +1,70 @@ +import isElectron from 'is-electron'; + +let electron = null; + +if (isElectron()) + electron = window.require('electron'); + +class ElectronScreenShare +{ + constructor() + { + this._stream = null; + } + + start() + { + return Promise.resolve() + .then(() => + { + return electron.desktopCapturer.getSources({ types: [ 'window', 'screen' ] }); + }) + .then((sources) => + { + for (const source of sources) + { + // Currently only getting whole screen + if (source.name === 'Entire Screen') + { + return navigator.mediaDevices.getUserMedia({ + audio : false, + video : + { + mandatory : + { + chromeMediaSource : 'desktop', + chromeMediaSourceId : source.id + } + } + }); + } + } + }) + .then((stream) => + { + this._stream = stream; + + return stream; + }); + } + + stop() + { + if (this._stream instanceof MediaStream === false) + { + return; + } + + this._stream.getTracks().forEach((track) => track.stop()); + this._stream = null; + } + + isScreenShareAvailable() + { + return true; + } +} + class DisplayMediaScreenShare { constructor() @@ -131,26 +198,31 @@ export default class ScreenShare { static create(device) { - switch (device.flag) + if (isElectron()) + return new ElectronScreenShare(); + else { - case 'firefox': + switch (device.flag) { - if (device.version < 66.0) - return new FirefoxScreenShare(); - else + case 'firefox': + { + if (device.version < 66.0) + return new FirefoxScreenShare(); + else + return new DisplayMediaScreenShare(); + } + case 'chrome': + { return new DisplayMediaScreenShare(); - } - case 'chrome': - { - return new DisplayMediaScreenShare(); - } - case 'msedge': - { - return new DisplayMediaScreenShare(); - } - default: - { - return new DefaultScreenShare(); + } + case 'msedge': + { + return new DisplayMediaScreenShare(); + } + default: + { + return new DefaultScreenShare(); + } } } } diff --git a/app/src/components/App.js b/app/src/components/App.js index c596efa..0b6b473 100644 --- a/app/src/components/App.js +++ b/app/src/components/App.js @@ -1,5 +1,5 @@ import React, { useEffect, Suspense } from 'react'; -import { useParams} from 'react-router'; +import { useParams } from 'react-router'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import JoinDialog from './JoinDialog'; @@ -14,7 +14,7 @@ const App = (props) => room } = props; - let { id } = useParams(); + const { id } = useParams(); useEffect(() => { diff --git a/app/src/components/ChooseRoom.js b/app/src/components/ChooseRoom.js index c588a1f..b83962b 100644 --- a/app/src/components/ChooseRoom.js +++ b/app/src/components/ChooseRoom.js @@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../RoomContext'; +import isElectron from 'is-electron'; import PropTypes from 'prop-types'; import { useIntl, FormattedMessage } from 'react-intl'; import randomString from 'random-string'; @@ -239,12 +240,14 @@ const ChooseRoom = ({ - - - + { !isElectron() && + + + + } ); diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index e549d47..040542e 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../RoomContext'; +import isElectron from 'is-electron'; import * as settingsActions from '../actions/settingsActions'; import PropTypes from 'prop-types'; import { useIntl, FormattedMessage } from 'react-intl'; @@ -334,12 +335,14 @@ const JoinDialog = ({ } - - - + { !isElectron() && + + + + } ); diff --git a/app/src/components/Room.js b/app/src/components/Room.js index 23d022a..2410687 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import * as appPropTypes from './appPropTypes'; import { withStyles } from '@material-ui/core/styles'; +import isElectron from 'is-electron'; import * as roomActions from '../actions/roomActions'; import * as toolareaActions from '../actions/toolareaActions'; import { idle } from '../utils'; @@ -152,12 +153,14 @@ class Room extends React.PureComponent return (
- - - + { !isElectron() && + + + + } diff --git a/app/src/electron-starter.js b/app/src/electron-starter.js new file mode 100644 index 0000000..f4a55f7 --- /dev/null +++ b/app/src/electron-starter.js @@ -0,0 +1,50 @@ +const electron = require('electron'); + +const app = electron.app; + +const BrowserWindow = electron.BrowserWindow; + +const path = require('path'); +const url = require('url'); + +let mainWindow; + +function createWindow() +{ + mainWindow = new BrowserWindow({ + width : 1280, + height : 720, + webPreferences : { nodeIntegration: true } + }); + + const startUrl = process.env.ELECTRON_START_URL || url.format({ + pathname : path.join(__dirname, '/../build/index.html'), + protocol : 'file:', + slashes : true + }); + + mainWindow.loadURL(startUrl); + + mainWindow.on('closed', () => + { + mainWindow = null; + }); +} + +app.on('ready', createWindow); + +app.on('window-all-closed', () => +{ + if (process.platform !== 'darwin') + { + app.quit(); + } +}); + +app.on('activate', () => +{ + if (mainWindow === null) + { + createWindow(); + } +}); diff --git a/app/src/electron-wait-react.js b/app/src/electron-wait-react.js new file mode 100644 index 0000000..a64517f --- /dev/null +++ b/app/src/electron-wait-react.js @@ -0,0 +1,39 @@ +const net = require('net'); +const port = process.env.PORT ? (process.env.PORT - 100) : 3000; + +process.env.ELECTRON_START_URL = `http://localhost:${port}`; + +const client = new net.Socket(); + +let startedElectron = false; + +const tryConnection = () => + client.connect({ port: port }, () => + { + client.end(); + + if (!startedElectron) + { + // eslint-disable-next-line no-console + console.log('starting electron'); + + startedElectron = true; + + const exec = require('child_process').exec; + + const electron = exec('npm run electron'); + + electron.stdout.on('data', (data) => + { + // eslint-disable-next-line no-console + console.log(`stdout: ${data.toString()}`); + }); + } + }); + +tryConnection(); + +client.on('error', () => +{ + setTimeout(tryConnection, 1000); +}); diff --git a/app/src/index.js b/app/src/index.js index 8f0c277..4da86bb 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -3,7 +3,7 @@ import React, { Suspense } from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl'; -import { Route, BrowserRouter as Router } from 'react-router-dom' +import { Route, HashRouter as Router } from 'react-router-dom'; import randomString from 'random-string'; import Logger from './Logger'; import debug from 'debug'; diff --git a/app/src/urlFactory.js b/app/src/urlFactory.js index 7c4dbcd..9f11115 100644 --- a/app/src/urlFactory.js +++ b/app/src/urlFactory.js @@ -1,8 +1,12 @@ export function getSignalingUrl(peerId, roomId) { - const hostname = window.location.hostname; + const hostname = window.config.multipartyServer; - const port = process.env.NODE_ENV !== 'production' ? window.config.developmentPort : window.location.port; + const port = + process.env.NODE_ENV !== 'production' ? + window.config.developmentPort + : + window.config.productionPort; const url = `wss://${hostname}:${port}/?peerId=${peerId}&roomId=${roomId}`;