From 1726e1b77a41dd213fbaf4afae4402db64a8a2e6 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Wed, 26 Feb 2020 17:53:39 -0800 Subject: [PATCH] Fix windows fast-glob usage in lint-deps test task * Introduce normalize-path wherever we use fastGlob * CI: Update yarn version; install yarn via npm on windows * Add more logging to Grunt file spectron usage * Lock core.js to what it was resolving to before: 2.4.1 * test/index.html: Remove nonexistent test file * test/index.html: Remove missing registration.js * preload.js: Introduce client-side logging for load failures * Gruntfile: Introduce better debuggability if prod test fails * Reintroduce glob for searches inside asar --- .travis.yml | 2 +- Gruntfile.js | 53 ++- app/attachments.js | 13 +- appveyor.yml | 2 +- build/install-yarn.ps1 | 12 - package.json | 4 +- preload.js | 801 +++++++++++++++++++++-------------------- test/backup_test.js | 17 +- test/index.html | 2 - ts/util/lint/linter.ts | 3 +- yarn.lock | 28 +- 11 files changed, 497 insertions(+), 440 deletions(-) delete mode 100644 build/install-yarn.ps1 diff --git a/.travis.yml b/.travis.yml index daed92e3bba6..d590e02313ab 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 c5a89e37addc..8d8724fcb6c0 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 b04a67304466..ef2191d6963d 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 c453a4157fd8..c7319102b319 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 30ed9c0e8e17..000000000000 --- 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 41c68185248d..9c15de929050 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 d69a9c8be45c..f5ba01cf38ed 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 e5a8ca78f1fa..eca7f3181862 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 eebbd075962a..1b5aae592759 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 24268b1c32f8..d5e88d11197d 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 cd51e55a5aed..ea991b452e6d 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"