2021-01-08 22:54:45 +01:00
|
|
|
import kdbxweb from 'kdbxweb';
|
2019-09-16 22:57:56 +02:00
|
|
|
import { Events } from 'framework/events';
|
2019-10-26 22:56:36 +02:00
|
|
|
import { RuntimeInfo } from 'const/runtime-info';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { Transport } from 'comp/browser/transport';
|
|
|
|
import { Launcher } from 'comp/launcher';
|
|
|
|
import { Links } from 'const/links';
|
|
|
|
import { AppSettingsModel } from 'models/app-settings-model';
|
|
|
|
import { UpdateModel } from 'models/update-model';
|
|
|
|
import { SemVer } from 'util/data/semver';
|
|
|
|
import { Logger } from 'util/logger';
|
2019-09-28 14:17:55 +02:00
|
|
|
import { SignatureVerifier } from 'util/data/signature-verifier';
|
2015-12-12 09:53:50 +01:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const logger = new Logger('updater');
|
2015-10-25 20:26:33 +01:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const Updater = {
|
2016-07-17 13:30:38 +02:00
|
|
|
UpdateInterval: 1000 * 60 * 60 * 24,
|
2015-11-14 12:09:36 +01:00
|
|
|
MinUpdateTimeout: 500,
|
2015-11-13 20:56:22 +01:00
|
|
|
MinUpdateSize: 10000,
|
2015-10-29 22:20:01 +01:00
|
|
|
nextCheckTimeout: null,
|
2015-11-13 20:56:22 +01:00
|
|
|
updateCheckDate: new Date(0),
|
2016-02-06 12:40:40 +01:00
|
|
|
enabled: Launcher && Launcher.updaterEnabled(),
|
2015-11-14 16:28:36 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getAutoUpdateType() {
|
2016-02-06 12:40:40 +01:00
|
|
|
if (!this.enabled) {
|
2015-11-16 20:04:33 +01:00
|
|
|
return false;
|
|
|
|
}
|
2019-09-17 19:50:42 +02:00
|
|
|
let autoUpdate = AppSettingsModel.autoUpdate;
|
2015-11-16 20:04:33 +01:00
|
|
|
if (autoUpdate && autoUpdate === true) {
|
|
|
|
autoUpdate = 'install';
|
|
|
|
}
|
|
|
|
return autoUpdate;
|
2015-10-29 22:20:01 +01:00
|
|
|
},
|
2015-11-14 16:28:36 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
updateInProgress() {
|
2019-08-16 23:05:39 +02:00
|
|
|
return (
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.status === 'checking' ||
|
2021-01-08 22:54:45 +01:00
|
|
|
['downloading', 'extracting', 'updating'].indexOf(UpdateModel.updateStatus) >= 0
|
2019-08-16 23:05:39 +02:00
|
|
|
);
|
2015-11-14 12:09:36 +01:00
|
|
|
},
|
2015-11-14 16:28:36 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
init() {
|
2016-04-23 21:53:48 +02:00
|
|
|
this.scheduleNextCheck();
|
2019-09-23 20:32:56 +02:00
|
|
|
if (!Launcher && navigator.serviceWorker && !RuntimeInfo.beta && !RuntimeInfo.devMode) {
|
|
|
|
navigator.serviceWorker
|
|
|
|
.register('service-worker.js')
|
2020-06-01 16:53:51 +02:00
|
|
|
.then((reg) => {
|
2019-09-23 20:32:56 +02:00
|
|
|
logger.info('Service worker registered');
|
|
|
|
reg.addEventListener('updatefound', () => {
|
|
|
|
if (reg.active) {
|
|
|
|
logger.info('Service worker found an update');
|
|
|
|
UpdateModel.set({ updateStatus: 'ready' });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
2020-06-01 16:53:51 +02:00
|
|
|
.catch((e) => {
|
2019-09-23 20:32:56 +02:00
|
|
|
logger.error('Failed to register a service worker', e);
|
|
|
|
});
|
2015-11-14 16:28:36 +01:00
|
|
|
}
|
2015-10-29 22:20:01 +01:00
|
|
|
},
|
2015-11-14 16:28:36 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
scheduleNextCheck() {
|
2015-10-29 22:20:01 +01:00
|
|
|
if (this.nextCheckTimeout) {
|
|
|
|
clearTimeout(this.nextCheckTimeout);
|
|
|
|
this.nextCheckTimeout = null;
|
|
|
|
}
|
2015-11-16 20:04:33 +01:00
|
|
|
if (!this.getAutoUpdateType()) {
|
2015-10-29 22:20:01 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-01-31 07:50:28 +01:00
|
|
|
let timeDiff = this.MinUpdateTimeout;
|
2019-09-17 21:56:58 +02:00
|
|
|
const lastCheckDate = UpdateModel.lastCheckDate;
|
2015-10-29 22:20:01 +01:00
|
|
|
if (lastCheckDate) {
|
2019-08-16 23:05:39 +02:00
|
|
|
timeDiff = Math.min(
|
|
|
|
Math.max(this.UpdateInterval + (lastCheckDate - new Date()), this.MinUpdateTimeout),
|
|
|
|
this.UpdateInterval
|
|
|
|
);
|
2015-10-29 22:20:01 +01:00
|
|
|
}
|
|
|
|
this.nextCheckTimeout = setTimeout(this.check.bind(this), timeDiff);
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.info('Next update check will happen in ' + Math.round(timeDiff / 1000) + 's');
|
2015-10-29 22:20:01 +01:00
|
|
|
},
|
2015-11-14 16:28:36 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
check(startedByUser) {
|
2016-02-06 12:40:40 +01:00
|
|
|
if (!this.enabled || this.updateInProgress()) {
|
2015-11-14 16:28:36 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({ status: 'checking' });
|
2015-11-13 20:56:22 +01:00
|
|
|
if (!startedByUser) {
|
|
|
|
// additional protection from broken program logic, to ensure that auto-checks are not performed more than once an hour
|
2017-01-31 07:50:28 +01:00
|
|
|
const diffMs = new Date() - this.updateCheckDate;
|
2015-11-13 20:56:22 +01:00
|
|
|
if (isNaN(diffMs) || diffMs < 1000 * 60 * 60) {
|
2019-08-18 08:05:38 +02:00
|
|
|
logger.error(
|
|
|
|
'Prevented update check; last check was performed at ' + this.updateCheckDate
|
|
|
|
);
|
2016-07-17 13:30:38 +02:00
|
|
|
this.scheduleNextCheck();
|
2015-11-13 20:56:22 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.updateCheckDate = new Date();
|
|
|
|
}
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.info('Checking for update...');
|
2015-11-13 20:56:22 +01:00
|
|
|
Transport.httpGet({
|
2021-01-07 23:00:16 +01:00
|
|
|
url: Links.UpdateJson,
|
|
|
|
json: true,
|
|
|
|
success: (updateJson) => {
|
2017-01-31 07:50:28 +01:00
|
|
|
const dt = new Date();
|
2021-01-07 23:00:16 +01:00
|
|
|
logger.info('Update check: ' + (updateJson.version || 'unknown'));
|
|
|
|
if (!updateJson.version) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const errMsg = 'No version info found';
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({
|
2019-08-18 08:05:38 +02:00
|
|
|
status: 'error',
|
|
|
|
lastCheckDate: dt,
|
|
|
|
lastCheckError: errMsg
|
|
|
|
});
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.save();
|
2016-07-17 13:30:38 +02:00
|
|
|
this.scheduleNextCheck();
|
2015-10-25 20:26:33 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-09-17 21:56:58 +02:00
|
|
|
const prevLastVersion = UpdateModel.lastVersion;
|
|
|
|
UpdateModel.set({
|
2015-11-13 20:56:22 +01:00
|
|
|
status: 'ok',
|
|
|
|
lastCheckDate: dt,
|
|
|
|
lastSuccessCheckDate: dt,
|
2021-01-07 23:00:16 +01:00
|
|
|
lastVersionReleaseDate: new Date(updateJson.date),
|
|
|
|
lastVersion: updateJson.version,
|
2016-01-17 21:34:40 +01:00
|
|
|
lastCheckError: null,
|
2021-01-07 23:00:16 +01:00
|
|
|
lastCheckUpdMin: updateJson.minVersion || null
|
2015-11-13 20:56:22 +01:00
|
|
|
});
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.save();
|
2016-07-17 13:30:38 +02:00
|
|
|
this.scheduleNextCheck();
|
|
|
|
if (!this.canAutoUpdate()) {
|
2016-02-03 21:44:18 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
if (
|
2019-09-17 21:56:58 +02:00
|
|
|
prevLastVersion === UpdateModel.lastVersion &&
|
|
|
|
UpdateModel.updateStatus === 'ready'
|
2019-08-16 23:05:39 +02:00
|
|
|
) {
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.info('Waiting for the user to apply downloaded update');
|
2015-11-14 12:09:36 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-07-17 13:30:38 +02:00
|
|
|
if (!startedByUser && this.getAutoUpdateType() === 'install') {
|
|
|
|
this.update(startedByUser);
|
2019-08-18 08:05:38 +02:00
|
|
|
} else if (
|
2019-09-17 21:56:58 +02:00
|
|
|
SemVer.compareVersions(UpdateModel.lastVersion, RuntimeInfo.version) > 0
|
2019-08-18 08:05:38 +02:00
|
|
|
) {
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({ updateStatus: 'found' });
|
2015-11-16 20:04:33 +01:00
|
|
|
}
|
2015-10-29 22:20:01 +01:00
|
|
|
},
|
2020-06-01 16:53:51 +02:00
|
|
|
error: (e) => {
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.error('Update check error', e);
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({
|
2015-11-13 20:56:22 +01:00
|
|
|
status: 'error',
|
|
|
|
lastCheckDate: new Date(),
|
|
|
|
lastCheckError: 'Error checking last version'
|
|
|
|
});
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.save();
|
2016-07-17 13:30:38 +02:00
|
|
|
this.scheduleNextCheck();
|
2015-10-29 22:20:01 +01:00
|
|
|
}
|
2015-10-25 20:26:33 +01:00
|
|
|
});
|
2015-10-29 22:20:01 +01:00
|
|
|
},
|
2015-11-14 16:28:36 +01:00
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
canAutoUpdate() {
|
2019-09-17 21:56:58 +02:00
|
|
|
const minLauncherVersion = UpdateModel.lastCheckUpdMin;
|
2016-02-03 21:44:18 +01:00
|
|
|
if (minLauncherVersion) {
|
2017-05-23 20:03:29 +02:00
|
|
|
const cmp = SemVer.compareVersions(Launcher.version, minLauncherVersion);
|
2016-02-07 12:49:31 +01:00
|
|
|
if (cmp < 0) {
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({ updateStatus: 'ready', updateManual: true });
|
2016-02-07 12:49:31 +01:00
|
|
|
return false;
|
2016-02-03 21:44:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2015-11-14 16:28:36 +01:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
update(startedByUser, successCallback) {
|
2019-09-17 21:56:58 +02:00
|
|
|
const ver = UpdateModel.lastVersion;
|
2016-02-06 12:40:40 +01:00
|
|
|
if (!this.enabled) {
|
|
|
|
logger.info('Updater is disabled');
|
|
|
|
return;
|
|
|
|
}
|
2017-05-23 20:03:29 +02:00
|
|
|
if (SemVer.compareVersions(RuntimeInfo.version, ver) >= 0) {
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.info('You are using the latest version');
|
2015-10-29 22:20:01 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({ updateStatus: 'downloading', updateError: null });
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.info('Downloading update', ver);
|
2021-01-08 22:54:45 +01:00
|
|
|
const updateAssetName = this.getUpdateAssetName(ver);
|
|
|
|
if (!updateAssetName) {
|
|
|
|
logger.error('Empty updater asset name for', Launcher.platform(), Launcher.arch());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const updateUrlBasePath = Links.UpdateBasePath.replace('{ver}', ver);
|
|
|
|
const updateAssetUrl = updateUrlBasePath + updateAssetName;
|
|
|
|
const useCache = !startedByUser;
|
2015-11-13 20:56:22 +01:00
|
|
|
Transport.httpGet({
|
2021-01-08 22:54:45 +01:00
|
|
|
url: updateAssetUrl,
|
|
|
|
file: updateAssetName,
|
|
|
|
cleanupOldFiles: true,
|
|
|
|
cache: useCache,
|
|
|
|
success: (assetFilePath) => {
|
|
|
|
logger.info('Downloading update signatures');
|
|
|
|
Transport.httpGet({
|
|
|
|
url: updateUrlBasePath + 'Verify.sign.sha256',
|
|
|
|
text: true,
|
|
|
|
file: updateAssetName + '.sign',
|
|
|
|
cleanupOldFiles: true,
|
|
|
|
cache: useCache,
|
|
|
|
success: (assetFileSignaturePath) => {
|
|
|
|
this.verifySignature(assetFilePath, updateAssetName, (err, valid) => {
|
|
|
|
if (err) {
|
|
|
|
UpdateModel.set({
|
|
|
|
updateStatus: 'error',
|
|
|
|
updateError: 'Error verifying update signature'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!valid) {
|
|
|
|
UpdateModel.set({
|
|
|
|
updateStatus: 'error',
|
|
|
|
updateError: 'Invalid update signature'
|
|
|
|
});
|
|
|
|
Launcher.deleteFile(assetFilePath);
|
|
|
|
Launcher.deleteFile(assetFileSignaturePath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
logger.info('Update is ready', assetFilePath);
|
|
|
|
UpdateModel.set({ updateStatus: 'ready', updateError: null });
|
|
|
|
if (!startedByUser) {
|
|
|
|
Events.emit('update-app');
|
|
|
|
}
|
|
|
|
if (typeof successCallback === 'function') {
|
|
|
|
successCallback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
error(e) {
|
|
|
|
logger.error('Error downloading update signatures', e);
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({
|
2019-08-18 08:05:38 +02:00
|
|
|
updateStatus: 'error',
|
2021-01-08 22:54:45 +01:00
|
|
|
updateError: 'Error downloading update signatures'
|
2019-08-18 08:05:38 +02:00
|
|
|
});
|
2015-11-13 20:56:22 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
error(e) {
|
2015-12-12 09:53:50 +01:00
|
|
|
logger.error('Error downloading update', e);
|
2019-09-17 21:56:58 +02:00
|
|
|
UpdateModel.set({
|
2019-08-18 08:05:38 +02:00
|
|
|
updateStatus: 'error',
|
|
|
|
updateError: 'Error downloading update'
|
|
|
|
});
|
2015-10-29 22:20:01 +01:00
|
|
|
}
|
2015-11-13 20:56:22 +01:00
|
|
|
});
|
|
|
|
},
|
2015-10-29 22:20:01 +01:00
|
|
|
|
2021-01-08 22:54:45 +01:00
|
|
|
verifySignature(assetFilePath, assetName, callback) {
|
|
|
|
logger.info('Verifying update signature', assetName);
|
|
|
|
const fs = Launcher.req('fs');
|
|
|
|
const signaturesTxt = fs.readFileSync(assetFilePath + '.sign', 'utf8');
|
|
|
|
const assetSignatureLine = signaturesTxt
|
|
|
|
.split('\n')
|
|
|
|
.find((line) => line.endsWith(assetName));
|
|
|
|
if (!assetSignatureLine) {
|
|
|
|
logger.error('Signature not found for asset', assetName);
|
|
|
|
callback('Asset signature not found');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const signature = kdbxweb.ByteUtils.hexToBytes(assetSignatureLine.split(' ')[0]);
|
|
|
|
const fileBytes = fs.readFileSync(assetFilePath);
|
|
|
|
SignatureVerifier.verify(fileBytes, signature)
|
|
|
|
.catch((e) => {
|
|
|
|
logger.error('Error verifying signature', e);
|
|
|
|
callback('Error verifying signature');
|
|
|
|
})
|
|
|
|
.then((valid) => {
|
|
|
|
logger.info(`Update asset signature is ${valid ? 'valid' : 'invalid'}`);
|
|
|
|
callback(undefined, valid);
|
2015-11-13 20:56:22 +01:00
|
|
|
});
|
2015-11-14 16:28:36 +01:00
|
|
|
},
|
|
|
|
|
2021-01-08 22:54:45 +01:00
|
|
|
getUpdateAssetName(ver) {
|
|
|
|
const platform = Launcher.platform();
|
|
|
|
const arch = Launcher.arch();
|
|
|
|
switch (platform) {
|
|
|
|
case 'win32':
|
|
|
|
switch (arch) {
|
|
|
|
case 'x64':
|
|
|
|
return `KeeWeb-${ver}.win.x64.exe`;
|
|
|
|
case 'ia32':
|
|
|
|
return `KeeWeb-${ver}.win.ia32.exe`;
|
|
|
|
case 'arm64':
|
|
|
|
return `KeeWeb-${ver}.win.arm64.exe`;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'darwin':
|
|
|
|
switch (arch) {
|
|
|
|
case 'x64':
|
|
|
|
return `KeeWeb-${ver}.mac.x64.dmg`;
|
|
|
|
case 'arm64':
|
|
|
|
return `KeeWeb-${ver}.mac.arm64.dmg`;
|
|
|
|
}
|
|
|
|
break;
|
2016-03-05 12:16:12 +01:00
|
|
|
}
|
2021-01-08 22:54:45 +01:00
|
|
|
return undefined;
|
|
|
|
},
|
|
|
|
|
|
|
|
installAndRestart() {
|
|
|
|
if (!Launcher) {
|
|
|
|
return;
|
2016-03-05 12:16:12 +01:00
|
|
|
}
|
2021-01-08 22:54:45 +01:00
|
|
|
const updateAssetName = this.getUpdateAssetName(UpdateModel.lastVersion);
|
|
|
|
const updateFilePath = Transport.cacheFilePath(updateAssetName);
|
|
|
|
Launcher.requestRestartAndUpdate(updateFilePath);
|
2015-10-25 20:26:33 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-09-15 14:16:32 +02:00
|
|
|
export { Updater };
|