diff --git a/.travis.yml b/.travis.yml
index daed92e3bb..d590e02313 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ os:
- linux
dist: trusty
before_install:
- - npm install -g yarn@1.17.3
+ - npm install -g yarn@1.22.0
install:
- yarn install --frozen-lockfile
script:
diff --git a/Gruntfile.js b/Gruntfile.js
index c5a89e37ad..8d8724fcb6 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,4 +1,4 @@
-const path = require('path');
+const { join } = require('path');
const packageJson = require('./package.json');
const importOnce = require('node-sass-import-once');
const rimraf = require('rimraf');
@@ -223,9 +223,13 @@ module.exports = grunt => {
const { Application } = spectron;
const electronBinary =
process.platform === 'win32' ? 'electron.cmd' : 'electron';
+
+ const path = join(__dirname, 'node_modules', '.bin', electronBinary);
+ const args = [join(__dirname, 'main.js')];
+ console.log('Starting path', path, 'with args', args);
const app = new Application({
- path: path.join(__dirname, 'node_modules', '.bin', electronBinary),
- args: [path.join(__dirname, 'main.js')],
+ path,
+ args,
env: {
NODE_ENV: environment,
},
@@ -239,19 +243,24 @@ module.exports = grunt => {
app
.start()
- .then(() =>
- app.client.waitUntil(
+ .then(() => {
+ console.log('App started. Now waiting for test results...');
+ return app.client.waitUntil(
() =>
app.client
.execute(getMochaResults)
.then(data => Boolean(data.value)),
25000,
'Expected to find window.mochaResults set!'
- )
- )
+ );
+ })
.then(() => app.client.execute(getMochaResults))
.then(data => {
const results = data.value;
+ if (!results) {
+ failure = () => grunt.fail.fatal("Couldn't extract test results.");
+ return app.client.log('browser');
+ }
if (results.failures > 0) {
console.error(results.reports);
failure = () =>
@@ -368,14 +377,23 @@ module.exports = grunt => {
// A simple test to verify a visible window is opened with a title
const { Application } = spectron;
+ const path = [dir, config.exe].join('/');
+ console.log('Starting path', path);
const app = new Application({
- path: [dir, config.exe].join('/'),
- requireName: 'unused',
+ path,
});
- app
- .start()
- .then(() => app.client.getWindowCount())
+ const sleep = millis =>
+ new Promise(resolve => setTimeout(resolve, millis));
+
+ Promise.race([app.start(), sleep(15000)])
+ .then(() => {
+ if (!app.isRunning()) {
+ throw new Error('Application failed to start');
+ }
+
+ return app.client.getWindowCount();
+ })
.then(count => {
assert.equal(count, 1);
console.log('window opened');
@@ -405,6 +423,17 @@ module.exports = grunt => {
grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`);
})
)
+ .catch(error => {
+ console.log('Main process logs:');
+ app.client.getMainProcessLogs().then(logs => {
+ logs.forEach(log => {
+ console.log(log);
+ });
+
+ // Test failed!
+ grunt.fail.fatal(`Failure! ${error.message} ${error.stack}`);
+ });
+ })
.then(done);
}
);
diff --git a/app/attachments.js b/app/attachments.js
index b04a673044..ef2191d696 100644
--- a/app/attachments.js
+++ b/app/attachments.js
@@ -3,9 +3,12 @@ const path = require('path');
const { app, dialog, shell, remote } = require('electron');
const fastGlob = require('fast-glob');
+const glob = require('glob');
+const pify = require('pify');
const fse = require('fs-extra');
const toArrayBuffer = require('to-arraybuffer');
const { map, isArrayBuffer, isString } = require('lodash');
+const normalizePath = require('normalize-path');
const sanitizeFilename = require('sanitize-filename');
const getGuid = require('uuid/v4');
@@ -25,7 +28,7 @@ const DRAFT_PATH = 'drafts.noindex';
exports.getAllAttachments = async userDataPath => {
const dir = exports.getPath(userDataPath);
- const pattern = path.join(dir, '**', '*');
+ const pattern = normalizePath(path.join(dir, '**', '*'));
const files = await fastGlob(pattern, { onlyFiles: true });
return map(files, file => path.relative(dir, file));
@@ -33,7 +36,7 @@ exports.getAllAttachments = async userDataPath => {
exports.getAllStickers = async userDataPath => {
const dir = exports.getStickersPath(userDataPath);
- const pattern = path.join(dir, '**', '*');
+ const pattern = normalizePath(path.join(dir, '**', '*'));
const files = await fastGlob(pattern, { onlyFiles: true });
return map(files, file => path.relative(dir, file));
@@ -41,7 +44,7 @@ exports.getAllStickers = async userDataPath => {
exports.getAllDraftAttachments = async userDataPath => {
const dir = exports.getDraftPath(userDataPath);
- const pattern = path.join(dir, '**', '*');
+ const pattern = normalizePath(path.join(dir, '**', '*'));
const files = await fastGlob(pattern, { onlyFiles: true });
return map(files, file => path.relative(dir, file));
@@ -51,7 +54,9 @@ exports.getBuiltInImages = async () => {
const dir = path.join(__dirname, '../images');
const pattern = path.join(dir, '**', '*.svg');
- const files = await fastGlob(pattern, { onlyFiles: true });
+ // Note: we cannot use fast-glob here because, inside of .asar files, readdir will not
+ // honor the withFileTypes flag: https://github.com/electron/electron/issues/19074
+ const files = await pify(glob)(pattern, { nodir: true });
return map(files, file => path.relative(dir, file));
};
diff --git a/appveyor.yml b/appveyor.yml
index c453a4157f..c7319102b3 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,7 +9,7 @@ install:
- systeminfo | findstr /C:"OS"
- set PATH=C:\Ruby23-x64\bin;%PATH%
- ps: Install-Product node 12.13.0 x64
- - ps: .\build\install-yarn.ps1
+ - npm install -g yarn@1.22.0
- yarn install --frozen-lockfile
build_script:
diff --git a/build/install-yarn.ps1 b/build/install-yarn.ps1
deleted file mode 100644
index 30ed9c0e8e..0000000000
--- a/build/install-yarn.ps1
+++ /dev/null
@@ -1,12 +0,0 @@
-# via https://gist.github.com/FeodorFitsner/1056703ec92bd6a3012c15fe78a5e162
-
-Write-Host "Installing Yarn..." -ForegroundColor Cyan
-
-Write-Host "Downloading..."
-$msiPath = "$env:TEMP\yarn.msi"
-(New-Object Net.WebClient).DownloadFile('https://github.com/yarnpkg/yarn/releases/download/v1.17.3/yarn-1.17.3.msi', $msiPath)
-
-Write-Host "Installing..."
-cmd /c start /wait msiexec /i "$msiPath" /quiet
-
-Write-Host "Yarn installed" -ForegroundColor Green
diff --git a/package.json b/package.json
index 41c6818524..9c15de9290 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"form-data": "2.3.2",
"fs-extra": "5.0.0",
"fuse.js": "3.4.4",
+ "glob": "7.1.6",
"google-libphonenumber": "3.2.6",
"got": "8.2.0",
"he": "1.2.0",
@@ -173,6 +174,7 @@
"@types/memoizee": "0.4.2",
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.0.0",
+ "@types/normalize-path": "3.0.0",
"@types/pify": "3.0.2",
"@types/qs": "6.5.1",
"@types/react": "16.8.5",
@@ -200,7 +202,7 @@
"babel-plugin-lodash": "3.3.4",
"bower": "1.8.2",
"chai": "4.1.2",
- "core-js": "2.4.0",
+ "core-js": "2.4.1",
"cross-env": "5.2.0",
"css-loader": "3.2.0",
"dashdash": "1.14.1",
diff --git a/preload.js b/preload.js
index d69a9c8be4..f5ba01cf38 100644
--- a/preload.js
+++ b/preload.js
@@ -1,408 +1,419 @@
/* global Whisper, window */
-const electron = require('electron');
-const semver = require('semver');
-const curve = require('curve25519-n');
-const { installGetter, installSetter } = require('./preload_utils');
+/* eslint-disable global-require, no-inner-declarations */
-const { deferredToPromise } = require('./js/modules/deferred_to_promise');
+try {
+ const electron = require('electron');
+ const semver = require('semver');
+ const curve = require('curve25519-n');
+ const { installGetter, installSetter } = require('./preload_utils');
-const { remote } = electron;
-const { app } = remote;
-const { systemPreferences } = remote.require('electron');
+ const { deferredToPromise } = require('./js/modules/deferred_to_promise');
-window.PROTO_ROOT = 'protos';
-const config = require('url').parse(window.location.toString(), true).query;
+ const { remote } = electron;
+ const { app } = remote;
+ const { systemPreferences } = remote.require('electron');
-let title = config.name;
-if (config.environment !== 'production') {
- title += ` - ${config.environment}`;
-}
-if (config.appInstance) {
- title += ` - ${config.appInstance}`;
-}
+ window.PROTO_ROOT = 'protos';
+ const config = require('url').parse(window.location.toString(), true).query;
-window.platform = process.platform;
-window.getTitle = () => title;
-window.getEnvironment = () => config.environment;
-window.getAppInstance = () => config.appInstance;
-window.getVersion = () => config.version;
-window.isImportMode = () => config.importMode;
-window.getExpiration = () => config.buildExpiration;
-window.getNodeVersion = () => config.node_version;
-window.getHostName = () => config.hostname;
-window.getServerTrustRoot = () => config.serverTrustRoot;
-window.isBehindProxy = () => Boolean(config.proxyUrl);
-
-function setSystemTheme() {
- window.systemTheme = systemPreferences.isDarkMode() ? 'dark' : 'light';
-}
-
-setSystemTheme();
-
-window.subscribeToSystemThemeChange = fn => {
- if (!systemPreferences.subscribeNotification) {
- return;
+ let title = config.name;
+ if (config.environment !== 'production') {
+ title += ` - ${config.environment}`;
}
- systemPreferences.subscribeNotification(
- 'AppleInterfaceThemeChangedNotification',
- () => {
- setSystemTheme();
- fn();
+ if (config.appInstance) {
+ title += ` - ${config.appInstance}`;
+ }
+
+ window.platform = process.platform;
+ window.getTitle = () => title;
+ window.getEnvironment = () => config.environment;
+ window.getAppInstance = () => config.appInstance;
+ window.getVersion = () => config.version;
+ window.isImportMode = () => config.importMode;
+ window.getExpiration = () => config.buildExpiration;
+ window.getNodeVersion = () => config.node_version;
+ window.getHostName = () => config.hostname;
+ window.getServerTrustRoot = () => config.serverTrustRoot;
+ window.isBehindProxy = () => Boolean(config.proxyUrl);
+
+ function setSystemTheme() {
+ window.systemTheme = systemPreferences.isDarkMode() ? 'dark' : 'light';
+ }
+
+ setSystemTheme();
+
+ window.subscribeToSystemThemeChange = fn => {
+ if (!systemPreferences.subscribeNotification) {
+ return;
}
- );
-};
-
-window.isBeforeVersion = (toCheck, baseVersion) => {
- try {
- return semver.lt(toCheck, baseVersion);
- } catch (error) {
- window.log.error(
- `isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
- error && error.stack ? error.stack : error
- );
- return true;
- }
-};
-
-window.wrapDeferred = deferredToPromise;
-
-const ipc = electron.ipcRenderer;
-const localeMessages = ipc.sendSync('locale-data');
-
-window.setBadgeCount = count => ipc.send('set-badge-count', count);
-
-// We never do these in our code, so we'll prevent it everywhere
-window.open = () => null;
-// eslint-disable-next-line no-eval, no-multi-assign
-window.eval = global.eval = () => null;
-
-window.drawAttention = () => {
- window.log.info('draw attention');
- ipc.send('draw-attention');
-};
-window.showWindow = () => {
- window.log.info('show window');
- ipc.send('show-window');
-};
-
-window.setAutoHideMenuBar = autoHide =>
- ipc.send('set-auto-hide-menu-bar', autoHide);
-
-window.setMenuBarVisibility = visibility =>
- ipc.send('set-menu-bar-visibility', visibility);
-
-window.restart = () => {
- window.log.info('restart');
- ipc.send('restart');
-};
-
-window.closeAbout = () => ipc.send('close-about');
-window.readyForUpdates = () => ipc.send('ready-for-updates');
-
-window.updateTrayIcon = unreadCount =>
- ipc.send('update-tray-icon', unreadCount);
-
-ipc.on('set-up-with-import', () => {
- Whisper.events.trigger('setupWithImport');
-});
-
-ipc.on('set-up-as-new-device', () => {
- Whisper.events.trigger('setupAsNewDevice');
-});
-
-ipc.on('set-up-as-standalone', () => {
- Whisper.events.trigger('setupAsStandalone');
-});
-
-// Settings-related events
-
-window.showSettings = () => ipc.send('show-settings');
-window.showPermissionsPopup = () => ipc.send('show-permissions-popup');
-
-ipc.on('show-keyboard-shortcuts', () => {
- window.Events.showKeyboardShortcuts();
-});
-ipc.on('add-dark-overlay', () => {
- window.Events.addDarkOverlay();
-});
-ipc.on('remove-dark-overlay', () => {
- window.Events.removeDarkOverlay();
-});
-
-installGetter('device-name', 'getDeviceName');
-
-installGetter('theme-setting', 'getThemeSetting');
-installSetter('theme-setting', 'setThemeSetting');
-installGetter('hide-menu-bar', 'getHideMenuBar');
-installSetter('hide-menu-bar', 'setHideMenuBar');
-
-installGetter('notification-setting', 'getNotificationSetting');
-installSetter('notification-setting', 'setNotificationSetting');
-installGetter('audio-notification', 'getAudioNotification');
-installSetter('audio-notification', 'setAudioNotification');
-
-installGetter('spell-check', 'getSpellCheck');
-installSetter('spell-check', 'setSpellCheck');
-
-window.getMediaPermissions = () =>
- new Promise((resolve, reject) => {
- ipc.once('get-success-media-permissions', (_event, error, value) => {
- if (error) {
- return reject(new Error(error));
+ systemPreferences.subscribeNotification(
+ 'AppleInterfaceThemeChangedNotification',
+ () => {
+ setSystemTheme();
+ fn();
}
-
- return resolve(value);
- });
- ipc.send('get-media-permissions');
- });
-
-window.getBuiltInImages = () =>
- new Promise((resolve, reject) => {
- ipc.once('get-success-built-in-images', (_event, error, value) => {
- if (error) {
- return reject(new Error(error));
- }
-
- return resolve(value);
- });
- ipc.send('get-built-in-images');
- });
-
-installGetter('is-primary', 'isPrimary');
-installGetter('sync-request', 'getSyncRequest');
-installGetter('sync-time', 'getLastSyncTime');
-installSetter('sync-time', 'setLastSyncTime');
-
-ipc.on('delete-all-data', () => {
- const { deleteAllData } = window.Events;
- if (deleteAllData) {
- deleteAllData();
- }
-});
-
-ipc.on('show-sticker-pack', (_event, info) => {
- const { packId, packKey } = info;
- const { showStickerPack } = window.Events;
- if (showStickerPack) {
- showStickerPack(packId, packKey);
- }
-});
-
-ipc.on('install-sticker-pack', (_event, info) => {
- const { packId, packKey } = info;
- const { installStickerPack } = window.Events;
- if (installStickerPack) {
- installStickerPack(packId, packKey);
- }
-});
-
-ipc.on('get-ready-for-shutdown', async () => {
- const { shutdown } = window.Events || {};
- if (!shutdown) {
- window.log.error('preload shutdown handler: shutdown method not found');
- ipc.send('now-ready-for-shutdown');
- return;
- }
-
- try {
- await shutdown();
- ipc.send('now-ready-for-shutdown');
- } catch (error) {
- ipc.send(
- 'now-ready-for-shutdown',
- error && error.stack ? error.stack : error
);
- }
-});
-
-window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
-window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
-
-// We pull these dependencies in now, from here, because they have Node.js dependencies
-
-require('./js/logging');
-
-if (config.proxyUrl) {
- window.log.info('Using provided proxy url');
-}
-
-window.nodeSetImmediate = setImmediate;
-
-const { initialize: initializeWebAPI } = require('./js/modules/web_api');
-
-window.WebAPI = initializeWebAPI({
- url: config.serverUrl,
- cdnUrl: config.cdnUrl,
- certificateAuthority: config.certificateAuthority,
- contentProxyUrl: config.contentProxyUrl,
- proxyUrl: config.proxyUrl,
- version: config.version,
-});
-
-// Linux seems to periodically let the event loop stop, so this is a global workaround
-setInterval(() => {
- window.nodeSetImmediate(() => {});
-}, 1000);
-
-const { autoOrientImage } = require('./js/modules/auto_orient_image');
-
-window.autoOrientImage = autoOrientImage;
-window.dataURLToBlobSync = require('blueimp-canvas-to-blob');
-window.emojiData = require('emoji-datasource');
-window.filesize = require('filesize');
-window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance();
-window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
-window.loadImage = require('blueimp-load-image');
-window.getGuid = require('uuid/v4');
-
-window.React = require('react');
-window.ReactDOM = require('react-dom');
-window.moment = require('moment');
-window.PQueue = require('p-queue').default;
-
-const Signal = require('./js/modules/signal');
-const i18n = require('./js/modules/i18n');
-const Attachments = require('./app/attachments');
-
-const { locale } = config;
-window.i18n = i18n.setup(locale, localeMessages);
-window.moment.updateLocale(locale, {
- relativeTime: {
- s: window.i18n('timestamp_s'),
- m: window.i18n('timestamp_m'),
- h: window.i18n('timestamp_h'),
- },
-});
-window.moment.locale(locale);
-
-const userDataPath = app.getPath('userData');
-window.baseAttachmentsPath = Attachments.getPath(userDataPath);
-window.baseStickersPath = Attachments.getStickersPath(userDataPath);
-window.baseTempPath = Attachments.getTempPath(userDataPath);
-window.baseDraftPath = Attachments.getDraftPath(userDataPath);
-window.Signal = Signal.setup({
- Attachments,
- userDataPath,
- getRegionCode: () => window.storage.get('regionCode'),
- logger: window.log,
-});
-
-function wrapWithPromise(fn) {
- return (...args) => Promise.resolve(fn(...args));
-}
-function typedArrayToArrayBuffer(typedArray) {
- const { buffer, byteOffset, byteLength } = typedArray;
- return buffer.slice(byteOffset, byteLength + byteOffset);
-}
-const externalCurve = {
- generateKeyPair: () => {
- const { privKey, pubKey } = curve.generateKeyPair();
-
- return {
- privKey: typedArrayToArrayBuffer(privKey),
- pubKey: typedArrayToArrayBuffer(pubKey),
- };
- },
- createKeyPair: incomingKey => {
- const incomingKeyBuffer = Buffer.from(incomingKey);
- const { privKey, pubKey } = curve.createKeyPair(incomingKeyBuffer);
-
- return {
- privKey: typedArrayToArrayBuffer(privKey),
- pubKey: typedArrayToArrayBuffer(pubKey),
- };
- },
- calculateAgreement: (pubKey, privKey) => {
- const pubKeyBuffer = Buffer.from(pubKey);
- const privKeyBuffer = Buffer.from(privKey);
-
- const buffer = curve.calculateAgreement(pubKeyBuffer, privKeyBuffer);
-
- return typedArrayToArrayBuffer(buffer);
- },
- verifySignature: (pubKey, message, signature) => {
- const pubKeyBuffer = Buffer.from(pubKey);
- const messageBuffer = Buffer.from(message);
- const signatureBuffer = Buffer.from(signature);
-
- const result = curve.verifySignature(
- pubKeyBuffer,
- messageBuffer,
- signatureBuffer
- );
-
- return result;
- },
- calculateSignature: (privKey, message) => {
- const privKeyBuffer = Buffer.from(privKey);
- const messageBuffer = Buffer.from(message);
-
- const buffer = curve.calculateSignature(privKeyBuffer, messageBuffer);
-
- return typedArrayToArrayBuffer(buffer);
- },
- validatePubKeyFormat: pubKey => {
- const pubKeyBuffer = Buffer.from(pubKey);
-
- return curve.validatePubKeyFormat(pubKeyBuffer);
- },
-};
-externalCurve.ECDHE = externalCurve.calculateAgreement;
-externalCurve.Ed25519Sign = externalCurve.calculateSignature;
-externalCurve.Ed25519Verify = externalCurve.verifySignature;
-const externalCurveAsync = {
- generateKeyPair: wrapWithPromise(externalCurve.generateKeyPair),
- createKeyPair: wrapWithPromise(externalCurve.createKeyPair),
- calculateAgreement: wrapWithPromise(externalCurve.calculateAgreement),
- verifySignature: async (...args) => {
- // The async verifySignature function has a different signature than the sync function
- const verifyFailed = externalCurve.verifySignature(...args);
- if (verifyFailed) {
- throw new Error('Invalid signature');
- }
- },
- calculateSignature: wrapWithPromise(externalCurve.calculateSignature),
- validatePubKeyFormat: wrapWithPromise(externalCurve.validatePubKeyFormat),
- ECDHE: wrapWithPromise(externalCurve.ECDHE),
- Ed25519Sign: wrapWithPromise(externalCurve.Ed25519Sign),
- Ed25519Verify: wrapWithPromise(externalCurve.Ed25519Verify),
-};
-window.libsignal = window.libsignal || {};
-window.libsignal.externalCurve = externalCurve;
-window.libsignal.externalCurveAsync = externalCurveAsync;
-
-// Pulling these in separately since they access filesystem, electron
-window.Signal.Backup = require('./js/modules/backup');
-window.Signal.Debug = require('./js/modules/debug');
-window.Signal.Logs = require('./js/modules/logs');
-
-// Add right-click listener for selected text and urls
-const contextMenu = require('electron-context-menu');
-
-contextMenu({
- showInspectElement: false,
- shouldShowMenu: (event, params) =>
- Boolean(
- !params.isEditable &&
- params.mediaType === 'none' &&
- (params.linkURL || params.selectionText)
- ),
-});
-
-// We pull this in last, because the native module involved appears to be sensitive to
-// /tmp mounted as noexec on Linux.
-require('./js/spell_check');
-
-if (config.environment === 'test') {
- /* eslint-disable global-require, import/no-extraneous-dependencies */
- window.test = {
- fastGlob: require('fast-glob'),
- fse: require('fs-extra'),
- tmp: require('tmp'),
- path: require('path'),
- basePath: __dirname,
- attachmentsPath: window.Signal.Migrations.attachmentsPath,
};
- /* eslint-enable global-require, import/no-extraneous-dependencies */
+
+ window.isBeforeVersion = (toCheck, baseVersion) => {
+ try {
+ return semver.lt(toCheck, baseVersion);
+ } catch (error) {
+ window.log.error(
+ `isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
+ error && error.stack ? error.stack : error
+ );
+ return true;
+ }
+ };
+
+ window.wrapDeferred = deferredToPromise;
+
+ const ipc = electron.ipcRenderer;
+ const localeMessages = ipc.sendSync('locale-data');
+
+ window.setBadgeCount = count => ipc.send('set-badge-count', count);
+
+ // We never do these in our code, so we'll prevent it everywhere
+ window.open = () => null;
+ // eslint-disable-next-line no-eval, no-multi-assign
+ window.eval = global.eval = () => null;
+
+ window.drawAttention = () => {
+ window.log.info('draw attention');
+ ipc.send('draw-attention');
+ };
+ window.showWindow = () => {
+ window.log.info('show window');
+ ipc.send('show-window');
+ };
+
+ window.setAutoHideMenuBar = autoHide =>
+ ipc.send('set-auto-hide-menu-bar', autoHide);
+
+ window.setMenuBarVisibility = visibility =>
+ ipc.send('set-menu-bar-visibility', visibility);
+
+ window.restart = () => {
+ window.log.info('restart');
+ ipc.send('restart');
+ };
+
+ window.closeAbout = () => ipc.send('close-about');
+ window.readyForUpdates = () => ipc.send('ready-for-updates');
+
+ window.updateTrayIcon = unreadCount =>
+ ipc.send('update-tray-icon', unreadCount);
+
+ ipc.on('set-up-with-import', () => {
+ Whisper.events.trigger('setupWithImport');
+ });
+
+ ipc.on('set-up-as-new-device', () => {
+ Whisper.events.trigger('setupAsNewDevice');
+ });
+
+ ipc.on('set-up-as-standalone', () => {
+ Whisper.events.trigger('setupAsStandalone');
+ });
+
+ // Settings-related events
+
+ window.showSettings = () => ipc.send('show-settings');
+ window.showPermissionsPopup = () => ipc.send('show-permissions-popup');
+
+ ipc.on('show-keyboard-shortcuts', () => {
+ window.Events.showKeyboardShortcuts();
+ });
+ ipc.on('add-dark-overlay', () => {
+ window.Events.addDarkOverlay();
+ });
+ ipc.on('remove-dark-overlay', () => {
+ window.Events.removeDarkOverlay();
+ });
+
+ installGetter('device-name', 'getDeviceName');
+
+ installGetter('theme-setting', 'getThemeSetting');
+ installSetter('theme-setting', 'setThemeSetting');
+ installGetter('hide-menu-bar', 'getHideMenuBar');
+ installSetter('hide-menu-bar', 'setHideMenuBar');
+
+ installGetter('notification-setting', 'getNotificationSetting');
+ installSetter('notification-setting', 'setNotificationSetting');
+ installGetter('audio-notification', 'getAudioNotification');
+ installSetter('audio-notification', 'setAudioNotification');
+
+ installGetter('spell-check', 'getSpellCheck');
+ installSetter('spell-check', 'setSpellCheck');
+
+ window.getMediaPermissions = () =>
+ new Promise((resolve, reject) => {
+ ipc.once('get-success-media-permissions', (_event, error, value) => {
+ if (error) {
+ return reject(new Error(error));
+ }
+
+ return resolve(value);
+ });
+ ipc.send('get-media-permissions');
+ });
+
+ window.getBuiltInImages = () =>
+ new Promise((resolve, reject) => {
+ ipc.once('get-success-built-in-images', (_event, error, value) => {
+ if (error) {
+ return reject(new Error(error));
+ }
+
+ return resolve(value);
+ });
+ ipc.send('get-built-in-images');
+ });
+
+ installGetter('is-primary', 'isPrimary');
+ installGetter('sync-request', 'getSyncRequest');
+ installGetter('sync-time', 'getLastSyncTime');
+ installSetter('sync-time', 'setLastSyncTime');
+
+ ipc.on('delete-all-data', () => {
+ const { deleteAllData } = window.Events;
+ if (deleteAllData) {
+ deleteAllData();
+ }
+ });
+
+ ipc.on('show-sticker-pack', (_event, info) => {
+ const { packId, packKey } = info;
+ const { showStickerPack } = window.Events;
+ if (showStickerPack) {
+ showStickerPack(packId, packKey);
+ }
+ });
+
+ ipc.on('install-sticker-pack', (_event, info) => {
+ const { packId, packKey } = info;
+ const { installStickerPack } = window.Events;
+ if (installStickerPack) {
+ installStickerPack(packId, packKey);
+ }
+ });
+
+ ipc.on('get-ready-for-shutdown', async () => {
+ const { shutdown } = window.Events || {};
+ if (!shutdown) {
+ window.log.error('preload shutdown handler: shutdown method not found');
+ ipc.send('now-ready-for-shutdown');
+ return;
+ }
+
+ try {
+ await shutdown();
+ ipc.send('now-ready-for-shutdown');
+ } catch (error) {
+ ipc.send(
+ 'now-ready-for-shutdown',
+ error && error.stack ? error.stack : error
+ );
+ }
+ });
+
+ window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
+ window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
+
+ // We pull these dependencies in now, from here, because they have Node.js dependencies
+
+ require('./js/logging');
+
+ if (config.proxyUrl) {
+ window.log.info('Using provided proxy url');
+ }
+
+ window.nodeSetImmediate = setImmediate;
+
+ const { initialize: initializeWebAPI } = require('./js/modules/web_api');
+
+ window.WebAPI = initializeWebAPI({
+ url: config.serverUrl,
+ cdnUrl: config.cdnUrl,
+ certificateAuthority: config.certificateAuthority,
+ contentProxyUrl: config.contentProxyUrl,
+ proxyUrl: config.proxyUrl,
+ version: config.version,
+ });
+
+ // Linux seems to periodically let the event loop stop, so this is a global workaround
+ setInterval(() => {
+ window.nodeSetImmediate(() => {});
+ }, 1000);
+
+ const { autoOrientImage } = require('./js/modules/auto_orient_image');
+
+ window.autoOrientImage = autoOrientImage;
+ window.dataURLToBlobSync = require('blueimp-canvas-to-blob');
+ window.emojiData = require('emoji-datasource');
+ window.filesize = require('filesize');
+ window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance();
+ window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
+ window.loadImage = require('blueimp-load-image');
+ window.getGuid = require('uuid/v4');
+
+ window.React = require('react');
+ window.ReactDOM = require('react-dom');
+ window.moment = require('moment');
+ window.PQueue = require('p-queue').default;
+
+ const Signal = require('./js/modules/signal');
+ const i18n = require('./js/modules/i18n');
+ const Attachments = require('./app/attachments');
+
+ const { locale } = config;
+ window.i18n = i18n.setup(locale, localeMessages);
+ window.moment.updateLocale(locale, {
+ relativeTime: {
+ s: window.i18n('timestamp_s'),
+ m: window.i18n('timestamp_m'),
+ h: window.i18n('timestamp_h'),
+ },
+ });
+ window.moment.locale(locale);
+
+ const userDataPath = app.getPath('userData');
+ window.baseAttachmentsPath = Attachments.getPath(userDataPath);
+ window.baseStickersPath = Attachments.getStickersPath(userDataPath);
+ window.baseTempPath = Attachments.getTempPath(userDataPath);
+ window.baseDraftPath = Attachments.getDraftPath(userDataPath);
+ window.Signal = Signal.setup({
+ Attachments,
+ userDataPath,
+ getRegionCode: () => window.storage.get('regionCode'),
+ logger: window.log,
+ });
+
+ function wrapWithPromise(fn) {
+ return (...args) => Promise.resolve(fn(...args));
+ }
+ function typedArrayToArrayBuffer(typedArray) {
+ const { buffer, byteOffset, byteLength } = typedArray;
+ return buffer.slice(byteOffset, byteLength + byteOffset);
+ }
+ const externalCurve = {
+ generateKeyPair: () => {
+ const { privKey, pubKey } = curve.generateKeyPair();
+
+ return {
+ privKey: typedArrayToArrayBuffer(privKey),
+ pubKey: typedArrayToArrayBuffer(pubKey),
+ };
+ },
+ createKeyPair: incomingKey => {
+ const incomingKeyBuffer = Buffer.from(incomingKey);
+ const { privKey, pubKey } = curve.createKeyPair(incomingKeyBuffer);
+
+ return {
+ privKey: typedArrayToArrayBuffer(privKey),
+ pubKey: typedArrayToArrayBuffer(pubKey),
+ };
+ },
+ calculateAgreement: (pubKey, privKey) => {
+ const pubKeyBuffer = Buffer.from(pubKey);
+ const privKeyBuffer = Buffer.from(privKey);
+
+ const buffer = curve.calculateAgreement(pubKeyBuffer, privKeyBuffer);
+
+ return typedArrayToArrayBuffer(buffer);
+ },
+ verifySignature: (pubKey, message, signature) => {
+ const pubKeyBuffer = Buffer.from(pubKey);
+ const messageBuffer = Buffer.from(message);
+ const signatureBuffer = Buffer.from(signature);
+
+ const result = curve.verifySignature(
+ pubKeyBuffer,
+ messageBuffer,
+ signatureBuffer
+ );
+
+ return result;
+ },
+ calculateSignature: (privKey, message) => {
+ const privKeyBuffer = Buffer.from(privKey);
+ const messageBuffer = Buffer.from(message);
+
+ const buffer = curve.calculateSignature(privKeyBuffer, messageBuffer);
+
+ return typedArrayToArrayBuffer(buffer);
+ },
+ validatePubKeyFormat: pubKey => {
+ const pubKeyBuffer = Buffer.from(pubKey);
+
+ return curve.validatePubKeyFormat(pubKeyBuffer);
+ },
+ };
+ externalCurve.ECDHE = externalCurve.calculateAgreement;
+ externalCurve.Ed25519Sign = externalCurve.calculateSignature;
+ externalCurve.Ed25519Verify = externalCurve.verifySignature;
+ const externalCurveAsync = {
+ generateKeyPair: wrapWithPromise(externalCurve.generateKeyPair),
+ createKeyPair: wrapWithPromise(externalCurve.createKeyPair),
+ calculateAgreement: wrapWithPromise(externalCurve.calculateAgreement),
+ verifySignature: async (...args) => {
+ // The async verifySignature function has a different signature than the
+ // sync function
+ const verifyFailed = externalCurve.verifySignature(...args);
+ if (verifyFailed) {
+ throw new Error('Invalid signature');
+ }
+ },
+ calculateSignature: wrapWithPromise(externalCurve.calculateSignature),
+ validatePubKeyFormat: wrapWithPromise(externalCurve.validatePubKeyFormat),
+ ECDHE: wrapWithPromise(externalCurve.ECDHE),
+ Ed25519Sign: wrapWithPromise(externalCurve.Ed25519Sign),
+ Ed25519Verify: wrapWithPromise(externalCurve.Ed25519Verify),
+ };
+ window.libsignal = window.libsignal || {};
+ window.libsignal.externalCurve = externalCurve;
+ window.libsignal.externalCurveAsync = externalCurveAsync;
+
+ // Pulling these in separately since they access filesystem, electron
+ window.Signal.Backup = require('./js/modules/backup');
+ window.Signal.Debug = require('./js/modules/debug');
+ window.Signal.Logs = require('./js/modules/logs');
+
+ // Add right-click listener for selected text and urls
+ const contextMenu = require('electron-context-menu');
+
+ contextMenu({
+ showInspectElement: false,
+ shouldShowMenu: (event, params) =>
+ Boolean(
+ !params.isEditable &&
+ params.mediaType === 'none' &&
+ (params.linkURL || params.selectionText)
+ ),
+ });
+
+ // We pull this in last, because the native module involved appears to be sensitive to
+ // /tmp mounted as noexec on Linux.
+ require('./js/spell_check');
+
+ if (config.environment === 'test') {
+ /* eslint-disable global-require, import/no-extraneous-dependencies */
+ window.test = {
+ fastGlob: require('fast-glob'),
+ normalizePath: require('normalize-path'),
+ fse: require('fs-extra'),
+ tmp: require('tmp'),
+ path: require('path'),
+ basePath: __dirname,
+ attachmentsPath: window.Signal.Migrations.attachmentsPath,
+ };
+ /* eslint-enable global-require, import/no-extraneous-dependencies */
+ }
+} catch (error) {
+ window.log.info('preload error!', error.stack);
+ throw error;
}
+
+window.log.info('preload complete');
diff --git a/test/backup_test.js b/test/backup_test.js
index e5a8ca78f1..eca7f31818 100644
--- a/test/backup_test.js
+++ b/test/backup_test.js
@@ -239,14 +239,23 @@ describe('Backup', () => {
it('exports then imports to produce the same data we started with', async function thisNeeded() {
this.timeout(6000);
- const { attachmentsPath, fse, fastGlob, path, tmp } = window.test;
+ const {
+ attachmentsPath,
+ fse,
+ fastGlob,
+ normalizePath,
+ path,
+ tmp,
+ } = window.test;
const {
upgradeMessageSchema,
loadAttachmentData,
} = window.Signal.Migrations;
const staticKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
- const attachmentsPattern = path.join(attachmentsPath, '**');
+ const attachmentsPattern = normalizePath(
+ path.join(attachmentsPath, '**')
+ );
const OUR_NUMBER = '+12025550000';
const CONTACT_ONE_NUMBER = '+12025550001';
@@ -528,7 +537,9 @@ describe('Backup', () => {
console.log(
'Backup test: Ensure that all attachments made it to backup dir'
);
- const backupAttachmentPattern = path.join(backupDir, 'attachments/*');
+ const backupAttachmentPattern = normalizePath(
+ path.join(backupDir, 'attachments/*')
+ );
const backupAttachments = fastGlob.sync(backupAttachmentPattern);
console.log({ backupAttachments });
assert.strictEqual(ATTACHMENT_COUNT, backupAttachments.length);
diff --git a/test/index.html b/test/index.html
index eebbd07596..1b5aae5927 100644
--- a/test/index.html
+++ b/test/index.html
@@ -433,7 +433,6 @@
-
@@ -477,7 +476,6 @@
-
diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts
index 24268b1c32..d5e88d1119 100644
--- a/ts/util/lint/linter.ts
+++ b/ts/util/lint/linter.ts
@@ -2,6 +2,7 @@
import { readFileSync } from 'fs';
import { join, relative } from 'path';
+import normalizePath from 'normalize-path';
import { sync as fgSync } from 'fast-glob';
import { forEach, some, values } from 'lodash';
@@ -36,7 +37,7 @@ const rulesPath = join(__dirname, 'rules.json');
const exceptionsPath = join(__dirname, 'exceptions.json');
const basePath = join(__dirname, '../../..');
-const searchPattern = join(basePath, '**/*.{js,ts,tsx}');
+const searchPattern = normalizePath(join(basePath, '**/*.{js,ts,tsx}'));
const rules: Array = loadJSON(rulesPath);
const exceptions: Array = loadJSON(exceptionsPath);
diff --git a/yarn.lock b/yarn.lock
index cd51e55a5a..ea991b452e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2081,6 +2081,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.40.tgz#4314888d5cd537945d73e9ce165c04cc550144a4"
integrity sha512-RRSjdwz63kS4u7edIwJUn8NqKLLQ6LyqF/X4+4jp38MBT3Vwetewi2N4dgJEshLbDwNgOJXNYoOwzVZUSSLhkQ==
+"@types/normalize-path@3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/normalize-path/-/normalize-path-3.0.0.tgz#bb5c46cab77b93350b4cf8d7ff1153f47189ae31"
+ integrity sha512-Nd8y/5t/7CRakPYiyPzr/IAfYusy1FkcZYFEAcoMZkwpJv2n4Wm+olW+e7xBdHEXhOnWdG9ddbar0gqZWS4x5Q==
+
"@types/pify@3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/pify/-/pify-3.0.2.tgz#1bc75dac43e31dba981c37e0a08edddc1b49cd39"
@@ -5139,19 +5144,14 @@ core-js-pure@^3.0.1:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.2.1.tgz#879a23699cff46175bfd2d09158b5c50645a3c45"
integrity sha512-+qpvnYrsi/JDeQTArB7NnNc2VoMYLE1YSkziCDHgjexC2KH7OFiGhLUd3urxfyWmNjSwSW7NYXPWHMhuIJx9Ow==
-core-js@2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.0.tgz#df408ab46d01aff91c01c3e7971935d422c54f81"
- integrity sha1-30CKtG0Br/kcAcPnlxk11CLFT4E=
+core-js@2.4.1, core-js@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
-core-js@^2.4.0:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
-
core-js@^2.4.1:
version "2.5.4"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.4.tgz#f2c8bf181f2a80b92f360121429ce63a2f0aeae0"
@@ -7919,6 +7919,18 @@ glob@7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@7.1.6:
+ version "7.1.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+ integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
glob@^6.0.1, glob@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"