From fe37f46bdeb8ed9b7600405f984802e938ac7bb8 Mon Sep 17 00:00:00 2001 From: Oj Date: Sun, 6 Feb 2022 17:48:27 +0000 Subject: [PATCH] [Splash] Fix accidental push of stripped testing --- src/splash/index.js | 108 ++++++++++++++++++++++++++++++++++++++---- src/splash/preload.js | 5 ++ 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/splash/index.js b/src/splash/index.js index 04e075e..4c89d60 100644 --- a/src/splash/index.js +++ b/src/splash/index.js @@ -1,4 +1,5 @@ "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); @@ -6,25 +7,42 @@ exports.initSplash = initSplash; exports.focusWindow = focusWindow; exports.pageReady = pageReady; exports.events = exports.APP_SHOULD_SHOW = exports.APP_SHOULD_LAUNCH = void 0; + var _electron = require("electron"); + var _events = require("events"); + var _fs = _interopRequireDefault(require("fs")); + var _path = _interopRequireDefault(require("path")); + var _url = _interopRequireDefault(require("url")); + var _Backoff = _interopRequireDefault(require("../utils/Backoff")); + var moduleUpdater = _interopRequireWildcard(require("../updater/moduleUpdater")); + var paths = _interopRequireWildcard(require("../paths")); + var _securityUtils = require("../utils/securityUtils"); + var _updater = require("../updater/updater"); + const ipcMain = _electron.ipcMain; + function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } + function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + const UPDATE_TIMEOUT_WAIT = 10000; -const RETRY_CAP_SECONDS = 60; +const RETRY_CAP_SECONDS = 60; // citron note: atom seems to add about 50px height to the frame on mac but not windows // TODO: see if we can eliminate fudge by using useContentSize BrowserWindow option + const LOADING_WINDOW_WIDTH = 300; -const LOADING_WINDOW_HEIGHT = process.platform === 'darwin' ? 300 : 350; +const LOADING_WINDOW_HEIGHT = process.platform === 'darwin' ? 300 : 350; // TODO: addModulesListener events should use Module's constants + const CHECKING_FOR_UPDATES = 'checking-for-updates'; const UPDATE_CHECK_FINISHED = 'update-check-finished'; const UPDATE_FAILURE = 'update-failure'; @@ -47,15 +65,19 @@ const APP_SHOULD_SHOW = 'APP_SHOULD_SHOW'; exports.APP_SHOULD_SHOW = APP_SHOULD_SHOW; const events = new _events.EventEmitter(); exports.events = events; + function webContentsSend(win, event, ...args) { + // log('Splash', `Sending to webcontents:`, event, args); + if (splashWindow != null && !splashWindow.isDestroyed() && !splashWindow.webContents.isDestroyed()) { try { win.webContents.send(`DISCORD_${event}`, ...args); - } catch (e) { + } catch (e) { // Mostly ignore, probably just destroyed log('Splash', 'Failed to send to webcontents'); } } } + let splashWindow; let modulesListeners; let updateTimeout; @@ -64,30 +86,37 @@ let splashState; let launchedMainWindow; let restartRequired = false; let newUpdater; -const updateBackoff = new _Backoff.default(1000, 30000); +const updateBackoff = new _Backoff.default(1000, 30000); // TODO(eiz): some of this logic should probably not live in the splash. // // Disabled because Rust interop stuff is going on in here. + class TaskProgress { constructor() { this.inProgress = new Map(); this.finished = new Set(); this.allTasks = new Set(); } + recordProgress(progress, task) { this.allTasks.add(task.package_sha256); + if (progress.state !== _updater.TASK_STATE_WAITING) { this.inProgress.set(task.package_sha256, progress.percent); + if (progress.state === _updater.TASK_STATE_COMPLETE) { this.finished.add(task.package_sha256); } } } + updateSplashState(newState) { if (this.inProgress.size > 0 && this.inProgress.size > this.finished.size) { let totalPercent = 0; + for (const item of this.inProgress.values()) { totalPercent += item; } + totalPercent /= this.allTasks.size; splashState = { current: this.finished.size + 1, @@ -97,16 +126,21 @@ class TaskProgress { updateSplashState(newState); return true; } + return false; } + } + async function updateUntilCurrent() { const retryOptions = { skip_host_delta: false, skip_module_delta: {} }; + while (true) { updateSplashState(CHECKING_FOR_UPDATES); + try { let installedAnything = false; const downloads = new TaskProgress(); @@ -116,11 +150,14 @@ async function updateUntilCurrent() { const downloadTask = task.HostDownload || task.ModuleDownload; const installTask = task.HostInstall || task.ModuleInstall; installedAnything = true; + if (downloadTask != null) { downloads.recordProgress(progress, downloadTask); } + if (installTask != null) { installs.recordProgress(progress, installTask); + if (progress.state.Failed != null) { if (task.HostInstall != null) { retryOptions.skip_host_delta = true; @@ -129,10 +166,12 @@ async function updateUntilCurrent() { } } } + if (!downloads.updateSplashState(DOWNLOADING_UPDATES)) { installs.updateSplashState(INSTALLING_UPDATES); } }); + if (!installedAnything) { await newUpdater.startCurrentVersion(); newUpdater.setRunningInBackground(); @@ -152,17 +191,20 @@ async function updateUntilCurrent() { } } } + const oldCheckForUpdates = () => { if (oaConfig.skipStartupUpdateChecks !== true) { moduleUpdater.checkForUpdates(); } else { log('Splash', 'Skipping startup update checking (enabled)'); + modulesListeners[UPDATE_CHECK_FINISHED]({ succeeded: true, updateCount: 0 }); } }; + function initOldUpdater() { modulesListeners = {}; addModulesListener(CHECKING_FOR_UPDATES, () => { @@ -175,6 +217,7 @@ function initOldUpdater() { manualRequired }) => { stopUpdateTimeout(); + if (!succeeded) { scheduleUpdateCheck(); updateSplashState(UPDATE_FAILURE); @@ -210,6 +253,7 @@ function initOldUpdater() { succeeded }) => { delete splashState.progress; + if (name === 'host') { restartRequired = true; } @@ -267,44 +311,59 @@ function initOldUpdater() { updateSplashState(UPDATE_MANUALLY); }); } + function initSplash(startMinimized = false) { log('Splash', `Initing splash`); + splashState = {}; launchedMainWindow = false; updateAttempt = 0; newUpdater = (0, _updater.getUpdater)(); + if (newUpdater == null) { initOldUpdater(); } + launchSplashWindow(startMinimized); + log('Splash', 'Quickstart config:', process.env.OPENASAR_QUICKSTART || oaConfig.quickstart, '-', process.env.OPENASAR_QUICKSTART, oaConfig.quickstart); + if (newUpdater != null) { updateUntilCurrent(); } else { moduleUpdater.installPendingUpdates(); } + if (process.env.OPENASAR_QUICKSTART || oaConfig.quickstart) setTimeout(() => { destroySplash(); - if (newUpdater != null) { + + if (newUpdater != null) { // Manually load desktop_core module path for faster requiring require('../utils/u2LoadModulePath')(); } + /* if (newUpdater != null) { updateUntilCurrent(); } else { moduleUpdater.installPendingUpdates(); moduleUpdater.setInBackground(); } */ + launchMainWindow(); + setTimeout(() => { events.emit(APP_SHOULD_SHOW); }, 100); }, 50); } + function destroySplash() { log('Splash', `Destroying splash`); + stopUpdateTimeout(); + if (splashWindow) { - splashWindow.setSkipTaskbar(true); + splashWindow.setSkipTaskbar(true); // defer the window hiding for a short moment so it gets covered by the main window + const _nukeWindow = () => { if (splashWindow != null) { splashWindow.hide(); @@ -312,37 +371,45 @@ function destroySplash() { splashWindow = null; } }; + setTimeout(_nukeWindow, 100); } } + function addModulesListener(event, listener) { if (newUpdater != null) return; modulesListeners[event] = listener; moduleUpdater.events.addListener(event, listener); } + function removeModulesListeners() { if (newUpdater != null) return; + for (const event of Object.keys(modulesListeners)) { moduleUpdater.events.removeListener(event, modulesListeners[event]); } } + function startUpdateTimeout() { if (!updateTimeout) { updateTimeout = setTimeout(() => scheduleUpdateCheck(), UPDATE_TIMEOUT_WAIT); } } + function stopUpdateTimeout() { if (updateTimeout) { clearTimeout(updateTimeout); updateTimeout = null; } } + function updateSplashState(event) { webContentsSend(splashWindow, 'SPLASH_UPDATE_STATE', { status: event, ...splashState }); } + function launchSplashWindow(startMinimized) { const windowConfig = { width: LOADING_WINDOW_WIDTH, @@ -359,67 +426,90 @@ function launchSplashWindow(startMinimized) { preload: _path.default.join(__dirname, 'preload.js') } }; - splashWindow = new _electron.BrowserWindow(windowConfig); + splashWindow = new _electron.BrowserWindow(windowConfig); // prevent users from dropping links to navigate in splash window log('Splash', 'Created BrowserWindow'); + splashWindow.webContents.on('will-navigate', e => e.preventDefault()); splashWindow.webContents.on('new-window', (e, windowURL) => { e.preventDefault(); - (0, _securityUtils.saferShellOpenExternal)(windowURL); + (0, _securityUtils.saferShellOpenExternal)(windowURL); // exit, but delay half a second because openExternal is about to fire + // some events to things that are freed by app.quit. + setTimeout(_electron.app.quit, 500); }); + if (process.platform !== 'darwin') { + // citron note: this causes a crash on quit while the window is open on osx splashWindow.on('closed', () => { splashWindow = null; + if (!launchedMainWindow) { + // user has closed this window before we launched the app, so let's quit _electron.app.quit(); } }); } + ipcMain.on('DISCORD_SPLASH_SCREEN_READY', () => { log('Splash', 'Window declared ready, showing and starting update process'); - if (oaConfig.themeSync !== false) try { + + if (oaConfig.themeSync !== false) try { // Inject themesync CSS splashWindow.webContents.insertCSS(JSON.parse(_fs.default.readFileSync(_path.default.join(paths.getUserData(), 'userDataCache.json'), 'utf8')).openasarSplashCSS); } catch (e) { } + if (oaConfig.splashText === true) try { const buildInfo = require('../utils/buildInfo.js'); splashWindow.webContents.executeJavaScript(`debug.textContent = '${buildInfo.releaseChannel} ${buildInfo.version}\\nOpenAsar ${oaVersion}'`); } catch (e) { } + if (splashWindow && !startMinimized) { splashWindow.show(); } }); + ipcMain.on('DISCORD_SPLASH_SCREEN_QUIT', () => { _electron.app.quit(); }); + const splashUrl = _url.default.format({ protocol: 'file', slashes: true, pathname: _path.default.join(__dirname, 'index.html') }); + splashWindow.loadURL(splashUrl); + log('Splash', `Loading window (with url ${splashUrl})`); } + function launchMainWindow() { removeModulesListeners(); + if (!launchedMainWindow && splashWindow != null) { launchedMainWindow = true; events.emit(APP_SHOULD_LAUNCH); } } + function scheduleUpdateCheck() { + // TODO: can we use backoff here? updateAttempt += 1; const retryInSeconds = Math.min(updateAttempt * 10, RETRY_CAP_SECONDS); splashState.seconds = retryInSeconds; setTimeout(() => moduleUpdater.checkForUpdates(), retryInSeconds * 1000); } + function focusWindow() { log('Splash', `Told to focus splash window`); + if (splashWindow != null) { splashWindow.focus(); } } + function pageReady() { log('Splash', `Page ready called, destroying splash and marking app to show`); + destroySplash(); process.nextTick(() => events.emit(APP_SHOULD_SHOW)); } \ No newline at end of file diff --git a/src/splash/preload.js b/src/splash/preload.js index c1b351f..ab15b9d 100644 --- a/src/splash/preload.js +++ b/src/splash/preload.js @@ -1,8 +1,13 @@ const { contextBridge, ipcRenderer } = require('electron'); + const { saferShellOpenExternal } = require('../utils/securityUtils'); + + contextBridge.exposeInMainWorld('DiscordSplash', { signalReady: () => ipcRenderer.send('DISCORD_SPLASH_SCREEN_READY'), + onStateUpdate: callback => ipcRenderer.on('DISCORD_SPLASH_UPDATE_STATE', (_, state) => callback(state)), + openUrl: saferShellOpenExternal, quitDiscord: () => ipcRenderer.send('DISCORD_SPLASH_SCREEN_QUIT') }); \ No newline at end of file