add.[ALL]

This commit is contained in:
|| Prof. - Xadk3!#0000 || @naryal2580 2023-05-21 16:28:12 +05:30
parent 5ac24c8cea
commit 780ad9a200
54 changed files with 3733 additions and 0 deletions

View 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;

View 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;

View 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;

View 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;
}

View 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]));
}
}
}

View 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
});
}
}

View 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;
}

View 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;
}

View 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;
}
}

View 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;
}

View 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>;
}

View 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
};