diff --git a/app/user_config.js b/app/user_config.js
index 4da1f62a0..44a0b1171 100644
--- a/app/user_config.js
+++ b/app/user_config.js
@@ -1,6 +1,6 @@
const path = require('path');
-const { app } = require('electron');
+const app = require('electron').app || require('electron').remote.app;
const { start } = require('./base_config');
const config = require('./config');
diff --git a/js/launcher_start.js b/js/launcher_start.js
new file mode 100644
index 000000000..38ec1e9e5
--- /dev/null
+++ b/js/launcher_start.js
@@ -0,0 +1,7 @@
+/* global $, Whisper, storage */
+const $body = $(document.body);
+
+// eslint-disable-next-line strict
+window.view = new Whisper.LauncherView();
+$body.html('');
+window.view.$el.prependTo($body);
diff --git a/js/views/launcher_view.js b/js/views/launcher_view.js
new file mode 100644
index 000000000..4c7c8282b
--- /dev/null
+++ b/js/views/launcher_view.js
@@ -0,0 +1,26 @@
+/* global i18n: false */
+/* global Whisper: false */
+/* global $: false */
+
+/* eslint-disable no-new */
+
+// eslint-disable-next-line func-names
+(function() {
+ 'use strict';
+
+ window.Whisper = window.Whisper || {};
+
+ Whisper.LauncherView = Whisper.View.extend({
+ className: 'launcher',
+ templateName: 'launcher',
+ initialize() {
+ this.render();
+ },
+ render_attributes() {
+ return {
+ title: 'WOOOWEEE',
+ };
+ },
+ });
+
+})();
diff --git a/launcher.html b/launcher.html
new file mode 100644
index 000000000..448dfe36b
--- /dev/null
+++ b/launcher.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
diff --git a/launcher_preload.js b/launcher_preload.js
new file mode 100644
index 000000000..fdd5041da
--- /dev/null
+++ b/launcher_preload.js
@@ -0,0 +1,31 @@
+/* global window */
+
+const { ipcRenderer } = require('electron');
+const url = require('url');
+const i18n = require('./js/modules/i18n');
+
+const userConfig = require('./app/user_config');
+
+const config = url.parse(window.location.toString(), true).query;
+const { locale } = config;
+const localeMessages = ipcRenderer.sendSync('locale-data');
+
+window.theme = config.theme;
+window.i18n = i18n.setup(locale, localeMessages);
+
+// So far we're only using this for Signal.Types
+const Signal = require('./js/modules/signal');
+
+window.Signal = Signal.setup({
+ Attachments: null,
+ userDataPath: null,
+ getRegionCode: () => null,
+});
+
+window.userConfig = userConfig;
+window.getEnvironment = () => config.environment;
+window.getVersion = () => config.version;
+window.getAppInstance = () => config.appInstance;
+
+window.onLogin = (passPhrase) => ipcRenderer.send('launcher_login', passPhrase);
+require('./js/logging');
diff --git a/main.js b/main.js
index 1e794c6da..94a02b58f 100644
--- a/main.js
+++ b/main.js
@@ -420,6 +420,93 @@ function setupAsStandalone() {
}
}
+let launcherWindow;
+function showLauncher() {
+ if (launcherWindow) {
+ launcherWindow.show();
+ return;
+ }
+
+ const windowOptions = Object.assign(
+ {
+ show: !startInTray, // allow to start minimised in tray
+ width: DEFAULT_WIDTH,
+ height: DEFAULT_HEIGHT,
+ minWidth: MIN_WIDTH,
+ minHeight: MIN_HEIGHT,
+ autoHideMenuBar: false,
+ webPreferences: {
+ nodeIntegration: false,
+ nodeIntegrationInWorker: false,
+ // sandbox: true,
+ preload: path.join(__dirname, 'launcher_preload.js'),
+ nativeWindowOpen: true,
+ },
+ icon: path.join(__dirname, 'images', 'icon_256.png'),
+ },
+ _.pick(windowConfig, [
+ 'maximized',
+ 'autoHideMenuBar',
+ 'width',
+ 'height',
+ 'x',
+ 'y',
+ ])
+ );
+
+ launcherWindow = new BrowserWindow(windowOptions);
+
+ launcherWindow.loadURL(prepareURL([__dirname, 'launcher.html']));
+
+ captureClicks(launcherWindow);
+
+ // Ingested in preload.js via a sendSync call
+ ipc.on('locale-data', event => {
+ // eslint-disable-next-line no-param-reassign
+ event.returnValue = locale.messages;
+ });
+
+ launcherWindow.on('close', e => {
+ // If the application is terminating, just do the default
+ if (
+ config.environment === 'test' ||
+ config.environment === 'test-lib' ||
+ (windowState.shouldQuit())
+ ) {
+ return;
+ }
+
+ // Prevent the shutdown
+ e.preventDefault();
+ launcherWindow.hide();
+
+ // On Mac, or on other platforms when the tray icon is in use, the window
+ // should be only hidden, not closed, when the user clicks the close button
+ if (
+ !windowState.shouldQuit() &&
+ (usingTrayIcon || process.platform === 'darwin')
+ ) {
+ // toggle the visibility of the show/hide tray icon menu entries
+ if (tray) {
+ tray.updateContextMenu();
+ }
+
+ return;
+ }
+
+ launcherWindow.readyForShutdown = true;
+ app.quit();
+ });
+
+ launcherWindow.on('closed', () => {
+ launcherWindow = null;
+ });
+
+ launcherWindow.once('ready-to-show', () => {
+ launcherWindow.show();
+ });
+}
+
let aboutWindow;
function showAbout() {
if (aboutWindow) {
@@ -654,7 +741,21 @@ app.on('ready', async () => {
key = crypto.randomBytes(32).toString('hex');
userConfig.set('key', key);
}
- await sql.initialize({ configDir: userDataPath, key });
+
+ // If we have a password set then show the launcher
+ // Otherwise show the main window
+ const passHash = userConfig.get('passHash');
+ if (!passHash) {
+ showLauncher();
+ } else {
+ await showMainWindow(key);
+ }
+});
+
+async function showMainWindow(sqlKey) {
+ const userDataPath = await getRealPath(app.getPath('userData'));
+
+ await sql.initialize({ configDir: userDataPath, key: sqlKey });
await sqlChannels.initialize();
try {
@@ -698,7 +799,7 @@ app.on('ready', async () => {
}
setupMenu();
-});
+}
function setupMenu(options) {
const { platform } = process;
diff --git a/package.json b/package.json
index ef51ba443..b37c2b068 100644
--- a/package.json
+++ b/package.json
@@ -224,6 +224,7 @@
"background.html",
"about.html",
"settings.html",
+ "launcher.html",
"permissions_popup.html",
"debug_log.html",
"_locales/**",