add.[ALL]
This commit is contained in:
		
							parent
							
								
									5ac24c8cea
								
							
						
					
					
						commit
						780ad9a200
					
				
					 54 changed files with 3733 additions and 0 deletions
				
			
		
							
								
								
									
										37
									
								
								.resources/app/app_bootstrap/Constants.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.resources/app/app_bootstrap/Constants.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| var _appSettings = require("./appSettings"); | ||||
| // bootstrap constants
 | ||||
| // after startup, these constants will be merged into core module constants
 | ||||
| // since they are used in both locations (see app/Constants.js)
 | ||||
| 
 | ||||
| const { | ||||
|   releaseChannel | ||||
| } = require('./buildInfo'); | ||||
| const settings = (0, _appSettings.getSettings)(); | ||||
| function capitalizeFirstLetter(s) { | ||||
|   return s.charAt(0).toUpperCase() + s.slice(1); | ||||
| } | ||||
| const appNameSuffix = releaseChannel === 'stable' ? '' : capitalizeFirstLetter(releaseChannel); | ||||
| const APP_COMPANY = 'Discord Inc'; | ||||
| const APP_DESCRIPTION = 'Discord - https://discord.com'; | ||||
| const APP_NAME = 'Discord' + appNameSuffix; | ||||
| const APP_NAME_FOR_HUMANS = 'Discord' + (appNameSuffix !== '' ? ' ' + appNameSuffix : ''); | ||||
| const APP_ID_BASE = 'com.squirrel'; | ||||
| const APP_ID = `${APP_ID_BASE}.${APP_NAME}.${APP_NAME}`; | ||||
| const APP_PROTOCOL = 'Discord'; | ||||
| const API_ENDPOINT = settings.get('API_ENDPOINT') || 'https://discord.com/api'; | ||||
| const UPDATE_ENDPOINT = settings.get('UPDATE_ENDPOINT') || API_ENDPOINT; | ||||
| const NEW_UPDATE_ENDPOINT = settings.get('NEW_UPDATE_ENDPOINT') || 'https://updates.discord.com/'; | ||||
| const bootstrapConstants = { | ||||
|   APP_COMPANY, | ||||
|   APP_DESCRIPTION, | ||||
|   APP_NAME, | ||||
|   APP_NAME_FOR_HUMANS, | ||||
|   APP_ID, | ||||
|   APP_PROTOCOL, | ||||
|   API_ENDPOINT, | ||||
|   NEW_UPDATE_ENDPOINT, | ||||
|   UPDATE_ENDPOINT | ||||
| }; | ||||
| module.exports = bootstrapConstants; | ||||
							
								
								
									
										17
									
								
								.resources/app/app_bootstrap/GPUSettings.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.resources/app/app_bootstrap/GPUSettings.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| // this file is here for two reasons:
 | ||||
| // 1. web requires ./GPUSettings file from electron app (bad!), and requires are
 | ||||
| //    relative to process.main (bootstrap's index.js)
 | ||||
| // 2. GPUSettings has been refactored into GPUSettings, and because we want to
 | ||||
| //    be able to update GPUSettings OTA, we will have the core module provide
 | ||||
| //    us with the GPUSettings
 | ||||
| // so tl;dr this is core module's GPUSettings, providing compat for web
 | ||||
| 
 | ||||
| exports.replace = function (GPUSettings) { | ||||
|   // replacing module.exports directly would have no effect, since requires are cached
 | ||||
|   // so we mutate the existing object
 | ||||
|   for (const name of Object.keys(GPUSettings)) { | ||||
|     exports[name] = GPUSettings[name]; | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										19
									
								
								.resources/app/app_bootstrap/appSettings.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.resources/app/app_bootstrap/appSettings.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.getSettings = getSettings; | ||||
| exports.init = init; | ||||
| var _Settings = _interopRequireDefault(require("../common/Settings")); | ||||
| var paths = _interopRequireWildcard(require("../common/paths")); | ||||
| 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 }; } | ||||
| let settings; | ||||
| function init() { | ||||
|   settings = new _Settings.default(paths.getUserData()); | ||||
| } | ||||
| function getSettings() { | ||||
|   return settings; | ||||
| } | ||||
							
								
								
									
										55
									
								
								.resources/app/app_bootstrap/appUpdater.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								.resources/app/app_bootstrap/appUpdater.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.focusSplash = focusSplash; | ||||
| exports.update = update; | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var moduleUpdater = _interopRequireWildcard(require("../common/moduleUpdater")); | ||||
| var paths = _interopRequireWildcard(require("../common/paths")); | ||||
| var _updater = require("../common/updater"); | ||||
| var _appSettings = require("./appSettings"); | ||||
| var autoStart = _interopRequireWildcard(require("./autoStart")); | ||||
| var _buildInfo = _interopRequireDefault(require("./buildInfo")); | ||||
| var _errorHandler = require("./errorHandler"); | ||||
| var firstRun = _interopRequireWildcard(require("./firstRun")); | ||||
| var splashScreen = _interopRequireWildcard(require("./splashScreen")); | ||||
| var _Constants = require("./Constants"); | ||||
| 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 }; } | ||||
| // settings
 | ||||
| const USE_PINNED_UPDATE_MANIFEST = 'USE_PINNED_UPDATE_MANIFEST'; | ||||
| function update(startMinimized, doneCallback, showCallback) { | ||||
|   const settings = (0, _appSettings.getSettings)(); | ||||
|   if ((0, _updater.tryInitUpdater)(_buildInfo.default, _Constants.NEW_UPDATE_ENDPOINT)) { | ||||
|     const updater = (0, _updater.getUpdater)(); | ||||
|     const usePinnedUpdateManifest = settings.get(USE_PINNED_UPDATE_MANIFEST); | ||||
|     updater.on('host-updated', () => { | ||||
|       autoStart.update(() => {}); | ||||
|     }); | ||||
|     updater.on('unhandled-exception', _errorHandler.fatal); | ||||
|     updater.on(_updater.INCONSISTENT_INSTALLER_STATE_ERROR, _errorHandler.fatal); | ||||
|     updater.on('update-error', _errorHandler.handled); | ||||
|     updater.on('starting-new-host', () => { | ||||
|       // dont run stale launch events--the host we're updating to will run its own callbacks
 | ||||
|       splashScreen.events.removeListener(splashScreen.APP_SHOULD_LAUNCH, doneCallback); | ||||
|       splashScreen.events.removeListener(splashScreen.APP_SHOULD_SHOW, showCallback); | ||||
|     }); | ||||
|     if (usePinnedUpdateManifest) { | ||||
|       const manifestPath = _path.default.join(paths.getUserData(), 'pinned_update.json'); | ||||
|       updater.setPinnedManifestSync(JSON.parse(_fs.default.readFileSync(manifestPath))); | ||||
|     } | ||||
|     firstRun.performFirstRunTasks(updater); | ||||
|   } else { | ||||
|     moduleUpdater.init(_Constants.UPDATE_ENDPOINT, settings, _buildInfo.default); | ||||
|   } | ||||
|   splashScreen.initSplash(startMinimized); | ||||
|   splashScreen.events.once(splashScreen.APP_SHOULD_LAUNCH, doneCallback); | ||||
|   splashScreen.events.once(splashScreen.APP_SHOULD_SHOW, showCallback); | ||||
| } | ||||
| function focusSplash() { | ||||
|   splashScreen.focusWindow(); | ||||
| } | ||||
							
								
								
									
										21
									
								
								.resources/app/app_bootstrap/autoStart/darwin.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.resources/app/app_bootstrap/autoStart/darwin.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.install = install; | ||||
| exports.isInstalled = isInstalled; | ||||
| exports.uninstall = uninstall; | ||||
| exports.update = update; | ||||
| function install(callback) { | ||||
|   return callback(); | ||||
| } | ||||
| function update(callback) { | ||||
|   return callback(); | ||||
| } | ||||
| function isInstalled(callback) { | ||||
|   return callback(false); | ||||
| } | ||||
| function uninstall(callback) { | ||||
|   return callback(); | ||||
| } | ||||
							
								
								
									
										3
									
								
								.resources/app/app_bootstrap/autoStart/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.resources/app/app_bootstrap/autoStart/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| module.exports = require('./' + process.platform); | ||||
							
								
								
									
										71
									
								
								.resources/app/app_bootstrap/autoStart/linux.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								.resources/app/app_bootstrap/autoStart/linux.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.install = install; | ||||
