jitsi-meet-electron/main.js
csett86 295ea6d1ec
Switch to local storage and migrate old settings (#638)
This is a preparation for electron 14+, where electron-store is no longer
in a usable state (currently used version does not work due to remote module
removed in electron 14, latest version extends every app startup to 10 seconds).

Once sufficient migration period has passed, electron-store and
redux-persist-electron-storage and the ipc call introduced here
(electron-store-exists) can be removed.
2021-09-20 10:40:27 +02:00

403 lines
11 KiB
JavaScript

/* global __dirname, process */
const {
BrowserWindow,
Menu,
app,
ipcMain
} = require('electron');
const contextMenu = require('electron-context-menu');
const debug = require('electron-debug');
const isDev = require('electron-is-dev');
const { autoUpdater } = require('electron-updater');
const windowStateKeeper = require('electron-window-state');
const {
initPopupsConfigurationMain,
getPopupTarget,
setupAlwaysOnTopMain,
setupPowerMonitorMain,
setupScreenSharingMain
} = require('jitsi-meet-electron-utils');
const path = require('path');
const URL = require('url');
const config = require('./app/features/config');
const { openExternalLink } = require('./app/features/utils/openExternalLink');
const pkgJson = require('./package.json');
const { existsSync } = require('fs');
const showDevTools = Boolean(process.env.SHOW_DEV_TOOLS) || (process.argv.indexOf('--show-dev-tools') > -1);
// We need this because of https://github.com/electron/electron/issues/18214
app.commandLine.appendSwitch('disable-site-isolation-trials');
// This allows BrowserWindow.setContentProtection(true) to work on macOS.
// https://github.com/electron/electron/issues/19880
app.commandLine.appendSwitch('disable-features', 'IOSurfaceCapturer');
// Enable Opus RED field trial.
app.commandLine.appendSwitch('force-fieldtrials', 'WebRTC-Audio-Red-For-Opus/Enabled/');
// Enable optional PipeWire support.
if (!app.commandLine.hasSwitch('enable-features')) {
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
}
// Needed until robot.js is fixed: https://github.com/octalmage/robotjs/issues/580
app.allowRendererProcessReuse = false;
autoUpdater.logger = require('electron-log');
autoUpdater.logger.transports.file.level = 'info';
// Enable context menu so things like copy and paste work in input fields.
contextMenu({
showLookUpSelection: false,
showSearchWithGoogle: false,
showCopyImage: false,
showCopyImageAddress: false,
showSaveImage: false,
showSaveImageAs: false,
showInspectElement: true,
showServices: false
});
// Enable DevTools also on release builds to help troubleshoot issues. Don't
// show them automatically though.
debug({
isEnabled: true,
showDevTools
});
/**
* When in development mode:
* - Enable automatic reloads
*/
if (isDev) {
require('electron-reload')(path.join(__dirname, 'build'));
}
/**
* The window object that will load the iframe with Jitsi Meet.
* IMPORTANT: Must be defined as global in order to not be garbage collected
* acidentally.
*/
let mainWindow = null;
let webrtcInternalsWindow = 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.
*/
function setApplicationMenu() {
if (process.platform === 'darwin') {
const template = [ {
label: app.name,
submenu: [
{
role: 'services',
submenu: []
},
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}, {
label: 'Edit',
submenu: [ {
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:'
} ]
}, {
label: '&Window',
role: 'window',
submenu: [
{ role: 'minimize' },
{ role: 'close' }
]
} ];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
} else {
Menu.setApplicationMenu(null);
}
}
/**
* Opens new window with index.html(Jitsi Meet is loaded in iframe there).
*/
function createJitsiMeetWindow() {
// Application menu.
setApplicationMenu();
// Check for Updates.
if (!process.mas) {
autoUpdater.checkForUpdatesAndNotify();
}
// Load the previous window state with fallback to defaults.
const windowState = windowStateKeeper({
defaultWidth: 800,
defaultHeight: 600
});
// Path to root directory.
let basePath = isDev ? __dirname : app.getAppPath();
// runtime detection on mac if this is a universal build with app-arm64.asar'
// as prepared in https://github.com/electron/universal/blob/master/src/index.ts
// if universal build, load the arch-specific real asar as the app does not load otherwise
if (process.platform === 'darwin' && existsSync(path.join(app.getAppPath(), '..', 'app-arm64.asar'))) {
if (process.arch === 'arm64') {
basePath = app.getAppPath().replace('app.asar', 'app-arm64.asar');
} else if (process.arch === 'x64') {
basePath = app.getAppPath().replace('app.asar', 'app-x64.asar');
}
}
// URL for index.html which will be our entry point.
const indexURL = URL.format({
pathname: path.resolve(basePath, './build/index.html'),
protocol: 'file:',
slashes: true
});
// Options used when creating the main Jitsi Meet window.
// Use a preload script in order to provide node specific functionality
// to a isolated BrowserWindow in accordance with electron security
// guideline.
const options = {
x: windowState.x,
y: windowState.y,
width: windowState.width,
height: windowState.height,
icon: path.resolve(basePath, './resources/icon.png'),
minWidth: 800,
minHeight: 600,
show: false,
webPreferences: {
enableBlinkFeatures: 'RTCInsertableStreams,WebAssemblySimd,WebAssemblyCSP',
enableRemoteModule: true,
contextIsolation: false,
nativeWindowOpen: true,
nodeIntegration: false,
preload: path.resolve(basePath, './build/preload.js')
}
};
mainWindow = new BrowserWindow(options);
windowState.manage(mainWindow);
mainWindow.loadURL(indexURL);
initPopupsConfigurationMain(mainWindow);
setupAlwaysOnTopMain(mainWindow);
setupPowerMonitorMain(mainWindow);
setupScreenSharingMain(mainWindow, config.default.appName, pkgJson.build.appId);
mainWindow.webContents.on('new-window', (event, url, frameName) => {
const target = getPopupTarget(url, frameName);
if (!target || target === 'browser') {
event.preventDefault();
openExternalLink(url);
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
/**
* When someone tries to enter something like jitsi-meet://test
* while app is closed
* it will trigger this event below
*/
handleProtocolCall(process.argv.pop());
}
/**
* Opens new window with WebRTC internals.
*/
function createWebRTCInternalsWindow() {
const options = {
minWidth: 800,
minHeight: 600,
show: true
};
webrtcInternalsWindow = new BrowserWindow(options);
webrtcInternalsWindow.loadURL('chrome://webrtc-internals');
}
/**
* 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);
}
}
/**
* Force Single Instance Application.
* Handle this on darwin via LSMultipleInstancesProhibited in Info.plist as below does not work on MAS
*/
const gotInstanceLock = process.platform === 'darwin' ? true : app.requestSingleInstanceLock();
if (!gotInstanceLock) {
app.quit();
process.exit(0);
}
/**
* Run the application.
*/
app.on('activate', () => {
if (mainWindow === null) {
createJitsiMeetWindow();
}
});
app.on('certificate-error',
// eslint-disable-next-line max-params
(event, webContents, url, error, certificate, callback) => {
if (isDev) {
event.preventDefault();
callback(true);
} else {
callback(false);
}
}
);
app.on('ready', createJitsiMeetWindow);
if (isDev) {
app.on('ready', createWebRTCInternalsWindow);
}
app.on('second-instance', (event, commandLine) => {
/**
* If someone creates second instance of the application, set focus on
* existing window.
*/
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());
}
});
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);
}
});
ipcMain.on('electron-store-exists', event => {
event.returnValue = existsSync(path.join(app.getPath('userData'), 'config.json'));
});