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>
This commit is contained in:
parent
b662c93ac7
commit
d12611d79c
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -15,6 +15,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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
const conference = createConferenceObjectFromURL(inputURL);
|
||||
|
||||
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) {
|
||||
// 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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
94
main.js
94
main.js
|
@ -4,6 +4,7 @@ const {
|
|||
BrowserWindow,
|
||||
Menu,
|
||||
app,
|
||||
ipcMain,
|
||||
shell
|
||||
} = require('electron');
|
||||
const contextMenu = require('electron-context-menu');
|
||||
|
@ -71,6 +72,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);
|
||||
}
|
||||
});
|
||||
|
|
11
package.json
11
package.json
|
@ -63,7 +63,16 @@
|
|||
},
|
||||
"directories": {
|
||||
"buildResources": "resources"
|
||||
}
|
||||
},
|
||||
"protocols": [
|
||||
{
|
||||
"name": "jitsi-protocol",
|
||||
"role": "Viewer",
|
||||
"schemes": [
|
||||
"jitsi-meet"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint"
|
||||
|
|
Loading…
Reference in New Issue