2016-11-03 02:49:28 +08:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
/*
|
2016-11-04 01:06:41 +08:00
|
|
|
Copyright 2016 Aviral Dasgupta
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
2016-11-03 02:49:28 +08:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2016-12-05 22:08:27 +08:00
|
|
|
// Squirrel on windows starts the app with various flags
|
|
|
|
// as hooks to tell us when we've been installed/uninstalled
|
|
|
|
// etc.
|
|
|
|
const check_squirrel_hooks = require('./squirrelhooks');
|
|
|
|
if (check_squirrel_hooks()) return;
|
2016-12-01 19:35:26 +08:00
|
|
|
|
2016-12-05 22:08:27 +08:00
|
|
|
const electron = require('electron');
|
2016-11-03 02:49:28 +08:00
|
|
|
const url = require('url');
|
|
|
|
|
2017-01-19 22:29:07 +08:00
|
|
|
const tray = require('./tray');
|
|
|
|
|
2016-11-03 02:49:28 +08:00
|
|
|
const VectorMenu = require('./vectormenu');
|
|
|
|
|
2017-04-13 01:13:25 +08:00
|
|
|
const windowStateKeeper = require('electron-window-state');
|
|
|
|
|
2016-11-08 19:07:36 +08:00
|
|
|
let vectorConfig = {};
|
|
|
|
try {
|
2016-11-26 04:09:21 +08:00
|
|
|
vectorConfig = require('../../webapp/config.json');
|
2016-11-08 19:07:36 +08:00
|
|
|
} catch (e) {
|
|
|
|
// it would be nice to check the error code here and bail if the config
|
|
|
|
// is unparseable, but we get MODULE_NOT_FOUND in the case of a missing
|
|
|
|
// file or invalid json, so node is just very unhelpful.
|
|
|
|
// Continue with the defaults (ie. an empty config)
|
|
|
|
}
|
|
|
|
|
2016-11-03 02:49:28 +08:00
|
|
|
const PERMITTED_URL_SCHEMES = [
|
|
|
|
'http:',
|
|
|
|
'https:',
|
|
|
|
'mailto:',
|
|
|
|
];
|
|
|
|
|
|
|
|
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
|
2016-12-05 22:08:27 +08:00
|
|
|
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
|
2016-11-03 02:49:28 +08:00
|
|
|
|
|
|
|
let mainWindow = null;
|
|
|
|
let appQuitting = false;
|
|
|
|
|
|
|
|
function safeOpenURL(target) {
|
|
|
|
// openExternal passes the target to open/start/xdg-open,
|
|
|
|
// so put fairly stringent limits on what can be opened
|
|
|
|
// (for instance, open /bin/sh does indeed open a terminal
|
|
|
|
// with a shell, albeit with no arguments)
|
|
|
|
const parsed_url = url.parse(target);
|
|
|
|
if (PERMITTED_URL_SCHEMES.indexOf(parsed_url.protocol) > -1) {
|
|
|
|
// explicitly use the URL re-assembled by the url library,
|
|
|
|
// so we know the url parser has understood all the parts
|
|
|
|
// of the input string
|
|
|
|
const new_target = url.format(parsed_url);
|
|
|
|
electron.shell.openExternal(new_target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onWindowOrNavigate(ev, target) {
|
|
|
|
// always prevent the default: if something goes wrong,
|
|
|
|
// we don't want to end up opening it in the electron
|
|
|
|
// app, as we could end up opening any sort of random
|
|
|
|
// url in a window that has node scripting access.
|
|
|
|
ev.preventDefault();
|
|
|
|
safeOpenURL(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onLinkContextMenu(ev, params) {
|
|
|
|
const popup_menu = new electron.Menu();
|
|
|
|
popup_menu.append(new electron.MenuItem({
|
|
|
|
label: params.linkURL,
|
|
|
|
click() {
|
|
|
|
safeOpenURL(params.linkURL);
|
|
|
|
},
|
|
|
|
}));
|
2017-05-18 18:32:10 +08:00
|
|
|
popup_menu.append(new electron.MenuItem({
|
|
|
|
label: 'Copy Link Address',
|
|
|
|
click() {
|
|
|
|
electron.clipboard.writeText(params.linkURL);
|
|
|
|
},
|
|
|
|
}));
|
2016-11-03 02:49:28 +08:00
|
|
|
popup_menu.popup();
|
|
|
|
ev.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
function installUpdate() {
|
|
|
|
// for some reason, quitAndInstall does not fire the
|
|
|
|
// before-quit event, so we need to set the flag here.
|
|
|
|
appQuitting = true;
|
2016-12-05 22:08:27 +08:00
|
|
|
electron.autoUpdater.quitAndInstall();
|
2016-11-03 02:49:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function pollForUpdates() {
|
|
|
|
try {
|
2016-12-05 22:08:27 +08:00
|
|
|
electron.autoUpdater.checkForUpdates();
|
2016-11-03 02:49:28 +08:00
|
|
|
} catch (e) {
|
|
|
|
console.log("Couldn't check for update", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-05 22:08:27 +08:00
|
|
|
function startAutoUpdate(update_base_url) {
|
|
|
|
if (update_base_url.slice(-1) !== '/') {
|
|
|
|
update_base_url = update_base_url + '/';
|
2016-11-08 19:07:36 +08:00
|
|
|
}
|
2016-11-03 02:49:28 +08:00
|
|
|
try {
|
2016-12-05 22:08:27 +08:00
|
|
|
// For reasons best known to Squirrel, the way it checks for updates
|
|
|
|
// is completely different between macOS and windows. On macOS, it
|
|
|
|
// hits a URL that either gives it a 200 with some json or
|
|
|
|
// 204 No Content. On windows it takes a base path and looks for
|
|
|
|
// files under that path.
|
2016-11-03 02:49:28 +08:00
|
|
|
if (process.platform == 'darwin') {
|
2017-01-17 21:16:08 +08:00
|
|
|
// include the current version in the URL we hit. Electron doesn't add
|
|
|
|
// it anywhere (apart from the User-Agent) so it's up to us. We could
|
|
|
|
// (and previously did) just use the User-Agent, but this doesn't
|
|
|
|
// rely on NSURLConnection setting the User-Agent to what we expect,
|
2017-01-17 22:04:42 +08:00
|
|
|
// and also acts as a convenient cache-buster to ensure that when the
|
|
|
|
// app updates it always gets a fresh value to avoid update-looping.
|
2017-01-17 21:16:08 +08:00
|
|
|
electron.autoUpdater.setFeedURL(
|
|
|
|
update_base_url +
|
|
|
|
'macos/?localVersion=' + encodeURIComponent(electron.app.getVersion())
|
|
|
|
);
|
2016-12-05 22:08:27 +08:00
|
|
|
} else if (process.platform == 'win32') {
|
|
|
|
electron.autoUpdater.setFeedURL(update_base_url + 'win32/' + process.arch + '/');
|
2016-12-02 03:33:42 +08:00
|
|
|
} else {
|
2016-12-05 22:08:27 +08:00
|
|
|
// Squirrel / electron only supports auto-update on these two platforms.
|
|
|
|
// I'm not even going to try to guess which feed style they'd use if they
|
|
|
|
// implemented it on Linux, or if it would be different again.
|
|
|
|
console.log("Auto update not supported on this platform");
|
2016-11-03 02:49:28 +08:00
|
|
|
}
|
|
|
|
// We check for updates ourselves rather than using 'updater' because we need to
|
|
|
|
// do it in the main process (and we don't really need to check every 10 minutes:
|
|
|
|
// every hour should be just fine for a desktop app)
|
|
|
|
// However, we still let the main window listen for the update events.
|
2016-12-05 22:08:27 +08:00
|
|
|
// We also wait a short time before checking for updates the first time because
|
|
|
|
// of squirrel on windows and it taking a small amount of time to release a
|
|
|
|
// lock file.
|
2016-11-07 19:41:41 +08:00
|
|
|
setTimeout(pollForUpdates, INITIAL_UPDATE_DELAY_MS);
|
2016-11-03 02:49:28 +08:00
|
|
|
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
|
|
|
|
} catch (err) {
|
|
|
|
// will fail if running in debug mode
|
|
|
|
console.log("Couldn't enable update checking", err);
|
|
|
|
}
|
2016-11-08 19:07:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// handle uncaught errors otherwise it displays
|
|
|
|
// stack traces in popup dialogs, which is terrible (which
|
|
|
|
// it will do any time the auto update poke fails, and there's
|
|
|
|
// no other way to catch this error).
|
|
|
|
// Assuming we generally run from the console when developing,
|
|
|
|
// this is far preferable.
|
2017-05-17 17:39:43 +08:00
|
|
|
process.on('uncaughtException', function(error) {
|
2016-11-08 19:07:36 +08:00
|
|
|
console.log("Unhandled exception", error);
|
|
|
|
});
|
|
|
|
|
|
|
|
electron.ipcMain.on('install_update', installUpdate);
|
|
|
|
|
2017-05-17 17:39:43 +08:00
|
|
|
let focusHandlerAttached = false;
|
|
|
|
electron.ipcMain.on('setBadgeCount', function(ev, count) {
|
|
|
|
electron.app.setBadgeCount(count);
|
|
|
|
if (process.platform === 'win32' && mainWindow && !mainWindow.isFocused()) {
|
|
|
|
if (count > 0) {
|
|
|
|
if (!focusHandlerAttached) {
|
|
|
|
mainWindow.once('focus', () => {
|
|
|
|
mainWindow.flashFrame(false);
|
|
|
|
focusHandlerAttached = false;
|
|
|
|
});
|
|
|
|
focusHandlerAttached = true;
|
|
|
|
}
|
|
|
|
mainWindow.flashFrame(true);
|
|
|
|
} else {
|
|
|
|
mainWindow.flashFrame(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-01-11 02:39:21 +08:00
|
|
|
electron.app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
|
|
|
|
2017-01-20 00:01:37 +08:00
|
|
|
const shouldQuit = electron.app.makeSingleInstance((commandLine, workingDirectory) => {
|
|
|
|
// Someone tried to run a second instance, we should focus our window.
|
|
|
|
if (mainWindow) {
|
|
|
|
if (!mainWindow.isVisible()) mainWindow.show();
|
|
|
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
|
|
mainWindow.focus();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (shouldQuit) {
|
2017-04-25 17:18:17 +08:00
|
|
|
console.log("Other instance detected: exiting");
|
2017-01-20 00:01:37 +08:00
|
|
|
electron.app.quit()
|
|
|
|
}
|
|
|
|
|
2016-11-08 19:07:36 +08:00
|
|
|
electron.app.on('ready', () => {
|
2016-12-05 22:08:27 +08:00
|
|
|
if (vectorConfig.update_base_url) {
|
|
|
|
console.log("Starting auto update with base URL: " + vectorConfig.update_base_url);
|
|
|
|
startAutoUpdate(vectorConfig.update_base_url);
|
|
|
|
} else {
|
|
|
|
console.log("No update_base_url is defined: auto update is disabled");
|
|
|
|
}
|
2016-11-03 02:49:28 +08:00
|
|
|
|
2016-12-07 19:19:51 +08:00
|
|
|
const icon_path = `${__dirname}/../img/riot.` + (
|
|
|
|
process.platform == 'win32' ? 'ico' : 'png'
|
|
|
|
);
|
|
|
|
|
2017-04-13 01:13:25 +08:00
|
|
|
// Load the previous window state with fallback to defaults
|
|
|
|
let mainWindowState = windowStateKeeper({
|
|
|
|
defaultWidth: 1024,
|
|
|
|
defaultHeight: 768,
|
|
|
|
});
|
|
|
|
|
2016-11-03 02:49:28 +08:00
|
|
|
mainWindow = new electron.BrowserWindow({
|
2016-12-07 19:19:51 +08:00
|
|
|
icon: icon_path,
|
2016-12-06 17:45:31 +08:00
|
|
|
show: false,
|
2017-01-18 18:39:59 +08:00
|
|
|
autoHideMenuBar: true,
|
2017-04-13 01:13:25 +08:00
|
|
|
|
|
|
|
x: mainWindowState.x,
|
|
|
|
y: mainWindowState.y,
|
|
|
|
width: mainWindowState.width,
|
|
|
|
height: mainWindowState.height,
|
2016-11-03 02:49:28 +08:00
|
|
|
});
|
2016-11-08 23:46:21 +08:00
|
|
|
mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`);
|
2016-11-03 02:49:28 +08:00
|
|
|
electron.Menu.setApplicationMenu(VectorMenu);
|
|
|
|
|
2017-01-19 22:29:07 +08:00
|
|
|
// Create trayIcon icon
|
|
|
|
tray.create(mainWindow, {
|
|
|
|
icon_path: icon_path,
|
|
|
|
brand: vectorConfig.brand || 'Riot'
|
|
|
|
});
|
|
|
|
|
2017-04-18 05:10:56 +08:00
|
|
|
if (!process.argv.includes('--hidden')) {
|
|
|
|
mainWindow.once('ready-to-show', () => {
|
|
|
|
mainWindow.show();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-11-03 02:49:28 +08:00
|
|
|
mainWindow.on('closed', () => {
|
|
|
|
mainWindow = null;
|
|
|
|
});
|
|
|
|
mainWindow.on('close', (e) => {
|
2017-01-19 22:29:07 +08:00
|
|
|
if (!appQuitting && (tray.hasTray() || process.platform == 'darwin')) {
|
2016-11-03 02:49:28 +08:00
|
|
|
// On Mac, closing the window just hides it
|
|
|
|
// (this is generally how single-window Mac apps
|
|
|
|
// behave, eg. Mail.app)
|
|
|
|
e.preventDefault();
|
|
|
|
mainWindow.hide();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
mainWindow.webContents.on('new-window', onWindowOrNavigate);
|
|
|
|
mainWindow.webContents.on('will-navigate', onWindowOrNavigate);
|
|
|
|
|
|
|
|
mainWindow.webContents.on('context-menu', function(ev, params) {
|
|
|
|
if (params.linkURL) {
|
|
|
|
onLinkContextMenu(ev, params);
|
|
|
|
}
|
|
|
|
});
|
2017-04-13 01:13:25 +08:00
|
|
|
|
|
|
|
mainWindowState.manage(mainWindow);
|
2016-11-03 02:49:28 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
electron.app.on('window-all-closed', () => {
|
|
|
|
electron.app.quit();
|
|
|
|
});
|
|
|
|
|
|
|
|
electron.app.on('activate', () => {
|
|
|
|
mainWindow.show();
|
|
|
|
});
|
|
|
|
|
|
|
|
electron.app.on('before-quit', () => {
|
|
|
|
appQuitting = true;
|
|
|
|
});
|
2016-12-06 21:28:59 +08:00
|
|
|
|
|
|
|
// Set the App User Model ID to match what the squirrel
|
|
|
|
// installer uses for the shortcut icon.
|
|
|
|
// This makes notifications work on windows 8.1 (and is
|
|
|
|
// a noop on other platforms).
|
|
|
|
electron.app.setAppUserModelId('com.squirrel.riot-web.Riot');
|