mirror of https://github.com/msgbyte/tailchat
refactor: 调整桌面版依赖
parent
385efc6b6e
commit
fc8915430f
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* Fork from https://github.com/ankurk91/electron-update-notifier/blob/master/src/index.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import axios from 'axios';
|
||||||
|
import electron from 'electron';
|
||||||
|
import compareVersions from 'compare-versions';
|
||||||
|
import gh from 'github-url-to-object';
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
repository?: string;
|
||||||
|
token?: string;
|
||||||
|
debug?: boolean;
|
||||||
|
silent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GithubReleaseObject {
|
||||||
|
tag_name: string;
|
||||||
|
body: string;
|
||||||
|
html_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOptions: Options = {
|
||||||
|
debug: false, // force run in development
|
||||||
|
silent: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setUpdateNotification(options: Options = defaultOptions) {
|
||||||
|
const withDefaults = Object.assign(defaultOptions, options);
|
||||||
|
|
||||||
|
if (electron.app.isReady()) {
|
||||||
|
checkForUpdates(withDefaults);
|
||||||
|
} else {
|
||||||
|
electron.app.on('ready', () => {
|
||||||
|
checkForUpdates(withDefaults);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkForUpdates({
|
||||||
|
repository,
|
||||||
|
token,
|
||||||
|
debug,
|
||||||
|
silent,
|
||||||
|
}: Options = defaultOptions) {
|
||||||
|
if (!electron.app.isPackaged && !debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!repository) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const pkg = require(path.join(electron.app.getAppPath(), 'package.json'));
|
||||||
|
const ghObj = gh(pkg.repository);
|
||||||
|
|
||||||
|
if (!ghObj) {
|
||||||
|
throw new Error('Repository URL not found in package.json file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
repository = ghObj.user + '/' + ghObj.repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
let latestRelease: null | GithubReleaseObject = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: releases } = await axios.get(
|
||||||
|
`https://api.github.com/repos/${repository}/releases`,
|
||||||
|
{
|
||||||
|
headers: token ? { authorization: `token ${token}` } : {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
latestRelease = releases[0] as GithubReleaseObject;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
showDialog(
|
||||||
|
'Unable to check for updates at this moment. Try again.',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!latestRelease) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
compareVersions.compare(
|
||||||
|
latestRelease.tag_name,
|
||||||
|
electron.app.getVersion(),
|
||||||
|
'>'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
showUpdateDialog(latestRelease);
|
||||||
|
} else {
|
||||||
|
if (!silent) {
|
||||||
|
showDialog(`You are already running the latest version.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showUpdateDialog(release: GithubReleaseObject) {
|
||||||
|
electron.dialog
|
||||||
|
.showMessageBox({
|
||||||
|
title: electron.app.getName(),
|
||||||
|
type: 'info',
|
||||||
|
message: `New release available`,
|
||||||
|
detail:
|
||||||
|
`Installed Version: ${electron.app.getVersion()}\nLatest Version: ${
|
||||||
|
release.tag_name
|
||||||
|
}\n\n${release.body}`.trim(),
|
||||||
|
buttons: ['Download', 'Later'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 1,
|
||||||
|
})
|
||||||
|
.then(({ response }) => {
|
||||||
|
if (response === 0) {
|
||||||
|
setImmediate(() => {
|
||||||
|
electron.shell.openExternal(release.html_url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
throw new Error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const showDialog = (detail: string, type = 'info') => {
|
||||||
|
electron.dialog.showMessageBox({
|
||||||
|
title: electron.app.getName(),
|
||||||
|
message: 'Update checker',
|
||||||
|
buttons: ['Close'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0,
|
||||||
|
type,
|
||||||
|
detail,
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,217 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import isURL from 'is-url';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
|
import ms from 'ms';
|
||||||
|
import gh from 'github-url-to-object';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import os from 'os';
|
||||||
|
import { format } from 'util';
|
||||||
|
import pkg from '../../package.json';
|
||||||
|
const userAgent = format(
|
||||||
|
'%s/%s (%s: %s)',
|
||||||
|
pkg.name,
|
||||||
|
pkg.version,
|
||||||
|
os.platform(),
|
||||||
|
os.arch()
|
||||||
|
);
|
||||||
|
const supportedPlatforms = ['darwin', 'win32'];
|
||||||
|
|
||||||
|
interface ILogger {
|
||||||
|
log(...args: any[]): void;
|
||||||
|
info(...args: any[]): void;
|
||||||
|
error(...args: any[]): void;
|
||||||
|
warn(...args: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IUpdateElectronAppOptions<L = ILogger> {
|
||||||
|
/**
|
||||||
|
* @param {String} repo A GitHub repository in the format `owner/repo`.
|
||||||
|
* Defaults to your `package.json`'s `"repository"` field
|
||||||
|
*/
|
||||||
|
readonly repo?: string;
|
||||||
|
/**
|
||||||
|
* @param {String} host Defaults to `https://update.electronjs.org`
|
||||||
|
*/
|
||||||
|
readonly host?: string;
|
||||||
|
/**
|
||||||
|
* @param {String} updateInterval How frequently to check for updates. Defaults to `10 minutes`.
|
||||||
|
* Minimum allowed interval is `5 minutes`.
|
||||||
|
*/
|
||||||
|
readonly updateInterval?: string;
|
||||||
|
/**
|
||||||
|
* @param {Object} logger A custom logger object that defines a `log` function.
|
||||||
|
* Defaults to `console`. See electron-log, a module
|
||||||
|
* that aggregates logs from main and renderer processes into a single file.
|
||||||
|
*/
|
||||||
|
readonly logger?: L;
|
||||||
|
/**
|
||||||
|
* @param {Boolean} notifyUser Defaults to `true`. When enabled the user will be
|
||||||
|
* prompted to apply the update immediately after download.
|
||||||
|
*/
|
||||||
|
readonly notifyUser?: boolean;
|
||||||
|
|
||||||
|
electron?: typeof Electron.CrossProcessExports;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateElectronApp<L extends ILogger = ILogger>(
|
||||||
|
opts: IUpdateElectronAppOptions<L> = {}
|
||||||
|
) {
|
||||||
|
// check for bad input early, so it will be logged during development
|
||||||
|
opts = validateInput(opts);
|
||||||
|
|
||||||
|
// don't attempt to update during development
|
||||||
|
if (isDev) {
|
||||||
|
const message =
|
||||||
|
'update-electron-app config looks good; aborting updates since app is in development mode';
|
||||||
|
opts.logger ? opts.logger.log(message) : console.log(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.electron.app.isReady()
|
||||||
|
? initUpdater(opts)
|
||||||
|
: opts.electron.app.on('ready', () => initUpdater(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initUpdater<L extends ILogger = ILogger>(
|
||||||
|
opts: IUpdateElectronAppOptions<L>
|
||||||
|
) {
|
||||||
|
const { host, repo, updateInterval, logger, electron } = opts;
|
||||||
|
const { app, autoUpdater, dialog } = electron;
|
||||||
|
const feedURL = `${host}/${repo}/${process.platform}-${
|
||||||
|
process.arch
|
||||||
|
}/${app.getVersion()}`;
|
||||||
|
const requestHeaders = { 'User-Agent': userAgent };
|
||||||
|
|
||||||
|
function log(...args: any[]) {
|
||||||
|
logger.log(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit early on unsupported platforms, e.g. `linux`
|
||||||
|
if (
|
||||||
|
typeof process !== 'undefined' &&
|
||||||
|
process.platform &&
|
||||||
|
!supportedPlatforms.includes(process.platform)
|
||||||
|
) {
|
||||||
|
log(
|
||||||
|
`Electron's autoUpdater does not support the '${process.platform}' platform`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('feedURL', feedURL);
|
||||||
|
log('requestHeaders', requestHeaders);
|
||||||
|
autoUpdater.setFeedURL({
|
||||||
|
url: feedURL,
|
||||||
|
headers: requestHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('error', (err) => {
|
||||||
|
log('updater error');
|
||||||
|
log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('checking-for-update', () => {
|
||||||
|
log('checking-for-update');
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-available', () => {
|
||||||
|
log('update-available; downloading...');
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-not-available', () => {
|
||||||
|
log('update-not-available');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts.notifyUser) {
|
||||||
|
autoUpdater.on(
|
||||||
|
'update-downloaded',
|
||||||
|
(event, releaseNotes, releaseName, releaseDate, updateURL) => {
|
||||||
|
log('update-downloaded', [
|
||||||
|
event,
|
||||||
|
releaseNotes,
|
||||||
|
releaseName,
|
||||||
|
releaseDate,
|
||||||
|
updateURL,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dialogOpts = {
|
||||||
|
type: 'info',
|
||||||
|
buttons: ['Restart', 'Later'],
|
||||||
|
title: 'Application Update',
|
||||||
|
message: process.platform === 'win32' ? releaseNotes : releaseName,
|
||||||
|
detail:
|
||||||
|
'A new version has been downloaded. Restart the application to apply the updates.',
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.showMessageBox(dialogOpts).then(({ response }) => {
|
||||||
|
if (response === 0) autoUpdater.quitAndInstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for updates right away and keep checking later
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
setInterval(() => {
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
}, ms(updateInterval));
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateInput<L extends ILogger = ILogger>(
|
||||||
|
opts: IUpdateElectronAppOptions<L>
|
||||||
|
): IUpdateElectronAppOptions<L> {
|
||||||
|
const defaults = {
|
||||||
|
host: 'https://update.electronjs.org',
|
||||||
|
updateInterval: '10 minutes',
|
||||||
|
logger: console,
|
||||||
|
notifyUser: true,
|
||||||
|
};
|
||||||
|
const { host, updateInterval, logger, notifyUser } = Object.assign(
|
||||||
|
{},
|
||||||
|
defaults,
|
||||||
|
opts
|
||||||
|
);
|
||||||
|
|
||||||
|
// allows electron to be mocked in tests
|
||||||
|
const electron = opts.electron || require('electron');
|
||||||
|
|
||||||
|
let repo = opts.repo;
|
||||||
|
if (!repo) {
|
||||||
|
const pkgBuf = fs.readFileSync(
|
||||||
|
path.join(electron.app.getAppPath(), 'package.json')
|
||||||
|
);
|
||||||
|
const pkg = JSON.parse(pkgBuf.toString());
|
||||||
|
const repoString = (pkg.repository && pkg.repository.url) || pkg.repository;
|
||||||
|
const repoObject = gh(repoString);
|
||||||
|
assert(
|
||||||
|
repoObject,
|
||||||
|
"repo not found. Add repository string to your app's package.json file"
|
||||||
|
);
|
||||||
|
repo = `${repoObject.user}/${repoObject.repo}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(
|
||||||
|
repo && repo.length && repo.includes('/'),
|
||||||
|
'repo is required and should be in the format `owner/repo`'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
isURL(host) && host.startsWith('https'),
|
||||||
|
'host must be a valid HTTPS URL'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
typeof updateInterval === 'string' && updateInterval.match(/^\d+/),
|
||||||
|
'updateInterval must be a human-friendly string interval like `20 minutes`'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
ms(updateInterval) >= 5 * 60 * 1000,
|
||||||
|
'updateInterval must be `5 minutes` or more'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(logger && typeof logger.log, 'function');
|
||||||
|
|
||||||
|
return { host, repo, updateInterval, logger, electron, notifyUser };
|
||||||
|
}
|
Loading…
Reference in New Issue