mattermost-desktop/src/main.js

571 lines
17 KiB
JavaScript
Raw Normal View History

'use strict';
2015-10-23 09:44:10 -07:00
2016-06-12 05:06:49 -07:00
const {
app,
BrowserWindow,
Menu,
Tray,
ipcMain,
nativeImage,
2016-06-12 05:33:00 -07:00
dialog,
systemPreferences,
session
2016-06-12 05:06:49 -07:00
} = require('electron');
2017-03-04 07:27:40 -08:00
const isDev = require('electron-is-dev');
2017-03-06 06:29:56 -08:00
const installExtension = require('electron-devtools-installer');
2016-09-25 07:14:01 -07:00
const AutoLaunch = require('auto-launch');
process.on('uncaughtException', (error) => {
console.error(error);
});
var willAppQuit = false;
if (process.platform === 'win32') {
var cmd = process.argv[1];
var appLauncher = new AutoLaunch({
name: 'Mattermost',
isHidden: true
});
if (cmd === '--squirrel-uninstall') {
// If we're uninstalling, make sure we also delete our auto launch registry key
2016-09-25 07:14:01 -07:00
appLauncher.isEnabled().then((enabled) => {
if (enabled) {
appLauncher.disable();
2016-09-25 07:14:01 -07:00
}
});
2016-09-25 07:14:01 -07:00
} else if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
// If we're updating and already have an registry entry for auto launch, make sure to update the path
2016-09-25 07:14:01 -07:00
appLauncher.isEnabled().then((enabled) => {
if (enabled) {
2016-09-25 07:14:01 -07:00
return appLauncher.disable().then(() => {
return appLauncher.enable();
});
}
2016-09-25 07:14:01 -07:00
return true;
});
}
}
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
if (require('electron-squirrel-startup')) {
willAppQuit = true;
}
const fs = require('fs');
2016-09-25 07:14:01 -07:00
const os = require('os');
2016-01-30 07:50:43 -08:00
const path = require('path');
2015-12-23 02:25:14 -08:00
var settings = require('./common/settings');
const osVersion = require('./common/osVersion');
var certificateStore = require('./main/certificateStore').load(path.resolve(app.getPath('userData'), 'certificate.json'));
2016-09-25 07:14:01 -07:00
const appMenu = require('./main/menus/app');
const trayMenu = require('./main/menus/tray');
const allowProtocolDialog = require('./main/allowProtocolDialog');
2015-10-23 09:44:10 -07:00
const assetsDir = path.resolve(app.getAppPath(), 'assets');
2016-09-25 07:14:01 -07:00
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow = null;
var argv = require('yargs').parse(process.argv.slice(1));
2016-08-02 12:29:24 -07:00
var hideOnStartup;
if (argv.hidden) {
hideOnStartup = true;
}
if (argv['data-dir']) {
app.setPath('userData', path.resolve(argv['data-dir']));
}
2017-03-06 06:37:35 -08:00
global.isDev = isDev && !argv.disableDevMode;
2017-03-06 05:00:21 -08:00
var config = {};
2015-12-23 02:25:14 -08:00
try {
const configFile = app.getPath('userData') + '/config.json';
config = settings.readFileSync(configFile);
2016-09-25 07:14:01 -07:00
if (config.version !== settings.version || wasUpdated()) {
2016-10-19 02:35:32 -07:00
clearAppCache();
config = settings.upgrade(config, app.getVersion());
2015-12-23 02:25:14 -08:00
settings.writeFileSync(configFile, config);
}
2016-09-25 07:14:01 -07:00
} catch (e) {
2016-01-30 07:50:43 -08:00
config = settings.loadDefault();
2016-10-19 02:35:32 -07:00
console.log('Failed to read or upgrade config.json', e);
2015-12-23 02:25:14 -08:00
}
2016-09-25 07:14:01 -07:00
ipcMain.on('update-config', () => {
const configFile = app.getPath('userData') + '/config.json';
config = settings.readFileSync(configFile);
});
2015-12-23 02:25:14 -08:00
2016-06-12 05:33:00 -07:00
// Only for OS X
2016-09-25 07:14:01 -07:00
function switchMenuIconImages(icons, isDarkMode) {
2016-06-12 05:33:00 -07:00
if (isDarkMode) {
icons.normal = icons.clicked.normal;
icons.unread = icons.clicked.unread;
icons.mention = icons.clicked.mention;
2016-09-25 07:14:01 -07:00
} else {
2016-06-12 05:33:00 -07:00
icons.normal = icons.light.normal;
icons.unread = icons.light.unread;
icons.mention = icons.light.mention;
}
2016-09-25 07:14:01 -07:00
}
2016-06-12 05:33:00 -07:00
var trayIcon = null;
2016-09-25 07:14:01 -07:00
const trayImages = (() => {
2016-04-07 07:15:12 -07:00
switch (process.platform) {
2016-09-25 07:14:01 -07:00
case 'win32':
return {
normal: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray.ico')),
unread: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_unread.ico')),
mention: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_mention.ico'))
2016-09-25 07:14:01 -07:00
};
case 'darwin':
{
2016-06-12 05:33:00 -07:00
const icons = {
light: {
normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIcon.png')),
unread: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIconUnread.png')),
mention: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIconMention.png'))
2016-06-12 05:33:00 -07:00
},
2016-06-12 04:51:43 -07:00
clicked: {
normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIcon.png')),
unread: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIconUnread.png')),
mention: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIconMention.png'))
2016-06-12 04:51:43 -07:00
}
2016-04-07 07:15:12 -07:00
};
2016-06-12 05:33:00 -07:00
switchMenuIconImages(icons, systemPreferences.isDarkMode());
return icons;
2016-09-25 07:14:01 -07:00
}
case 'linux':
{
const theme = config.trayIconTheme;
try {
return {
normal: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconTemplate.png')),
unread: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconUnreadTemplate.png')),
mention: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconMentionTemplate.png'))
};
} catch (e) {
//Fallback for invalid theme setting
return {
normal: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconTemplate.png')),
unread: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconUnreadTemplate.png')),
mention: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconMentionTemplate.png'))
};
}
}
2016-09-25 07:14:01 -07:00
default:
return {};
2016-04-07 07:15:12 -07:00
}
2016-09-25 07:14:01 -07:00
})();
2015-10-23 09:44:10 -07:00
// If there is already an instance, activate the window in the existing instace and quit this one
2016-09-25 07:14:01 -07:00
if (app.makeSingleInstance((/*commandLine, workingDirectory*/) => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
} else {
mainWindow.show();
2016-08-16 07:34:04 -07:00
}
2016-09-25 07:14:01 -07:00
}
})) {
app.quit();
}
2016-04-11 05:51:24 -07:00
function shouldShowTrayIcon() {
if (process.platform === 'win32') {
return true;
}
if (['darwin', 'linux'].includes(process.platform) && config.showTrayIcon === true) {
2016-04-11 05:51:24 -07:00
return true;
}
return false;
}
2016-10-19 02:35:32 -07:00
function wasUpdated() {
2016-09-25 07:14:01 -07:00
return config.lastMattermostVersion !== app.getVersion();
2016-10-19 02:35:32 -07:00
}
function clearAppCache() {
2016-09-25 07:14:01 -07:00
if (mainWindow) {
2016-10-19 02:35:32 -07:00
console.log('Clear cache after update');
2016-09-25 07:14:01 -07:00
mainWindow.webContents.session.clearCache(() => {
2016-10-19 02:35:32 -07:00
//Restart after cache clear
mainWindow.reload();
});
2016-09-25 07:14:01 -07:00
} else {
//Wait for mainWindow
setTimeout(clearAppCache, 100);
2016-10-19 02:35:32 -07:00
}
}
2016-12-06 11:53:06 -08:00
function getValidWindowPosition(state) {
// Screen cannot be required before app is ready
const {screen} = require('electron');
// Check if the previous position is out of the viewable area
// (e.g. because the screen has been plugged off)
const displays = screen.getAllDisplays();
let minX = 0;
let maxX = 0;
let minY = 0;
let maxY = 0;
for (let i = 0; i < displays.length; i++) {
const display = displays[i];
maxX = Math.max(maxX, display.bounds.x + display.bounds.width);
maxY = Math.max(maxY, display.bounds.y + display.bounds.height);
minX = Math.min(minX, display.bounds.x);
minY = Math.min(minY, display.bounds.y);
}
if (state.x > maxX || state.y > maxY || state.x < minX || state.y < minY) {
Reflect.deleteProperty(state, 'x');
Reflect.deleteProperty(state, 'y');
Reflect.deleteProperty(state, 'width');
Reflect.deleteProperty(state, 'height');
}
return state;
}
2015-10-23 09:44:10 -07:00
// Quit when all windows are closed.
2016-09-25 07:14:01 -07:00
app.on('window-all-closed', () => {
2015-10-23 09:44:10 -07:00
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
// For OSX, show hidden mainWindow when clicking dock icon.
2016-09-25 07:14:01 -07:00
app.on('activate', () => {
2015-10-23 09:44:10 -07:00
mainWindow.show();
});
2016-09-25 07:14:01 -07:00
app.on('before-quit', () => {
// Make sure tray icon gets removed if the user exits via CTRL-Q
if (process.platform === 'win32') {
trayIcon.destroy();
}
2015-10-23 09:44:10 -07:00
willAppQuit = true;
});
2016-09-25 07:14:01 -07:00
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
if (certificateStore.isTrusted(url, certificate)) {
event.preventDefault();
callback(true);
2016-09-25 07:14:01 -07:00
} else {
var detail = `URL: ${url}\nError: ${error}`;
if (certificateStore.isExisting(url)) {
2016-11-30 03:37:39 -08:00
detail = 'Certificate is different from previous one.\n\n' + detail;
}
2016-06-12 05:06:49 -07:00
dialog.showMessageBox(mainWindow, {
title: 'Certificate error',
message: `Do you trust certificate from "${certificate.issuerName}"?`,
2016-09-25 07:14:01 -07:00
detail,
type: 'warning',
buttons: [
'Yes',
'No'
],
cancelId: 1
2016-09-25 07:14:01 -07:00
}, (response) => {
if (response === 0) {
certificateStore.add(url, certificate);
certificateStore.save();
webContents.loadURL(url);
}
});
callback(false);
}
});
2016-04-21 06:43:18 -07:00
const loginCallbackMap = new Map();
2016-09-25 07:14:01 -07:00
ipcMain.on('login-credentials', (event, request, user, password) => {
2016-04-21 06:43:18 -07:00
const callback = loginCallbackMap.get(JSON.stringify(request));
if (callback != null) {
callback(user, password);
}
});
2016-04-21 06:43:18 -07:00
2016-09-25 07:14:01 -07:00
app.on('login', (event, webContents, request, authInfo, callback) => {
2016-04-21 06:43:18 -07:00
event.preventDefault();
loginCallbackMap.set(JSON.stringify(request), callback);
mainWindow.webContents.send('login-request', request, authInfo);
});
allowProtocolDialog.init(mainWindow);
2016-05-21 21:39:52 -07:00
2015-10-23 09:44:10 -07:00
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
2016-09-25 07:14:01 -07:00
app.on('ready', () => {
if (willAppQuit) {
return;
}
2017-03-06 06:29:56 -08:00
if (global.isDev) {
installExtension.default(installExtension.REACT_DEVELOPER_TOOLS).
then((name) => console.log(`Added Extension: ${name}`)).
catch((err) => console.log('An error occurred: ', err));
}
2016-09-25 07:14:01 -07:00
ipcMain.on('notified', () => {
2016-07-25 04:54:46 -07:00
if (process.platform === 'win32' || process.platform === 'linux') {
if (config.notifications.flashWindow === 2) {
mainWindow.flashFrame(true);
}
}
});
2016-09-25 07:14:01 -07:00
ipcMain.on('update-title', (event, arg) => {
mainWindow.setTitle(arg.title);
});
2016-04-11 05:51:24 -07:00
if (shouldShowTrayIcon()) {
2016-04-06 08:49:20 -07:00
// set up tray icon
2016-04-07 07:15:12 -07:00
trayIcon = new Tray(trayImages.normal);
2016-06-12 05:40:59 -07:00
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.normal);
2016-09-25 07:14:01 -07:00
systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => {
2016-06-12 05:40:59 -07:00
switchMenuIconImages(trayImages, systemPreferences.isDarkMode());
trayIcon.setImage(trayImages.normal);
});
}
2016-06-12 05:33:00 -07:00
trayIcon.setToolTip(app.getName());
2016-09-25 07:14:01 -07:00
trayIcon.on('click', () => {
2016-08-26 07:56:50 -07:00
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
2016-09-25 07:14:01 -07:00
} else {
mainWindow.show();
}
2016-07-05 09:37:18 -07:00
mainWindow.focus();
if (process.platform === 'darwin') {
app.dock.show();
}
2016-09-25 07:14:01 -07:00
} else {
2016-07-05 09:37:18 -07:00
mainWindow.focus();
}
});
trayIcon.on('right-click', () => {
trayIcon.popUpContextMenu();
});
2016-09-25 07:14:01 -07:00
trayIcon.on('balloon-click', () => {
if (process.platform === 'win32' || process.platform === 'darwin') {
2016-09-25 07:14:01 -07:00
if (mainWindow.isMinimized()) {
mainWindow.restore();
} else {
mainWindow.show();
}
}
if (process.platform === 'darwin') {
app.dock.show();
}
mainWindow.focus();
});
2016-09-25 07:14:01 -07:00
ipcMain.on('notified', (event, arg) => {
2016-07-12 13:14:32 -07:00
if (process.platform === 'win32') {
// On Windows 8.1 and Windows 8, a shortcut with a Application User Model ID must be installed to the Start screen.
// In current version, use tray balloon for notification
if (osVersion.isLowerThanOrEqualWindows8_1()) {
trayIcon.displayBalloon({
icon: path.resolve(assetsDir, 'appicon.png'),
title: arg.title,
content: arg.options.body
});
}
}
});
// Set overlay icon from dataURL
// Set trayicon to show "dot"
2016-09-25 07:14:01 -07:00
ipcMain.on('update-unread', (event, arg) => {
2016-04-06 08:49:20 -07:00
if (process.platform === 'win32') {
2016-06-12 05:06:49 -07:00
const overlay = arg.overlayDataURL ? nativeImage.createFromDataURL(arg.overlayDataURL) : null;
2016-04-06 08:49:20 -07:00
mainWindow.setOverlayIcon(overlay, arg.description);
}
2016-04-07 07:15:12 -07:00
if (arg.mentionCount > 0) {
trayIcon.setImage(trayImages.mention);
2016-06-12 05:40:59 -07:00
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.mention);
}
2016-06-03 05:45:02 -07:00
trayIcon.setToolTip(arg.mentionCount + ' unread mentions');
2016-09-25 07:14:01 -07:00
} else if (arg.unreadCount > 0) {
2016-04-07 07:15:12 -07:00
trayIcon.setImage(trayImages.unread);
2016-06-12 05:40:59 -07:00
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.unread);
}
2016-06-03 05:45:02 -07:00
trayIcon.setToolTip(arg.unreadCount + ' unread channels');
2016-09-25 07:14:01 -07:00
} else {
2016-04-07 07:15:12 -07:00
trayIcon.setImage(trayImages.normal);
2016-06-12 05:40:59 -07:00
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.normal);
}
2016-06-03 05:45:02 -07:00
trayIcon.setToolTip(app.getName());
}
});
}
2015-10-23 09:44:10 -07:00
// Create the browser window.
2016-09-25 07:14:01 -07:00
var boundsInfoPath = path.resolve(app.getPath('userData'), 'bounds-info.json');
var windowOptions;
try {
2016-12-06 11:53:06 -08:00
windowOptions = getValidWindowPosition(JSON.parse(fs.readFileSync(boundsInfoPath, 'utf-8')));
2016-09-25 07:14:01 -07:00
} catch (e) {
// Follow Electron's defaults, except for window dimensions which targets 1024x768 screen resolution.
2017-01-18 07:50:12 -08:00
windowOptions = {width: 1000, height: 700};
}
if (process.platform === 'linux') {
windowOptions.icon = path.resolve(assetsDir, 'appicon.png');
}
2017-01-17 06:36:43 -08:00
Object.assign(windowOptions, {
title: app.getName(),
fullscreenable: true,
2017-01-17 06:36:43 -08:00
show: false,
minWidth: 400,
minHeight: 240
});
2016-09-25 07:14:01 -07:00
mainWindow = new BrowserWindow(windowOptions);
mainWindow.once('ready-to-show', () => {
2017-01-22 02:58:42 -08:00
if (process.platform !== 'darwin') {
mainWindow.show();
} else if (hideOnStartup !== true) {
mainWindow.show();
}
});
if (process.platform === 'darwin') {
2016-09-25 07:14:01 -07:00
session.defaultSession.on('will-download', (event, item) => {
var filename = item.getFilename();
var savePath = dialog.showSaveDialog({
title: filename,
2016-09-25 07:14:01 -07:00
defaultPath: os.homedir() + '/Downloads/' + filename
});
if (savePath) {
item.setSavePath(savePath);
2016-09-25 07:14:01 -07:00
} else {
item.cancel();
}
});
}
mainWindow.webContents.on('crashed', () => {
console.log('The application has crashed.');
});
mainWindow.on('unresponsive', () => {
console.log('The application has become unresponsive.');
});
2016-08-02 12:29:24 -07:00
if (hideOnStartup) {
if (windowOptions.maximized) {
mainWindow.maximize();
}
2017-01-22 02:58:42 -08:00
// on MacOS, the window is already hidden until 'ready-to-show'
if (process.platform !== 'darwin') {
mainWindow.minimize();
2016-08-02 12:29:24 -07:00
}
2017-01-22 02:58:42 -08:00
} else if (windowOptions.maximized) {
mainWindow.maximize();
}
2015-10-23 09:44:10 -07:00
// and load the index.html of the app.
const indexURL = global.isDev ? 'http://localhost:8080/browser/index.html' : `file://${app.getAppPath()}/browser/index.html`;
2017-03-06 05:00:21 -08:00
mainWindow.loadURL(indexURL);
2015-10-23 09:44:10 -07:00
2016-05-21 00:13:22 -07:00
// Set application menu
2016-09-25 07:14:01 -07:00
ipcMain.on('update-menu', (event, configData) => {
2017-03-06 05:00:21 -08:00
var aMenu = appMenu.createMenu(mainWindow, configData, global.isDev);
2016-09-25 07:14:01 -07:00
Menu.setApplicationMenu(aMenu);
// set up context menu for tray icon
if (shouldShowTrayIcon()) {
2017-03-06 05:00:21 -08:00
const tMenu = trayMenu.createMenu(mainWindow, configData, global.isDev);
2016-09-25 07:14:01 -07:00
trayIcon.setContextMenu(tMenu);
if (process.platform === 'darwin' || process.platform === 'linux') {
// store the information, if the tray was initialized, for checking in the settings, if the application
// was restarted after setting "Show icon on menu bar"
2016-09-25 07:14:01 -07:00
if (trayIcon) {
mainWindow.trayWasVisible = true;
2016-09-25 07:14:01 -07:00
} else {
mainWindow.trayWasVisible = false;
2016-09-25 07:14:01 -07:00
}
}
}
2016-05-21 00:13:22 -07:00
});
2016-06-12 05:06:49 -07:00
ipcMain.emit('update-menu', true, config);
2016-05-21 00:13:22 -07:00
2015-10-23 09:44:10 -07:00
// Open the DevTools.
// mainWindow.openDevTools();
2016-09-25 07:14:01 -07:00
function saveWindowState(file, window) {
var windowState = window.getBounds();
windowState.maximized = window.isMaximized();
windowState.fullscreen = window.isFullScreen();
2016-05-03 08:36:54 -07:00
try {
2016-09-25 07:14:01 -07:00
fs.writeFileSync(boundsInfoPath, JSON.stringify(windowState));
} catch (e) {
2016-05-03 08:36:54 -07:00
// [Linux] error happens only when the window state is changed before the config dir is creatied.
}
2016-09-25 07:14:01 -07:00
}
2016-09-25 07:14:01 -07:00
mainWindow.on('close', (event) => {
if (willAppQuit) { // when [Ctrl|Cmd]+Q
2016-09-25 07:14:01 -07:00
saveWindowState(boundsInfoPath, mainWindow);
} else { // Minimize or hide the window for close button.
2015-10-23 09:44:10 -07:00
event.preventDefault();
2016-09-25 07:14:01 -07:00
function hideWindow(window) {
window.blur(); // To move focus to the next top-level window in Windows
window.hide();
2016-09-25 07:14:01 -07:00
}
2015-10-23 09:44:10 -07:00
switch (process.platform) {
2016-09-25 07:14:01 -07:00
case 'win32':
hideWindow(mainWindow);
break;
case 'linux':
if (config.minimizeToTray) {
hideWindow(mainWindow);
} else {
mainWindow.minimize();
}
break;
case 'darwin':
hideWindow(mainWindow);
break;
default:
2015-10-23 09:44:10 -07:00
}
}
});
// App should save bounds when a window is closed.
// However, 'close' is not fired in some situations(shutdown, ctrl+c)
// because main process is killed in such situations.
// 'blur' event was effective in order to avoid this.
// Ideally, app should detect that OS is shutting down.
2016-09-25 07:14:01 -07:00
mainWindow.on('blur', () => {
saveWindowState(boundsInfoPath, mainWindow);
});
2015-10-23 09:44:10 -07:00
// Emitted when the window is closed.
2016-09-25 07:14:01 -07:00
mainWindow.on('closed', () => {
2015-10-23 09:44:10 -07:00
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
});