diff --git a/desktop/package.json b/desktop/package.json index d35b6046..67bd9df8 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -30,6 +30,9 @@ "@electron-forge/maker-zip": "^6.0.0-beta.63", "@types/adm-zip": "^0.4.34", "@types/gh-pages": "^3.2.1", + "@types/github-url-to-object": "^4.0.1", + "@types/is-url": "^1.2.30", + "@types/ms": "^0.7.31", "adm-zip": "^0.5.9", "electron": "17.0.1", "gh-pages": "^3.2.3", @@ -37,11 +40,15 @@ "typescript": "~4.5.4" }, "dependencies": { + "axios": "^0.27.2", + "compare-versions": "^4.1.3", + "electron-is-dev": "^2.0.0", "electron-log": "^4.4.6", "electron-squirrel-startup": "^1.0.0", "electron-unhandled": "^4.0.0", - "electron-update-notifier": "^1.5.3", "electron-window-state": "^5.0.3", - "update-electron-app": "^2.0.1" + "github-url-to-object": "^4.0.6", + "is-url": "^1.2.4", + "ms": "^2.1.3" } } diff --git a/desktop/src/lib/electron-update-notifier.ts b/desktop/src/lib/electron-update-notifier.ts new file mode 100644 index 00000000..7be70ee5 --- /dev/null +++ b/desktop/src/lib/electron-update-notifier.ts @@ -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, + }); +}; diff --git a/desktop/src/lib/update-electron-app.ts b/desktop/src/lib/update-electron-app.ts new file mode 100644 index 00000000..95633e94 --- /dev/null +++ b/desktop/src/lib/update-electron-app.ts @@ -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 { + /** + * @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( + opts: IUpdateElectronAppOptions = {} +) { + // 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( + opts: IUpdateElectronAppOptions +) { + 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( + opts: IUpdateElectronAppOptions +): IUpdateElectronAppOptions { + 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 }; +} diff --git a/desktop/src/main/update.ts b/desktop/src/main/update.ts index 506acb09..59d5c5e5 100644 --- a/desktop/src/main/update.ts +++ b/desktop/src/main/update.ts @@ -2,26 +2,28 @@ import { app } from 'electron'; import { setUpdateNotification, checkForUpdates, -} from 'electron-update-notifier'; -import updateElectronApp from 'update-electron-app'; -import logger from 'electron-log'; +} from '../lib/electron-update-notifier'; +// import updateElectronApp from 'update-electron-app'; +// import logger from 'electron-log'; const repo = 'msgbyte/tailchat'; app.whenReady().then(() => { switch (process.platform) { // case 'darwin': // NOTICE: require codesign - case 'win32': - updateElectronApp({ - repo, - logger, - }); - break; - default: + // case 'win32': + // updateElectronApp({ + // repo, + // logger, + // }); + // break; + default: { + // 检测更新 setUpdateNotification({ repository: repo, // Optional, use repository field from your package.json when not specified silent: true, // Optional, notify when new version available, otherwise remain silent }); + } } }); diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 83f4b77d..ddce8891 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -413,6 +413,11 @@ resolved "https://registry.npmmirror.com/@types/gh-pages/-/gh-pages-3.2.1.tgz#0c47e024774461fce161570bca309c51d787ef67" integrity sha512-y5ULkwfoOEUa6sp2te+iEODv2S//DRiKmxpeXboXhhv+s758rSSxLUiBd6NnlR7aAY4nw1X4FGovLrSWEXWLow== +"@types/github-url-to-object@^4.0.1": + version "4.0.1" + resolved "https://registry.npmmirror.com/@types/github-url-to-object/-/github-url-to-object-4.0.1.tgz#aee08297988f2e2e4a98c19687e5ffdbf6f9d3d8" + integrity sha512-hrBTsGO3AGAvfOW4gpiaq7GCvi5c7bNlfbSetgf5eUwPGBKX88EpBdMxqhI1Q2LsrhsFXNQ7MsnkYDCA1wVfRw== + "@types/glob@^7.1.1": version "7.2.0" resolved "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -426,6 +431,11 @@ resolved "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== +"@types/is-url@^1.2.30": + version "1.2.30" + resolved "https://registry.npmmirror.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a" + integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw== + "@types/keyv@*": version "3.1.3" resolved "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41" @@ -438,6 +448,11 @@ resolved "https://registry.npmmirror.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== +"@types/ms@^0.7.31": + version "0.7.31" + resolved "https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== + "@types/node@*": version "17.0.18" resolved "https://registry.npmmirror.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" @@ -641,12 +656,13 @@ aws4@^1.8.0: resolved "https://registry.npmmirror.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.24.0: - version "0.24.0" - resolved "https://registry.npmmirror.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" - integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.npmmirror.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== dependencies: - follow-redirects "^1.14.4" + follow-redirects "^1.14.9" + form-data "^4.0.0" balanced-match@^1.0.0: version "1.0.2" @@ -897,7 +913,7 @@ color-support@^1.1.2: resolved "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -929,10 +945,10 @@ compare-version@^0.1.2: resolved "https://registry.npmmirror.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" integrity sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA= -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.npmmirror.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +compare-versions@^4.1.3: + version "4.1.3" + resolved "https://registry.npmmirror.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== concat-map@0.0.1: version "0.0.1" @@ -1179,11 +1195,6 @@ electron-installer-redhat@^3.2.0: word-wrap "^1.2.3" yargs "^16.0.2" -electron-is-dev@^0.3.0: - version "0.3.0" - resolved "https://registry.npmmirror.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe" - integrity sha512-jLttuuq8QK67n3mXmIe9pkrO7IH3LGIk12xJkhTmc852U2sCJaRAOpRGPSh+1Xnzck5v9escd9YXzuze9nGejg== - electron-is-dev@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/electron-is-dev/-/electron-is-dev-2.0.0.tgz#833487a069b8dad21425c67a19847d9064ab19bd" @@ -1275,15 +1286,6 @@ electron-unhandled@^4.0.0: ensure-error "^2.0.0" lodash.debounce "^4.0.8" -electron-update-notifier@^1.5.3: - version "1.5.3" - resolved "https://registry.npmmirror.com/electron-update-notifier/-/electron-update-notifier-1.5.3.tgz#23c4e6f255e2b8a8cc24061c742c180e2fd5fe99" - integrity sha512-nIru/heVVrlv8WI578KfaB/8oXlpLdkiAgzv6fb5x8O2kkWDymDGo4QJrWJPtPzB/TABnJhUURocN37r6XJdXQ== - dependencies: - axios "^0.24.0" - compare-versions "^3.6.0" - github-url-to-object "^4.0.4" - electron-window-state@^5.0.3: version "5.0.3" resolved "https://registry.npmmirror.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8" @@ -1551,16 +1553,25 @@ flora-colossus@^1.0.0: debug "^4.1.1" fs-extra "^7.0.0" -follow-redirects@^1.14.4: - version "1.14.9" - resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -1726,7 +1737,7 @@ gh-pages@^3.2.3: fs-extra "^8.1.0" globby "^6.1.0" -github-url-to-object@^4.0.4: +github-url-to-object@^4.0.6: version "4.0.6" resolved "https://registry.npmmirror.com/github-url-to-object/-/github-url-to-object-4.0.6.tgz#5ea8701dc8c336b8d582dc3fa5bf964165c3b365" integrity sha512-NaqbYHMUAlPcmWFdrAB7bcxrNIiiJWJe8s/2+iOc9vlcHlwHqSGrPk+Yi3nu6ebTwgsZEa7igz+NH2vEq3gYwQ== @@ -2492,7 +2503,7 @@ ms@2.1.2: resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1: +ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -3646,16 +3657,6 @@ universalify@^2.0.0: resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -update-electron-app@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/update-electron-app/-/update-electron-app-2.0.1.tgz#229dbeb4534f51ec949e6a46ae1ac32f68a17eed" - integrity sha512-e4xEner89UZZaBGYJbYlMdL1uUrC0VjOsTAL2N4opPjzFtn+j7mdsJJsnyXZzUVeLY+8tuCX4XEsUM98oBHmZg== - dependencies: - electron-is-dev "^0.3.0" - github-url-to-object "^4.0.4" - is-url "^1.2.4" - ms "^2.1.1" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"