add.[ALL]
This commit is contained in:
parent
5ac24c8cea
commit
780ad9a200
54 changed files with 3733 additions and 0 deletions
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
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue