aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Hamerling <chamerling@linagora.com>2020-06-10 14:21:13 +0200
committerGitHub <noreply@github.com>2020-06-10 14:21:13 +0200
commitd12611d79cc21f5b7b3c900c3930dcffa2fa001e (patch)
tree7970bae46bfff32c53e2ad9723e9ee79b19c6383
parentb662c93ac76eff9ffc989458df7d36dfb289007b (diff)
Add protocol to open conference links with the app
Co-authored-by: Christophe HAMERLING <chamerling@linagora.com> Co-authored-by: Klemens Arro <klemens.arro@admcloudtech.com> Co-authored-by: Goran Urukalo <goran.urukalo@teletrader.com>
-rw-r--r--README.md1
-rw-r--r--app/features/app/components/App.js68
-rw-r--r--app/features/config/index.js6
-rw-r--r--app/features/utils/functions.js37
-rw-r--r--app/features/welcome/components/Welcome.js29
-rw-r--r--app/preload/preload.js30
-rw-r--r--main.js94
-rw-r--r--package.json11
8 files changed, 243 insertions, 33 deletions
diff --git a/README.md b/README.md
index 58ff8f3..9519763 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ Desktop application for [Jitsi Meet] built with [Electron].
- Builtin auto-updates
- Remote control
- Always-On-Top window
+- Support for deeplinks such as `jitsi-meet://myroom` (will open `myroom` on the configured Jitsi instance) or `jitsi-meet://jitsi.mycompany.com/myroom` (will open `myroom` on the Jitsi instance running on `jitsi.mycompany.com`)
## Installation
diff --git a/app/features/app/components/App.js b/app/features/app/components/App.js
index b934ddf..c34bbbf 100644
--- a/app/features/app/components/App.js
+++ b/app/features/app/components/App.js
@@ -4,26 +4,84 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React, { Component } from 'react';
import { Route, Switch } from 'react-router';
-import { ConnectedRouter as Router } from 'react-router-redux';
+import { connect } from 'react-redux';
+import { ConnectedRouter as Router, push } from 'react-router-redux';
import { Conference } from '../../conference';
import config from '../../config';
import { history } from '../../router';
+import { createConferenceObjectFromURL } from '../../utils';
import { Welcome } from '../../welcome';
/**
* Main component encapsulating the entire application.
*/
-export default class App extends Component<*> {
+class App extends Component<*> {
/**
* Initializes a new {@code App} instance.
*
* @inheritdoc
*/
- constructor() {
- super();
+ constructor(props) {
+ super(props);
document.title = config.appName;
+
+ this._listenOnProtocolMessages
+ = this._listenOnProtocolMessages.bind(this);
+ }
+
+ /**
+ * Implements React's {@link Component#componentDidMount()}.
+ *
+ * @returns {void}
+ */
+ componentDidMount() {
+ // start listening on this events
+ window.jitsiNodeAPI.ipc.on('protocol-data-msg', this._listenOnProtocolMessages);
+
+ // send notification to main process
+ window.jitsiNodeAPI.ipc.send('renderer-ready');
+ }
+
+ /**
+ * Implements React's {@link Component#componentWillUnmount()}.
+ *
+ * @returns {void}
+ */
+ componentWillUnmount() {
+ // remove listening for this events
+ window.jitsiNodeAPI.ipc.removeListener(
+ 'protocol-data-msg',
+ this._listenOnProtocolMessages
+ );
+ }
+
+ _listenOnProtocolMessages: (*) => void;
+
+ /**
+ * Handler when main proccess contact us.
+ *
+ * @param {Object} event - Message event.
+ * @param {string} inputURL - String with room name.
+ *
+ * @returns {void}
+ */
+ _listenOnProtocolMessages(event, inputURL: string) {
+ // Remove trailing slash if one exists.
+ if (inputURL.substr(-1) === '/') {
+ inputURL = inputURL.substr(0, inputURL.length - 1); // eslint-disable-line no-param-reassign
+ }
+
+ const conference = createConferenceObjectFromURL(inputURL);
+
+ // Don't navigate if conference couldn't be created
+ if (!conference) {
+ return;
+ }
+
+ // change route when we are notified
+ this.props.dispatch(push('/conference', conference));
}
/**
@@ -50,3 +108,5 @@ export default class App extends Component<*> {
);
}
}
+
+export default connect()(App);
diff --git a/app/features/config/index.js b/app/features/config/index.js
index dde9fb3..9983fe9 100644
--- a/app/features/config/index.js
+++ b/app/features/config/index.js
@@ -16,6 +16,12 @@ export default {
appName: 'Jitsi Meet',
/**
+ * The prefix for application protocol.
+ * You will also need to replace this in package.json.
+ */
+ appProtocolPrefix: 'jitsi-meet',
+
+ /**
* The default server URL of Jitsi Meet Deployment that will be used.
*/
defaultServerURL: 'https://meet.jit.si',
diff --git a/app/features/utils/functions.js b/app/features/utils/functions.js
index 4c7bf5a..e8e11e2 100644
--- a/app/features/utils/functions.js
+++ b/app/features/utils/functions.js
@@ -54,3 +54,40 @@ export function normalizeServerURL(url: string) {
export function openExternalLink(link: string) {
window.jitsiNodeAPI.openExternalLink(link);
}
+
+
+/**
+ * Get URL, extract room name from it and create a Conference object.
+ *
+ * @param {string} inputURL - Combined server url with room separated by /.
+ * @returns {Object}
+ */
+export function createConferenceObjectFromURL(inputURL: string) {
+ const lastIndexOfSlash = inputURL.lastIndexOf('/');
+ let room;
+ let serverURL;
+
+ if (lastIndexOfSlash === -1) {
+ // This must be only the room name.
+ room = inputURL;
+ } else {
+ // Take the substring after last slash to be the room name.
+ room = inputURL.substring(lastIndexOfSlash + 1);
+
+ // Take the substring before last slash to be the Server URL.
+ serverURL = inputURL.substring(0, lastIndexOfSlash);
+
+ // Normalize the server URL.
+ serverURL = normalizeServerURL(serverURL);
+ }
+
+ // Don't navigate if no room was specified.
+ if (!room) {
+ return;
+ }
+
+ return {
+ room,
+ serverURL
+ };
+}
diff --git a/app/features/welcome/components/Welcome.js b/app/features/welcome/components/Welcome.js
index 4a04661..f0bb138 100644
--- a/app/features/welcome/components/Welcome.js
+++ b/app/features/welcome/components/Welcome.js
@@ -15,7 +15,7 @@ import { push } from 'react-router-redux';
import { Navbar } from '../../navbar';
import { Onboarding, startOnboarding } from '../../onboarding';
import { RecentList } from '../../recent-list';
-import { normalizeServerURL } from '../../utils';
+import { createConferenceObjectFromURL } from '../../utils';
import { Body, FieldWrapper, Form, Header, Label, Wrapper } from '../styled';
@@ -206,33 +206,14 @@ class Welcome extends Component<Props, State> {
*/
_onJoin() {
const inputURL = this.state.url || this.state.generatedRoomname;
- const lastIndexOfSlash = inputURL.lastIndexOf('/');
- let room;
- let serverURL;
-
- if (lastIndexOfSlash === -1) {
- // This must be only the room name.
- room = inputURL;
- } else {
- // Take the substring after last slash to be the room name.
- room = inputURL.substring(lastIndexOfSlash + 1);
-
- // Take the substring before last slash to be the Server URL.
- serverURL = inputURL.substring(0, lastIndexOfSlash);
-
- // Normalize the server URL.
- serverURL = normalizeServerURL(serverURL);
- }
+ const conference = createConferenceObjectFromURL(inputURL);
- // Don't navigate if no room was specified.
- if (!room) {
+ // Don't navigate if conference couldn't be created
+ if (!conference) {
return;
}
- this.props.dispatch(push('/conference', {
- room,
- serverURL
- }));
+ this.props.dispatch(push('/conference', conference));
}
_onURLChange: (*) => void;
diff --git a/app/preload/preload.js b/app/preload/preload.js
index cbe9aab..85064ea 100644
--- a/app/preload/preload.js
+++ b/app/preload/preload.js
@@ -1,11 +1,10 @@
const createElectronStorage = require('redux-persist-electron-storage');
-const { shell } = require('electron');
+const { ipcRenderer, shell } = require('electron');
const os = require('os');
const url = require('url');
const jitsiMeetElectronUtils = require('jitsi-meet-electron-utils');
-
const protocolRegex = /^https?:/i;
/**
@@ -28,10 +27,35 @@ function openExternalLink(link) {
}
}
+const whitelistedIpcChannels = [ 'protocol-data-msg', 'renderer-ready' ];
window.jitsiNodeAPI = {
createElectronStorage,
osUserInfo: os.userInfo,
openExternalLink,
- jitsiMeetElectronUtils
+ jitsiMeetElectronUtils,
+ shellOpenExternal: shell.openExternal,
+ ipc: {
+ on: (channel, listener) => {
+ if (!whitelistedIpcChannels.includes(channel)) {
+ return;
+ }
+
+ return ipcRenderer.on(channel, listener);
+ },
+ send: channel => {
+ if (!whitelistedIpcChannels.includes(channel)) {
+ return;
+ }
+
+ return ipcRenderer.send(channel);
+ },
+ removeListener: (channel, listener) => {
+ if (!whitelistedIpcChannels.includes(channel)) {
+ return;
+ }
+
+ return ipcRenderer.removeListener(channel, listener);
+ }
+ }
};
diff --git a/main.js b/main.js
index 296741d..0ca6cde 100644
--- a/main.js
+++ b/main.js
@@ -4,6 +4,7 @@ const {
BrowserWindow,
Menu,
app,
+ ipcMain,
shell
} = require('electron');
const contextMenu = require('electron-context-menu');
@@ -72,6 +73,14 @@ if (isDev) {
let mainWindow = null;
/**
+ * Add protocol data
+ */
+const appProtocolSurplus = `${config.default.appProtocolPrefix}://`;
+let rendererReady = false;
+let protocolDataForFrontApp = null;
+
+
+/**
* Sets the application menu. It is hidden on all platforms except macOS because
* otherwise copy and paste functionality is not available.
*/
@@ -211,6 +220,44 @@ function createJitsiMeetWindow() {
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
+
+ /**
+ * This is for windows [win32]
+ * so when someone tries to enter something like jitsi-meet://test
+ * while app is closed
+ * it will trigger this event below
+ */
+ if (process.platform === 'win32') {
+ handleProtocolCall(process.argv.pop());
+ }
+}
+
+/**
+ * Handler for application protocol links to initiate a conference.
+ */
+function handleProtocolCall(fullProtocolCall) {
+ // don't touch when something is bad
+ if (
+ !fullProtocolCall
+ || fullProtocolCall.trim() === ''
+ || fullProtocolCall.indexOf(appProtocolSurplus) !== 0
+ ) {
+ return;
+ }
+
+ const inputURL = fullProtocolCall.replace(appProtocolSurplus, '');
+
+ if (app.isReady() && mainWindow === null) {
+ createJitsiMeetWindow();
+ }
+
+ protocolDataForFrontApp = inputURL;
+
+ if (rendererReady) {
+ mainWindow
+ .webContents
+ .send('protocol-data-msg', inputURL);
+ }
}
/**
@@ -247,7 +294,7 @@ app.on('certificate-error',
app.on('ready', createJitsiMeetWindow);
-app.on('second-instance', () => {
+app.on('second-instance', (event, commandLine) => {
/**
* If someone creates second instance of the application, set focus on
* existing window.
@@ -255,6 +302,13 @@ app.on('second-instance', () => {
if (mainWindow) {
mainWindow.isMinimized() && mainWindow.restore();
mainWindow.focus();
+
+ /**
+ * This is for windows [win32]
+ * so when someone tries to enter something like jitsi-meet://test
+ * while app is opened it will trigger protocol handler.
+ */
+ handleProtocolCall(commandLine.pop());
}
});
@@ -264,3 +318,41 @@ app.on('window-all-closed', () => {
app.quit();
}
});
+
+// remove so we can register each time as we run the app.
+app.removeAsDefaultProtocolClient(config.default.appProtocolPrefix);
+
+// If we are running a non-packaged version of the app && on windows
+if (isDev && process.platform === 'win32') {
+ // Set the path of electron.exe and your app.
+ // These two additional parameters are only available on windows.
+ app.setAsDefaultProtocolClient(
+ config.default.appProtocolPrefix,
+ process.execPath,
+ [ path.resolve(process.argv[1]) ]
+ );
+} else {
+ app.setAsDefaultProtocolClient(config.default.appProtocolPrefix);
+}
+
+/**
+ * This is for mac [darwin]
+ * so when someone tries to enter something like jitsi-meet://test
+ * it will trigger this event below
+ */
+app.on('open-url', (event, data) => {
+ event.preventDefault();
+ handleProtocolCall(data);
+});
+
+/**
+ * This is to notify main.js [this] that front app is ready to receive messages.
+ */
+ipcMain.on('renderer-ready', () => {
+ rendererReady = true;
+ if (protocolDataForFrontApp) {
+ mainWindow
+ .webContents
+ .send('protocol-data-msg', protocolDataForFrontApp);
+ }
+});
diff --git a/package.json b/package.json
index 0e52f08..d1fea59 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,16 @@
},
"directories": {
"buildResources": "resources"
- }
+ },
+ "protocols": [
+ {
+ "name": "jitsi-protocol",
+ "role": "Viewer",
+ "schemes": [
+ "jitsi-meet"
+ ]
+ }
+ ]
},
"pre-commit": [
"lint"