Skip to content

Instantly share code, notes, and snippets.

@daqi
Last active January 24, 2024 03:36
Show Gist options
  • Save daqi/39ad2082da525b0c08de79c7cc34fddb to your computer and use it in GitHub Desktop.
Save daqi/39ad2082da525b0c08de79c7cc34fddb to your computer and use it in GitHub Desktop.
linux-updater
import { EventEmitter } from 'events';
import {
eq as isVersionsEqual,
gt as isVersionGreaterThan,
lt as isVersionLessThan,
parse as parseVersion,
} from 'semver';
import { safeLoad } from 'js-yaml';
import { app, shell, net } from 'electron';
import { Logger } from '../logger';
function newError(message: string, code: string) {
const error = new Error(message);
(error as NodeJS.ErrnoException).code = code;
return error;
}
type UpdateCheckResult = any;
type UpdateInfo = {
version: string;
files: { url: string }[];
};
type Option = {
url: string;
downloadUrl: string;
};
class LinuxUpdater extends EventEmitter {
_logger = Logger;
checkForUpdatesPromise: Promise<UpdateCheckResult> | null = null;
currentVersion: string;
allowDowngrade = false;
url: string;
downloadUrl: string;
constructor(options: Option) {
super();
this.url = options.url;
this.downloadUrl = options.downloadUrl;
this.currentVersion = app.getVersion();
}
checkForUpdates(): Promise<UpdateCheckResult> {
let checkForUpdatesPromise = this.checkForUpdatesPromise;
if (checkForUpdatesPromise != null) {
this._logger.info('[LinuxUpdater] Checking for update (already in progress)');
return checkForUpdatesPromise;
}
const nullizePromise = () => (this.checkForUpdatesPromise = null);
this._logger.info('[LinuxUpdater] Checking for update');
checkForUpdatesPromise = this.doCheckForUpdates()
.then((it) => {
nullizePromise();
return it;
})
.catch((e) => {
nullizePromise();
this.emit('error', e, `Cannot check for updates: ${(e.stack || e).toString()}`);
throw e;
});
this.checkForUpdatesPromise = checkForUpdatesPromise;
return checkForUpdatesPromise;
}
private async doCheckForUpdates(): Promise<UpdateCheckResult> {
this.emit('checking-for-update');
const updateInfo = await this.getUpdateInfo();
if (!(await this.isUpdateAvailable(updateInfo))) {
this._logger.info(
`[LinuxUpdater] Update for version ${this.currentVersion} is not available (latest version: ${
updateInfo.version
}, downgrade is ${this.allowDowngrade ? 'allowed' : 'disallowed'}).`
);
this.emit('update-not-available', updateInfo);
return {
versionInfo: updateInfo,
updateInfo,
};
}
this.onUpdateAvailable(updateInfo);
//noinspection ES6MissingAwait
return {
versionInfo: updateInfo,
updateInfo,
};
}
protected onUpdateAvailable(updateInfo: UpdateInfo): void {
this._logger.info(
`[LinuxUpdater] Found version ${updateInfo.version} (url: ${updateInfo.files
.map((it) => it.url)
.join(', ')})`
);
this.emit('update-available', updateInfo);
}
private async getUpdateInfo(): Promise<UpdateInfo> {
const text = await new Promise<string>((rz, rj) => {
const request = net.request(this.url + `?noCache=${Date.now()}`);
let body = '';
request.on('response', (response) => {
if (response.statusCode !== 200) {
rj(response.statusMessage);
return;
}
response.on('data', (chunk) => {
body += chunk.toString();
});
response.on('end', () => {
rz(body);
});
});
request.on('error', (err) => {
rj(err);
});
request.end();
});
const info = safeLoad(text) as UpdateInfo;
return info;
}
private async isUpdateAvailable(updateInfo: UpdateInfo): Promise<boolean> {
const latestVersion = parseVersion(updateInfo.version);
if (latestVersion == null) {
throw newError(
`This file could not be downloaded, or the latest version (from update server) does not have a valid semver version: "${updateInfo.version}"`,
'ERR_UPDATER_INVALID_VERSION'
);
}
const currentVersion = this.currentVersion;
if (isVersionsEqual(latestVersion, currentVersion)) {
return false;
}
const isLatestVersionNewer = isVersionGreaterThan(latestVersion, currentVersion);
const isLatestVersionOlder = isVersionLessThan(latestVersion, currentVersion);
if (isLatestVersionNewer) {
return true;
}
return this.allowDowngrade && isLatestVersionOlder;
}
public goDownload() {
shell.openExternal(this.downloadUrl);
}
}
const url = 'https://artifact.xxxxxxxx.com/download/ynote-electron/latest-linux.yml';
const downloadUrl = 'https://note.youdao.com/download.html';
let _autoUpdater: any;
export declare const linuxUpdater: LinuxUpdater;
Object.defineProperty(exports, 'linuxUpdater', {
enumerable: true,
get: () => {
if (_autoUpdater) return _autoUpdater;
_autoUpdater = new LinuxUpdater({ url, downloadUrl });
return _autoUpdater;
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment