mattermost-desktop/src/main.js

549 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const {
app,
Menu,
Tray,
ipcMain,
nativeImage,
dialog,
systemPreferences,
session
} = require('electron');
const isDev = require('electron-is-dev');
const installExtension = require('electron-devtools-installer');
const squirrelStartup = require('./main/squirrelStartup');
process.on('uncaughtException', (error) => {
console.error(error);
});
global.willAppQuit = false;
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
if (squirrelStartup()) {
global.willAppQuit = true;
}
const os = require('os');
const path = require('path');
var settings = require('./common/settings');
var certificateStore = require('./main/certificateStore').load(path.resolve(app.getPath('userData'), 'certificate.json'));
const {createMainWindow} = require('./main/mainWindow');
const appMenu = require('./main/menus/app');
const trayMenu = require('./main/menus/tray');
const downloadURL = require('./main/downloadURL');
const allowProtocolDialog = require('./main/allowProtocolDialog');
const SpellChecker = require('./main/SpellChecker');
const assetsDir = path.resolve(app.getAppPath(), 'assets');
// 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;
let spellChecker = null;
let deeplinkingUrl = null;
var argv = require('yargs').parse(process.argv.slice(1));
var hideOnStartup;
if (argv.hidden) {
hideOnStartup = true;
}
if (argv['data-dir']) {
app.setPath('userData', path.resolve(argv['data-dir']));
}
global.isDev = isDev && !argv.disableDevMode;
var config = {};
try {
const configFile = app.getPath('userData') + '/config.json';
config = settings.readFileSync(configFile);
if (config.version !== settings.version || wasUpdated()) {
clearAppCache();
config = settings.upgrade(config, app.getVersion());
settings.writeFileSync(configFile, config);
}
} catch (e) {
const spellCheckerLocale = SpellChecker.getSpellCheckerLocale(app.getLocale());
config = settings.loadDefault(null, spellCheckerLocale);
console.log('Failed to read or upgrade config.json', e);
if (!config.teams.length && config.defaultTeam) {
config.teams.push(config.defaultTeam);
const configFile = app.getPath('userData') + '/config.json';
settings.writeFileSync(configFile, config);
}
}
ipcMain.on('update-config', () => {
const configFile = app.getPath('userData') + '/config.json';
config = settings.readFileSync(configFile);
ipcMain.emit('update-dict', true, config.spellCheckerLocale);
});
// Only for OS X
function switchMenuIconImages(icons, isDarkMode) {
if (isDarkMode) {
icons.normal = icons.clicked.normal;
icons.unread = icons.clicked.unread;
icons.mention = icons.clicked.mention;
} else {
icons.normal = icons.light.normal;
icons.unread = icons.light.unread;
icons.mention = icons.light.mention;
}
}
var trayIcon = null;
const trayImages = (() => {
switch (process.platform) {
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'))
};
case 'darwin':
{
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'))
},
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'))
}
};
switchMenuIconImages(icons, systemPreferences.isDarkMode());
return icons;
}
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'))
};
}
}
default:
return {};
}
})();
// 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
deeplinkingUrl = commandLine.slice(1);
}
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
} else {
mainWindow.show();
}
}
})) {
app.quit();
}
function shouldShowTrayIcon() {
if (process.platform === 'win32') {
return true;
}
if (['darwin', 'linux'].includes(process.platform) && config.showTrayIcon === true) {
return true;
}
return false;
}
function wasUpdated() {
return config.lastMattermostVersion !== app.getVersion();
}
function clearAppCache() {
if (mainWindow) {
console.log('Clear cache after update');
mainWindow.webContents.session.clearCache(() => {
//Restart after cache clear
mainWindow.reload();
});
} else {
//Wait for mainWindow
setTimeout(clearAppCache, 100);
}
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// 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);
});
// For OSX, show hidden mainWindow when clicking dock icon.
app.on('activate', () => {
mainWindow.show();
});
app.on('before-quit', () => {
// Make sure tray icon gets removed if the user exits via CTRL-Q
if (process.platform === 'win32') {
trayIcon.destroy();
}
global.willAppQuit = true;
});
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
if (certificateStore.isTrusted(url, certificate)) {
event.preventDefault();
callback(true);
} else {
var detail = `URL: ${url}\nError: ${error}`;
if (certificateStore.isExisting(url)) {
detail = 'Certificate is different from previous one.\n\n' + detail;
}
dialog.showMessageBox(mainWindow, {
title: 'Certificate error',
message: `Do you trust certificate from "${certificate.issuerName}"?`,
detail,
type: 'warning',
buttons: [
'Yes',
'No'
],
cancelId: 1
}, (response) => {
if (response === 0) {
certificateStore.add(url, certificate);
certificateStore.save();
webContents.loadURL(url);
}
});
callback(false);
}
});
const loginCallbackMap = new Map();
ipcMain.on('login-credentials', (event, request, user, password) => {
const callback = loginCallbackMap.get(JSON.stringify(request));
if (callback != null) {
callback(user, password);
}
});
app.on('login', (event, webContents, request, authInfo, callback) => {
event.preventDefault();
loginCallbackMap.set(JSON.stringify(request), callback);
mainWindow.webContents.send('login-request', request, authInfo);
});
allowProtocolDialog.init(mainWindow);
ipcMain.on('download-url', (event, URL) => {
downloadURL(mainWindow, URL, (err) => {
if (err) {
dialog.showMessageBox(mainWindow, {
type: 'error',
message: err.toString()
});
console.log(err);
}
});
});
app.setAsDefaultProtocolClient('Mattermost');
// Protocol handler for osx
app.on('open-url', function(event, url) {
event.preventDefault();
deeplinkingUrl = url.replace('Mattermost', 'https');
mainWindow.webContents.send('protocol-deeplink', deeplinkingUrl);
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', () => {
if (global.willAppQuit) {
return;
}
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 arguments
deeplinkingUrl = process.argv.slice(1);
}
mainWindow = createMainWindow(config, {
hideOnStartup,
linuxAppIcon: path.join(assetsDir, 'appicon.png'),
deeplinkingUrl
});
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;
});
mainWindow.on('unresponsive', () => {
console.log('The application has become unresponsive.');
});
mainWindow.webContents.on('crashed', () => {
console.log('The application has crashed.');
});
ipcMain.on('notified', () => {
if (process.platform === 'win32' || process.platform === 'linux') {
if (config.notifications.flashWindow === 2) {
mainWindow.flashFrame(true);
}
}
});
ipcMain.on('update-title', (event, arg) => {
mainWindow.setTitle(arg.title);
});
if (shouldShowTrayIcon()) {
// set up tray icon
trayIcon = new Tray(trayImages.normal);
if (process.platform === 'darwin') {
trayIcon.setPressedImage(trayImages.clicked.normal);
systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => {
switchMenuIconImages(trayImages, systemPreferences.isDarkMode());
trayIcon.setImage(trayImages.normal);
});
}
trayIcon.setToolTip(app.getName());
trayIcon.on('click', () => {
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
} else {
mainWindow.show();
}
mainWindow.focus();
if (process.platform === 'darwin') {
app.dock.show();
}
} else {
mainWindow.focus();
}
});
trayIcon.on('right-click', () => {
trayIcon.popUpContextMenu();
});
trayIcon.on('balloon-click', () => {
if (process.platform === 'win32' || process.platform === 'darwin') {
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"
ipcMain.on('update-unread', (event, arg) => {
if (process.platform === 'win32') {
const overlay = arg.overlayDataURL ? nativeImage.createFromDataURL(arg.overlayDataURL) : null;
if (mainWindow) {
mainWindow.setOverlayIcon(overlay, arg.description);
}
}
if (trayIcon) {
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());
}
}
});
}
if (process.platform === 'darwin') {
session.defaultSession.on('will-download', (event, item) => {
var filename = item.getFilename();
var savePath = dialog.showSaveDialog({
title: filename,
defaultPath: os.homedir() + '/Downloads/' + filename
});
if (savePath) {
item.setSavePath(savePath);
} else {
item.cancel();
}
});
}
// Set application menu
ipcMain.on('update-menu', (event, configData) => {
var aMenu = appMenu.createMenu(mainWindow, configData, global.isDev);
Menu.setApplicationMenu(aMenu);
// set up context menu for tray icon
if (shouldShowTrayIcon()) {
const tMenu = trayMenu.createMenu(mainWindow, configData, global.isDev);
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"
if (trayIcon) {
mainWindow.trayWasVisible = true;
} else {
mainWindow.trayWasVisible = false;
}
}
}
});
ipcMain.emit('update-menu', true, config);
ipcMain.on('update-dict', () => {
if (config.useSpellChecker) {
spellChecker = new SpellChecker(
config.spellCheckerLocale,
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) => {
if (spellChecker.isReady()) {
event.sender.send('spellchecker-is-ready');
return;
}
spellChecker.once('ready', () => {
event.sender.send('spellchecker-is-ready');
});
});
ipcMain.emit('update-dict');
// Open the DevTools.
// mainWindow.openDevTools();
});