// Copyright 2017-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only /* global Whisper, window */ /* eslint-disable global-require, no-inner-declarations */ const preloadStartTime = Date.now(); let preloadEndTime = 0; try { const electron = require('electron'); const semver = require('semver'); const _ = require('lodash'); const { strictAssert } = require('./ts/util/assert'); const { parseIntWithFallback } = require('./ts/util/parseIntWithFallback'); // It is important to call this as early as possible const { SignalContext } = require('./ts/windows/context'); const { getEnvironment, Environment } = require('./ts/environment'); const ipc = electron.ipcRenderer; const { remote } = electron; const { app } = remote; const config = require('url').parse(window.location.toString(), true).query; const log = require('./ts/logging/log'); let title = config.name; if (getEnvironment() !== Environment.Production) { title += ` - ${getEnvironment()}`; } if (config.appInstance) { title += ` - ${config.appInstance}`; } // Flags for testing window.GV2_ENABLE_SINGLE_CHANGE_PROCESSING = true; window.GV2_ENABLE_CHANGE_PROCESSING = true; window.GV2_ENABLE_STATE_PROCESSING = true; window.GV2_MIGRATION_DISABLE_ADD = false; window.GV2_MIGRATION_DISABLE_INVITE = false; window.RETRY_DELAY = false; window.platform = process.platform; window.getTitle = () => title; window.getLocale = () => config.locale; window.getEnvironment = getEnvironment; window.getAppInstance = () => config.appInstance; window.getVersion = () => config.version; window.getBuildCreation = () => parseIntWithFallback(config.buildCreation, 0); window.getExpiration = () => { const sixtyDays = 60 * 86400 * 1000; const remoteBuildExpiration = window.storage.get('remoteBuildExpiration'); const localBuildExpiration = window.Events.getAutoDownloadUpdate() ? config.buildExpiration : config.buildExpiration - sixtyDays; if (remoteBuildExpiration) { return remoteBuildExpiration < config.buildExpiration ? remoteBuildExpiration : localBuildExpiration; } return localBuildExpiration; }; window.getHostName = () => config.hostname; window.getServerTrustRoot = () => config.serverTrustRoot; window.getServerPublicParams = () => config.serverPublicParams; window.getSfuUrl = () => config.sfuUrl; window.isBehindProxy = () => Boolean(config.proxyUrl); window.getAutoLaunch = () => app.getLoginItemSettings().openAtLogin; window.setAutoLaunch = value => { app.setLoginItemSettings({ openAtLogin: Boolean(value) }); }; window.isBeforeVersion = (toCheck, baseVersion) => { try { return semver.lt(toCheck, baseVersion); } catch (error) { log.error( `isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`, error && error.stack ? error.stack : error ); return true; } }; window.isAfterVersion = (toCheck, baseVersion) => { try { return semver.gt(toCheck, baseVersion); } catch (error) { log.error( `isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`, error && error.stack ? error.stack : error ); return true; } }; window.setBadgeCount = count => ipc.send('set-badge-count', count); let connectStartTime = 0; window.logAuthenticatedConnect = () => { if (connectStartTime === 0) { connectStartTime = Date.now(); } }; window.logAppLoadedEvent = ({ processedCount }) => ipc.send('signal-app-loaded', { preloadTime: preloadEndTime - preloadStartTime, connectTime: connectStartTime - preloadEndTime, processedCount, }); // 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 = () => { log.info('draw attention'); ipc.send('draw-attention'); }; window.showWindow = () => { log.info('show window'); ipc.send('show-window'); }; window.titleBarDoubleClick = () => { ipc.send('title-bar-double-click'); }; window.setAutoHideMenuBar = autoHide => ipc.send('set-auto-hide-menu-bar', autoHide); window.setMenuBarVisibility = visibility => ipc.send('set-menu-bar-visibility', visibility); window.updateSystemTraySetting = ( systemTraySetting /* : Readonly */ ) => { ipc.send('update-system-tray-setting', systemTraySetting); }; window.restart = () => { log.info('restart'); ipc.send('restart'); }; window.shutdown = () => { log.info('shutdown'); ipc.send('shutdown'); }; window.showDebugLog = () => { log.info('showDebugLog'); ipc.send('show-debug-log'); }; window.closeAbout = () => ipc.send('close-about'); window.readyForUpdates = () => ipc.send('ready-for-updates'); window.updateTrayIcon = unreadCount => ipc.send('update-tray-icon', unreadCount); ipc.on('additional-log-data-request', async event => { const ourConversation = window.ConversationController.getOurConversation(); const ourCapabilities = ourConversation ? ourConversation.get('capabilities') : undefined; const remoteConfig = window.storage.get('remoteConfig') || {}; let statistics; try { statistics = await window.Signal.Data.getStatisticsForLogging(); } catch (error) { statistics = {}; } const ourUuid = window.textsecure.storage.user.getUuid(); event.sender.send('additional-log-data-response', { capabilities: ourCapabilities || {}, remoteConfig: _.mapValues(remoteConfig, ({ value, enabled }) => { const enableString = enabled ? 'enabled' : 'disabled'; const valueString = value && value !== 'TRUE' ? ` ${value}` : ''; return `${enableString}${valueString}`; }), statistics, user: { deviceId: window.textsecure.storage.user.getDeviceId(), e164: window.textsecure.storage.user.getNumber(), uuid: ourUuid && ourUuid.toString(), conversationId: ourConversation && ourConversation.id, }, }); }); ipc.on('set-up-as-new-device', () => { Whisper.events.trigger('setupAsNewDevice'); }); ipc.on('set-up-as-standalone', () => { Whisper.events.trigger('setupAsStandalone'); }); ipc.on('challenge:response', (_event, response) => { Whisper.events.trigger('challengeResponse', response); }); ipc.on('power-channel:suspend', () => { Whisper.events.trigger('powerMonitorSuspend'); }); ipc.on('power-channel:resume', () => { Whisper.events.trigger('powerMonitorResume'); }); window.sendChallengeRequest = request => ipc.send('challenge:request', request); { let isFullScreen = config.isFullScreen === 'true'; window.isFullScreen = () => isFullScreen; // This is later overwritten. window.onFullScreenChange = _.noop; ipc.on('full-screen-change', (_event, isFull) => { isFullScreen = Boolean(isFull); window.onFullScreenChange(isFullScreen); }); } // Settings-related events window.showSettings = () => ipc.send('show-settings'); window.showPermissionsPopup = () => ipc.send('show-permissions-popup'); window.showCallingPermissionsPopup = forCamera => ipc.invoke('show-calling-permissions-popup', forCamera); ipc.on('show-keyboard-shortcuts', () => { window.Events.showKeyboardShortcuts(); }); ipc.on('add-dark-overlay', () => { window.Events.addDarkOverlay(); }); ipc.on('remove-dark-overlay', () => { window.Events.removeDarkOverlay(); }); require('./ts/windows/preload'); 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'); }); ipc.on('delete-all-data', async () => { const { deleteAllData } = window.Events; if (!deleteAllData) { return; } try { await deleteAllData(); } catch (error) { log.error('delete-all-data: error', error && error.stack); } }); ipc.on('show-sticker-pack', (_event, info) => { const { packId, packKey } = info; const { showStickerPack } = window.Events; if (showStickerPack) { showStickerPack(packId, packKey); } }); ipc.on('show-group-via-link', (_event, info) => { const { hash } = info; const { showGroupViaLink } = window.Events; if (showGroupViaLink) { showGroupViaLink(hash); } }); ipc.on('show-conversation-via-signal.me', (_event, info) => { const { hash } = info; strictAssert(typeof hash === 'string', 'Got an invalid hash over IPC'); const { showConversationViaSignalDotMe } = window.Events; if (showConversationViaSignalDotMe) { showConversationViaSignalDotMe(hash); } }); ipc.on('unknown-sgnl-link', () => { const { unknownSignalLink } = window.Events; if (unknownSignalLink) { unknownSignalLink(); } }); 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) { 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 if (config.proxyUrl) { log.info('Using provided proxy url'); } window.nodeSetImmediate = setImmediate; window.Backbone = require('backbone'); window.textsecure = require('./ts/textsecure').default; window.WebAPI = window.textsecure.WebAPI.initialize({ url: config.serverUrl, storageUrl: config.storageUrl, directoryUrl: config.directoryUrl, directoryEnclaveId: config.directoryEnclaveId, directoryTrustAnchor: config.directoryTrustAnchor, cdnUrlObject: { 0: config.cdnUrl0, 2: config.cdnUrl2, }, certificateAuthority: config.certificateAuthority, contentProxyUrl: config.contentProxyUrl, proxyUrl: config.proxyUrl, version: config.version, }); const { imageToBlurHash } = require('./ts/util/imageToBlurHash'); const { isValidGuid } = require('./ts/util/isValidGuid'); const { ActiveWindowService } = require('./ts/services/ActiveWindowService'); window.imageToBlurHash = imageToBlurHash; window.emojiData = require('emoji-datasource'); window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance(); window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat; window.getGuid = require('uuid/v4'); const activeWindowService = new ActiveWindowService(); activeWindowService.initialize(window.document, ipc); window.isActive = activeWindowService.isActive.bind(activeWindowService); window.registerForActive = activeWindowService.registerForActive.bind( activeWindowService ); window.unregisterForActive = activeWindowService.unregisterForActive.bind( activeWindowService ); window.Accessibility = { reducedMotionSetting: Boolean(config.reducedMotionSetting), }; window.isValidGuid = isValidGuid; 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 Attachments = require('./app/attachments'); const { locale } = config; window.i18n = SignalContext.i18n; 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); const { addSensitivePath } = require('./ts/util/privacy'); addSensitivePath(window.baseAttachmentsPath); window.Signal = Signal.setup({ Attachments, userDataPath, getRegionCode: () => window.storage.get('regionCode'), logger: log, }); if (config.enableCI) { const { CI, electronRequire } = require('./ts/CI'); window.CI = new CI(title); window.electronRequire = electronRequire; } // these need access to window.Signal: require('./ts/models/messages'); require('./ts/models/conversations'); require('./ts/backbone/views/whisper_view'); require('./ts/views/conversation_view'); require('./ts/views/inbox_view'); require('./ts/views/install_view'); require('./ts/views/standalone_registration_view'); require('./ts/SignalProtocolStore'); require('./ts/background'); // Pulling these in separately since they access filesystem, electron window.Signal.Debug = require('./js/modules/debug'); window.addEventListener('contextmenu', e => { const editable = e.target.closest( 'textarea, input, [contenteditable="true"]' ); const link = e.target.closest('a'); const selection = Boolean(window.getSelection().toString()); const image = e.target.closest('.Lightbox img'); if (!editable && !selection && !link && !image) { e.preventDefault(); } }); if (config.environment === 'test') { require('./preload_test'); } log.info('preload complete'); } catch (error) { /* eslint-disable no-console */ if (console._log) { console._log('preload error!', error.stack); } else { console.log('preload error!', error.stack); } /* eslint-enable no-console */ throw error; } preloadEndTime = Date.now();