| exports.isInstalled = isInstalled; | ||||
| exports.uninstall = uninstall; | ||||
| exports.update = update; | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var _electron = require("electron"); | ||||
| var _buildInfo = _interopRequireDefault(require("../buildInfo")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| // TODO: We should use Constant's APP_NAME, but only once
 | ||||
| //       we set up backwards compat with this.
 | ||||
| const appName = _path.default.basename(process.execPath, '.exe'); | ||||
| const exePath = _electron.app.getPath('exe'); | ||||
| const exeDir = _path.default.dirname(exePath); | ||||
| const iconPath = _path.default.join(exeDir, 'discord.png'); | ||||
| const autostartDir = _path.default.join(_electron.app.getPath('appData'), 'autostart'); | ||||
| const electronAppName = _electron.app.name ? _electron.app.name : _electron.app.getName(); | ||||
| const autostartFileName = _path.default.join(autostartDir, electronAppName + '-' + _buildInfo.default.releaseChannel + '.desktop'); | ||||
| const desktopFile = `[Desktop Entry]
 | ||||
| Type=Application | ||||
| Exec=${exePath} | ||||
| Hidden=false | ||||
| NoDisplay=false | ||||
| Name=${appName} | ||||
| Icon=${iconPath} | ||||
| Comment=Text and voice chat for gamers. | ||||
| X-GNOME-Autostart-enabled=true | ||||
| `;
 | ||||
| function ensureDir() { | ||||
|   try { | ||||
|     _fs.default.mkdirSync(autostartDir); | ||||
|     return true; | ||||
|   } catch (e) { | ||||
|     // catch for when it already exists.
 | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| function install(callback) { | ||||
|   // TODO: This could fail. We should read its return value
 | ||||
|   ensureDir(); | ||||
|   try { | ||||
|     return _fs.default.writeFile(autostartFileName, desktopFile, callback); | ||||
|   } catch (e) { | ||||
|     // I guess we don't autostart then
 | ||||
|     return callback(); | ||||
|   } | ||||
| } | ||||
| function update(callback) { | ||||
|   // TODO: We might need to implement this later on
 | ||||
|   return callback(); | ||||
| } | ||||
| function isInstalled(callback) { | ||||
|   try { | ||||
|     _fs.default.stat(autostartFileName, (err, stats) => { | ||||
|       if (err) { | ||||
|         return callback(false); | ||||
|       } | ||||
|       return callback(stats.isFile()); | ||||
|     }); | ||||
|   } catch (e) { | ||||
|     return callback(false); | ||||
|   } | ||||
| } | ||||
| function uninstall(callback) { | ||||
|   return _fs.default.unlink(autostartFileName, callback); | ||||
| } | ||||
							
								
								
									
										55
									
								
								.resources/app/app_bootstrap/autoStart/win32.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								.resources/app/app_bootstrap/autoStart/win32.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.install = install; | ||||
| exports.isInstalled = isInstalled; | ||||
| exports.uninstall = uninstall; | ||||
| exports.update = update; | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var windowsUtils = _interopRequireWildcard(require("../windowsUtils")); | ||||
| var _appSettings = require("../appSettings"); | ||||
| 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 settings = (0, _appSettings.getSettings)(); | ||||
| 
 | ||||
| // TODO: We should use Constant's APP_NAME, but only once
 | ||||
| //       we set up backwards compat with this.
 | ||||
| const appName = _path.default.basename(process.execPath, '.exe'); | ||||
| const fullExeName = _path.default.basename(process.execPath); | ||||
| const updatePath = _path.default.join(_path.default.dirname(process.execPath), '..', 'Update.exe'); | ||||
| function install(callback) { | ||||
|   const startMinimized = settings.get('START_MINIMIZED', false); | ||||
|   let execPath = `"${updatePath}" --processStart ${fullExeName}`; | ||||
|   if (startMinimized) { | ||||
|     execPath = `${execPath} --process-start-args --start-minimized`; | ||||
|   } | ||||
|   const queue = [['HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', '/v', appName, '/d', execPath]]; | ||||
|   windowsUtils.addToRegistry(queue, callback); | ||||
| } | ||||
| function update(callback) { | ||||
|   isInstalled(installed => { | ||||
|     if (installed) { | ||||
|       install(callback); | ||||
|     } else { | ||||
|       callback(); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| function isInstalled(callback) { | ||||
|   const queryValue = ['HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', '/v', appName]; | ||||
|   queryValue.unshift('query'); | ||||
|   windowsUtils.spawnReg(queryValue, (error, stdout) => { | ||||
|     const doesOldKeyExist = stdout.indexOf(appName) >= 0; | ||||
|     callback(doesOldKeyExist); | ||||
|   }); | ||||
| } | ||||
| function uninstall(callback) { | ||||
|   const queryValue = ['HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', '/v', appName, '/f']; | ||||
|   queryValue.unshift('delete'); | ||||
|   windowsUtils.spawnReg(queryValue, (error, stdout) => { | ||||
|     callback(); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										167
									
								
								.resources/app/app_bootstrap/bootstrap.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								.resources/app/app_bootstrap/bootstrap.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,167 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| // bootstrap, or what runs before the rest of desktop does
 | ||||
| // responsible for handling updates and updating modules before continuing startup
 | ||||
| 
 | ||||
| if (process.platform === 'linux') { | ||||
|   // Some people are reporting audio problems on Linux that are fixed by setting
 | ||||
|   // an environment variable PULSE_LATENCY_MSEC=30 -- the "real" fix is to see
 | ||||
|   // what conditions require this and set this then (also to set it directly in
 | ||||
|   // our webrtc setup code rather than here) but this should fix the bug for now.
 | ||||
|   if (process.env.PULSE_LATENCY_MSEC === undefined) { | ||||
|     process.env.PULSE_LATENCY_MSEC = 30; | ||||
|   } | ||||
| } | ||||
| const { | ||||
|   app, | ||||
|   Menu | ||||
| } = require('electron'); | ||||
| const sentry = require('@sentry/node'); | ||||
| const buildInfo = require('./buildInfo'); | ||||
| app.setVersion(buildInfo.version); | ||||
| 
 | ||||
| // expose releaseChannel to a global, since it's used by splash screen
 | ||||
| global.releaseChannel = buildInfo.releaseChannel; | ||||
| const errorHandler = require('./errorHandler'); | ||||
| errorHandler.init(); | ||||
| const crashReporterSetup = require('../common/crashReporterSetup'); | ||||
| crashReporterSetup.init(buildInfo, sentry); | ||||
| const paths = require('../common/paths'); | ||||
| paths.init(buildInfo); | ||||
| global.moduleDataPath = paths.getModuleDataPath(); | ||||
| const appSettings = require('./appSettings'); | ||||
| appSettings.init(); | ||||
| const Constants = require('./Constants'); | ||||
| const GPUSettings = require('./GPUSettings'); | ||||
| function setupHardwareAcceleration() { | ||||
|   const settings = appSettings.getSettings(); | ||||
|   // TODO: this is a copy of gpuSettings.getEnableHardwareAcceleration
 | ||||
|   if (!settings.get('enableHardwareAcceleration', true)) { | ||||
|     app.disableHardwareAcceleration(); | ||||
|   } | ||||
| } | ||||
| setupHardwareAcceleration(); | ||||
| 
 | ||||
| // [adill] work around chrome 66 disabling autoplay by default
 | ||||
| app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required'); | ||||
| 
 | ||||
| // WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
 | ||||
| // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
 | ||||
| app.commandLine.appendSwitch('disable-features', 'WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService'); | ||||
| function hasArgvFlag(flag) { | ||||
|   return (process.argv || []).slice(1).includes(flag); | ||||
| } | ||||
| console.log(`${Constants.APP_NAME} ${app.getVersion()}`); | ||||
| let pendingAppQuit = false; | ||||
| if (process.platform === 'win32') { | ||||
|   // this tells Windows (in particular Windows 10) which icon to associate your app with, important for correctly
 | ||||
|   // pinning app to task bar.
 | ||||
|   app.setAppUserModelId(Constants.APP_ID); | ||||
|   const { | ||||
|     handleStartupEvent | ||||
|   } = require('./squirrelUpdate'); | ||||
|   // TODO: Isn't using argv[1] fragile?
 | ||||
|   const squirrelCommand = process.argv[1]; | ||||
|   // TODO: Is protocol case sensitive?
 | ||||
|   if (handleStartupEvent(Constants.APP_PROTOCOL, app, squirrelCommand)) { | ||||
|     pendingAppQuit = true; | ||||
|   } | ||||
| } | ||||
| const appUpdater = require('./appUpdater'); | ||||
| const moduleUpdater = require('../common/moduleUpdater'); | ||||
| const updater = require('../common/updater'); | ||||
| const splashScreen = require('./splashScreen'); | ||||
| const autoStart = require('./autoStart'); | ||||
| const requireNative = require('./requireNative'); | ||||
| let coreModule; | ||||
| const allowMultipleInstances = hasArgvFlag('--multi-instance'); | ||||
| const isFirstInstance = allowMultipleInstances ? true : app.requestSingleInstanceLock(); | ||||
| function extractUrlFromArgs(args) { | ||||
|   const urlArgIndex = args.indexOf('--url'); | ||||
|   if (urlArgIndex < 0) { | ||||
|     return null; | ||||
|   } | ||||
|   const passThroughArgsIndex = args.indexOf('--'); | ||||
|   if (passThroughArgsIndex < 0 || passThroughArgsIndex < urlArgIndex) { | ||||
|     return null; | ||||
|   } | ||||
|   const url = args[passThroughArgsIndex + 1]; | ||||
|   if (url == null) { | ||||
|     return null; | ||||
|   } | ||||
|   return url; | ||||
| } | ||||
| let initialUrl = extractUrlFromArgs(process.argv); | ||||
| if (!allowMultipleInstances) { | ||||
|   app.on('second-instance', (_event, args, _workingDirectory) => { | ||||
|     if (args != null && args.indexOf('--squirrel-uninstall') > -1) { | ||||
|       app.quit(); | ||||
|       return; | ||||
|     } | ||||
|     const url = extractUrlFromArgs(args); | ||||
|     if (coreModule) { | ||||
|       // url can be null, as a user opening the executable again will focus the app from background
 | ||||
|       coreModule.handleOpenUrl(url); | ||||
|     } else if (url != null) { | ||||
|       initialUrl = url; | ||||
|     } | ||||
|     if (!coreModule) { | ||||
|       appUpdater.focusSplash(); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| app.on('will-finish-launching', () => { | ||||
|   // on macos protocol links are handled entirely through this event
 | ||||
|   app.on('open-url', (event, url) => { | ||||
|     event.preventDefault(); | ||||
|     if (coreModule) { | ||||
|       coreModule.handleOpenUrl(url); | ||||
|     } else { | ||||
|       initialUrl = url; | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
| function startUpdate() { | ||||
|   console.log('Starting updater.'); | ||||
|   const startMinimized = hasArgvFlag('--start-minimized'); | ||||
|   appUpdater.update(startMinimized, () => { | ||||
|     try { | ||||
|       coreModule = requireNative('discord_desktop_core'); | ||||
|       coreModule.startup({ | ||||
|         paths, | ||||
|         splashScreen, | ||||
|         moduleUpdater, | ||||
|         autoStart, | ||||
|         buildInfo, | ||||
|         appSettings, | ||||
|         Constants, | ||||
|         GPUSettings, | ||||
|         updater, | ||||
|         crashReporterSetup | ||||
|       }); | ||||
|       if (initialUrl != null) { | ||||
|         coreModule.handleOpenUrl(initialUrl); | ||||
|         initialUrl = null; | ||||
|       } | ||||
|     } catch (err) { | ||||
|       return errorHandler.fatal(err); | ||||
|     } | ||||
|   }, () => { | ||||
|     coreModule.setMainWindowVisible(!startMinimized); | ||||
|   }); | ||||
| } | ||||
| function startApp() { | ||||
|   console.log('Starting app.'); | ||||
|   paths.cleanOldVersions(buildInfo); | ||||
|   const startupMenu = require('./startupMenu'); | ||||
|   Menu.setApplicationMenu(startupMenu); | ||||
|   startUpdate(); | ||||
| } | ||||
| if (pendingAppQuit) { | ||||
|   console.log('Startup prevented.'); | ||||
| } else if (!isFirstInstance && !allowMultipleInstances) { | ||||
|   console.log('Quitting secondary instance.'); | ||||
|   app.quit(); | ||||
| } else { | ||||
|   app.whenReady().then(() => startApp()); | ||||
| } | ||||
							
								
								
									
										12
									
								
								.resources/app/app_bootstrap/buildInfo.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.resources/app/app_bootstrap/buildInfo.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| const buildInfo = require(_path.default.join(process.resourcesPath, 'build_info.json')); | ||||
| var _default = buildInfo; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										14
									
								
								.resources/app/app_bootstrap/data/quotes_copy.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.resources/app/app_bootstrap/data/quotes_copy.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| [ | ||||
|   "Upsorbing the Contents", | ||||
|   "Additive Parsing the Load", | ||||
|   "Commence Monosaturated Goodening", | ||||
|   "Kick Off the Multi-Core Widening", | ||||
|   "Bastening the Game Turkey", | ||||
|   "Abstracting the Rummage Disc", | ||||
|   "Undecerealenizing the Process", | ||||
|   "Postrefragmenting the Widget Layer", | ||||
|   "Satisfying the Constraints", | ||||
|   "Abnoramalzing Some of the Matrices", | ||||
|   "Optimizing the People", | ||||
|   "Proclaigerizing the Network" | ||||
| ] | ||||
							
								
								
									
										85
									
								
								.resources/app/app_bootstrap/errorHandler.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								.resources/app/app_bootstrap/errorHandler.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.fatal = fatal; | ||||
| exports.handled = handled; | ||||
| exports.init = init; | ||||
| var Sentry = _interopRequireWildcard(require("@sentry/node")); | ||||
| var _electron = require("electron"); | ||||
| var _process = _interopRequireDefault(require("process")); | ||||
| var _crashReporterSetup = require("../common/crashReporterSetup"); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| 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; } | ||||
| const HANDLED_ERROR_INTERVAL = 3; | ||||
| const HANDLED_ERROR_LIMIT = 10; | ||||
| let handledErrorCounter = 0; | ||||
| let totalHandledErrors = 0; | ||||
| const consoleOutputOnly = _process.default.env.DISCORD_TEST != null; | ||||
| function isErrorSafeToSuppress(error) { | ||||
|   return /attempting to call a function in a renderer window/i.test(error.message); | ||||
| } | ||||
| function captureJSException(error) { | ||||
|   Sentry.captureException(error, scope => { | ||||
|     scope.clear(); | ||||
|     scope.setTag('nativeBuildNumber', _crashReporterSetup.metadata.nativeBuildNumber); | ||||
|     scope.setUser(_crashReporterSetup.metadata.sentry.user); | ||||
|     scope.setExtras({ | ||||
|       environment: _crashReporterSetup.metadata.sentry.environment, | ||||
|       release: _crashReporterSetup.metadata.sentry.release, | ||||
|       nativeBuildNumber: _crashReporterSetup.metadata.nativeBuildNumber | ||||
|     }); | ||||
|     return scope; | ||||
|   }); | ||||
| } | ||||
| function init() { | ||||
|   _process.default.on('uncaughtException', error => { | ||||
|     const stack = error.stack ? error.stack : String(error); | ||||
|     const message = `Uncaught exception:\n ${stack}`; | ||||
|     console.warn(message); | ||||
|     captureJSException(error); | ||||
|     if (!isErrorSafeToSuppress(error)) { | ||||
|       if (consoleOutputOnly) { | ||||
|         console.error(`${message} error: ${error}`); | ||||
|         _process.default.exit(-1); | ||||
|       } | ||||
|       _electron.dialog.showErrorBox('A JavaScript error occurred in the main process', message); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // show a similar error message to the error handler, except exit out the app
 | ||||
| // after the error message has been closed
 | ||||
| function fatal(err) { | ||||
|   const options = { | ||||
|     type: 'error', | ||||
|     message: 'A fatal Javascript error occured', | ||||
|     detail: err && err.stack ? err.stack : String(err) | ||||
|   }; | ||||
|   if (consoleOutputOnly) { | ||||
|     console.error(`fatal: ${err}\n${err === null || err === void 0 ? void 0 : err.stack}`); | ||||
|     _process.default.exit(-1); | ||||
|   } | ||||
|   const callback = _ => _electron.app.quit(); | ||||
|   const electronMajor = parseInt(_process.default.versions.electron.split('.')[0]); | ||||
|   if (electronMajor >= 6) { | ||||
|     _electron.dialog.showMessageBox(null, options).then(callback); | ||||
|   } else { | ||||
|     _electron.dialog.showMessageBox(options, callback); | ||||
|   } | ||||
|   captureJSException(err); | ||||
| } | ||||
| 
 | ||||
| // capture a handled error for telemetry purposes, e.g. finding update loops.
 | ||||
| function handled(err) { | ||||
|   if (global.releaseChannel !== 'ptb' && global.releaseChannel !== 'canary' && global.releaseChannel !== 'development') { | ||||
|     return; | ||||
|   } | ||||
|   if (totalHandledErrors < HANDLED_ERROR_LIMIT && handledErrorCounter++ % HANDLED_ERROR_INTERVAL == 0) { | ||||
|     console.warn('Reporting non-fatal error', err); | ||||
|     captureJSException(err); | ||||
|     totalHandledErrors++; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										9
									
								
								.resources/app/app_bootstrap/firstRun/darwin.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.resources/app/app_bootstrap/firstRun/darwin.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.performFirstRunTasks = performFirstRunTasks; | ||||
| function performFirstRunTasks(_updater) { | ||||
|   //
 | ||||
| } | ||||
							
								
								
									
										3
									
								
								.resources/app/app_bootstrap/firstRun/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.resources/app/app_bootstrap/firstRun/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| module.exports = require('./' + process.platform); | ||||
							
								
								
									
										9
									
								
								.resources/app/app_bootstrap/firstRun/linux.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.resources/app/app_bootstrap/firstRun/linux.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.performFirstRunTasks = performFirstRunTasks; | ||||
| function performFirstRunTasks(_updater) { | ||||
|   //
 | ||||
| } | ||||
							
								
								
									
										75
									
								
								.resources/app/app_bootstrap/firstRun/win32.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								.resources/app/app_bootstrap/firstRun/win32.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.performFirstRunTasks = performFirstRunTasks; | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var paths = _interopRequireWildcard(require("../../common/paths")); | ||||
| var _errorHandler = require("../errorHandler"); | ||||
| var _squirrelUpdate = require("../squirrelUpdate"); | ||||
| var _Constants = require("../Constants"); | ||||
| 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 appFolder = _path.default.resolve(process.execPath, '..'); | ||||
| const rootFolder = _path.default.resolve(appFolder, '..'); | ||||
| const exeName = _path.default.basename(process.execPath); | ||||
| const updateExe = _path.default.join(rootFolder, 'Update.exe'); | ||||
| function copyIconToRoot() { | ||||
|   const icoSrc = _path.default.join(appFolder, 'app.ico'); | ||||
|   const icoDest = _path.default.join(rootFolder, 'app.ico'); | ||||
|   try { | ||||
|     const ico = _fs.default.readFileSync(icoSrc); | ||||
|     _fs.default.writeFileSync(icoDest, ico); | ||||
|     return icoDest; | ||||
|   } catch (e) { | ||||
|     return icoSrc; | ||||
|   } | ||||
| } | ||||
| function updateShortcuts(updater) { | ||||
|   const shortcutFileName = `${_Constants.APP_NAME_FOR_HUMANS}.lnk`; | ||||
|   const shortcutPaths = [_path.default.join(updater.getKnownFolder('desktop'), shortcutFileName), _path.default.join(updater.getKnownFolder('programs'), _Constants.APP_COMPANY, shortcutFileName)]; | ||||
|   const iconPath = copyIconToRoot(); | ||||
|   for (const shortcutPath of shortcutPaths) { | ||||
|     if (!_fs.default.existsSync(shortcutPath)) { | ||||
|       // If the user deleted the shortcut, don't recreate it.
 | ||||
|       continue; | ||||
|     } | ||||
|     updater.createShortcut({ | ||||
|       /* eslint-disable camelcase */ | ||||
|       target_path: updateExe, | ||||
|       shortcut_path: shortcutPath, | ||||
|       arguments: `--processStart ${exeName}`, | ||||
|       icon_path: iconPath, | ||||
|       icon_index: 0, | ||||
|       description: _Constants.APP_DESCRIPTION, | ||||
|       app_user_model_id: _Constants.APP_ID, | ||||
|       working_directory: appFolder | ||||
|       /* eslint-enable camelcase */ | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function performFirstRunTasks(updater) { | ||||
|   const firstRunCompletePath = _path.default.join(paths.getUserDataVersioned(), '.first-run'); | ||||
|   if (!_fs.default.existsSync(firstRunCompletePath)) { | ||||
|     let updatedShortcuts = false; | ||||
|     try { | ||||
|       updateShortcuts(updater); | ||||
|       updatedShortcuts = true; | ||||
|     } catch (e) { | ||||
|       (0, _errorHandler.handled)(e); | ||||
|     } | ||||
|     (0, _squirrelUpdate.installProtocol)(_Constants.APP_PROTOCOL, () => { | ||||
|       try { | ||||
|         if (updatedShortcuts) { | ||||
|           _fs.default.writeFileSync(firstRunCompletePath, 'true'); | ||||
|         } | ||||
|       } catch (e) { | ||||
|         (0, _errorHandler.handled)(e); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										164
									
								
								.resources/app/app_bootstrap/hostUpdater.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								.resources/app/app_bootstrap/hostUpdater.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,164 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| var _events = require("events"); | ||||
| var _request = _interopRequireDefault(require("./request")); | ||||
| var squirrelUpdate = _interopRequireWildcard(require("./squirrelUpdate")); | ||||
| 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 }; } | ||||
| /* eslint-disable no-console */ | ||||
| 
 | ||||
| function versionParse(verString) { | ||||
|   return verString.split('.').map(i => parseInt(i)); | ||||
| } | ||||
| function versionNewer(verA, verB) { | ||||
|   let i = 0; | ||||
|   while (true) { | ||||
|     const a = verA[i]; | ||||
|     const b = verB[i]; | ||||
|     i++; | ||||
|     if (a === undefined) { | ||||
|       return false; | ||||
|     } else { | ||||
|       if (b === undefined || a > b) { | ||||
|         return true; | ||||
|       } | ||||
|       if (a < b) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| class AutoUpdaterWin32 extends _events.EventEmitter { | ||||
|   constructor() { | ||||
|     super(); | ||||
|     this.updateUrl = null; | ||||
|     this.updateVersion = null; | ||||
|   } | ||||
|   setFeedURL(updateUrl) { | ||||
|     this.updateUrl = updateUrl; | ||||
|   } | ||||
|   quitAndInstall() { | ||||
|     if (squirrelUpdate.updateExistsSync()) { | ||||
|       squirrelUpdate.restart(_electron.app, this.updateVersion ?? _electron.app.getVersion()); | ||||
|     } else { | ||||
|       /* eslint-disable-next-line */ | ||||
|       require('auto-updater').quitAndInstall(); | ||||
|     } | ||||
|   } | ||||
|   downloadAndInstallUpdate(callback) { | ||||
|     squirrelUpdate.spawnUpdateInstall(this.updateUrl, progress => { | ||||
|       this.emit('update-progress', progress); | ||||
|     }).catch(err => callback(err)).then(() => callback()); | ||||
|   } | ||||
|   checkForUpdates() { | ||||
|     if (this.updateUrl == null) { | ||||
|       throw new Error('Update URL is not set'); | ||||
|     } | ||||
|     this.emit('checking-for-update'); | ||||
|     if (!squirrelUpdate.updateExistsSync()) { | ||||
|       this.emit('update-not-available'); | ||||
|       return; | ||||
|     } | ||||
|     squirrelUpdate.spawnUpdate(['--check', this.updateUrl], (error, stdout) => { | ||||
|       if (error != null) { | ||||
|         this.emit('error', error); | ||||
|         return; | ||||
|       } | ||||
|       try { | ||||
|         // Last line of the output is JSON details about the releases
 | ||||
|         const json = stdout.trim().split('\n').pop(); | ||||
|         const releasesFound = JSON.parse(json).releasesToApply; | ||||
|         if (releasesFound == null || releasesFound.length === 0) { | ||||
|           this.emit('update-not-available'); | ||||
|           return; | ||||
|         } | ||||
|         const update = releasesFound.pop(); | ||||
|         this.emit('update-available'); | ||||
|         this.downloadAndInstallUpdate(error => { | ||||
|           if (error != null) { | ||||
|             this.emit('error', error); | ||||
|             return; | ||||
|           } | ||||
|           this.updateVersion = update.version; | ||||
|           this.emit('update-downloaded', {}, update.release, update.version, new Date(), this.updateUrl, this.quitAndInstall.bind(this)); | ||||
|         }); | ||||
|       } catch (error) { | ||||
|         error.stdout = stdout; | ||||
|         this.emit('error', error); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // todo
 | ||||
| class AutoUpdaterLinux extends _events.EventEmitter { | ||||
|   constructor() { | ||||
|     super(); | ||||
|     this.updateUrl = null; | ||||
|   } | ||||
|   setFeedURL(url) { | ||||
|     this.updateUrl = url; | ||||
|   } | ||||
|   quitAndInstall() { | ||||
|     // Just restart. The splash screen will hit the update manually state and
 | ||||
|     // prompt the user to download the new package.
 | ||||
|     _electron.app.relaunch(); | ||||
|     _electron.app.quit(); | ||||
|   } | ||||
|   async checkForUpdates() { | ||||
|     const currVersion = versionParse(_electron.app.getVersion()); | ||||
|     this.emit('checking-for-update'); | ||||
|     try { | ||||
|       const response = await _request.default.get(this.updateUrl); | ||||
|       if (response.statusCode === 204) { | ||||
|         // you are up to date
 | ||||
|         this.emit('update-not-available'); | ||||
|         return; | ||||
|       } | ||||
|       let latestVerStr = ''; | ||||
|       let latestVersion = []; | ||||
|       try { | ||||
|         const latestMetadata = JSON.parse(response.body); | ||||
|         latestVerStr = latestMetadata.name; | ||||
|         latestVersion = versionParse(latestVerStr); | ||||
|       } catch (_) {} | ||||
|       if (versionNewer(latestVersion, currVersion)) { | ||||
|         console.log('[Updates] You are out of date!'); | ||||
|         // you need to update
 | ||||
|         this.emit('update-manually', latestVerStr); | ||||
|       } else { | ||||
|         console.log('[Updates] You are living in the future!'); | ||||
|         this.emit('update-not-available'); | ||||
|       } | ||||
|     } catch (err) { | ||||
|       console.error('[Updates] Error fetching ' + this.updateUrl + ': ' + err.message); | ||||
|       this.emit('error', err); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| let autoUpdater; | ||||
| 
 | ||||
| // TODO
 | ||||
| // events: checking-for-update, update-available, update-not-available, update-manually, update-downloaded, error
 | ||||
| // also, checkForUpdates, setFeedURL, quitAndInstall
 | ||||
| // also, see electron.autoUpdater, and its API
 | ||||
| switch (process.platform) { | ||||
|   case 'darwin': | ||||
|     autoUpdater = require('electron').autoUpdater; | ||||
|     break; | ||||
|   case 'win32': | ||||
|     autoUpdater = new AutoUpdaterWin32(); | ||||
|     break; | ||||
|   case 'linux': | ||||
|     autoUpdater = new AutoUpdaterLinux(); | ||||
|     break; | ||||
| } | ||||
| var _default = autoUpdater; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										
											BIN
										
									
								
								.resources/app/app_bootstrap/images/img_lucky_dice.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.resources/app/app_bootstrap/images/img_lucky_dice.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.2 KiB | 
							
								
								
									
										39
									
								
								.resources/app/app_bootstrap/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.resources/app/app_bootstrap/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const buildInfo = require('./buildInfo'); | ||||
| const paths = require('../common/paths'); | ||||
| paths.init(buildInfo); | ||||
| const moduleUpdater = require('../common/moduleUpdater'); | ||||
| const updater = require('../common/updater'); | ||||
| const requireNative = require('./requireNative'); | ||||
| function getAppMode() { | ||||
|   if (process.argv && process.argv.includes('--overlay-host')) { | ||||
|     return 'overlay-host'; | ||||
|   } | ||||
|   return 'app'; | ||||
| } | ||||
| const mode = getAppMode(); | ||||
| if (mode === 'app') { | ||||
|   require('./bootstrap'); | ||||
| } else if (mode === 'overlay-host') { | ||||
|   // Initialize the update system just enough to find installed native modules.
 | ||||
|   const appSettings = require('./appSettings'); | ||||
|   appSettings.init(); | ||||
|   const { | ||||
|     NEW_UPDATE_ENDPOINT | ||||
|   } = require('./Constants'); | ||||
|   if (buildInfo.newUpdater) { | ||||
|     if (!updater.tryInitUpdater(buildInfo, NEW_UPDATE_ENDPOINT)) { | ||||
|       throw new Error('Failed to initialize modules in overlay host.'); | ||||
|     } | ||||
| 
 | ||||
|     // Load the module search path but if there's a pending host update, don't
 | ||||
|     // restart into it.
 | ||||
|     updater.getUpdater().startCurrentVersionSync({ | ||||
|       allowObsoleteHost: true | ||||
|     }); | ||||
|   } else { | ||||
|     moduleUpdater.initPathsOnly(buildInfo); | ||||
|   } | ||||
|   requireNative('discord_overlay2/standalone_host.js'); | ||||
| } | ||||
							
								
								
									
										19
									
								
								.resources/app/app_bootstrap/installDevTools.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.resources/app/app_bootstrap/installDevTools.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| // used in devtools to hook in additional dev tools
 | ||||
| // require('electron').remote.require('./installDevTools')()
 | ||||
| 
 | ||||
| function installDevTools() { | ||||
|   console.log(`Installing Devtron`); | ||||
|   const devtron = require('devtron'); | ||||
|   devtron.uninstall(); | ||||
|   devtron.install(); | ||||
|   console.log(`Installed Devtron`); | ||||
| } | ||||
| var _default = installDevTools; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										13
									
								
								.resources/app/app_bootstrap/ipcMain.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.resources/app/app_bootstrap/ipcMain.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| var _default = { | ||||
|   on: (event, callback) => _electron.ipcMain.on(`DISCORD_${event}`, callback), | ||||
|   removeListener: (event, callback) => _electron.ipcMain.removeListener(`DISCORD_${event}`, callback) | ||||
| }; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										162
									
								
								.resources/app/app_bootstrap/request.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								.resources/app/app_bootstrap/request.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| var _querystring = _interopRequireDefault(require("querystring")); | ||||
| var _request = _interopRequireDefault(require("request")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| const DEFAULT_REQUEST_TIMEOUT = 30000; | ||||
| function makeHTTPResponse({ | ||||
|   method, | ||||
|   url, | ||||
|   headers, | ||||
|   statusCode, | ||||
|   statusMessage | ||||
| }, body) { | ||||
|   return { | ||||
|     method, | ||||
|     url, | ||||
|     headers, | ||||
|     statusCode, | ||||
|     statusMessage, | ||||
|     body | ||||
|   }; | ||||
| } | ||||
| function makeHTTPStatusError(response) { | ||||
|   const err = new Error(`HTTP Error: Status Code ${response.statusCode}`); | ||||
|   err.response = response; | ||||
|   return err; | ||||
| } | ||||
| function handleHTTPResponse(resolve, reject, response, stream) { | ||||
|   const totalBytes = parseInt(response.headers['content-length'] || 1, 10); | ||||
|   let receivedBytes = 0; | ||||
|   const chunks = []; | ||||
| 
 | ||||
|   // don't stream response if it's a failure
 | ||||
|   if (response.statusCode >= 300) { | ||||
|     stream = null; | ||||
|   } | ||||
|   response.on('data', chunk => { | ||||
|     if (stream != null) { | ||||
|       receivedBytes += chunk.length; | ||||
|       stream.write(chunk); | ||||
|       stream.emit('progress', { | ||||
|         totalBytes, | ||||
|         receivedBytes | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     chunks.push(chunk); | ||||
|   }); | ||||
|   response.on('end', () => { | ||||
|     if (stream != null) { | ||||
|       stream.on('finish', () => resolve(makeHTTPResponse(response, null))); | ||||
|       stream.end(); | ||||
|       return; | ||||
|     } | ||||
|     const res = makeHTTPResponse(response, Buffer.concat(chunks)); | ||||
|     if (res.statusCode >= 300) { | ||||
|       reject(makeHTTPStatusError(res)); | ||||
|       return; | ||||
|     } | ||||
|     resolve(res); | ||||
|   }); | ||||
| } | ||||
| function nodeRequest({ | ||||
|   method, | ||||
|   url, | ||||
|   headers, | ||||
|   qs, | ||||
|   timeout, | ||||
|   body, | ||||
|   stream | ||||
| }) { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     const req = (0, _request.default)({ | ||||
|       method, | ||||
|       url, | ||||
|       qs, | ||||
|       headers, | ||||
|       followAllRedirects: true, | ||||
|       encoding: null, | ||||
|       timeout: timeout != null ? timeout : DEFAULT_REQUEST_TIMEOUT, | ||||
|       body | ||||
|     }); | ||||
|     req.on('response', response => handleHTTPResponse(resolve, reject, response, stream)); | ||||
|     req.on('error', err => reject(err)); | ||||
|   }); | ||||
| } | ||||
| async function electronRequest({ | ||||
|   method, | ||||
|   url, | ||||
|   headers, | ||||
|   qs, | ||||
|   timeout, | ||||
|   body, | ||||
|   stream | ||||
| }) { | ||||
|   await _electron.app.whenReady(); | ||||
|   const { | ||||
|     net, | ||||
|     session | ||||
|   } = require('electron'); | ||||
|   const req = net.request({ | ||||
|     method, | ||||
|     url: `${url}${qs != null ? `?${_querystring.default.stringify(qs)}` : ''}`, | ||||
|     redirect: 'follow', | ||||
|     session: session.defaultSession | ||||
|   }); | ||||
|   if (headers != null) { | ||||
|     for (const headerKey of Object.keys(headers)) { | ||||
|       req.setHeader(headerKey, headers[headerKey]); | ||||
|     } | ||||
|   } | ||||
|   if (body != null) { | ||||
|     req.write(body, 'utf-8'); | ||||
|   } | ||||
|   return new Promise((resolve, reject) => { | ||||
|     const reqTimeout = setTimeout(() => { | ||||
|       req.abort(); | ||||
|       reject(new Error(`network timeout: ${url}`)); | ||||
|     }, timeout != null ? timeout : DEFAULT_REQUEST_TIMEOUT); | ||||
|     req.on('login', (authInfo, callback) => callback()); | ||||
|     req.on('response', response => { | ||||
|       clearTimeout(reqTimeout); | ||||
|       handleHTTPResponse(resolve, reject, response, stream); | ||||
|     }); | ||||
|     req.on('error', err => { | ||||
|       clearTimeout(reqTimeout); | ||||
|       reject(err); | ||||
|     }); | ||||
|     req.end(); | ||||
|   }); | ||||
| } | ||||
| async function requestWithMethod(method, options) { | ||||
|   if (typeof options === 'string') { | ||||
|     options = { | ||||
|       url: options | ||||
|     }; | ||||
|   } | ||||
|   options = { | ||||
|     ...options, | ||||
|     method | ||||
|   }; | ||||
|   try { | ||||
|     return await electronRequest(options); | ||||
|   } catch (err) { | ||||
|     console.log(`Error downloading with electron net: ${err.message}`); | ||||
|     console.log('Falling back to node net library..'); | ||||
|   } | ||||
|   return nodeRequest(options); | ||||
| } | ||||
| 
 | ||||
| // only supports get for now, since retrying is non-idempotent and
 | ||||
| // we'd want to grovel the errors to make sure it's safe to retry
 | ||||
| var _default = { | ||||
|   get: requestWithMethod.bind(null, 'GET') | ||||
| }; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										5
									
								
								.resources/app/app_bootstrap/requireNative.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.resources/app/app_bootstrap/requireNative.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| // require(), with paths specialized for requiring only native modules.
 | ||||
| module.paths = []; | ||||
| module.exports = require; | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.2 KiB | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										11
									
								
								.resources/app/app_bootstrap/splash/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.resources/app/app_bootstrap/splash/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
|   <meta charset="utf-8" /> | ||||
|   <title>Discord Updater</title> | ||||
| </head> | ||||
| <body> | ||||
|   <div id="splash-mount"></div> | ||||
|   <script src="index.js"></script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										44
									
								
								.resources/app/app_bootstrap/splash/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								.resources/app/app_bootstrap/splash/index.js
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										6
									
								
								.resources/app/app_bootstrap/splash/variables.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.resources/app/app_bootstrap/splash/variables.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| { | ||||
|   "width": "300px", | ||||
|   "height": "300px", | ||||
|   "inDuration": 700, | ||||
|   "outDuration": 333 | ||||
| } | ||||
							
								
								
									
										421
									
								
								.resources/app/app_bootstrap/splashScreen.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								.resources/app/app_bootstrap/splashScreen.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,421 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.events = exports.APP_SHOULD_SHOW = exports.APP_SHOULD_LAUNCH = void 0; | ||||
| exports.focusWindow = focusWindow; | ||||
| exports.initSplash = initSplash; | ||||
| exports.pageReady = pageReady; | ||||
| 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("../common/Backoff")); | ||||
| var moduleUpdater = _interopRequireWildcard(require("../common/moduleUpdater")); | ||||
| var paths = _interopRequireWildcard(require("../common/paths")); | ||||
| var _securityUtils = require("../common/securityUtils"); | ||||
| var _updater = require("../common/updater"); | ||||
| var _ipcMain = _interopRequireDefault(require("./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; | ||||
| // 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; | ||||
| 
 | ||||
| // 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'; | ||||
| const LAUNCHING = 'launching'; | ||||
| const DOWNLOADING_MODULE = 'downloading-module'; | ||||
| const DOWNLOADING_UPDATES = 'downloading-updates'; | ||||
| const DOWNLOADING_MODULES_FINISHED = 'downloading-modules-finished'; | ||||
| const DOWNLOADING_MODULE_PROGRESS = 'downloading-module-progress'; | ||||
| const DOWNLOADED_MODULE = 'downloaded-module'; | ||||
| const NO_PENDING_UPDATES = 'no-pending-updates'; | ||||
| const INSTALLING_MODULE = 'installing-module'; | ||||
| const INSTALLING_UPDATES = 'installing-updates'; | ||||
| const INSTALLED_MODULE = 'installed-module'; | ||||
| const INSTALLING_MODULE_PROGRESS = 'installing-module-progress'; | ||||
| const INSTALLING_MODULES_FINISHED = 'installing-modules-finished'; | ||||
| const UPDATE_MANUALLY = 'update-manually'; | ||||
| const APP_SHOULD_LAUNCH = 'APP_SHOULD_LAUNCH'; | ||||
| exports.APP_SHOULD_LAUNCH = APP_SHOULD_LAUNCH; | ||||
| 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) { | ||||
|   if (win != null && win.webContents != null) { | ||||
|     win.webContents.send(`DISCORD_${event}`, ...args); | ||||
|   } | ||||
| } | ||||
| let splashWindow; | ||||
| let modulesListeners; | ||||
| let updateTimeout; | ||||
| let updateAttempt; | ||||
| let splashState; | ||||
| let launchedMainWindow; | ||||
| let quoteCachePath; | ||||
| let restartRequired = false; | ||||
| let newUpdater; | ||||
| 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.
 | ||||
| /* eslint-disable camelcase */ | ||||
| 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, | ||||
|         total: this.allTasks.size, | ||||
|         progress: totalPercent | ||||
|       }; | ||||
|       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(); | ||||
|       const installs = new TaskProgress(); | ||||
|       await newUpdater.updateToLatestWithOptions(retryOptions, progress => { | ||||
|         const task = progress.task; | ||||
|         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; | ||||
|             } else if (task.ModuleInstall != null) { | ||||
|               retryOptions.skip_module_delta[installTask.version.module.name] = true; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         if (!downloads.updateSplashState(DOWNLOADING_UPDATES)) { | ||||
|           installs.updateSplashState(INSTALLING_UPDATES); | ||||
|         } | ||||
|       }); | ||||
|       if (!installedAnything) { | ||||
|         await newUpdater.startCurrentVersion(); | ||||
|         newUpdater.setRunningInBackground(); | ||||
|         newUpdater.collectGarbage(); | ||||
|         launchMainWindow(); | ||||
|         updateBackoff.succeed(); | ||||
|         updateSplashState(LAUNCHING); | ||||
|         return; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       console.error('Update failed', e); | ||||
|       await new Promise(resolve => { | ||||
|         const delayMs = updateBackoff.fail(resolve); | ||||
|         splashState.seconds = Math.round(delayMs / 1000); | ||||
|         updateSplashState(UPDATE_FAILURE); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| /* eslint-enable camelcase */ | ||||
| 
 | ||||
| function initOldUpdater() { | ||||
|   modulesListeners = {}; | ||||
|   addModulesListener(CHECKING_FOR_UPDATES, () => { | ||||
|     startUpdateTimeout(); | ||||
|     updateSplashState(CHECKING_FOR_UPDATES); | ||||
|   }); | ||||
|   addModulesListener(UPDATE_CHECK_FINISHED, ({ | ||||
|     succeeded, | ||||
|     updateCount, | ||||
|     manualRequired | ||||
|   }) => { | ||||
|     stopUpdateTimeout(); | ||||
|     if (!succeeded) { | ||||
|       scheduleUpdateCheck(); | ||||
|       updateSplashState(UPDATE_FAILURE); | ||||
|     } else if (updateCount === 0) { | ||||
|       moduleUpdater.setInBackground(); | ||||
|       launchMainWindow(); | ||||
|       updateSplashState(LAUNCHING); | ||||
|     } | ||||
|   }); | ||||
|   addModulesListener(DOWNLOADING_MODULE, ({ | ||||
|     name, | ||||
|     current, | ||||
|     total | ||||
|   }) => { | ||||
|     stopUpdateTimeout(); | ||||
|     splashState = { | ||||
|       current, | ||||
|       total | ||||
|     }; | ||||
|     updateSplashState(DOWNLOADING_UPDATES); | ||||
|   }); | ||||
|   addModulesListener(DOWNLOADING_MODULE_PROGRESS, ({ | ||||
|     name, | ||||
|     progress | ||||
|   }) => { | ||||
|     splashState.progress = progress; | ||||
|     updateSplashState(DOWNLOADING_UPDATES); | ||||
|   }); | ||||
|   addModulesListener(DOWNLOADED_MODULE, ({ | ||||
|     name, | ||||
|     current, | ||||
|     total, | ||||
|     succeeded | ||||
|   }) => { | ||||
|     delete splashState.progress; | ||||
|     if (name === 'host') { | ||||
|       restartRequired = true; | ||||
|     } | ||||
|   }); | ||||
|   addModulesListener(DOWNLOADING_MODULES_FINISHED, ({ | ||||
|     succeeded, | ||||
|     failed | ||||
|   }) => { | ||||
|     if (failed > 0) { | ||||
|       scheduleUpdateCheck(); | ||||
|       updateSplashState(UPDATE_FAILURE); | ||||
|     } else { | ||||
|       process.nextTick(() => { | ||||
|         if (restartRequired) { | ||||
|           moduleUpdater.quitAndInstallUpdates(); | ||||
|         } else { | ||||
|           moduleUpdater.installPendingUpdates(); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
|   addModulesListener(NO_PENDING_UPDATES, () => moduleUpdater.checkForUpdates()); | ||||
|   addModulesListener(INSTALLING_MODULE, ({ | ||||
|     name, | ||||
|     current, | ||||
|     total | ||||
|   }) => { | ||||
|     splashState = { | ||||
|       current, | ||||
|       total | ||||
|     }; | ||||
|     updateSplashState(INSTALLING_UPDATES); | ||||
|   }); | ||||
|   addModulesListener(INSTALLED_MODULE, ({ | ||||
|     name, | ||||
|     current, | ||||
|     total, | ||||
|     succeeded | ||||
|   }) => delete splashState.progress); | ||||
|   addModulesListener(INSTALLING_MODULE_PROGRESS, ({ | ||||
|     name, | ||||
|     progress | ||||
|   }) => { | ||||
|     splashState.progress = progress; | ||||
|     updateSplashState(INSTALLING_UPDATES); | ||||
|   }); | ||||
|   addModulesListener(INSTALLING_MODULES_FINISHED, ({ | ||||
|     succeeded, | ||||
|     failed | ||||
|   }) => moduleUpdater.checkForUpdates()); | ||||
|   addModulesListener(UPDATE_MANUALLY, ({ | ||||
|     newVersion | ||||
|   }) => { | ||||
|     splashState.newVersion = newVersion; | ||||
|     updateSplashState(UPDATE_MANUALLY); | ||||
|   }); | ||||
| } | ||||
| function initSplash(startMinimized = false) { | ||||
|   splashState = {}; | ||||
|   launchedMainWindow = false; | ||||
|   updateAttempt = 0; | ||||
|   newUpdater = (0, _updater.getUpdater)(); | ||||
|   if (newUpdater == null) { | ||||
|     initOldUpdater(); | ||||
|   } | ||||
|   launchSplashWindow(startMinimized); | ||||
|   quoteCachePath = _path.default.join(paths.getUserData(), 'quotes.json'); | ||||
|   _ipcMain.default.on('UPDATED_QUOTES', (_event, quotes) => cacheLatestQuotes(quotes)); | ||||
| } | ||||
| function destroySplash() { | ||||
|   stopUpdateTimeout(); | ||||
|   if (splashWindow) { | ||||
|     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(); | ||||
|         splashWindow.close(); | ||||
|         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) { | ||||
|   if (splashWindow != null && !splashWindow.isDestroyed() && !splashWindow.webContents.isDestroyed()) { | ||||
|     webContentsSend(splashWindow, 'SPLASH_UPDATE_STATE', { | ||||
|       status: event, | ||||
|       ...splashState | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| function launchSplashWindow(startMinimized) { | ||||
|   const windowConfig = { | ||||
|     width: LOADING_WINDOW_WIDTH, | ||||
|     height: LOADING_WINDOW_HEIGHT, | ||||
|     transparent: false, | ||||
|     frame: false, | ||||
|     resizable: false, | ||||
|     center: true, | ||||
|     show: false, | ||||
|     webPreferences: { | ||||
|       nodeIntegration: false, | ||||
|       sandbox: false, | ||||
|       enableRemoteModule: false, | ||||
|       contextIsolation: true, | ||||
|       preload: _path.default.join(__dirname, 'splashScreenPreload.js') | ||||
|     } | ||||
|   }; | ||||
|   splashWindow = new _electron.BrowserWindow(windowConfig); | ||||
| 
 | ||||
|   // prevent users from dropping links to navigate in splash window
 | ||||
|   splashWindow.webContents.on('will-navigate', e => e.preventDefault()); | ||||
|   splashWindow.webContents.on('new-window', (e, windowURL) => { | ||||
|     e.preventDefault(); | ||||
|     (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.default.on('SPLASH_SCREEN_READY', () => { | ||||
|     const cachedQuote = chooseCachedQuote(); | ||||
|     if (cachedQuote) { | ||||
|       webContentsSend(splashWindow, 'SPLASH_SCREEN_QUOTE', cachedQuote); | ||||
|     } | ||||
|     if (splashWindow && !startMinimized) { | ||||
|       splashWindow.show(); | ||||
|     } | ||||
|     if (newUpdater != null) { | ||||
|       updateUntilCurrent(); | ||||
|     } else { | ||||
|       moduleUpdater.installPendingUpdates(); | ||||
|     } | ||||
|   }); | ||||
|   _ipcMain.default.on('SPLASH_SCREEN_QUIT', () => { | ||||
|     _electron.app.quit(); | ||||
|   }); | ||||
|   const splashUrl = _url.default.format({ | ||||
|     protocol: 'file', | ||||
|     slashes: true, | ||||
|     pathname: _path.default.join(__dirname, 'splash', 'index.html') | ||||
|   }); | ||||
|   splashWindow.loadURL(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() { | ||||
|   if (splashWindow != null) { | ||||
|     splashWindow.focus(); | ||||
|   } | ||||
| } | ||||
| function pageReady() { | ||||
|   destroySplash(); | ||||
|   process.nextTick(() => events.emit(APP_SHOULD_SHOW)); | ||||
| } | ||||
| function cacheLatestQuotes(quotes) { | ||||
|   _fs.default.writeFile(quoteCachePath, JSON.stringify(quotes), e => { | ||||
|     if (e) { | ||||
|       console.warn('Failed updating quote cache with error: ', e); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| function chooseCachedQuote() { | ||||
|   let cachedQuote = null; | ||||
|   try { | ||||
|     const cachedQuotes = JSON.parse(_fs.default.readFileSync(quoteCachePath)); | ||||
|     cachedQuote = cachedQuotes[Math.floor(Math.random() * cachedQuotes.length)]; | ||||
|   } catch (_err) {} | ||||
|   return cachedQuote; | ||||
| } | ||||
							
								
								
									
										33
									
								
								.resources/app/app_bootstrap/splashScreenPreload.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								.resources/app/app_bootstrap/splashScreenPreload.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| const { | ||||
|   app, | ||||
|   contextBridge, | ||||
|   ipcRenderer | ||||
| } = require('electron'); | ||||
| const { | ||||
|   saferShellOpenExternal | ||||
| } = require('../common/securityUtils'); | ||||
| contextBridge.exposeInMainWorld('DiscordSplash', { | ||||
|   getReleaseChannel: () => { | ||||
|     const buildInfo = require('./buildInfo'); | ||||
|     return buildInfo.releaseChannel; | ||||
|   }, | ||||
|   signalReady: () => { | ||||
|     ipcRenderer.send('DISCORD_SPLASH_SCREEN_READY'); | ||||
|   }, | ||||
|   onStateUpdate: callback => { | ||||
|     ipcRenderer.on('DISCORD_SPLASH_UPDATE_STATE', (_, state) => { | ||||
|       callback(state); | ||||
|     }); | ||||
|   }, | ||||
|   onQuoteUpdate: callback => { | ||||
|     ipcRenderer.on('DISCORD_SPLASH_SCREEN_QUOTE', (_, quote) => { | ||||
|       callback(quote); | ||||
|     }); | ||||
|   }, | ||||
|   openUrl: saferShellOpenExternal, | ||||
|   quitDiscord: () => { | ||||
|     ipcRenderer.send('DISCORD_SPLASH_SCREEN_QUIT'); | ||||
|   } | ||||
| }); | ||||
							
								
								
									
										189
									
								
								.resources/app/app_bootstrap/squirrelUpdate.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								.resources/app/app_bootstrap/squirrelUpdate.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,189 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.handleStartupEvent = handleStartupEvent; | ||||
| exports.installProtocol = installProtocol; | ||||
| exports.restart = restart; | ||||
| exports.spawnUpdate = spawnUpdate; | ||||
| exports.spawnUpdateInstall = spawnUpdateInstall; | ||||
| exports.updateExistsSync = updateExistsSync; | ||||
| var _child_process = _interopRequireDefault(require("child_process")); | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var autoStart = _interopRequireWildcard(require("./autoStart")); | ||||
| var windowsUtils = _interopRequireWildcard(require("./windowsUtils")); | ||||
| 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 }; } | ||||
| // citron note: this assumes the execPath is in the format Discord/someVersion/Discord.exe
 | ||||
| const appFolder = _path.default.resolve(process.execPath, '..'); | ||||
| const rootFolder = _path.default.resolve(appFolder, '..'); | ||||
| const exeName = _path.default.basename(process.execPath); | ||||
| const updateExe = _path.default.join(rootFolder, 'Update.exe'); | ||||
| 
 | ||||
| // Specialized spawn function specifically used for spawning the updater in
 | ||||
| // update mode. Calls back with progress percentages.
 | ||||
| // Returns Promise.
 | ||||
| function spawnUpdateInstall(updateUrl, progressCallback) { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     const proc = _child_process.default.spawn(updateExe, ['--update', updateUrl]); | ||||
|     proc.on('error', reject); | ||||
|     proc.on('exit', code => { | ||||
|       if (code !== 0) { | ||||
|         return reject(new Error(`Update failed with exit code ${code}`)); | ||||
|       } | ||||
|       return resolve(); | ||||
|     }); | ||||
|     let lastProgress = -1; | ||||
|     function parseProgress() { | ||||
|       const lines = stdout.split(/\r?\n/); | ||||
|       if (lines.length === 1) return; | ||||
|       // return the last (possibly incomplete) line to stdout for parsing again
 | ||||
|       stdout = lines.pop(); | ||||
|       let currentProgress; | ||||
|       for (const line of lines) { | ||||
|         if (!/^\d\d?$/.test(line)) continue; | ||||
|         const progress = Number(line); | ||||
|         // make sure that this number is steadily increasing
 | ||||
|         if (lastProgress > progress) continue; | ||||
|         currentProgress = progress; | ||||
|       } | ||||
|       if (currentProgress == null) return; | ||||
|       lastProgress = currentProgress; | ||||
|       progressCallback(Math.min(currentProgress, 100)); | ||||
|     } | ||||
|     let stdout = ''; | ||||
|     proc.stdout.on('data', chunk => { | ||||
|       stdout += String(chunk); | ||||
|       parseProgress(); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // Spawn the Update.exe with the given arguments and invoke the callback when
 | ||||
| // the command completes.
 | ||||
| function spawnUpdate(args, callback) { | ||||
|   windowsUtils.spawn(updateExe, args, callback); | ||||
| } | ||||
| 
 | ||||
| // Create a desktop and start menu shortcut by using the command line API
 | ||||
| // provided by Squirrel's Update.exe
 | ||||
| function createShortcuts(callback, updateOnly) { | ||||
|   // move icon out to a more stable location, to keep shortcuts from breaking as much
 | ||||
|   const icoSrc = _path.default.join(appFolder, 'app.ico'); | ||||
|   const icoDest = _path.default.join(rootFolder, 'app.ico'); | ||||
|   let icoForTarget = icoDest; | ||||
|   try { | ||||
|     const ico = _fs.default.readFileSync(icoSrc); | ||||
|     _fs.default.writeFileSync(icoDest, ico); | ||||
|   } catch (e) { | ||||
|     // if we can't write there for some reason, just use the source.
 | ||||
|     icoForTarget = icoSrc; | ||||
|   } | ||||
|   const createShortcutArgs = ['--createShortcut', exeName, '--setupIcon', icoForTarget]; | ||||
|   if (updateOnly) { | ||||
|     createShortcutArgs.push('--updateOnly'); | ||||
|   } | ||||
|   spawnUpdate(createShortcutArgs, callback); | ||||
| } | ||||
| 
 | ||||
| // Add a protocol registration for this application.
 | ||||
| function installProtocol(protocol, callback) { | ||||
|   const queue = [['HKCU\\Software\\Classes\\' + protocol, '/ve', '/d', `URL:${protocol} Protocol`], ['HKCU\\Software\\Classes\\' + protocol, '/v', 'URL Protocol'], ['HKCU\\Software\\Classes\\' + protocol + '\\DefaultIcon', '/ve', '/d', '"' + process.execPath + '",-1'], ['HKCU\\Software\\Classes\\' + protocol + '\\shell\\open\\command', '/ve', '/d', `"${process.execPath}" --url -- "%1"`]]; | ||||
|   windowsUtils.addToRegistry(queue, callback); | ||||
| } | ||||
| function terminate(app) { | ||||
|   app.quit(); | ||||
|   process.exit(0); | ||||
| } | ||||
| 
 | ||||
| // Remove the desktop and start menu shortcuts by using the command line API
 | ||||
| // provided by Squirrel's Update.exe
 | ||||
| function removeShortcuts(callback) { | ||||
|   spawnUpdate(['--removeShortcut', exeName], callback); | ||||
| } | ||||
| 
 | ||||
| // Update the desktop and start menu shortcuts by using the command line API
 | ||||
| // provided by Squirrel's Update.exe
 | ||||
| function updateShortcuts(callback) { | ||||
|   createShortcuts(callback, true); | ||||
| } | ||||
| 
 | ||||
| // Purge the protocol for this applicationstart.
 | ||||
| function uninstallProtocol(protocol, callback) { | ||||
|   windowsUtils.spawnReg(['delete', 'HKCU\\Software\\Classes\\' + protocol, '/f'], callback); | ||||
| } | ||||
| function maybeInstallNewUpdaterSeedDb() { | ||||
|   const installerDbSrc = _path.default.join(appFolder, 'installer.db'); | ||||
|   const installerDbDest = _path.default.join(rootFolder, 'installer.db'); | ||||
|   if (_fs.default.existsSync(installerDbSrc)) { | ||||
|     _fs.default.renameSync(installerDbSrc, installerDbDest); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Handle squirrel events denoted by --squirrel-* command line arguments.
 | ||||
| // returns `true` if regular startup should be prevented
 | ||||
| function handleStartupEvent(protocol, app, squirrelCommand) { | ||||
|   switch (squirrelCommand) { | ||||
|     case '--squirrel-install': | ||||
|       createShortcuts(() => { | ||||
|         autoStart.install(() => { | ||||
|           installProtocol(protocol, () => { | ||||
|             terminate(app); | ||||
|           }); | ||||
|         }); | ||||
|       }, false); | ||||
|       return true; | ||||
|     case '--squirrel-updated': | ||||
|       updateShortcuts(() => { | ||||
|         autoStart.update(() => { | ||||
|           installProtocol(protocol, () => { | ||||
|             terminate(app); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|       return true; | ||||
|     case '--squirrel-uninstall': | ||||
|       removeShortcuts(() => { | ||||
|         autoStart.uninstall(() => { | ||||
|           uninstallProtocol(protocol, () => { | ||||
|             terminate(app); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|       return true; | ||||
|     case '--squirrel-obsolete': | ||||
|       terminate(app); | ||||
|       return true; | ||||
|     case '--squirrel-firstrun': | ||||
|       // Squirrel doesn't have a way to include app-level files. We get around
 | ||||
|       // this for new updater hosts, which rely on a seeded manifest, by
 | ||||
|       // bubbling the db up from the versioned-app directory if it exists.
 | ||||
|       //
 | ||||
|       // Additionally, we run this in --squirrel-firstrun, not
 | ||||
|       // --squirrel-install, because the latter is unreliable with unicode
 | ||||
|       // paths. yay!
 | ||||
|       maybeInstallNewUpdaterSeedDb(); | ||||
|       return false; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Are we using Squirrel for updates?
 | ||||
| function updateExistsSync() { | ||||
|   return _fs.default.existsSync(updateExe); | ||||
| } | ||||
| 
 | ||||
| // Restart app as the new version
 | ||||
| function restart(app, newVersion) { | ||||
|   app.once('will-quit', () => { | ||||
|     const execPath = _path.default.resolve(rootFolder, `app-${newVersion}/${exeName}`); | ||||
|     _child_process.default.spawn(execPath, [], { | ||||
|       detached: true | ||||
|     }); | ||||
|   }); | ||||
|   app.quit(); | ||||
| } | ||||
							
								
								
									
										17
									
								
								.resources/app/app_bootstrap/startupMenu/darwin.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.resources/app/app_bootstrap/startupMenu/darwin.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| var _default = [{ | ||||
|   label: 'Discord', | ||||
|   submenu: [{ | ||||
|     label: 'Quit', | ||||
|     click: () => _electron.app.quit(), | ||||
|     accelerator: 'Command+Q' | ||||
|   }] | ||||
| }]; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										11
									
								
								.resources/app/app_bootstrap/startupMenu/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.resources/app/app_bootstrap/startupMenu/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| const menu = require('./' + process.platform); | ||||
| var _default = _electron.Menu.buildFromTemplate(menu); | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										17
									
								
								.resources/app/app_bootstrap/startupMenu/linux.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.resources/app/app_bootstrap/startupMenu/linux.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| var _default = [{ | ||||
|   label: '&File', | ||||
|   submenu: [{ | ||||
|     label: '&Exit', | ||||
|     click: () => _electron.app.quit(), | ||||
|     accelerator: 'Control+Q' | ||||
|   }] | ||||
| }]; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										17
									
								
								.resources/app/app_bootstrap/startupMenu/win32.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.resources/app/app_bootstrap/startupMenu/win32.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _electron = require("electron"); | ||||
| var _default = [{ | ||||
|   label: '&File', | ||||
|   submenu: [{ | ||||
|     label: '&Exit', | ||||
|     click: () => _electron.app.quit(), | ||||
|     accelerator: 'Alt+F4' | ||||
|   }] | ||||
| }]; | ||||
| exports.default = _default; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										
											BIN
										
									
								
								.resources/app/app_bootstrap/videos/connecting.webm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.resources/app/app_bootstrap/videos/connecting.webm
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								.resources/app/app_bootstrap/videos/connecting_halloween.webm
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.resources/app/app_bootstrap/videos/connecting_halloween.webm
									
										
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										76
									
								
								.resources/app/app_bootstrap/windowsUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								.resources/app/app_bootstrap/windowsUtils.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.addToRegistry = addToRegistry; | ||||
| exports.spawn = spawn; | ||||
| exports.spawnReg = spawnReg; | ||||
| var _child_process = _interopRequireDefault(require("child_process")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| const regExe = process.env.SystemRoot ? _path.default.join(process.env.SystemRoot, 'System32', 'reg.exe') : 'reg.exe'; | ||||
| 
 | ||||
| // Spawn a command and invoke the callback when it completes with an error
 | ||||
| // and the output from standard out.
 | ||||
| function spawn(command, args, callback) { | ||||
|   let stdout = ''; | ||||
|   let spawnedProcess; | ||||
|   try { | ||||
|     // TODO: contrary to below, it should not throw any error
 | ||||
|     spawnedProcess = _child_process.default.spawn(command, args); | ||||
|   } catch (err) { | ||||
|     // Spawn can throw an error
 | ||||
|     process.nextTick(() => { | ||||
|       if (callback != null) { | ||||
|         callback(err, stdout); | ||||
|       } | ||||
|     }); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // TODO: we need to specify the encoding for the data if we're going to concat it as a string
 | ||||
|   spawnedProcess.stdout.on('data', data => { | ||||
|     stdout += data; | ||||
|   }); | ||||
|   let err = null; | ||||
|   // TODO: close event might not get called, we should
 | ||||
|   //       callback on error https://nodejs.org/api/child_process.html#child_process_event_error
 | ||||
|   spawnedProcess.on('error', err => { | ||||
|     // TODO: there should always be an error
 | ||||
|     if (err != null) { | ||||
|       err = err; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   // TODO: don't listen to close, but listen to exit instead
 | ||||
|   spawnedProcess.on('close', (code, signal) => { | ||||
|     if (err === null && code !== 0) { | ||||
|       err = new Error('Command failed: ' + (signal || code)); | ||||
|     } | ||||
|     if (err != null) { | ||||
|       err.code = err.code || code; | ||||
|       err.stdout = err.stdout || stdout; | ||||
|     } | ||||
|     if (callback != null) { | ||||
|       callback(err, stdout); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // Spawn reg.exe and callback when it completes
 | ||||
| function spawnReg(args, callback) { | ||||
|   return spawn(regExe, args, callback); | ||||
| } | ||||
| 
 | ||||
| // TODO: since we're doing this one by one, we could have a more graceful way of processing the queue
 | ||||
| //       rather than mutating the array
 | ||||
| function addToRegistry(queue, callback) { | ||||
|   if (queue.length === 0) { | ||||
|     return callback && callback(); | ||||
|   } | ||||
|   const args = queue.shift(); | ||||
|   args.unshift('add'); | ||||
|   args.push('/f'); | ||||
|   return spawnReg(args, () => addToRegistry(queue, callback)); | ||||
| } | ||||
							
								
								
									
										90
									
								
								.resources/app/common/Backoff.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								.resources/app/common/Backoff.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,90 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| // copied from discord_app/lib because including from there is broken.
 | ||||
| 
 | ||||
| class Backoff { | ||||
|   /** | ||||
|    * Create a backoff instance can automatically backoff retries. | ||||
|    */ | ||||
|   constructor(min = 500, max = null, jitter = true) { | ||||
|     this.min = min; | ||||
|     this.max = max != null ? max : min * 10; | ||||
|     this.jitter = jitter; | ||||
|     this._current = min; | ||||
|     this._timeoutId = null; | ||||
|     this._fails = 0; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Return the number of failures. | ||||
|    */ | ||||
|   get fails() { | ||||
|     return this._fails; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Current backoff value in milliseconds. | ||||
|    */ | ||||
|   get current() { | ||||
|     return this._current; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * A callback is going to fire. | ||||
|    */ | ||||
|   get pending() { | ||||
|     return this._timeoutId != null; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Clear any pending callbacks and reset the backoff. | ||||
|    */ | ||||
|   succeed() { | ||||
|     this.cancel(); | ||||
|     this._fails = 0; | ||||
|     this._current = this.min; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Increment the backoff and schedule a callback if provided. | ||||
|    */ | ||||
|   fail(callback) { | ||||
|     this._fails += 1; | ||||
|     let delay = this._current * 2; | ||||
|     if (this.jitter) { | ||||
|       delay *= Math.random(); | ||||
|     } | ||||
|     this._current = Math.min(this._current + delay, this.max); | ||||
|     if (callback != null) { | ||||
|       if (this._timeoutId != null) { | ||||
|         throw new Error('callback already pending'); | ||||
|       } | ||||
|       this._timeoutId = setTimeout(() => { | ||||
|         try { | ||||
|           if (callback != null) { | ||||
|             callback(); | ||||
|           } | ||||
|         } finally { | ||||
|           this._timeoutId = null; | ||||
|         } | ||||
|       }, this._current); | ||||
|     } | ||||
|     return this._current; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    *  Clear any pending callbacks. | ||||
|    */ | ||||
|   cancel() { | ||||
|     if (this._timeoutId != null) { | ||||
|       clearTimeout(this._timeoutId); | ||||
|       this._timeoutId = null; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| exports.default = Backoff; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										26
									
								
								.resources/app/common/FeatureFlags.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.resources/app/common/FeatureFlags.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| class FeatureFlags { | ||||
|   constructor() { | ||||
|     this.flags = new Set(); | ||||
|   } | ||||
|   getSupported() { | ||||
|     return Array.from(this.flags); | ||||
|   } | ||||
|   supports(feature) { | ||||
|     return this.flags.has(feature); | ||||
|   } | ||||
|   declareSupported(feature) { | ||||
|     if (this.supports(feature)) { | ||||
|       console.error('Feature redeclared; is this a duplicate flag? ', feature); | ||||
|       return; | ||||
|     } | ||||
|     this.flags.add(feature); | ||||
|   } | ||||
| } | ||||
| exports.default = FeatureFlags; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										58
									
								
								.resources/app/common/Settings.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								.resources/app/common/Settings.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.default = void 0; | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| // TODO: sync fs operations could cause slowdown and/or freezes, depending on usage
 | ||||
| //       if this is fine, remove this todo
 | ||||
| class Settings { | ||||
|   constructor(root) { | ||||
|     this.path = _path.default.join(root, 'settings.json'); | ||||
|     try { | ||||
|       this.lastSaved = _fs.default.readFileSync(this.path); | ||||
|       this.settings = JSON.parse(this.lastSaved); | ||||
|     } catch (e) { | ||||
|       this.lastSaved = ''; | ||||
|       this.settings = {}; | ||||
|     } | ||||
|     this.lastModified = this._lastModified(); | ||||
|   } | ||||
|   _lastModified() { | ||||
|     try { | ||||
|       return _fs.default.statSync(this.path).mtime.getTime(); | ||||
|     } catch (e) { | ||||
|       return 0; | ||||
|     } | ||||
|   } | ||||
|   get(key, defaultValue = false) { | ||||
|     if (this.settings.hasOwnProperty(key)) { | ||||
|       return this.settings[key]; | ||||
|     } | ||||
|     return defaultValue; | ||||
|   } | ||||
|   set(key, value) { | ||||
|     this.settings[key] = value; | ||||
|   } | ||||
|   save() { | ||||
|     if (this.lastModified && this.lastModified !== this._lastModified()) { | ||||
|       console.warn('Not saving settings, it has been externally modified.'); | ||||
|       return; | ||||
|     } | ||||
|     try { | ||||
|       const toSave = JSON.stringify(this.settings, null, 2); | ||||
|       if (this.lastSaved != toSave) { | ||||
|         this.lastSaved = toSave; | ||||
|         _fs.default.writeFileSync(this.path, toSave); | ||||
|         this.lastModified = this._lastModified(); | ||||
|       } | ||||
|     } catch (err) { | ||||
|       console.warn('Failed saving settings with error: ', err); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| exports.default = Settings; | ||||
| module.exports = exports.default; | ||||
							
								
								
									
										101
									
								
								.resources/app/common/crashReporterSetup.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								.resources/app/common/crashReporterSetup.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.init = init; | ||||
| exports.isInitialized = isInitialized; | ||||
| exports.metadata = void 0; | ||||
| var processUtils = _interopRequireWildcard(require("./processUtils")); | ||||
| 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; } | ||||
| // @ts-nocheck
 | ||||
| /* eslint-disable */ | ||||
| /* eslint-disable no-console */ | ||||
| 
 | ||||
| const electron = require('electron'); | ||||
| const childProcess = require('child_process'); | ||||
| const { | ||||
|   flatten | ||||
| } = require('./crashReporterUtils'); | ||||
| let initialized = false; | ||||
| const metadata = {}; | ||||
| exports.metadata = metadata; | ||||
| const supportsTls13 = processUtils.supportsTls13(); | ||||
| const DEFAULT_SENTRY_KEY = '384ce4413de74fe0be270abe03b2b35a'; | ||||
| const TEST_SENTRY_KEY = '1a27a96457b24ff286a000266c573919'; | ||||
| const CHANNEL_SENTRY_KEYS = { | ||||
|   stable: DEFAULT_SENTRY_KEY, | ||||
|   ptb: TEST_SENTRY_KEY, | ||||
|   canary: TEST_SENTRY_KEY, | ||||
|   development: TEST_SENTRY_KEY | ||||
| }; | ||||
| function getCrashReporterArgs(metadata) { | ||||
|   // NB: we need to flatten the metadata because modern electron caps metadata values at 127 bytes,
 | ||||
|   // which our sentry subobject can easily exceed.
 | ||||
|   const flatMetadata = flatten(metadata); | ||||
|   const channel = metadata['channel']; | ||||
|   const sentryKey = CHANNEL_SENTRY_KEYS[channel] != null ? CHANNEL_SENTRY_KEYS[channel] : DEFAULT_SENTRY_KEY; | ||||
|   const sentryHost = supportsTls13 ? 'sentry.io' : 'insecure.sentry.io'; | ||||
|   return { | ||||
|     productName: 'Discord', | ||||
|     companyName: 'Discord Inc.', | ||||
|     submitURL: `https://${sentryHost}/api/146342/minidump/?sentry_key=${sentryKey}`, | ||||
|     uploadToServer: true, | ||||
|     ignoreSystemCrashHandler: false, | ||||
|     extra: flatMetadata | ||||
|   }; | ||||
| } | ||||
| function initializeSentrySdk(sentry) { | ||||
|   const sentryDsn = supportsTls13 ? 'https://8405981abe5045908f0d88135eba7ba5@o64374.ingest.sentry.io/1197903' : 'https://8405981abe5045908f0d88135eba7ba5@o64374.insecure.sentry.io/1197903'; | ||||
|   sentry.init({ | ||||
|     dsn: sentryDsn, | ||||
|     beforeSend(event) { | ||||
|       var _metadata$sentry, _metadata$sentry2; | ||||
|       // Currently beforeSend is only fired for discord-desktop-js project,
 | ||||
|       // due to outdated sentry/electron sdk
 | ||||
|       event.release = metadata === null || metadata === void 0 ? void 0 : (_metadata$sentry = metadata['sentry']) === null || _metadata$sentry === void 0 ? void 0 : _metadata$sentry['release']; | ||||
|       event.environment = metadata === null || metadata === void 0 ? void 0 : (_metadata$sentry2 = metadata['sentry']) === null || _metadata$sentry2 === void 0 ? void 0 : _metadata$sentry2['environment']; | ||||
|       return event; | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| function init(buildInfo, sentry) { | ||||
|   if (initialized) { | ||||
|     console.warn('Ignoring double initialization of crash reporter.'); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // It's desirable for test runs to have the stacktrace print to the console (and thusly, be shown in buildkite logs).
 | ||||
|   if (process.env.ELECTRON_ENABLE_STACK_DUMPING === 'true') { | ||||
|     console.warn('Not initializing crash reporter because ELECTRON_ENABLE_STACK_DUMPING is set.'); | ||||
|     return; | ||||
|   } | ||||
|   if (sentry != null) { | ||||
|     initializeSentrySdk(sentry); | ||||
|   } | ||||
|   metadata['channel'] = buildInfo.releaseChannel; | ||||
|   const sentryMetadata = metadata['sentry'] != null ? metadata['sentry'] : {}; | ||||
|   sentryMetadata['environment'] = buildInfo.releaseChannel; | ||||
|   sentryMetadata['release'] = buildInfo.version; | ||||
|   metadata['sentry'] = sentryMetadata; | ||||
|   if (processUtils.IS_LINUX) { | ||||
|     const XDG_CURRENT_DESKTOP = process.env.XDG_CURRENT_DESKTOP || 'unknown'; | ||||
|     const GDMSESSION = process.env.GDMSESSION || 'unknown'; | ||||
|     metadata['wm'] = `${XDG_CURRENT_DESKTOP},${GDMSESSION}`; | ||||
|     try { | ||||
|       metadata['distro'] = childProcess.execFileSync('lsb_release', ['-ds'], { | ||||
|         timeout: 100, | ||||
|         maxBuffer: 512, | ||||
|         encoding: 'utf-8' | ||||
|       }).trim(); | ||||
|     } catch (_) {} // just in case lsb_release doesn't exist
 | ||||
|   } | ||||
| 
 | ||||
|   const config = getCrashReporterArgs(metadata); | ||||
|   electron.crashReporter.start(config); | ||||
|   initialized = true; | ||||
| } | ||||
| function isInitialized() { | ||||
|   return initialized; | ||||
| } | ||||
							
								
								
									
										42
									
								
								.resources/app/common/crashReporterUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.resources/app/common/crashReporterUtils.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.flatten = flatten; | ||||
| exports.reconcileCrashReporterMetadata = reconcileCrashReporterMetadata; | ||||
| // @ts-nocheck
 | ||||
| /* eslint-disable */ | ||||
| const { | ||||
|   getElectronMajorVersion | ||||
| } = require('./processUtils'); | ||||
| function flatten(metadata, prefix, root) { | ||||
|   root = root ? root : {}; | ||||
|   prefix = prefix ? prefix : ''; | ||||
|   if (typeof metadata === 'object') { | ||||
|     for (const key in metadata) { | ||||
|       const next_prefix = prefix === '' ? key : `${prefix}[${key}]`; | ||||
|       flatten(metadata[key], next_prefix, root); | ||||
|     } | ||||
|   } else { | ||||
|     root[prefix] = metadata; | ||||
|   } | ||||
|   return root; | ||||
| } | ||||
| function reconcileCrashReporterMetadata(crashReporter, metadata) { | ||||
|   if (getElectronMajorVersion() < 8) { | ||||
|     return; | ||||
|   } | ||||
|   const new_metadata = flatten(metadata); | ||||
|   const old_metadata = crashReporter.getParameters(); | ||||
|   for (const key in old_metadata) { | ||||
|     if (!new_metadata.hasOwnProperty(key)) { | ||||
|       crashReporter.removeExtraParameter(key); | ||||
|     } | ||||
|   } | ||||
|   for (const key in new_metadata) { | ||||
|     if (!old_metadata.hasOwnProperty(key)) { | ||||
|       crashReporter.addExtraParameter(key, String(new_metadata[key])); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										859
									
								
								.resources/app/common/moduleUpdater.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										859
									
								
								.resources/app/common/moduleUpdater.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,859 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.UPDATE_MANUALLY = exports.UPDATE_CHECK_FINISHED = exports.NO_PENDING_UPDATES = exports.INSTALLING_MODULE_PROGRESS = exports.INSTALLING_MODULES_FINISHED = exports.INSTALLING_MODULE = exports.INSTALLED_MODULE = exports.DOWNLOADING_MODULE_PROGRESS = exports.DOWNLOADING_MODULES_FINISHED = exports.DOWNLOADING_MODULE = exports.DOWNLOADED_MODULE = exports.CHECKING_FOR_UPDATES = void 0; | ||||
| exports.checkForUpdates = checkForUpdates; | ||||
| exports.events = void 0; | ||||
| exports.getInstalled = getInstalled; | ||||
| exports.init = init; | ||||
| exports.initPathsOnly = initPathsOnly; | ||||
| exports.install = install; | ||||
| exports.installPendingUpdates = installPendingUpdates; | ||||
| exports.isInstalled = isInstalled; | ||||
| exports.quitAndInstallUpdates = quitAndInstallUpdates; | ||||
| exports.setInBackground = setInBackground; | ||||
| exports.supportsEventObjects = void 0; | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var _nodeGlobalPaths = require("./nodeGlobalPaths"); | ||||
| var _events = require("events"); | ||||
| var _mkdirp = _interopRequireDefault(require("mkdirp")); | ||||
| var _process = require("process"); | ||||
| var _yauzl = _interopRequireDefault(require("yauzl")); | ||||
| var _Backoff = _interopRequireDefault(require("./Backoff")); | ||||
| var paths = _interopRequireWildcard(require("./paths")); | ||||
| var _processUtils = require("./processUtils"); | ||||
| 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 }; } | ||||
| // Manages additional module installation and management.
 | ||||
| // We add the module folder path to require() lookup paths here.
 | ||||
| 
 | ||||
| // undocumented node API
 | ||||
| 
 | ||||
| const originalFs = require('original-fs'); | ||||
| 
 | ||||
| // events
 | ||||
| const CHECKING_FOR_UPDATES = 'checking-for-updates'; | ||||
| exports.CHECKING_FOR_UPDATES = CHECKING_FOR_UPDATES; | ||||
| const INSTALLED_MODULE = 'installed-module'; | ||||
| exports.INSTALLED_MODULE = INSTALLED_MODULE; | ||||
| const UPDATE_CHECK_FINISHED = 'update-check-finished'; | ||||
| exports.UPDATE_CHECK_FINISHED = UPDATE_CHECK_FINISHED; | ||||
| const DOWNLOADING_MODULE = 'downloading-module'; | ||||
| exports.DOWNLOADING_MODULE = DOWNLOADING_MODULE; | ||||
| const DOWNLOADING_MODULE_PROGRESS = 'downloading-module-progress'; | ||||
| exports.DOWNLOADING_MODULE_PROGRESS = DOWNLOADING_MODULE_PROGRESS; | ||||
| const DOWNLOADING_MODULES_FINISHED = 'downloading-modules-finished'; | ||||
| exports.DOWNLOADING_MODULES_FINISHED = DOWNLOADING_MODULES_FINISHED; | ||||
| const UPDATE_MANUALLY = 'update-manually'; | ||||
| exports.UPDATE_MANUALLY = UPDATE_MANUALLY; | ||||
| const DOWNLOADED_MODULE = 'downloaded-module'; | ||||
| exports.DOWNLOADED_MODULE = DOWNLOADED_MODULE; | ||||
| const INSTALLING_MODULES_FINISHED = 'installing-modules-finished'; | ||||
| exports.INSTALLING_MODULES_FINISHED = INSTALLING_MODULES_FINISHED; | ||||
| const INSTALLING_MODULE = 'installing-module'; | ||||
| exports.INSTALLING_MODULE = INSTALLING_MODULE; | ||||
| const INSTALLING_MODULE_PROGRESS = 'installing-module-progress'; | ||||
| exports.INSTALLING_MODULE_PROGRESS = INSTALLING_MODULE_PROGRESS; | ||||
| const NO_PENDING_UPDATES = 'no-pending-updates'; | ||||
| 
 | ||||
| // settings
 | ||||
| exports.NO_PENDING_UPDATES = NO_PENDING_UPDATES; | ||||
| const ALWAYS_ALLOW_UPDATES = 'ALWAYS_ALLOW_UPDATES'; | ||||
| const SKIP_HOST_UPDATE = 'SKIP_HOST_UPDATE'; | ||||
| const SKIP_MODULE_UPDATE = 'SKIP_MODULE_UPDATE'; | ||||
| const ALWAYS_BOOTSTRAP_MODULES = 'ALWAYS_BOOTSTRAP_MODULES'; | ||||
| const USE_LOCAL_MODULE_VERSIONS = 'USE_LOCAL_MODULE_VERSIONS'; | ||||
| class Events extends _events.EventEmitter { | ||||
|   constructor() { | ||||
|     super(); | ||||
|     this.history = []; | ||||
|   } | ||||
|   append(evt) { | ||||
|     evt.now = String(_process.hrtime.bigint()); | ||||
|     if (this._eventIsInteresting(evt)) { | ||||
|       this.history.push(evt); | ||||
|     } | ||||
|     process.nextTick(() => this.emit(evt.type, evt)); | ||||
|   } | ||||
|   _eventIsInteresting(evt) { | ||||
|     return evt.type !== DOWNLOADING_MODULE_PROGRESS && evt.type !== INSTALLING_MODULE_PROGRESS; | ||||
|   } | ||||
| } | ||||
| class LogStream { | ||||
|   constructor(logPath) { | ||||
|     try { | ||||
|       this.logStream = _fs.default.createWriteStream(logPath, { | ||||
|         flags: 'a' | ||||
|       }); | ||||
|     } catch (e) { | ||||
|       console.error(`Failed to create ${logPath}: ${String(e)}`); | ||||
|     } | ||||
|   } | ||||
|   log(message) { | ||||
|     message = `${new Date().toISOString()} [Modules] ${message}`; | ||||
|     console.log(message); | ||||
|     if (this.logStream) { | ||||
|       this.logStream.write(message); | ||||
|       this.logStream.write('\r\n'); | ||||
|     } | ||||
|   } | ||||
|   end() { | ||||
|     if (this.logStream) { | ||||
|       this.logStream.end(); | ||||
|       this.logStream = null; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| const request = require('../app_bootstrap/request'); | ||||
| const { | ||||
|   app | ||||
| } = require('electron'); | ||||
| const REQUEST_TIMEOUT = 15000; | ||||
| const backoff = new _Backoff.default(1000, 20000); | ||||
| const events = new Events(); | ||||
| exports.events = events; | ||||
| const supportsEventObjects = true; | ||||
| exports.supportsEventObjects = supportsEventObjects; | ||||
| let logger; | ||||
| let locallyInstalledModules; | ||||
| let moduleInstallPath; | ||||
| let installedModulesFilePath; | ||||
| let moduleDownloadPath; | ||||
| let bootstrapping; | ||||
| let hostUpdater; | ||||
| let hostUpdateAvailable; | ||||
| let skipHostUpdate; | ||||
| let skipModuleUpdate; | ||||
| let checkingForUpdates; | ||||
| let remoteBaseURL; | ||||
| let remoteQuery; | ||||
| let settings; | ||||
| let remoteModuleVersions; | ||||
| let installedModules; | ||||
| let download; | ||||
| let unzip; | ||||
| let newInstallInProgress; | ||||
| let localModuleVersionsFilePath; | ||||
| let updatable; | ||||
| let bootstrapManifestFilePath; | ||||
| let runningInBackground = false; | ||||
| function initPathsOnly(_buildInfo) { | ||||
|   if (locallyInstalledModules || moduleInstallPath) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   // If we have `localModulesRoot` in our buildInfo file, we do not fetch modules
 | ||||
|   // from remote, and rely on our locally bundled ones.
 | ||||
|   // Typically used for development mode, or private builds.
 | ||||
|   locallyInstalledModules = _buildInfo.localModulesRoot != null; | ||||
|   if (locallyInstalledModules) { | ||||
|     (0, _nodeGlobalPaths.addGlobalPath)(_buildInfo.localModulesRoot); | ||||
|   } else { | ||||
|     moduleInstallPath = _path.default.join(paths.getUserDataVersioned(), 'modules'); | ||||
|     (0, _nodeGlobalPaths.addGlobalPath)(moduleInstallPath); | ||||
|   } | ||||
| } | ||||
| function init(_endpoint, _settings, _buildInfo) { | ||||
|   const endpoint = _endpoint; | ||||
|   settings = _settings; | ||||
|   const buildInfo = _buildInfo; | ||||
|   updatable = buildInfo.version != '0.0.0' && !buildInfo.debug || settings.get(ALWAYS_ALLOW_UPDATES); | ||||
|   initPathsOnly(buildInfo); | ||||
|   logger = new LogStream(_path.default.join(paths.getUserData(), 'modules.log')); | ||||
|   bootstrapping = false; | ||||
|   hostUpdateAvailable = false; | ||||
|   checkingForUpdates = false; | ||||
|   skipHostUpdate = settings.get(SKIP_HOST_UPDATE) || !updatable; | ||||
|   skipModuleUpdate = settings.get(SKIP_MODULE_UPDATE) || locallyInstalledModules || !updatable; | ||||
|   localModuleVersionsFilePath = _path.default.join(paths.getUserData(), 'local_module_versions.json'); | ||||
|   bootstrapManifestFilePath = _path.default.join(paths.getResources(), 'bootstrap', 'manifest.json'); | ||||
|   installedModules = {}; | ||||
|   remoteModuleVersions = {}; | ||||
|   newInstallInProgress = {}; | ||||
|   download = { | ||||
|     // currently downloading
 | ||||
|     active: false, | ||||
|     // {name, version}
 | ||||
|     queue: [], | ||||
|     // current queue index being downloaded
 | ||||
|     next: 0, | ||||
|     // download failure count
 | ||||
|     failures: 0 | ||||
|   }; | ||||
|   unzip = { | ||||
|     // currently unzipping
 | ||||
|     active: false, | ||||
|     // {name, version, zipfile}
 | ||||
|     queue: [], | ||||
|     // current queue index being unzipped
 | ||||
|     next: 0, | ||||
|     // unzip failure count
 | ||||
|     failures: 0 | ||||
|   }; | ||||
|   logger.log(`Modules initializing`); | ||||
|   logger.log(`Distribution: ${locallyInstalledModules ? 'local' : 'remote'}`); | ||||
|   logger.log(`Host updates: ${skipHostUpdate ? 'disabled' : 'enabled'}`); | ||||
|   logger.log(`Module updates: ${skipModuleUpdate ? 'disabled' : 'enabled'}`); | ||||
|   if (!locallyInstalledModules) { | ||||
|     installedModulesFilePath = _path.default.join(moduleInstallPath, 'installed.json'); | ||||
|     moduleDownloadPath = _path.default.join(moduleInstallPath, 'pending'); | ||||
|     _mkdirp.default.sync(moduleDownloadPath); | ||||
|     logger.log(`Module install path: ${moduleInstallPath}`); | ||||
|     logger.log(`Module installed file path: ${installedModulesFilePath}`); | ||||
|     logger.log(`Module download path: ${moduleDownloadPath}`); | ||||
|     let failedLoadingInstalledModules = false; | ||||
|     try { | ||||
|       installedModules = JSON.parse(_fs.default.readFileSync(installedModulesFilePath)); | ||||
|     } catch (err) { | ||||
|       failedLoadingInstalledModules = true; | ||||
|     } | ||||
|     cleanDownloadedModules(installedModules); | ||||
|     bootstrapping = failedLoadingInstalledModules || settings.get(ALWAYS_BOOTSTRAP_MODULES); | ||||
|   } | ||||
|   hostUpdater = require('../app_bootstrap/hostUpdater'); | ||||
|   // TODO: hostUpdater constants
 | ||||
|   hostUpdater.on('checking-for-update', () => events.append({ | ||||
|     type: CHECKING_FOR_UPDATES | ||||
|   })); | ||||
|   hostUpdater.on('update-available', () => hostOnUpdateAvailable()); | ||||
|   hostUpdater.on('update-progress', progress => hostOnUpdateProgress(progress)); | ||||
|   hostUpdater.on('update-not-available', () => hostOnUpdateNotAvailable()); | ||||
|   hostUpdater.on('update-manually', newVersion => hostOnUpdateManually(newVersion)); | ||||
|   hostUpdater.on('update-downloaded', () => hostOnUpdateDownloaded()); | ||||
|   hostUpdater.on('error', err => hostOnError(err)); | ||||
|   const setFeedURL = hostUpdater.setFeedURL.bind(hostUpdater); | ||||
|   remoteBaseURL = `${endpoint}/modules/${buildInfo.releaseChannel}`; | ||||
|   // eslint-disable-next-line camelcase
 | ||||
|   remoteQuery = { | ||||
|     host_version: buildInfo.version | ||||
|   }; | ||||
| 
 | ||||
|   // For OSX platform try to move installer to Application folder, if currently running
 | ||||
|   // from read-only volume to avoid installation problems.
 | ||||
|   if (_processUtils.IS_OSX) { | ||||
|     const appFolder = _path.default.resolve(process.execPath); | ||||
|     _fs.default.access(appFolder, _fs.default.constants.W_OK, err => { | ||||
|       if (err) { | ||||
|         logger.log(`Installer is in read-only volume in OSX, moving to Application folder ${err}`); | ||||
|         try { | ||||
|           // On a successful move the app will quit and relaunch.
 | ||||
|           app.moveToApplicationsFolder(); | ||||
|         } catch (err) { | ||||
|           logger.log(`Could not move installer file to Application folder: ${err}`); | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|   switch (process.platform) { | ||||
|     case 'darwin': | ||||
|       setFeedURL(`${endpoint}/updates/${buildInfo.releaseChannel}?platform=osx&version=${buildInfo.version}`); | ||||
|       remoteQuery.platform = 'osx'; | ||||
|       break; | ||||
|     case 'win32': | ||||
|       // Squirrel for Windows can't handle query params
 | ||||
|       // https://github.com/Squirrel/Squirrel.Windows/issues/132
 | ||||
|       setFeedURL(`${endpoint}/updates/${buildInfo.releaseChannel}`); | ||||
|       remoteQuery.platform = 'win'; | ||||
|       break; | ||||
|     case 'linux': | ||||
|       setFeedURL(`${endpoint}/updates/${buildInfo.releaseChannel}?platform=linux&version=${buildInfo.version}`); | ||||
|       remoteQuery.platform = 'linux'; | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| function cleanDownloadedModules(installedModules) { | ||||
|   try { | ||||
|     const entries = _fs.default.readdirSync(moduleDownloadPath) || []; | ||||
|     entries.forEach(entry => { | ||||
|       const entryPath = _path.default.join(moduleDownloadPath, entry); | ||||
|       let isStale = true; | ||||
|       for (const moduleName of Object.keys(installedModules)) { | ||||
|         if (entryPath === installedModules[moduleName].updateZipfile) { | ||||
|           isStale = false; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       if (isStale) { | ||||
|         _fs.default.unlinkSync(_path.default.join(moduleDownloadPath, entry)); | ||||
|       } | ||||
|     }); | ||||
|   } catch (err) { | ||||
|     logger.log('Could not clean downloaded modules'); | ||||
|     logger.log(err.stack); | ||||
|   } | ||||
| } | ||||
| function hostOnUpdateAvailable() { | ||||
|   logger.log(`Host update is available.`); | ||||
|   hostUpdateAvailable = true; | ||||
|   events.append({ | ||||
|     type: UPDATE_CHECK_FINISHED, | ||||
|     succeeded: true, | ||||
|     updateCount: 1, | ||||
|     manualRequired: false | ||||
|   }); | ||||
|   events.append({ | ||||
|     type: DOWNLOADING_MODULE, | ||||
|     name: 'host', | ||||
|     current: 1, | ||||
|     total: 1, | ||||
|     foreground: !runningInBackground | ||||
|   }); | ||||
| } | ||||
| function hostOnUpdateProgress(progress) { | ||||
|   logger.log(`Host update progress: ${progress}%`); | ||||
|   events.append({ | ||||
|     type: DOWNLOADING_MODULE_PROGRESS, | ||||
|     name: 'host', | ||||
|     progress: progress | ||||
|   }); | ||||
| } | ||||
| function hostOnUpdateNotAvailable() { | ||||
|   logger.log(`Host is up to date.`); | ||||
|   if (!skipModuleUpdate) { | ||||
|     checkForModuleUpdates(); | ||||
|   } else { | ||||
|     events.append({ | ||||
|       type: UPDATE_CHECK_FINISHED, | ||||
|       succeeded: true, | ||||
|       updateCount: 0, | ||||
|       manualRequired: false | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| function hostOnUpdateManually(newVersion) { | ||||
|   logger.log(`Host update is available. Manual update required!`); | ||||
|   hostUpdateAvailable = true; | ||||
|   checkingForUpdates = false; | ||||
|   events.append({ | ||||
|     type: UPDATE_MANUALLY, | ||||
|     newVersion: newVersion | ||||
|   }); | ||||
|   events.append({ | ||||
|     type: UPDATE_CHECK_FINISHED, | ||||
|     succeeded: true, | ||||
|     updateCount: 1, | ||||
|     manualRequired: true | ||||
|   }); | ||||
| } | ||||
| function hostOnUpdateDownloaded() { | ||||
|   logger.log(`Host update downloaded.`); | ||||
|   checkingForUpdates = false; | ||||
|   events.append({ | ||||
|     type: DOWNLOADED_MODULE, | ||||
|     name: 'host', | ||||
|     current: 1, | ||||
|     total: 1, | ||||
|     succeeded: true | ||||
|   }); | ||||
|   events.append({ | ||||
|     type: DOWNLOADING_MODULES_FINISHED, | ||||
|     succeeded: 1, | ||||
|     failed: 0 | ||||
|   }); | ||||
| } | ||||
| function hostOnError(err) { | ||||
|   logger.log(`Host update failed: ${err}`); | ||||
| 
 | ||||
|   // [adill] osx unsigned builds will fire this code signing error inside setFeedURL and
 | ||||
|   // if we don't do anything about it hostUpdater.checkForUpdates() will never respond.
 | ||||
|   if (err && String(err).indexOf('Could not get code signature for running application') !== -1) { | ||||
|     console.warn('Skipping host updates due to code signing failure.'); | ||||
|     skipHostUpdate = true; | ||||
|   } | ||||
|   checkingForUpdates = false; | ||||
|   if (!hostUpdateAvailable) { | ||||
|     events.append({ | ||||
|       type: UPDATE_CHECK_FINISHED, | ||||
|       succeeded: false, | ||||
|       updateCount: 0, | ||||
|       manualRequired: false | ||||
|     }); | ||||
|   } else { | ||||
|     events.append({ | ||||
|       type: DOWNLOADED_MODULE, | ||||
|       name: 'host', | ||||
|       current: 1, | ||||
|       total: 1, | ||||
|       succeeded: false | ||||
|     }); | ||||
|     events.append({ | ||||
|       type: DOWNLOADING_MODULES_FINISHED, | ||||
|       succeeded: 0, | ||||
|       failed: 1 | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| function checkForUpdates() { | ||||
|   if (checkingForUpdates) return; | ||||
|   checkingForUpdates = true; | ||||
|   hostUpdateAvailable = false; | ||||
|   if (skipHostUpdate) { | ||||
|     events.append({ | ||||
|       type: CHECKING_FOR_UPDATES | ||||
|     }); | ||||
|     hostOnUpdateNotAvailable(); | ||||
|   } else { | ||||
|     logger.log('Checking for host updates.'); | ||||
|     hostUpdater.checkForUpdates(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Indicates that the initial update process is complete and that future updates
 | ||||
| // are background updates. This merely affects the content of the events sent to
 | ||||
| // the app so that analytics can correctly attribute module download/installs
 | ||||
| // depending on whether they were ui-blocking or not.
 | ||||
| function setInBackground() { | ||||
|   runningInBackground = true; | ||||
| } | ||||
| function getRemoteModuleName(name) { | ||||
|   if (_processUtils.IS_WIN && process.arch === 'x64') { | ||||
|     return `${name}.x64`; | ||||
|   } | ||||
|   return name; | ||||
| } | ||||
| async function checkForModuleUpdates() { | ||||
|   const query = { | ||||
|     ...remoteQuery, | ||||
|     _: Math.floor(Date.now() / 1000 / 60 / 5) | ||||
|   }; | ||||
|   const url = `${remoteBaseURL}/versions.json`; | ||||
|   logger.log(`Checking for module updates at ${url}`); | ||||
|   let response; | ||||
|   try { | ||||
|     response = await request.get({ | ||||
|       url, | ||||
|       qs: query, | ||||
|       timeout: REQUEST_TIMEOUT | ||||
|     }); | ||||
|     checkingForUpdates = false; | ||||
|   } catch (err) { | ||||
|     checkingForUpdates = false; | ||||
|     logger.log(`Failed fetching module versions: ${String(err)}`); | ||||
|     events.append({ | ||||
|       type: UPDATE_CHECK_FINISHED, | ||||
|       succeeded: false, | ||||
|       updateCount: 0, | ||||
|       manualRequired: false | ||||
|     }); | ||||
|     return; | ||||
|   } | ||||
|   remoteModuleVersions = JSON.parse(response.body); | ||||
|   if (settings.get(USE_LOCAL_MODULE_VERSIONS)) { | ||||
|     try { | ||||
|       remoteModuleVersions = JSON.parse(_fs.default.readFileSync(localModuleVersionsFilePath)); | ||||
|       console.log('Using local module versions: ', remoteModuleVersions); | ||||
|     } catch (err) { | ||||
|       console.warn('Failed to parse local module versions: ', err); | ||||
|     } | ||||
|   } | ||||
|   const updatesToDownload = []; | ||||
|   for (const moduleName of Object.keys(installedModules)) { | ||||
|     const installedModule = installedModules[moduleName]; | ||||
|     const installed = installedModule.installedVersion; | ||||
|     if (installed === null) { | ||||
|       continue; | ||||
|     } | ||||
|     const update = installedModule.updateVersion || 0; | ||||
|     const remote = remoteModuleVersions[getRemoteModuleName(moduleName)] || 0; | ||||
|     if (installed !== remote && update !== remote) { | ||||
|       logger.log(`Module update available: ${moduleName}@${remote} [installed: ${installed}]`); | ||||
|       updatesToDownload.push({ | ||||
|         name: moduleName, | ||||
|         version: remote | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   events.append({ | ||||
|     type: UPDATE_CHECK_FINISHED, | ||||
|     succeeded: true, | ||||
|     updateCount: updatesToDownload.length, | ||||
|     manualRequired: false | ||||
|   }); | ||||
|   if (updatesToDownload.length === 0) { | ||||
|     logger.log(`No module updates available.`); | ||||
|   } else { | ||||
|     updatesToDownload.forEach(e => addModuleToDownloadQueue(e.name, e.version)); | ||||
|   } | ||||
| } | ||||
| function addModuleToDownloadQueue(name, version, authToken) { | ||||
|   download.queue.push({ | ||||
|     name, | ||||
|     version, | ||||
|     authToken | ||||
|   }); | ||||
|   process.nextTick(() => processDownloadQueue()); | ||||
| } | ||||
| async function processDownloadQueue() { | ||||
|   if (download.active) return; | ||||
|   if (download.queue.length === 0) return; | ||||
|   download.active = true; | ||||
|   const queuedModule = download.queue[download.next]; | ||||
|   download.next += 1; | ||||
|   events.append({ | ||||
|     type: DOWNLOADING_MODULE, | ||||
|     name: queuedModule.name, | ||||
|     current: download.next, | ||||
|     total: download.queue.length, | ||||
|     foreground: !runningInBackground | ||||
|   }); | ||||
|   let progress = 0; | ||||
|   let receivedBytes = 0; | ||||
|   const url = `${remoteBaseURL}/${encodeURIComponent(getRemoteModuleName(queuedModule.name))}/${encodeURIComponent(queuedModule.version)}`; | ||||
|   logger.log(`Fetching ${queuedModule.name}@${queuedModule.version} from ${url}`); | ||||
|   const headers = {}; | ||||
|   if (queuedModule.authToken) { | ||||
|     headers['Authorization'] = queuedModule.authToken; | ||||
|   } | ||||
|   const moduleZipPath = _path.default.join(moduleDownloadPath, `${queuedModule.name}-${queuedModule.version}.zip`); | ||||
|   const stream = _fs.default.createWriteStream(moduleZipPath); | ||||
|   stream.on('progress', ({ | ||||
|     receivedBytes: newReceivedBytes, | ||||
|     totalBytes | ||||
|   }) => { | ||||
|     receivedBytes = newReceivedBytes; | ||||
|     const newProgress = Math.min(Math.floor(100 * (receivedBytes / totalBytes)), 100); | ||||
|     if (progress !== newProgress) { | ||||
|       progress = newProgress; | ||||
|       logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} to ${moduleZipPath}: ${progress}%`); | ||||
|       events.append({ | ||||
|         type: DOWNLOADING_MODULE_PROGRESS, | ||||
|         name: queuedModule.name, | ||||
|         progress: progress | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
|   logger.log(`Streaming ${queuedModule.name}@${queuedModule.version} to ${moduleZipPath}`); | ||||
|   try { | ||||
|     const response = await request.get({ | ||||
|       url, | ||||
|       qs: remoteQuery, | ||||
|       headers, | ||||
|       timeout: REQUEST_TIMEOUT, | ||||
|       stream | ||||
|     }); | ||||
|     finishModuleDownload(queuedModule.name, queuedModule.version, moduleZipPath, receivedBytes, response.statusCode === 200); | ||||
|   } catch (err) { | ||||
|     logger.log(`Failed fetching module ${queuedModule.name}@${queuedModule.version}: ${String(err)}`); | ||||
|     finishModuleDownload(queuedModule.name, queuedModule.version, null, receivedBytes, false); | ||||
|   } | ||||
| } | ||||
| function commitInstalledModules() { | ||||
|   const data = JSON.stringify(installedModules, null, 2); | ||||
|   _fs.default.writeFileSync(installedModulesFilePath, data); | ||||
| } | ||||
| function finishModuleDownload(name, version, zipfile, receivedBytes, succeeded) { | ||||
|   if (!installedModules[name]) { | ||||
|     installedModules[name] = {}; | ||||
|   } | ||||
|   if (succeeded) { | ||||
|     installedModules[name].updateVersion = version; | ||||
|     installedModules[name].updateZipfile = zipfile; | ||||
|     commitInstalledModules(); | ||||
|   } else { | ||||
|     download.failures += 1; | ||||
|   } | ||||
|   events.append({ | ||||
|     type: DOWNLOADED_MODULE, | ||||
|     name: name, | ||||
|     current: download.next, | ||||
|     total: download.queue.length, | ||||
|     succeeded: succeeded, | ||||
|     receivedBytes: receivedBytes | ||||
|   }); | ||||
|   if (download.next >= download.queue.length) { | ||||
|     const successes = download.queue.length - download.failures; | ||||
|     logger.log(`Finished module downloads. [success: ${successes}] [failure: ${download.failures}]`); | ||||
|     events.append({ | ||||
|       type: DOWNLOADING_MODULES_FINISHED, | ||||
|       succeeded: successes, | ||||
|       failed: download.failures | ||||
|     }); | ||||
|     download.queue = []; | ||||
|     download.next = 0; | ||||
|     download.failures = 0; | ||||
|     download.active = false; | ||||
|   } else { | ||||
|     const continueDownloads = () => { | ||||
|       download.active = false; | ||||
|       processDownloadQueue(); | ||||
|     }; | ||||
|     if (succeeded) { | ||||
|       backoff.succeed(); | ||||
|       process.nextTick(continueDownloads); | ||||
|     } else { | ||||
|       logger.log(`Waiting ${Math.floor(backoff.current)}ms before next download.`); | ||||
|       backoff.fail(continueDownloads); | ||||
|     } | ||||
|   } | ||||
|   if (newInstallInProgress[name]) { | ||||
|     addModuleToUnzipQueue(name, version, zipfile); | ||||
|   } | ||||
| } | ||||
| function addModuleToUnzipQueue(name, version, zipfile) { | ||||
|   unzip.queue.push({ | ||||
|     name, | ||||
|     version, | ||||
|     zipfile | ||||
|   }); | ||||
|   process.nextTick(() => processUnzipQueue()); | ||||
| } | ||||
| function processUnzipQueue() { | ||||
|   if (unzip.active) return; | ||||
|   if (unzip.queue.length === 0) return; | ||||
|   unzip.active = true; | ||||
|   const queuedModule = unzip.queue[unzip.next]; | ||||
|   const installedModule = installedModules[queuedModule.name]; | ||||
|   const installedVersion = installedModule != null ? installedModule.installedVersion : null; | ||||
|   unzip.next += 1; | ||||
|   events.append({ | ||||
|     type: INSTALLING_MODULE, | ||||
|     name: queuedModule.name, | ||||
|     current: unzip.next, | ||||
|     total: unzip.queue.length, | ||||
|     foreground: !runningInBackground, | ||||
|     oldVersion: installedVersion, | ||||
|     newVersion: queuedModule.version | ||||
|   }); | ||||
|   let hasErrored = false; | ||||
|   const onError = (error, zipfile) => { | ||||
|     if (hasErrored) return; | ||||
|     hasErrored = true; | ||||
|     logger.log(`Failed installing ${queuedModule.name}@${queuedModule.version}: ${String(error)}`); | ||||
|     succeeded = false; | ||||
|     if (zipfile) { | ||||
|       zipfile.close(); | ||||
|     } | ||||
|     finishModuleUnzip(queuedModule, succeeded); | ||||
|   }; | ||||
|   let succeeded = true; | ||||
|   const extractRoot = _path.default.join(moduleInstallPath, queuedModule.name); | ||||
|   logger.log(`Installing ${queuedModule.name}@${queuedModule.version} from ${queuedModule.zipfile}`); | ||||
|   const processZipfile = (err, zipfile) => { | ||||
|     if (err) { | ||||
|       onError(err, null); | ||||
|       return; | ||||
|     } | ||||
|     const totalEntries = zipfile.entryCount; | ||||
|     let processedEntries = 0; | ||||
|     zipfile.on('entry', entry => { | ||||
|       processedEntries += 1; | ||||
|       const percent = Math.min(Math.floor(processedEntries / totalEntries * 100), 100); | ||||
|       events.append({ | ||||
|         type: INSTALLING_MODULE_PROGRESS, | ||||
|         name: queuedModule.name, | ||||
|         progress: percent | ||||
|       }); | ||||
| 
 | ||||
|       // skip directories
 | ||||
|       if (/\/$/.test(entry.fileName)) { | ||||
|         zipfile.readEntry(); | ||||
|         return; | ||||
|       } | ||||
|       zipfile.openReadStream(entry, (err, stream) => { | ||||
|         if (err) { | ||||
|           onError(err, zipfile); | ||||
|           return; | ||||
|         } | ||||
|         stream.on('error', e => onError(e, zipfile)); | ||||
|         (0, _mkdirp.default)(_path.default.join(extractRoot, _path.default.dirname(entry.fileName)), err => { | ||||
|           if (err) { | ||||
|             onError(err, zipfile); | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           // [adill] createWriteStream via original-fs is broken in Electron 4.0.0-beta.6 with .asar files
 | ||||
|           // so we unzip to a temporary filename and rename it afterwards
 | ||||
|           const tempFileName = _path.default.join(extractRoot, entry.fileName + '.tmp'); | ||||
|           const finalFileName = _path.default.join(extractRoot, entry.fileName); | ||||
|           const writeStream = originalFs.createWriteStream(tempFileName); | ||||
|           writeStream.on('error', e => { | ||||
|             stream.destroy(); | ||||
|             try { | ||||
|               originalFs.unlinkSync(tempFileName); | ||||
|             } catch (err) {} | ||||
|             onError(e, zipfile); | ||||
|           }); | ||||
|           writeStream.on('finish', () => { | ||||
|             try { | ||||
|               originalFs.unlinkSync(finalFileName); | ||||
|             } catch (err) {} | ||||
|             try { | ||||
|               originalFs.renameSync(tempFileName, finalFileName); | ||||
|             } catch (err) { | ||||
|               onError(err, zipfile); | ||||
|               return; | ||||
|             } | ||||
|             zipfile.readEntry(); | ||||
|           }); | ||||
|           stream.pipe(writeStream); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|     zipfile.on('error', err => { | ||||
|       onError(err, zipfile); | ||||
|     }); | ||||
|     zipfile.on('end', () => { | ||||
|       if (!succeeded) return; | ||||
|       installedModules[queuedModule.name].installedVersion = queuedModule.version; | ||||
|       finishModuleUnzip(queuedModule, succeeded); | ||||
|     }); | ||||
|     zipfile.readEntry(); | ||||
|   }; | ||||
|   try { | ||||
|     _yauzl.default.open(queuedModule.zipfile, { | ||||
|       lazyEntries: true, | ||||
|       autoClose: true | ||||
|     }, processZipfile); | ||||
|   } catch (err) { | ||||
|     onError(err, null); | ||||
|   } | ||||
| } | ||||
| function finishModuleUnzip(unzippedModule, succeeded) { | ||||
|   delete newInstallInProgress[unzippedModule.name]; | ||||
|   delete installedModules[unzippedModule.name].updateZipfile; | ||||
|   delete installedModules[unzippedModule.name].updateVersion; | ||||
|   commitInstalledModules(); | ||||
|   if (!succeeded) { | ||||
|     unzip.failures += 1; | ||||
|   } | ||||
|   events.append({ | ||||
|     type: INSTALLED_MODULE, | ||||
|     name: unzippedModule.name, | ||||
|     current: unzip.next, | ||||
|     total: unzip.queue.length, | ||||
|     succeeded: succeeded | ||||
|   }); | ||||
|   if (unzip.next >= unzip.queue.length) { | ||||
|     const successes = unzip.queue.length - unzip.failures; | ||||
|     bootstrapping = false; | ||||
|     logger.log(`Finished module installations. [success: ${successes}] [failure: ${unzip.failures}]`); | ||||
|     unzip.queue = []; | ||||
|     unzip.next = 0; | ||||
|     unzip.failures = 0; | ||||
|     unzip.active = false; | ||||
|     events.append({ | ||||
|       type: INSTALLING_MODULES_FINISHED, | ||||
|       succeeded: successes, | ||||
|       failed: unzip.failures | ||||
|     }); | ||||
|     return; | ||||
|   } | ||||
|   process.nextTick(() => { | ||||
|     unzip.active = false; | ||||
|     processUnzipQueue(); | ||||
|   }); | ||||
| } | ||||
| function quitAndInstallUpdates() { | ||||
|   logger.log(`Relaunching to install ${hostUpdateAvailable ? 'host' : 'module'} updates...`); | ||||
|   if (hostUpdateAvailable) { | ||||
|     hostUpdater.quitAndInstall(); | ||||
|   } else { | ||||
|     relaunch(); | ||||
|   } | ||||
| } | ||||
| function relaunch() { | ||||
|   logger.end(); | ||||
|   const { | ||||
|     app | ||||
|   } = require('electron'); | ||||
|   app.relaunch(); | ||||
|   app.quit(); | ||||
| } | ||||
| function isInstalled(name, version) { | ||||
|   const metadata = installedModules[name]; | ||||
|   if (locallyInstalledModules) return true; | ||||
|   if (metadata && metadata.installedVersion > 0) { | ||||
|     if (!version) return true; | ||||
|     if (metadata.installedVersion === version) return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| function getInstalled() { | ||||
|   return { | ||||
|     ...installedModules | ||||
|   }; | ||||
| } | ||||
| function install(name, defer, options) { | ||||
|   let { | ||||
|     version, | ||||
|     authToken | ||||
|   } = options || {}; | ||||
|   if (isInstalled(name, version)) { | ||||
|     if (!defer) { | ||||
|       events.append({ | ||||
|         type: INSTALLED_MODULE, | ||||
|         name: name, | ||||
|         current: 1, | ||||
|         total: 1, | ||||
|         succeeded: true | ||||
|       }); | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
|   if (newInstallInProgress[name]) return; | ||||
|   if (!updatable) { | ||||
|     logger.log(`Not updatable; ignoring request to install ${name}...`); | ||||
|     return; | ||||
|   } | ||||
|   if (defer) { | ||||
|     if (version) { | ||||
|       throw new Error(`Cannot defer install for a specific version module (${name}, ${version})`); | ||||
|     } | ||||
|     logger.log(`Deferred install for ${name}...`); | ||||
|     installedModules[name] = { | ||||
|       installedVersion: 0 | ||||
|     }; | ||||
|     commitInstalledModules(); | ||||
|   } else { | ||||
|     logger.log(`Starting to install ${name}...`); | ||||
|     if (!version) { | ||||
|       version = remoteModuleVersions[name] || 0; | ||||
|     } | ||||
|     newInstallInProgress[name] = version; | ||||
|     addModuleToDownloadQueue(name, version, authToken); | ||||
|   } | ||||
| } | ||||
| function installPendingUpdates() { | ||||
|   const updatesToInstall = []; | ||||
|   if (bootstrapping) { | ||||
|     let modules = {}; | ||||
|     try { | ||||
|       modules = JSON.parse(_fs.default.readFileSync(bootstrapManifestFilePath)); | ||||
|     } catch (err) {} | ||||
|     for (const moduleName of Object.keys(modules)) { | ||||
|       installedModules[moduleName] = { | ||||
|         installedVersion: 0 | ||||
|       }; | ||||
|       const zipfile = _path.default.join(paths.getResources(), 'bootstrap', `${moduleName}.zip`); | ||||
|       updatesToInstall.push({ | ||||
|         moduleName, | ||||
|         update: modules[moduleName], | ||||
|         zipfile | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   for (const moduleName of Object.keys(installedModules)) { | ||||
|     const update = installedModules[moduleName].updateVersion || 0; | ||||
|     const zipfile = installedModules[moduleName].updateZipfile; | ||||
|     if (update > 0 && zipfile != null) { | ||||
|       updatesToInstall.push({ | ||||
|         moduleName, | ||||
|         update, | ||||
|         zipfile | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   if (updatesToInstall.length > 0) { | ||||
|     logger.log(`${bootstrapping ? 'Bootstrapping' : 'Installing updates'}...`); | ||||
|     updatesToInstall.forEach(e => addModuleToUnzipQueue(e.moduleName, e.update, e.zipfile)); | ||||
|   } else { | ||||
|     logger.log('No updates to install'); | ||||
|     events.append({ | ||||
|       type: NO_PENDING_UPDATES | ||||
|     }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										31
									
								
								.resources/app/common/nodeGlobalPaths.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.resources/app/common/nodeGlobalPaths.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.addGlobalPath = addGlobalPath; | ||||
| exports.getGlobalPaths = getGlobalPaths; | ||||
| exports.globalPathExists = globalPathExists; | ||||
| var _module = _interopRequireDefault(require("module")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| const resolveLookupPaths = _module.default._resolveLookupPaths; | ||||
| _module.default._resolveLookupPaths = (request, parent) => { | ||||
|   var _parent$paths; | ||||
|   if (parent === null || parent === void 0 ? void 0 : (_parent$paths = parent.paths) === null || _parent$paths === void 0 ? void 0 : _parent$paths.length) { | ||||
|     parent.paths = parent.paths.concat(_module.default.globalPaths); | ||||
|   } else { | ||||
|     parent.paths = _module.default.globalPaths; | ||||
|   } | ||||
|   return resolveLookupPaths(request, parent); | ||||
| }; | ||||
| function getGlobalPaths() { | ||||
|   return _module.default.globalPaths; | ||||
| } | ||||
| function addGlobalPath(path) { | ||||
|   if (_module.default.globalPaths.indexOf(path) === -1) { | ||||
|     _module.default.globalPaths.push(path); | ||||
|   } | ||||
| } | ||||
| function globalPathExists(path) { | ||||
|   return _module.default.globalPaths.indexOf(path) !== -1; | ||||
| } | ||||
							
								
								
									
										101
									
								
								.resources/app/common/paths.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								.resources/app/common/paths.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.cleanOldVersions = cleanOldVersions; | ||||
| exports.getInstallPath = getInstallPath; | ||||
| exports.getModuleDataPath = getModuleDataPath; | ||||
| exports.getResources = getResources; | ||||
| exports.getUserData = getUserData; | ||||
| exports.getUserDataVersioned = getUserDataVersioned; | ||||
| exports.init = init; | ||||
| var _fs = _interopRequireDefault(require("fs")); | ||||
| var _mkdirp = _interopRequireDefault(require("mkdirp")); | ||||
| var _originalFs = _interopRequireDefault(require("original-fs")); | ||||
| var _path = _interopRequireDefault(require("path")); | ||||
| var _rimraf = _interopRequireDefault(require("rimraf")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| /* eslint-disable no-console */ | ||||
| // Determines environment-specific paths based on info provided
 | ||||
| 
 | ||||
| let userDataPath = null; | ||||
| let userDataVersionedPath = null; | ||||
| let resourcesPath = null; | ||||
| let moduleDataPath = null; | ||||
| let installPath = null; | ||||
| function determineAppUserDataRoot() { | ||||
|   // Allow overwriting the user data directory. This can be important when using --multi-instance
 | ||||
|   const userDataPath = process.env.DISCORD_USER_DATA_DIR; | ||||
|   if (userDataPath) { | ||||
|     return userDataPath; | ||||
|   } | ||||
|   const { | ||||
|     app | ||||
|   } = require('electron'); | ||||
|   return app.getPath('appData'); | ||||
| } | ||||
| function determineUserData(userDataRoot, buildInfo) { | ||||
|   return _path.default.join(userDataRoot, 'discord' + (buildInfo.releaseChannel == 'stable' ? '' : buildInfo.releaseChannel)); | ||||
| } | ||||
| 
 | ||||
| // cleans old version data in the background
 | ||||
| function cleanOldVersions(buildInfo) { | ||||
|   const entries = _fs.default.readdirSync(userDataPath) || []; | ||||
|   entries.forEach(entry => { | ||||
|     const fullPath = _path.default.join(userDataPath, entry); | ||||
|     let stat; | ||||
|     try { | ||||
|       stat = _fs.default.lstatSync(fullPath); | ||||
|     } catch (e) { | ||||
|       return; | ||||
|     } | ||||
|     if (stat.isDirectory() && entry.indexOf(buildInfo.version) === -1) { | ||||
|       if (entry.match('^[0-9]+.[0-9]+.[0-9]+') != null) { | ||||
|         console.log('Removing old directory ', entry); | ||||
|         (0, _rimraf.default)(fullPath, _originalFs.default, error => { | ||||
|           if (error) { | ||||
|             console.warn('...failed with error: ', error); | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| function init(buildInfo) { | ||||
|   resourcesPath = _path.default.join(require.main.filename, '..', '..', '..'); | ||||
|   const userDataRoot = determineAppUserDataRoot(); | ||||
|   userDataPath = determineUserData(userDataRoot, buildInfo); | ||||
|   const { | ||||
|     app | ||||
|   } = require('electron'); | ||||
|   app.setPath('userData', userDataPath); | ||||
|   userDataVersionedPath = _path.default.join(userDataPath, buildInfo.version); | ||||
|   _mkdirp.default.sync(userDataVersionedPath); | ||||
|   if (buildInfo.localModulesRoot != null) { | ||||
|     moduleDataPath = buildInfo.localModulesRoot; | ||||
|   } else if (buildInfo.newUpdater) { | ||||
|     moduleDataPath = _path.default.join(userDataPath, 'module_data'); | ||||
|   } else { | ||||
|     moduleDataPath = _path.default.join(userDataVersionedPath, 'modules'); | ||||
|   } | ||||
|   const exeDir = _path.default.dirname(app.getPath('exe')); | ||||
|   if (/^app-[0-9]+\.[0-9]+\.[0-9]+/.test(_path.default.basename(exeDir))) { | ||||
|     installPath = _path.default.join(exeDir, '..'); | ||||
|   } | ||||
| } | ||||
| function getUserData() { | ||||
|   return userDataPath; | ||||
| } | ||||
| function getUserDataVersioned() { | ||||
|   return userDataVersionedPath; | ||||
| } | ||||
| function getResources() { | ||||
|   return resourcesPath; | ||||
| } | ||||
| function getModuleDataPath() { | ||||
|   return moduleDataPath; | ||||
| } | ||||
| function getInstallPath() { | ||||
|   return installPath; | ||||
| } | ||||
							
								
								
									
										51
									
								
								.resources/app/common/processUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								.resources/app/common/processUtils.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.IS_WIN = exports.IS_OSX = exports.IS_LINUX = void 0; | ||||
| exports.getElectronMajorVersion = getElectronMajorVersion; | ||||
| exports.supportsTls13 = supportsTls13; | ||||
| var _os = _interopRequireDefault(require("os")); | ||||
| var _process = _interopRequireDefault(require("process")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| function getElectronMajorVersion() { | ||||
|   return _process.default.versions.electron != null ? parseInt(_process.default.versions.electron.split('.')[0]) : 0; | ||||
| } | ||||
| const IS_WIN = _process.default.platform === 'win32'; | ||||
| exports.IS_WIN = IS_WIN; | ||||
| const IS_OSX = _process.default.platform === 'darwin'; | ||||
| exports.IS_OSX = IS_OSX; | ||||
| const IS_LINUX = _process.default.platform === 'linux'; | ||||
| exports.IS_LINUX = IS_LINUX; | ||||
| function isWindowsVersionOrEarlier(major, minor) { | ||||
|   if (!IS_WIN) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Keep it resilient.
 | ||||
|   const osRelease = _os.default.release(); | ||||
|   if (osRelease == null || typeof osRelease !== 'string') { | ||||
|     return false; | ||||
|   } | ||||
|   const [actualMajor, actualMinor] = osRelease.split('.').map(v => parseInt(v, 10)); | ||||
|   if (actualMajor < major) { | ||||
|     return true; | ||||
|   } | ||||
|   if (actualMajor === major && actualMinor <= minor) { | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| function supportsTls13() { | ||||
|   // The nodejs `tls` module does not appear to provide a proper way to sniff tls1.2+ support. Sentry has depricated
 | ||||
|   // tls < 1.2, and since this TLS fissure is between OS versions, we instead detect Windows 7 and handle it
 | ||||
|   // accordingly. Windows 7 is version 6.1, so detect 6.1 or lower.
 | ||||
|   try { | ||||
|     return !isWindowsVersionOrEarlier(6, 1); | ||||
|   } catch { | ||||
|     // Who knows what wacky stuff random hacked up operating systems are reporting.
 | ||||
|     // Lets presume no one is using this old of an OS if we hit random exceptional cases.
 | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										42
									
								
								.resources/app/common/securityUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.resources/app/common/securityUtils.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| Object.defineProperty(exports, "__esModule", { | ||||
|   value: true | ||||
| }); | ||||
| exports.checkUrlOriginMatches = checkUrlOriginMatches; | ||||
| exports.saferShellOpenExternal = saferShellOpenExternal; | ||||
| exports.shouldOpenExternalUrl = shouldOpenExternalUrl; | ||||
| var _electron = require("electron"); | ||||
| var _url = _interopRequireDefault(require("url")); | ||||
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||||
| const BLOCKED_URL_PROTOCOLS = ['file:', 'javascript:', 'vbscript:', 'data:', 'about:', 'chrome:', 'ms-cxh:', 'ms-cxh-full:', 'ms-word:']; | ||||
| function shouldOpenExternalUrl(externalUrl) { | ||||
|   let parsedUrl; | ||||
|   try { | ||||
|     parsedUrl = _url.default.parse(externalUrl); | ||||
|   } catch (_) { | ||||
|     return false; | ||||
|   } | ||||
|   if (parsedUrl.protocol == null || BLOCKED_URL_PROTOCOLS.includes(parsedUrl.protocol.toLowerCase())) { | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| function saferShellOpenExternal(externalUrl) { | ||||
|   if (shouldOpenExternalUrl(externalUrl)) { | ||||
|     return _electron.shell.openExternal(externalUrl); | ||||
|   } else { | ||||
|     return Promise.reject(new Error('External url open request blocked')); | ||||
|   } | ||||
| } | ||||
| function checkUrlOriginMatches(urlA, urlB) { | ||||
|   let parsedUrlA; | ||||
|   let parsedUrlB; | ||||
|   try { | ||||
|     parsedUrlA = _url.default.parse(urlA); | ||||
|     parsedUrlB = _url.default.parse(urlB); | ||||
|   } catch (_) { | ||||
|     return false; | ||||
|   } | ||||
|   return parsedUrlA.protocol === parsedUrlB.protocol && parsedUrlA.slashes === parsedUrlB.slashes && parsedUrlA.host === parsedUrlB.host; | ||||
| } | ||||
							
								
								
									
										14
									
								
								.resources/app/common/typings/global.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.resources/app/common/typings/global.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| import type NodeModule from 'module'; | ||||
| import type {DiscordNativeType} from '@discordapp/discord-native-types'; | ||||
| 
 | ||||
| declare module 'module' { | ||||
|   var globalPaths: string[]; | ||||
|   var _resolveLookupPaths: (request: string, parent: NodeModule) => string[]; | ||||
| } | ||||
| 
 | ||||
| declare global { | ||||
|   var moduleDataPath: string | undefined; | ||||
|   var modulePath: string | undefined; | ||||
|   var DiscordNative: DiscordNativeType; | ||||
|   var popouts: Map<string, Window>; | ||||
| } | ||||
							
								
								
									
										394
									
								
								.resources/app/common/updater.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								.resources/app/common/updater.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,394 @@ | |||
| "use strict"; | ||||
| 
 | ||||
| // Too much Rust integration stuff in here.
 | ||||
| /* eslint camelcase: 0 */ | ||||
| const childProcess = require('child_process'); | ||||
| const { | ||||
|   app | ||||
| } = require('electron'); | ||||
| const { | ||||
|   EventEmitter | ||||
| } = require('events'); | ||||
| const NodeModule = require('module'); | ||||
| const path = require('path'); | ||||
| const { | ||||
|   hrtime | ||||
| } = require('process'); | ||||
| let instance; | ||||
| const TASK_STATE_COMPLETE = 'Complete'; | ||||
| const TASK_STATE_FAILED = 'Failed'; | ||||
| const TASK_STATE_WAITING = 'Waiting'; | ||||
| const TASK_STATE_WORKING = 'Working'; | ||||
| const INCONSISTENT_INSTALLER_STATE_ERROR = 'InconsistentInstallerState'; | ||||
| 
 | ||||
| // The dumb linters are mad at each other.
 | ||||
| // eslint-disable-next-line quotes
 | ||||
| const INVALID_UPDATER_ERROR = "Can't send request to updater because the native updater isn't loaded."; | ||||
| class Updater extends EventEmitter { | ||||
|   constructor(options) { | ||||
|     super(); | ||||
|     let nativeUpdaterModule = options.nativeUpdaterModule; | ||||
|     if (nativeUpdaterModule == null) { | ||||
|       try { | ||||
|         // eslint-disable-next-line import/no-unresolved
 | ||||
|         nativeUpdaterModule = require('../../../updater'); | ||||
|       } catch (e) { | ||||
|         if (e.code === 'MODULE_NOT_FOUND') { | ||||
|           return; | ||||
|         } | ||||
|         throw e; | ||||
|       } | ||||
|     } | ||||
|     this.committedHostVersion = null; | ||||
|     this.committedModules = new Set(); | ||||
|     this.rootPath = options.root_path; | ||||
|     this.nextRequestId = 0; | ||||
|     this.requests = new Map(); | ||||
|     this.updateEventHistory = []; | ||||
|     this.isRunningInBackground = false; | ||||
|     this.currentlyDownloading = {}; | ||||
|     this.currentlyInstalling = {}; | ||||
|     this.hasEmittedUnhandledException = false; | ||||
|     this.nativeUpdater = new nativeUpdaterModule.Updater({ | ||||
|       response_handler: this._handleResponse.bind(this), | ||||
|       ...options | ||||
|     }); | ||||
|   } | ||||
|   get valid() { | ||||
|     return this.nativeUpdater != null; | ||||
|   } | ||||
|   _sendRequest(detail, progressCallback = null) { | ||||
|     if (!this.valid) { | ||||
|       throw new Error(INVALID_UPDATER_ERROR); | ||||
|     } | ||||
|     const requestId = this.nextRequestId++; | ||||
|     return new Promise((resolve, reject) => { | ||||
|       this.requests.set(requestId, { | ||||
|         resolve, | ||||
|         reject, | ||||
|         progressCallback | ||||
|       }); | ||||
|       this.nativeUpdater.command(JSON.stringify([requestId, detail])); | ||||
|     }); | ||||
|   } | ||||
|   _sendRequestSync(detail) { | ||||
|     if (!this.valid) { | ||||
|       throw new Error(INVALID_UPDATER_ERROR); | ||||
|     } | ||||
|     const requestId = this.nextRequestId++; | ||||
|     return this.nativeUpdater.command_blocking(JSON.stringify([requestId, detail])); | ||||
|   } | ||||
|   _handleResponse(response) { | ||||
|     try { | ||||
|       const [id, detail] = JSON.parse(response); | ||||
|       const request = this.requests.get(id); | ||||
|       if (request == null) { | ||||
|         console.error('Received response ', detail, ' for a request (', id, ') not in the updater request map.'); | ||||
|         return; | ||||
|       } | ||||
|       if (detail['Error'] != null) { | ||||
|         const { | ||||
|           kind, | ||||
|           details, | ||||
|           severity | ||||
|         } = detail['Error']; | ||||
|         const e = new Error(`(${kind}) ${details}`); | ||||
|         if (severity === 'Fatal') { | ||||
|           const handled = this.emit(kind, e); | ||||
|           if (!handled) { | ||||
|             throw e; | ||||
|           } | ||||
|         } else { | ||||
|           this.emit('update-error', e); | ||||
|           request.reject(e); | ||||
|           this.requests.delete(id); | ||||
|         } | ||||
|       } else if (detail === 'Ok') { | ||||
|         request.resolve(); | ||||
|         this.requests.delete(id); | ||||
|       } else if (detail['VersionInfo'] != null) { | ||||
|         request.resolve(detail['VersionInfo']); | ||||
|         this.requests.delete(id); | ||||
|       } else if (detail['ManifestInfo'] != null) { | ||||
|         request.resolve(detail['ManifestInfo']); | ||||
|         this.requests.delete(id); | ||||
|       } else if (detail['TaskProgress'] != null) { | ||||
|         const msg = detail['TaskProgress']; | ||||
|         const progress = { | ||||
|           task: msg[0], | ||||
|           state: msg[1], | ||||
|           percent: msg[2], | ||||
|           bytesProcessed: msg[3] | ||||
|         }; | ||||
|         this._recordTaskProgress(progress); | ||||
|         if (request.progressCallback != null) { | ||||
|           request.progressCallback(progress); | ||||
|         } | ||||
|         if (progress.task['HostInstall'] != null && progress.state === TASK_STATE_COMPLETE) { | ||||
|           this.emit('host-updated'); | ||||
|         } | ||||
|       } else { | ||||
|         console.warn('Unknown updater response', detail); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       console.error('Unhandled exception in updater response handler:', e); | ||||
| 
 | ||||
|       // Report the first time this happens, but don't spam.
 | ||||
|       if (!this.hasEmittedUnhandledException) { | ||||
|         this.hasEmittedUnhandledException = true; | ||||
|         this.emit('unhandled-exception', e); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   _handleSyncResponse(response) { | ||||
|     const detail = JSON.parse(response); | ||||
|     if (detail['Error'] != null) { | ||||
|       throw new Error(detail['Error']); | ||||
|     } else if (detail === 'Ok') { | ||||
|       return; | ||||
|     } else if (detail['VersionInfo'] != null) { | ||||
|       return detail['VersionInfo']; | ||||
|     } | ||||
|     console.warn('Unknown updater response', detail); | ||||
|   } | ||||
|   _getHostPath() { | ||||
|     const [major, minor, revision] = this.committedHostVersion; | ||||
|     const hostVersionStr = `${major}.${minor}.${revision}`; | ||||
|     return path.join(this.rootPath, `app-${hostVersionStr}`); | ||||
|   } | ||||
|   _startCurrentVersionInner(options, versions) { | ||||
|     if (this.committedHostVersion == null) { | ||||
|       this.committedHostVersion = versions.current_host; | ||||
|     } | ||||
|     const hostPath = this._getHostPath(); | ||||
|     const hostExePath = path.join(hostPath, path.basename(process.execPath)); | ||||
|     if (path.resolve(hostExePath) != path.resolve(process.execPath) && !(options === null || options === void 0 ? void 0 : options.allowObsoleteHost)) { | ||||
|       app.once('will-quit', () => { | ||||
|         // TODO(eiz): the actual, correct way to do this (win32) is to inherit a
 | ||||
|         // handle to the current process into a new child process which then
 | ||||
|         // waits for that process handle to exit, then runs the new electron.
 | ||||
|         // This requires either implementing a separate updater exe process (big
 | ||||
|         // todo item atm) or likely modifying Electron?
 | ||||
|         //
 | ||||
|         // I intend to do it properly once the new production updater .exe is a
 | ||||
|         // thing.
 | ||||
|         childProcess.spawn(hostExePath, [], { | ||||
|           detached: true, | ||||
|           stdio: 'inherit' | ||||
|         }); | ||||
|       }); | ||||
|       console.log(`Restarting from ${path.resolve(process.execPath)} to ${path.resolve(hostExePath)}`); | ||||
|       app.quit(); | ||||
|       this.emit('starting-new-host'); | ||||
|       return; | ||||
|     } | ||||
|     this._commitModulesInner(versions); | ||||
|   } | ||||
|   _commitModulesInner(versions) { | ||||
|     const { | ||||
|       addGlobalPath, | ||||
|       globalPathExists | ||||
|     } = require('./nodeGlobalPaths'); | ||||
|     const hostPath = this._getHostPath(); | ||||
|     const modulesPath = path.join(hostPath, 'modules'); | ||||
|     for (const module in versions.current_modules) { | ||||
|       const moduleVersion = versions.current_modules[module]; | ||||
|       const moduleSearchPath = path.join(modulesPath, `${module}-${moduleVersion}`); | ||||
|       if (!this.committedModules.has(module) && !globalPathExists(moduleSearchPath)) { | ||||
|         this.committedModules.add(module); | ||||
|         addGlobalPath(moduleSearchPath); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   _recordDownloadProgress(name, progress) { | ||||
|     const now = String(hrtime.bigint()); | ||||
|     if (progress.state === TASK_STATE_WORKING && !this.currentlyDownloading[name]) { | ||||
|       this.currentlyDownloading[name] = true; | ||||
|       this.updateEventHistory.push({ | ||||
|         type: 'downloading-module', | ||||
|         name: name, | ||||
|         now: now | ||||
|       }); | ||||
|     } else if (progress.state === TASK_STATE_COMPLETE || progress.state === TASK_STATE_FAILED) { | ||||
|       this.currentlyDownloading[name] = false; | ||||
|       this.updateEventHistory.push({ | ||||
|         type: 'downloaded-module', | ||||
|         name: name, | ||||
|         now: now, | ||||
|         succeeded: progress.state === TASK_STATE_COMPLETE, | ||||
|         receivedBytes: progress.bytesProcessed | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   _recordInstallProgress(name, progress, newVersion, isDelta) { | ||||
|     const now = String(hrtime.bigint()); | ||||
|     if (progress.state === TASK_STATE_WORKING && !this.currentlyInstalling[name]) { | ||||
|       this.currentlyInstalling[name] = true; | ||||
|       this.updateEventHistory.push({ | ||||
|         type: 'installing-module', | ||||
|         name, | ||||
|         now, | ||||
|         newVersion, | ||||
|         foreground: !this.isRunningInBackground | ||||
|       }); | ||||
|     } else if (progress.state === TASK_STATE_COMPLETE || progress.state === TASK_STATE_FAILED) { | ||||
|       this.currentlyInstalling[name] = false; | ||||
|       this.updateEventHistory.push({ | ||||
|         type: 'installed-module', | ||||
|         name, | ||||
|         now, | ||||
|         newVersion, | ||||
|         succeeded: progress.state === TASK_STATE_COMPLETE, | ||||
|         delta: isDelta, | ||||
|         foreground: !this.isRunningInBackground | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|   _recordTaskProgress(progress) { | ||||
|     if (progress.task.HostDownload != null) { | ||||
|       this._recordDownloadProgress('host', progress); | ||||
|     } else if (progress.task.HostInstall != null) { | ||||
|       this._recordInstallProgress('host', progress, null, progress.task.HostInstall.from_version != null); | ||||
|     } else if (progress.task.ModuleDownload != null) { | ||||
|       this._recordDownloadProgress(progress.task.ModuleDownload.version.module.name, progress); | ||||
|     } else if (progress.task.ModuleInstall != null) { | ||||
|       this._recordInstallProgress(progress.task.ModuleInstall.version.module.name, progress, progress.task.ModuleInstall.version.version, progress.task.ModuleInstall.from_version != null); | ||||
|     } | ||||
|   } | ||||
|   queryCurrentVersions() { | ||||
|     return this._sendRequest('QueryCurrentVersions'); | ||||
|   } | ||||
|   queryCurrentVersionsSync() { | ||||
|     return this._handleSyncResponse(this._sendRequestSync('QueryCurrentVersions')); | ||||
|   } | ||||
|   repair(progressCallback) { | ||||
|     return this.repairWithOptions(null, progressCallback); | ||||
|   } | ||||
|   repairWithOptions(options, progressCallback) { | ||||
|     return this._sendRequest({ | ||||
|       Repair: { | ||||
|         options | ||||
|       } | ||||
|     }, progressCallback); | ||||
|   } | ||||
|   collectGarbage() { | ||||
|     return this._sendRequest('CollectGarbage'); | ||||
|   } | ||||
|   setRunningManifest(manifest) { | ||||
|     return this._sendRequest({ | ||||
|       SetManifests: ['Running', manifest] | ||||
|     }); | ||||
|   } | ||||
|   setPinnedManifestSync(manifest) { | ||||
|     return this._handleSyncResponse(this._sendRequestSync({ | ||||
|       SetManifests: ['Pinned', manifest] | ||||
|     })); | ||||
|   } | ||||
|   installModule(name, progressCallback) { | ||||
|     return this.installModuleWithOptions(name, null, progressCallback); | ||||
|   } | ||||
|   installModuleWithOptions(name, options, progressCallback) { | ||||
|     return this._sendRequest({ | ||||
|       InstallModule: { | ||||
|         name, | ||||
|         options | ||||
|       } | ||||
|     }, progressCallback); | ||||
|   } | ||||
|   updateToLatest(progressCallback) { | ||||
|     return this.updateToLatestWithOptions(null, progressCallback); | ||||
|   } | ||||
|   updateToLatestWithOptions(options, progressCallback) { | ||||
|     return this._sendRequest({ | ||||
|       UpdateToLatest: { | ||||
|         options | ||||
|       } | ||||
|     }, progressCallback); | ||||
|   } | ||||
| 
 | ||||
|   // If the running host is current, adopt the current installed modules and
 | ||||
|   // set up the module search path accordingly. If the running host is not
 | ||||
|   // current, start the new current host and exit this process.
 | ||||
|   async startCurrentVersion(options) { | ||||
|     const versions = await this.queryCurrentVersions(); | ||||
|     await this.setRunningManifest(versions.last_successful_update); | ||||
|     this._startCurrentVersionInner(options, versions); | ||||
|   } | ||||
|   startCurrentVersionSync(options) { | ||||
|     const versions = this.queryCurrentVersionsSync(); | ||||
|     this._startCurrentVersionInner(options, versions); | ||||
|   } | ||||
|   async commitModules(versions) { | ||||
|     if (this.committedHostVersion == null) { | ||||
|       throw new Error('Cannot commit modules before host version.'); | ||||
|     } | ||||
|     if (versions == null) { | ||||
|       versions = await this.queryCurrentVersions(); | ||||
|     } | ||||
|     this._commitModulesInner(versions); | ||||
|   } | ||||
|   setRunningInBackground() { | ||||
|     this.isRunningInBackground = true; | ||||
|   } | ||||
|   queryAndTruncateHistory() { | ||||
|     const history = this.updateEventHistory; | ||||
|     this.updateEventHistory = []; | ||||
|     return history; | ||||
|   } | ||||
|   getKnownFolder(name) { | ||||
|     if (!this.valid) { | ||||
|       throw new Error(INVALID_UPDATER_ERROR); | ||||
|     } | ||||
|     return this.nativeUpdater.known_folder(name); | ||||
|   } | ||||
|   createShortcut(options) { | ||||
|     if (!this.valid) { | ||||
|       throw new Error(INVALID_UPDATER_ERROR); | ||||
|     } | ||||
|     return this.nativeUpdater.create_shortcut(options); | ||||
|   } | ||||
| } | ||||
| function getUpdaterPlatformName(platform) { | ||||
|   switch (platform) { | ||||
|     case 'darwin': | ||||
|       return 'osx'; | ||||
|     case 'win32': | ||||
|       return 'win'; | ||||
|     default: | ||||
|       return platform; | ||||
|   } | ||||
| } | ||||
| function tryInitUpdater(buildInfo, repositoryUrl) { | ||||
|   // We can't require this in module scope because it's not part of the
 | ||||
|   // bootstrapper, which carries a copy of the Updater class.
 | ||||
|   const paths = require('./paths'); | ||||
|   const rootPath = paths.getInstallPath(); | ||||
| 
 | ||||
|   // If we're not running from an actual install directory, don't bother trying
 | ||||
|   // to initialize the updater.
 | ||||
|   if (rootPath == null) { | ||||
|     return false; | ||||
|   } | ||||
|   instance = new Updater({ | ||||
|     release_channel: buildInfo.releaseChannel, | ||||
|     platform: getUpdaterPlatformName(process.platform), | ||||
|     repository_url: repositoryUrl, | ||||
|     root_path: rootPath | ||||
|   }); | ||||
|   return instance.valid; | ||||
| } | ||||
| function getUpdater() { | ||||
|   if (instance != null && instance.valid) { | ||||
|     return instance; | ||||
|   } | ||||
| } | ||||
| module.exports = { | ||||
|   Updater, | ||||
|   tryInitUpdater, | ||||
|   getUpdater, | ||||
|   TASK_STATE_COMPLETE, | ||||
|   TASK_STATE_FAILED, | ||||
|   TASK_STATE_WAITING, | ||||
|   TASK_STATE_WORKING, | ||||
|   INCONSISTENT_INSTALLER_STATE_ERROR | ||||
| }; | ||||
							
								
								
									
										17
									
								
								.resources/app/package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.resources/app/package.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| { | ||||
|   "name": "discord", | ||||
|   "description": "Discord Client for Desktop - Bootstrapper", | ||||
|   "main": "app_bootstrap/index.js", | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@sentry/node": "7.47.0", | ||||
|     "mkdirp": "^0.5.1", | ||||
|     "request": "2.88.0", | ||||
|     "rimraf": "^2.6.3", | ||||
|     "yauzl": "^2.10.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/electron": "1.6.10", | ||||
|     "devtron": "1.4.0" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								.resources/bootstrap/manifest.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.resources/bootstrap/manifest.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| { | ||||
|   "discord_desktop_core": 0,  | ||||
|   "discord_erlpack": 0,  | ||||
|   "discord_spellcheck": 0,  | ||||
|   "discord_utils": 0,  | ||||
|   "discord_voice": 0 | ||||
| } | ||||
							
								
								
									
										4
									
								
								.resources/build_info.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.resources/build_info.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| { | ||||
|   "releaseChannel": "stable",  | ||||
|   "version": "0.0.27" | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue