mattermost-desktop/src/main.js

583 lines
18 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,
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');
const os = require('os');
const path = require('path');
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');
const squirrelStartup = require('./main/squirrelStartup');
const CriticalErrorHandler = require('./main/CriticalErrorHandler');
2016-09-25 07:14:01 -07:00
const protocols = require('../electron-builder.json').protocols;
const criticalErrorHandler = new CriticalErrorHandler();
process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHandler.bind(criticalErrorHandler));
2017-03-18 08:07:29 -07:00
global.willAppQuit = false;
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
if (squirrelStartup()) {
2017-03-18 08:07:29 -07:00
global.willAppQuit = true;
}
2015-12-23 02:25:14 -08:00
var settings = require('./common/settings');
var certificateStore = require('./main/certificateStore').load(path.resolve(app.getPath('userData'), 'certificate.json'));
2017-03-18 08:07:29 -07:00
const {createMainWindow} = require('./main/mainWindow');
2016-09-25 07:14:01 -07:00
const appMenu = require('./main/menus/app');
const trayMenu = require('./main/menus/tray');
const downloadURL = require('./main/downloadURL');
const allowProtocolDialog = require('./main/allowProtocolDialog');
2015-10-23 09:44:10 -07:00
2017-04-20 05:32:34 -07:00
const SpellChecker = require('./main/SpellChecker');
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;
2017-04-20 05:32:34 -07:00
let spellChecker = null;
let deeplinkingUrl = null;
let scheme = null;
2016-09-25 07:14:01 -07:00
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) {
const spellCheckerLocale = SpellChecker.getSpellCheckerLocale(app.getLocale());
config = settings.loadDefault(null, spellCheckerLocale);
2016-10-19 02:35:32 -07:00
console.log('Failed to read or upgrade config.json', e);
2017-09-07 16:20:48 -07:00
if (!config.teams.length && config.defaultTeam) {
config.teams.push(config.defaultTeam);
const configFile = app.getPath('userData') + '/config.json';
settings.writeFileSync(configFile, config);
}
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);
2017-04-20 05:32:34 -07:00
ipcMain.emit('update-dict', true, config.spellCheckerLocale);
});
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
if (app.makeSingleInstance((commandLine/*, workingDirectory*/) => {
// Protocol handler for win32
// argv: An array of the second instances (command line / deep linked) arguments
if (process.platform === 'win32') {
// Keep only command line / deep linked arguments
2017-10-09 14:33:02 -07:00
if (Array.isArray(commandLine.slice(1)) && commandLine.slice(1).length > 0) {
setDeeplinkingUrl(commandLine.slice(1)[0]);
mainWindow.webContents.send('protocol-deeplink', deeplinkingUrl);
2017-10-09 14:33:02 -07:00
}
}
2016-09-25 07:14:01 -07:00
// 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.exit();
}
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
}
}
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();
}
});
function getValidWindowPosition(state, screen) {
// 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;
}
function handleScreenResize(screen, browserWindow) {
function handle() {
const position = browserWindow.getPosition();
const size = browserWindow.getSize();
const validPosition = getValidWindowPosition({
x: position[0],
y: position[1],
width: size[0],
height: size[1]
}, screen);
browserWindow.setPosition(validPosition.x || 0, validPosition.y || 0);
}
browserWindow.on('restore', handle);
handle();
}
app.on('browser-window-created', (e, newWindow) => {
// Screen cannot be required before app is ready
const {screen} = require('electron'); // eslint-disable-line global-require
handleScreenResize(screen, newWindow);
});
2015-10-23 09:44:10 -07:00
// 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();
}
2017-03-18 08:07:29 -07:00
global.willAppQuit = true;
2015-10-23 09:44:10 -07:00
});
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);
}
});
app.on('gpu-process-crashed', () => {
throw new Error('The GPU process has crached');
});
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
ipcMain.on('download-url', (event, URL) => {
downloadURL(mainWindow, URL, (err) => {
if (err) {
dialog.showMessageBox(mainWindow, {
type: 'error',
message: err.toString()
});
console.log(err);
}
});
});
if (isDev) {
console.log('In development mode, deeplinking is disabled');
} else if (protocols && protocols[0] &&
protocols[0].schemes && protocols[0].schemes[0]
) {
scheme = protocols[0].schemes[0];
app.setAsDefaultProtocolClient(scheme);
}
2017-10-09 14:33:02 -07:00
function setDeeplinkingUrl(url) {
if (scheme) {
deeplinkingUrl = url.replace(new RegExp('^' + scheme), 'https');
}
2017-10-09 14:33:02 -07:00
}
// Protocol handler for osx
2017-10-09 14:33:02 -07:00
app.on('open-url', (event, url) => {
event.preventDefault();
setDeeplinkingUrl(url);
mainWindow.webContents.send('protocol-deeplink', deeplinkingUrl);
mainWindow.show();
});
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', () => {
2017-03-18 08:07:29 -07:00
if (global.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));
}
// Protocol handler for win32
if (process.platform === 'win32') {
// Keep only command line / deep linked argument. Make sure it's not squirrel command
const tmpArgs = process.argv.slice(1);
if (
Array.isArray(tmpArgs) && tmpArgs.length > 0 &&
tmpArgs[0].match(/^--squirrel-/) === null
) {
setDeeplinkingUrl(tmpArgs[0]);
2017-10-09 14:33:02 -07:00
}
}
2017-03-18 08:07:29 -07:00
mainWindow = createMainWindow(config, {
hideOnStartup,
linuxAppIcon: path.join(assetsDir, 'appicon.png'),
deeplinkingUrl
2017-03-18 08:07:29 -07:00
});
2017-10-09 14:33:02 -07:00
2017-03-18 08:07:29 -07:00
mainWindow.on('closed', () => {
// 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;
});
criticalErrorHandler.setMainWindow(mainWindow);
mainWindow.on('unresponsive', criticalErrorHandler.windowUnresponsiveHandler.bind(criticalErrorHandler));
2017-03-18 08:07:29 -07:00
mainWindow.webContents.on('crashed', () => {
throw new Error('webContents \'crashed\' event has been emitted');
2017-03-18 08:07:29 -07:00
});
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();
});
// 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;
2017-03-18 08:07:29 -07:00
if (mainWindow) {
mainWindow.setOverlayIcon(overlay, arg.description);
}
2016-04-06 08:49:20 -07:00
}
if (trayIcon && !trayIcon.isDestroyed()) {
2017-03-18 08:07:29 -07:00
if (arg.mentionCount > 0) {
trayIcon.setImage(trayImages.mention);
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.mention);
}
trayIcon.setToolTip(arg.mentionCount + ' unread mentions');
} else if (arg.unreadCount > 0) {
trayIcon.setImage(trayImages.unread);
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.unread);
}
trayIcon.setToolTip(arg.unreadCount + ' unread channels');
} else {
trayIcon.setImage(trayImages.normal);
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.normal);
}
trayIcon.setToolTip(app.getName());
2016-06-12 05:40:59 -07:00
}
}
});
}
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();
}
});
}
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
ipcMain.on('update-dict', () => {
2017-04-20 05:32:34 -07:00
if (config.useSpellChecker) {
spellChecker = new SpellChecker(
config.spellCheckerLocale,
2017-04-20 05:32:34 -07:00
path.resolve(app.getAppPath(), 'node_modules/simple-spellchecker/dict'),
(err) => {
if (err) {
console.error(err);
}
});
}
});
ipcMain.on('checkspell', (event, word) => {
let res = null;
if (config.useSpellChecker && spellChecker.isReady() && word !== null) {
res = spellChecker.spellCheck(word);
}
event.returnValue = res;
});
ipcMain.on('get-spelling-suggestions', (event, word) => {
if (config.useSpellChecker && spellChecker.isReady() && word !== null) {
event.returnValue = spellChecker.getSuggestions(word, 10);
} else {
event.returnValue = [];
}
});
ipcMain.on('get-spellchecker-locale', (event) => {
event.returnValue = config.spellCheckerLocale;
});
ipcMain.on('reply-on-spellchecker-is-ready', (event) => {
2017-10-07 14:05:49 -07:00
if (!spellChecker) {
return;
}
if (spellChecker.isReady()) {
event.sender.send('spellchecker-is-ready');
return;
}
spellChecker.once('ready', () => {
event.sender.send('spellchecker-is-ready');
});
});
ipcMain.emit('update-dict');
2017-04-20 05:32:34 -07:00
2015-10-23 09:44:10 -07:00
// Open the DevTools.
// mainWindow.openDevTools();
});