[Updater > Host] Self rewrite, remove SquirrelUpdate
This commit is contained in:
parent
2347621828
commit
33da0fa68f
3 changed files with 38 additions and 392 deletions
|
@ -1,7 +1,7 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('../paths');
|
||||
const squirrel = require('../updater/squirrelUpdate');
|
||||
const windowsUtils = require('../utils/windowsUtils');
|
||||
const Constants = require('../Constants');
|
||||
|
||||
const appPath = path.resolve(process.execPath, '..');
|
||||
|
@ -48,6 +48,8 @@ const updateShortcuts = (updater) => {
|
|||
}
|
||||
};
|
||||
|
||||
const installProtocol = (protocol, callback) => windowsUtils.addToRegistry([['HKCU\\Software\\Classes\\' + protocol, '/ve', '/d', `URL:${protocol} Protocol`], ['HKCU\\Software\\Classes\\' + protocol, '/v', 'URL Protocol'], ['HKCU\\Software\\Classes\\' + protocol + '\\DefaultIcon', '/ve', '/d', '"' + process.execPath + '",-1'], ['HKCU\\Software\\Classes\\' + protocol + '\\shell\\open\\command', '/ve', '/d', `"${process.execPath}" --url -- "%1"`]], callback);
|
||||
|
||||
exports.performFirstRunTasks = (updater) => {
|
||||
log('FirstRun', 'Perform');
|
||||
|
||||
|
@ -63,13 +65,13 @@ exports.performFirstRunTasks = (updater) => {
|
|||
log('FirstRun', 'Error updating shortcuts', e);
|
||||
}
|
||||
|
||||
squirrel.installProtocol(Constants.APP_PROTOCOL, () => {
|
||||
if (shortcutSuccess) {
|
||||
try {
|
||||
fs.writeFileSync(firstRunCompletePath, 'true');
|
||||
} catch (e) {
|
||||
log('FirstRun', 'Error writing .first-run', e);
|
||||
}
|
||||
installProtocol(Constants.APP_PROTOCOL, () => {
|
||||
if (!shortcutSuccess) return;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(firstRunCompletePath, 'true');
|
||||
} catch (e) {
|
||||
log('FirstRun', 'Error writing .first-run', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,199 +1,61 @@
|
|||
"use strict";
|
||||
const { app, autoUpdater } = require('electron');
|
||||
const events = require('events');
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
const request = require('request');
|
||||
|
||||
const versionParse = (s) => s.split('.').map((x) => parseInt(x));
|
||||
const versionNewer = (a, b) => a.some((x, i) => {
|
||||
const y = b[i];
|
||||
if (x == undefined) return false;
|
||||
if (y == undefined) return true;
|
||||
|
||||
if (x === y) return undefined;
|
||||
return x > b[i];
|
||||
});
|
||||
exports.default = void 0;
|
||||
|
||||
var _electron = require("electron");
|
||||
|
||||
var _events = require("events");
|
||||
|
||||
var _request = _interopRequireDefault(require("./request"));
|
||||
|
||||
var squirrelUpdate = _interopRequireWildcard(require("./squirrelUpdate"));
|
||||
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function versionParse(verString) {
|
||||
return verString.split('.').map(i => parseInt(i));
|
||||
}
|
||||
|
||||
function versionNewer(verA, verB) {
|
||||
let i = 0;
|
||||
|
||||
while (true) {
|
||||
const a = verA[i];
|
||||
const b = verB[i];
|
||||
i++;
|
||||
|
||||
if (a === undefined) {
|
||||
return false;
|
||||
} else {
|
||||
if (b === undefined || a > b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AutoUpdaterWin32 extends _events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.updateUrl = null;
|
||||
this.updateVersion = null;
|
||||
}
|
||||
|
||||
setFeedURL(updateUrl) {
|
||||
this.updateUrl = updateUrl;
|
||||
}
|
||||
|
||||
quitAndInstall() {
|
||||
if (squirrelUpdate.updateExistsSync()) {
|
||||
squirrelUpdate.restart(_electron.app, this.updateVersion || _electron.app.getVersion());
|
||||
} else {
|
||||
require('auto-updater').quitAndInstall();
|
||||
}
|
||||
}
|
||||
|
||||
downloadAndInstallUpdate(callback) {
|
||||
squirrelUpdate.spawnUpdateInstall(this.updateUrl, progress => {
|
||||
this.emit('update-progress', progress);
|
||||
}).catch(err => callback(err)).then(() => callback());
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
if (this.updateUrl == null) {
|
||||
throw new Error('Update URL is not set');
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
|
||||
if (!squirrelUpdate.updateExistsSync()) {
|
||||
this.emit('update-not-available');
|
||||
return;
|
||||
}
|
||||
|
||||
squirrelUpdate.spawnUpdate(['--check', this.updateUrl], (error, stdout) => {
|
||||
if (error != null) {
|
||||
this.emit('error', error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Last line of the output is JSON details about the releases
|
||||
const json = stdout.trim().split('\n').pop();
|
||||
const releasesFound = JSON.parse(json).releasesToApply;
|
||||
|
||||
if (releasesFound == null || releasesFound.length == 0) {
|
||||
this.emit('update-not-available');
|
||||
return;
|
||||
}
|
||||
|
||||
const update = releasesFound.pop();
|
||||
this.emit('update-available');
|
||||
this.downloadAndInstallUpdate(error => {
|
||||
if (error != null) {
|
||||
this.emit('error', error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateVersion = update.version;
|
||||
this.emit('update-downloaded', {}, update.release, update.version, new Date(), this.updateUrl, this.quitAndInstall.bind(this));
|
||||
});
|
||||
} catch (error) {
|
||||
error.stdout = stdout;
|
||||
this.emit('error', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // todo
|
||||
|
||||
|
||||
class AutoUpdaterLinux extends _events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.updateUrl = null;
|
||||
}
|
||||
|
||||
class HostLinux extends events.EventEmitter {
|
||||
setFeedURL(url) {
|
||||
this.updateUrl = url;
|
||||
}
|
||||
|
||||
quitAndInstall() {
|
||||
// Just restart. The splash screen will hit the update manually state and
|
||||
// prompt the user to download the new package.
|
||||
_electron.app.relaunch();
|
||||
|
||||
_electron.app.quit();
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
|
||||
async checkForUpdates() {
|
||||
const currVersion = versionParse(_electron.app.getVersion());
|
||||
const current = versionParse(app.getVersion());
|
||||
this.emit('checking-for-update');
|
||||
|
||||
try {
|
||||
const response = await _request.default.get(this.updateUrl);
|
||||
const [, response ] = await new Promise((res) => request.get(this.updateUrl, res));
|
||||
if (response.statusCode === 204) return this.emit('update-not-available');
|
||||
|
||||
if (response.statusCode === 204) {
|
||||
// you are up to date
|
||||
this.emit('update-not-available');
|
||||
return;
|
||||
const latest = versionParse(JSON.parse(response.body).name);
|
||||
|
||||
if (versionNewer(latest, current)) {
|
||||
log('HostLinux', 'Outdated');
|
||||
return this.emit('update-manually', latestVerStr);
|
||||
}
|
||||
|
||||
let latestVerStr = '';
|
||||
let latestVersion = [];
|
||||
|
||||
try {
|
||||
const latestMetadata = JSON.parse(response.body);
|
||||
latestVerStr = latestMetadata.name;
|
||||
latestVersion = versionParse(latestVerStr);
|
||||
} catch (_) {}
|
||||
|
||||
if (versionNewer(latestVersion, currVersion)) {
|
||||
console.log('[Updates] You are out of date!'); // you need to update
|
||||
|
||||
this.emit('update-manually', latestVerStr);
|
||||
} else {
|
||||
console.log('[Updates] You are living in the future!');
|
||||
this.emit('update-not-available');
|
||||
}
|
||||
log('HostLinux', 'Not outdated');
|
||||
this.emit('update-not-available');
|
||||
} catch (err) {
|
||||
console.error('[Updates] Error fetching ' + this.updateUrl + ': ' + err.message);
|
||||
log('HostLinux', 'Error', this.updateUrl, err.message);
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let autoUpdater; // TODO
|
||||
// events: checking-for-update, update-available, update-not-available, update-manually, update-downloaded, error
|
||||
// also, checkForUpdates, setFeedURL, quitAndInstall
|
||||
// also, see electron.autoUpdater, and its API
|
||||
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
autoUpdater = require('electron').autoUpdater;
|
||||
break;
|
||||
|
||||
case 'win32':
|
||||
autoUpdater = new AutoUpdaterWin32();
|
||||
exports.default = autoUpdater;
|
||||
break;
|
||||
|
||||
case 'linux':
|
||||
autoUpdater = new AutoUpdaterLinux();
|
||||
exports.default = new HostLinux();
|
||||
break;
|
||||
}
|
||||
|
||||
var _default = autoUpdater;
|
||||
exports.default = _default;
|
||||
module.exports = exports.default;
|
|
@ -1,218 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.spawnUpdateInstall = spawnUpdateInstall;
|
||||
exports.spawnUpdate = spawnUpdate;
|
||||
exports.installProtocol = installProtocol;
|
||||
exports.handleStartupEvent = handleStartupEvent;
|
||||
exports.updateExistsSync = updateExistsSync;
|
||||
exports.restart = restart;
|
||||
|
||||
var _child_process = _interopRequireDefault(require("child_process"));
|
||||
|
||||
var _fs = _interopRequireDefault(require("fs"));
|
||||
|
||||
var _path = _interopRequireDefault(require("path"));
|
||||
|
||||
var autoStart = _interopRequireWildcard(require("../autoStart"));
|
||||
|
||||
var windowsUtils = _interopRequireWildcard(require("../utils/windowsUtils"));
|
||||
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
// citron note: this assumes the execPath is in the format Discord/someVersion/Discord.exe
|
||||
const appFolder = _path.default.resolve(process.execPath, '..');
|
||||
|
||||
const rootFolder = _path.default.resolve(appFolder, '..');
|
||||
|
||||
const exeName = _path.default.basename(process.execPath);
|
||||
|
||||
const updateExe = _path.default.join(rootFolder, 'Update.exe'); // Specialized spawn function specifically used for spawning the updater in
|
||||
// update mode. Calls back with progress percentages.
|
||||
// Returns Promise.
|
||||
|
||||
|
||||
function spawnUpdateInstall(updateUrl, progressCallback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = _child_process.default.spawn(updateExe, ['--update', updateUrl]);
|
||||
|
||||
proc.on('error', reject);
|
||||
proc.on('exit', code => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Update failed with exit code ${code}`));
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
let lastProgress = -1;
|
||||
|
||||
function parseProgress() {
|
||||
const lines = stdout.split(/\r?\n/);
|
||||
if (lines.length === 1) return; // return the last (possibly incomplete) line to stdout for parsing again
|
||||
|
||||
stdout = lines.pop();
|
||||
let currentProgress;
|
||||
|
||||
for (const line of lines) {
|
||||
if (!/^\d\d?$/.test(line)) continue;
|
||||
const progress = Number(line); // make sure that this number is steadily increasing
|
||||
|
||||
if (lastProgress > progress) continue;
|
||||
currentProgress = progress;
|
||||
}
|
||||
|
||||
if (currentProgress == null) return;
|
||||
lastProgress = currentProgress;
|
||||
progressCallback(Math.min(currentProgress, 100));
|
||||
}
|
||||
|
||||
let stdout = '';
|
||||
proc.stdout.on('data', chunk => {
|
||||
stdout += String(chunk);
|
||||
parseProgress();
|
||||
});
|
||||
});
|
||||
} // Spawn the Update.exe with the given arguments and invoke the callback when
|
||||
// the command completes.
|
||||
|
||||
|
||||
function spawnUpdate(args, callback) {
|
||||
windowsUtils.spawn(updateExe, args, callback);
|
||||
} // Create a desktop and start menu shortcut by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
|
||||
|
||||
function createShortcuts(callback, updateOnly) {
|
||||
// move icon out to a more stable location, to keep shortcuts from breaking as much
|
||||
const icoSrc = _path.default.join(appFolder, 'app.ico');
|
||||
|
||||
const icoDest = _path.default.join(rootFolder, 'app.ico');
|
||||
|
||||
let icoForTarget = icoDest;
|
||||
|
||||
try {
|
||||
const ico = _fs.default.readFileSync(icoSrc);
|
||||
|
||||
_fs.default.writeFileSync(icoDest, ico);
|
||||
} catch (e) {
|
||||
// if we can't write there for some reason, just use the source.
|
||||
icoForTarget = icoSrc;
|
||||
}
|
||||
|
||||
const createShortcutArgs = ['--createShortcut', exeName, '--setupIcon', icoForTarget];
|
||||
|
||||
if (updateOnly) {
|
||||
createShortcutArgs.push('--updateOnly');
|
||||
}
|
||||
|
||||
spawnUpdate(createShortcutArgs, callback);
|
||||
} // Add a protocol registration for this application.
|
||||
|
||||
|
||||
function installProtocol(protocol, callback) {
|
||||
const queue = [['HKCU\\Software\\Classes\\' + protocol, '/ve', '/d', `URL:${protocol} Protocol`], ['HKCU\\Software\\Classes\\' + protocol, '/v', 'URL Protocol'], ['HKCU\\Software\\Classes\\' + protocol + '\\DefaultIcon', '/ve', '/d', '"' + process.execPath + '",-1'], ['HKCU\\Software\\Classes\\' + protocol + '\\shell\\open\\command', '/ve', '/d', `"${process.execPath}" --url -- "%1"`]];
|
||||
windowsUtils.addToRegistry(queue, callback);
|
||||
}
|
||||
|
||||
function terminate(app) {
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
} // Remove the desktop and start menu shortcuts by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
|
||||
|
||||
function removeShortcuts(callback) {
|
||||
spawnUpdate(['--removeShortcut', exeName], callback);
|
||||
} // Update the desktop and start menu shortcuts by using the command line API
|
||||
// provided by Squirrel's Update.exe
|
||||
|
||||
|
||||
function updateShortcuts(callback) {
|
||||
createShortcuts(callback, true);
|
||||
} // Purge the protocol for this applicationstart.
|
||||
|
||||
|
||||
function uninstallProtocol(protocol, callback) {
|
||||
windowsUtils.spawnReg(['delete', 'HKCU\\Software\\Classes\\' + protocol, '/f'], callback);
|
||||
}
|
||||
|
||||
function maybeInstallNewUpdaterSeedDb() {
|
||||
const installerDbSrc = _path.default.join(appFolder, 'installer.db');
|
||||
|
||||
const installerDbDest = _path.default.join(rootFolder, 'installer.db');
|
||||
|
||||
if (_fs.default.existsSync(installerDbSrc)) {
|
||||
_fs.default.renameSync(installerDbSrc, installerDbDest);
|
||||
}
|
||||
} // Handle squirrel events denoted by --squirrel-* command line arguments.
|
||||
// returns `true` if regular startup should be prevented
|
||||
|
||||
|
||||
function handleStartupEvent(protocol, app, squirrelCommand) {
|
||||
switch (squirrelCommand) {
|
||||
case '--squirrel-install':
|
||||
createShortcuts(() => {
|
||||
autoStart.install(() => {
|
||||
installProtocol(protocol, () => {
|
||||
// Squirrel doesn't have a way to include app-level files.
|
||||
// We get around this for new updater hosts, which rely on
|
||||
// a seeded manifest, by bubbling the db up from the versioned-app
|
||||
// directory if it exists.
|
||||
maybeInstallNewUpdaterSeedDb();
|
||||
terminate(app);
|
||||
});
|
||||
});
|
||||
}, false);
|
||||
return true;
|
||||
|
||||
case '--squirrel-updated':
|
||||
updateShortcuts(() => {
|
||||
autoStart.update(() => {
|
||||
installProtocol(protocol, () => {
|
||||
terminate(app);
|
||||
});
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
case '--squirrel-uninstall':
|
||||
removeShortcuts(() => {
|
||||
autoStart.uninstall(() => {
|
||||
uninstallProtocol(protocol, () => {
|
||||
terminate(app);
|
||||
});
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
case '--squirrel-obsolete':
|
||||
terminate(app);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} // Are we using Squirrel for updates?
|
||||
|
||||
|
||||
function updateExistsSync() {
|
||||
return _fs.default.existsSync(updateExe);
|
||||
} // Restart app as the new version
|
||||
|
||||
|
||||
function restart(app, newVersion) {
|
||||
app.once('will-quit', () => {
|
||||
const execPath = _path.default.resolve(rootFolder, `app-${newVersion}/${exeName}`);
|
||||
|
||||
_child_process.default.spawn(execPath, [], {
|
||||
detached: true
|
||||
});
|
||||
});
|
||||
app.quit();
|
||||
}
|
Loading…
Reference in a new issue