Signal-Desktop/main.js

263 lines
7.1 KiB
JavaScript
Raw Normal View History

const path = require('path');
const url = require('url');
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow;
const app = electron.app;
const ipc = electron.ipcMain;
2017-04-05 18:29:43 +00:00
const Menu = electron.Menu;
2017-04-12 17:56:22 +00:00
const shell = electron.shell;
const autoUpdate = require('./app/auto_update');
const windowState = require('./app/window_state');
console.log('setting AUMID');
app.setAppUserModelId('org.whispersystems.signal-desktop')
// 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.
2017-06-22 01:28:07 +00:00
let mainWindow;
const config = require("./app/config");
if (config.environment === 'production' && !process.mas) {
console.log('making app single instance');
2017-04-25 00:26:05 +00:00
var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) {
// Someone tried to run a second instance, we should focus our window
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
return true;
});
if (shouldQuit) {
console.log('quitting');
2017-04-25 00:26:05 +00:00
app.quit();
return;
}
}
const userConfig = require('./app/user_config');
const logging = require('./app/logging');
// this must be after we set up appPath in user_config.js
logging.initialize();
const logger = logging.getLogger();
let windowConfig = userConfig.get('window');
const loadLocale = require('./app/locale').load;
let locale;
function createWindow () {
const windowOptions = Object.assign({
2017-03-30 23:30:01 +00:00
width: 800,
2017-04-11 22:54:49 +00:00
height: 610,
2017-06-14 16:39:17 +00:00
minWidth: 700,
minHeight: 360,
webPreferences: {
nodeIntegration: false,
//sandbox: true,
preload: path.join(__dirname, 'preload.js')
}
}, windowConfig);
if (windowOptions.fullscreen === false) {
delete windowOptions.fullscreen;
}
logger.info('Initializing BrowserWindow config: %s', JSON.stringify(windowOptions));
// Create the browser window.
mainWindow = new BrowserWindow(windowOptions);
function captureAndSaveWindowStats() {
const size = mainWindow.getSize();
const position = mainWindow.getPosition();
// so if we need to recreate the window, we have the most recent settings
windowConfig = {
maximized: mainWindow.isMaximized(),
width: size[0],
height: size[1],
x: position[0],
y: position[1]
};
if (mainWindow.isFullScreen()) {
// Only include this property if true, because when explicitly set to
// false the fullscreen button will be disabled on osx
windowConfig.fullscreen = true;
}
logger.info('Updating BrowserWindow config: %s', JSON.stringify(windowConfig));
userConfig.set('window', windowConfig);
}
mainWindow.on('resize', captureAndSaveWindowStats);
mainWindow.on('move', captureAndSaveWindowStats);
mainWindow.on('focus', function() {
mainWindow.flashFrame(false);
});
// Ingested in preload.js via a sendSync call
ipc.on('locale-data', function(event, arg) {
event.returnValue = locale.messages;
});
function prepareURL(pathSegments) {
return url.format({
pathname: path.join.apply(null, pathSegments),
protocol: 'file:',
slashes: true,
query: {
locale: locale.name,
2017-06-21 23:59:23 +00:00
version: app.getVersion(),
buildExpiration: config.get('buildExpiration'),
serverUrl: config.get('serverUrl'),
Profiles (#1453) * Add AES-GCM encryption for profiles With tests. * Add profileKey to DataMessage protobuf // FREEBIE * Decrypt and save profile names // FREEBIE * Save incoming profile keys * Move pad/unpad to crypto module // FREEBIE * Support fetching avatars from the cdn // FREEBIE * Translate failed authentication errors When AES-GCM authentication fails, webcrypto returns a very generic error. The same error is thrown for invalid length inputs, but our earlier checks in decryptProfile should rule out those failure modes and leave us safe to assume that we either had bad ciphertext or the wrong key. // FREEBIE * Handle profile avatars (wip) and log decrypt errors // FREEBIE * Display profile avatars Synced contact avatars will still override profile avatars. * Display profile names in convo list Only if we don't have a synced contact name. // FREEBIE * Make cdn url an environment config Use different ones for staging and production // FREEBIE * Display profile name in conversation header * Display profile name in group messages * Update conversation header if profile avatar changes // FREEBIE * Style profile names small with ~ * Save profileKeys from contact sync messages // FREEBIE * Save profile keys from provisioning messages For standalone accounts, generate a random profile key. // FREEBIE * Special case for one-time sync of our profile key Android will use a contact sync message to sync a profile key from Android clients who have just upgraded and generated their profile key. Normally we should receive this data in a provisioning message. // FREEBIE * Infer profile sharing from synced data messages * Populate profile keys on outgoing messages Requires that `profileSharing` be set on the conversation. // FREEBIE * Support for the profile key update flag When receiving a message with this flag, don't init a message record, just process the profile key and move on. // FREEBIE * Display profile names in group member list * Refresh contact's profile on profile key changes // FREEBIE * Catch errors on profile save // FREEBIE * Save our own synced contact info Don't return early if we get a contact sync for our own number // FREEBIE
2017-09-11 16:50:35 +00:00
cdnUrl: config.get('cdnUrl'),
Certificate pinning via node XMLHttpRequest implementation (#1394) * Add certificate pinning on https service requests Make https requests to the server using node apis instead of browser apis, so we can specify our own CA list, which contains only our own CA. This protects us from MITM by a rogue CA. As a bonus, this let's us drop the use of non-standard ports and just use good ol' default 443 all the time, at least for http requests. // FREEBIE * Make certificateAuthorities an option on requests Modify node-based xhr implementation based on driverdan/node-XMLHttpRequest, adding support for setting certificate authorities on each request. This allows us to pin our master CA for requests to the server and cdn but not to the s3 attachment server, for instance. Also fix an exception when sending binary data in a request: it is submitted as an array buffer, and must be converted to a node Buffer since we are now using a node based request api. // FREEBIE * Import node-based xhr implementation Add a copy of https://github.com/driverdan/node-XMLHttpRequest@86ff70e, and expose it to the renderer in the preload script. In later commits this module will be extended to support custom certificate authorities. // FREEBIE * Support "arraybuffer" responseType on requests When fetching attachments, we want the result as binary data rather than a utf8 string. This lets our node-based XMLHttpRequest honor the responseType property if it is set on the xhr. Note that naively using the raw `.buffer` from a node Buffer won't work, since it is a reuseable backing buffer that is often much larger than the actual content defined by the Buffer's offset and length. Instead, we'll prepare a return buffer based on the response's content length header, and incrementally write chunks of data into it as they arrive. // FREEBIE * Switch to self-signed server endpoint * Log more error info on failed requests With the node-based xhr, relevant error info are stored in statusText and responseText when a request fails. // FREEBIE * Add node-based websocket w/ support for custom CA // FREEBIE * Support handling array buffers instead of blobs Our node-based websocket calls onmessage with an arraybuffer instead of a blob. For robustness (on the off chance we switch or update the socket implementation agian) I've kept the machinery for converting blobs to array buffers. // FREEBIE * Destroy all wacky server ports // FREEBIE
2017-09-01 15:58:58 +00:00
certificateAuthorities: config.get('certificateAuthorities'),
environment: config.environment,
node_version: process.versions.node
}
})
}
if (config.environment === 'test') {
mainWindow.loadURL(prepareURL([__dirname, 'test', 'index.html']));
} else {
mainWindow.loadURL(prepareURL([__dirname, 'background.html']));
}
if (config.get('openDevTools')) {
2017-04-05 18:42:30 +00:00
// Open the DevTools.
mainWindow.webContents.openDevTools()
}
2017-04-12 17:56:22 +00:00
mainWindow.webContents.on('new-window', (e, url) => {
e.preventDefault();
const protocol = require('url').parse(url).protocol
if (protocol === 'http:' || protocol === 'https:') {
shell.openExternal(url)
}
});
mainWindow.webContents.on('will-navigate', function(e) {
logger.info('will-navigate');
e.preventDefault();
});
2017-04-12 17:56:22 +00:00
// Emitted when the window is about to be closed.
mainWindow.on('close', function (e) {
if (process.platform === 'darwin' && !windowState.shouldQuit() && config.environment !== 'test') {
e.preventDefault();
mainWindow.hide();
}
});
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// 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
});
ipc.on('show-window', function() {
// Using focus() instead of show() seems to be important on Windows when our window
// has been docked using Aero Snap/Snap Assist. A full .show() call here will cause
// the window to reposition:
// https://github.com/WhisperSystems/Signal-Desktop/issues/1429
if (mainWindow.isVisible()) {
mainWindow.focus();
} else {
mainWindow.show();
}
});
}
function showDebugLog() {
if (mainWindow) {
mainWindow.webContents.send('debug-log')
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
2017-03-30 18:43:58 +00:00
app.on('ready', function() {
logger.info('app ready');
if (!locale) {
locale = loadLocale();
}
autoUpdate.initialize(locale.messages);
2017-03-30 18:43:58 +00:00
createWindow();
let template = require('./app/menu.js');
2017-05-05 02:26:04 +00:00
if (process.platform === 'darwin') {
template[3].submenu[3].click = function() {
mainWindow.show();
};
template[2].submenu[0].click = showDebugLog;
} else {
template[1].submenu[0].click = showDebugLog;
2017-05-05 02:26:04 +00:00
}
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
2017-03-30 18:43:58 +00:00
})
app.on('before-quit', function() {
windowState.markShouldQuit();
});
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// 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' || config.environment === 'test') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow) {
mainWindow.show();
} else {
createWindow();
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
ipc.on('set-badge-count', function(event, count) {
app.setBadgeCount(count);
});
ipc.on('draw-attention', function(event, count) {
if (process.platform === 'darwin') {
app.dock.bounce();
} else if (process.platform == 'win32') {
mainWindow.flashFrame(true);
setTimeout(function() {
mainWindow.flashFrame(false);
}, 1000);
}
});
ipc.on('restart', function(event) {
app.relaunch();
app.quit();
});