@todesktop/runtime
Advanced tools
Comparing version 0.2.0 to 0.2.1
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) { | ||
@@ -72,15 +81,17 @@ if (!privateMap.has(receiver)) { | ||
} | ||
async checkForUpdates({ source = BuiltInSources.programmaticCall, } = {}) { | ||
this._log("info", ".checkForUpdates called"); | ||
if (!__classPrivateFieldGet(this, _hasAppFinishedLaunching)) { | ||
const error = new Error("Cannot checkForUpdates before app is ready (see https://www.electronjs.org/docs/api/app#event-ready)"); | ||
this._log("error", error); | ||
throw error; | ||
} | ||
if (!__classPrivateFieldGet(this, _isActive)) { | ||
return { | ||
updateInfo: null, | ||
}; | ||
} | ||
return await this._check({ source }); | ||
checkForUpdates({ source = BuiltInSources.programmaticCall, } = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this._log("info", ".checkForUpdates called"); | ||
if (!__classPrivateFieldGet(this, _hasAppFinishedLaunching)) { | ||
const error = new Error("Cannot checkForUpdates before app is ready (see https://www.electronjs.org/docs/api/app#event-ready)"); | ||
this._log("error", error); | ||
throw error; | ||
} | ||
if (!__classPrivateFieldGet(this, _isActive)) { | ||
return { | ||
updateInfo: null, | ||
}; | ||
} | ||
return yield this._check({ source }); | ||
}); | ||
} | ||
@@ -97,95 +108,97 @@ restartAndInstall() { | ||
} | ||
async _actuallyPerformCheck() { | ||
this._log("debug", "_actuallyPerformCheck called"); | ||
let updateInfo; | ||
try { | ||
updateInfo = await __classPrivateFieldGet(this, _updaterAgent).checkAndDownload(); | ||
} | ||
catch (e) { | ||
this._log("debug", "Error during check & download; resetting this.hasUpdateReadyToInstall to be safe"); | ||
// When there's an error during the download, electron-updater wipes its temp directory | ||
__classPrivateFieldSet(this, _hasUpdateReadyToInstall, false); | ||
throw e; | ||
} | ||
// Reset the collected sources | ||
const previouslyPendingCheckSources = [ | ||
...__classPrivateFieldGet(this, _pendingCheckSources), | ||
]; | ||
__classPrivateFieldSet(this, _pendingCheckSources, []); | ||
// No update available; exit early | ||
if (!updateInfo) { | ||
this._log("debug", "No update available"); | ||
// To be safe | ||
__classPrivateFieldSet(this, _hasUpdateReadyToInstall, false); | ||
return { | ||
_actuallyPerformCheck() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this._log("debug", "_actuallyPerformCheck called"); | ||
let updateInfo; | ||
try { | ||
updateInfo = yield __classPrivateFieldGet(this, _updaterAgent).checkAndDownload(); | ||
} | ||
catch (e) { | ||
this._log("debug", "Error during check & download; resetting this.hasUpdateReadyToInstall to be safe"); | ||
// When there's an error during the download, electron-updater wipes its temp directory | ||
__classPrivateFieldSet(this, _hasUpdateReadyToInstall, false); | ||
throw e; | ||
} | ||
// Reset the collected sources | ||
const previouslyPendingCheckSources = [ | ||
...__classPrivateFieldGet(this, _pendingCheckSources), | ||
]; | ||
__classPrivateFieldSet(this, _pendingCheckSources, []); | ||
// No update available; exit early | ||
if (!updateInfo) { | ||
this._log("debug", "No update available"); | ||
// To be safe | ||
__classPrivateFieldSet(this, _hasUpdateReadyToInstall, false); | ||
return { | ||
updateInfo, | ||
}; | ||
} | ||
this._log("debug", "Update available", updateInfo); | ||
__classPrivateFieldSet(this, _hasUpdateReadyToInstall, true); | ||
// Emit the event | ||
const eventPayload = { | ||
sources: previouslyPendingCheckSources, | ||
updateInfo, | ||
}; | ||
} | ||
this._log("debug", "Update available", updateInfo); | ||
__classPrivateFieldSet(this, _hasUpdateReadyToInstall, true); | ||
// Emit the event | ||
const eventPayload = { | ||
sources: previouslyPendingCheckSources, | ||
updateInfo, | ||
}; | ||
this._log("debug", "Emitting update-downloaded event"); | ||
const emitPromise = this.emitAsync("update-downloaded", eventPayload); | ||
/* | ||
If they haven't disabled the default behaviour or they've already been notified of an update | ||
(we don't want to notify them on interval) | ||
*/ | ||
if (__classPrivateFieldGet(this, _shouldNotify) && !__classPrivateFieldGet(this, _hasNotified)) { | ||
this._log("debug", "Emitting update-downloaded event"); | ||
const emitPromise = this.emitAsync("update-downloaded", eventPayload); | ||
/* | ||
The default behaviour is to notify the end user. | ||
We allow this to be cancelled by returning false from any event listener (plain/Promise), | ||
within a time window. If the time elapses, we carry on with the default behaviour and | ||
ignore the listener response(s). | ||
If they haven't disabled the default behaviour or they've already been notified of an update | ||
(we don't want to notify them on interval) | ||
*/ | ||
// This is needed in case they plainly return false and then the setTimeout fires afterwards | ||
let hasReturnedFalseInEventListener = false; | ||
// This will notify the first time it's called. Subsequent calls are ignored | ||
const notify = once(() => { | ||
if (hasReturnedFalseInEventListener) { | ||
return; | ||
} | ||
__classPrivateFieldSet(this, _hasNotified, true); | ||
const notification = new electron.Notification({ | ||
title: "A new update is ready to install", | ||
body: `${electron.app.name} version ${updateInfo.version} has been downloaded and will be automatically installed on exit`, | ||
if (__classPrivateFieldGet(this, _shouldNotify) && !__classPrivateFieldGet(this, _hasNotified)) { | ||
/* | ||
The default behaviour is to notify the end user. | ||
We allow this to be cancelled by returning false from any event listener (plain/Promise), | ||
within a time window. If the time elapses, we carry on with the default behaviour and | ||
ignore the listener response(s). | ||
*/ | ||
// This is needed in case they plainly return false and then the setTimeout fires afterwards | ||
let hasReturnedFalseInEventListener = false; | ||
// This will notify the first time it's called. Subsequent calls are ignored | ||
const notify = once(() => { | ||
if (hasReturnedFalseInEventListener) { | ||
return; | ||
} | ||
__classPrivateFieldSet(this, _hasNotified, true); | ||
const notification = new electron.Notification({ | ||
title: "A new update is ready to install", | ||
body: `${electron.app.name} version ${updateInfo.version} has been downloaded and will be automatically installed on exit`, | ||
}); | ||
notification.show(); | ||
}); | ||
notification.show(); | ||
}); | ||
// They have a time-limited window to respond. Try to notify when it ends | ||
setTimeout_1.default(() => { | ||
this._log("debug", "before notify call in setTimeout"); | ||
notify(); | ||
}, 500); | ||
emitPromise | ||
.catch((e) => { | ||
this._log("debug", "event callback errored", e); | ||
// Ignore the error but immediately continue with the default behaviour | ||
notify(); | ||
}) | ||
.then((responses = []) => { | ||
// Ignore if they have returned false from any listener | ||
if (responses.some((response) => response === false)) { | ||
hasReturnedFalseInEventListener = true; | ||
return; | ||
} | ||
this._log("debug", "calling notify after event listener Promise settled"); | ||
// This won't do anything if they took too long to respond | ||
notify(); | ||
}); | ||
} | ||
else { | ||
// i.e. !shouldNotify | ||
// Reset the collected sources | ||
__classPrivateFieldSet(this, _pendingCheckSources, []); | ||
emitPromise.catch((e) => { | ||
this._log("debug", "event callback errored", e); | ||
// ignore | ||
}); | ||
} | ||
// Return as soon as possible (don't wait for listener responses) | ||
return { updateInfo }; | ||
// They have a time-limited window to respond. Try to notify when it ends | ||
setTimeout_1.default(() => { | ||
this._log("debug", "before notify call in setTimeout"); | ||
notify(); | ||
}, 500); | ||
emitPromise | ||
.catch((e) => { | ||
this._log("debug", "event callback errored", e); | ||
// Ignore the error but immediately continue with the default behaviour | ||
notify(); | ||
}) | ||
.then((responses = []) => { | ||
// Ignore if they have returned false from any listener | ||
if (responses.some((response) => response === false)) { | ||
hasReturnedFalseInEventListener = true; | ||
return; | ||
} | ||
this._log("debug", "calling notify after event listener Promise settled"); | ||
// This won't do anything if they took too long to respond | ||
notify(); | ||
}); | ||
} | ||
else { | ||
// i.e. !shouldNotify | ||
// Reset the collected sources | ||
__classPrivateFieldSet(this, _pendingCheckSources, []); | ||
emitPromise.catch((e) => { | ||
this._log("debug", "event callback errored", e); | ||
// ignore | ||
}); | ||
} | ||
// Return as soon as possible (don't wait for listener responses) | ||
return { updateInfo }; | ||
}); | ||
} | ||
@@ -195,5 +208,5 @@ _autoCheckOnInterval(interval) { | ||
this._log("debug", "checking for update on interval"); | ||
setTimeout_1.default(async () => { | ||
setTimeout_1.default(() => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
await this._check({ | ||
yield this._check({ | ||
source: BuiltInSources.autoCheckOnInterval, | ||
@@ -208,47 +221,51 @@ }); | ||
} | ||
}, interval); | ||
}), interval); | ||
}; | ||
checkAfterTimeout(); | ||
} | ||
async _autoCheckOnLaunch() { | ||
this._log("debug", "checking for update on launch"); | ||
try { | ||
await this._check({ | ||
source: BuiltInSources.autoCheckOnLaunch, | ||
_autoCheckOnLaunch() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this._log("debug", "checking for update on launch"); | ||
try { | ||
yield this._check({ | ||
source: BuiltInSources.autoCheckOnLaunch, | ||
}); | ||
} | ||
catch (e) { | ||
this._log("error", e); | ||
// Ignore. Auto-check on interval and programmatic API still supported | ||
} | ||
}); | ||
} | ||
_check({ source, }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
/* | ||
We collect the sources and emit them with the update-downloaded event. | ||
Other noteworthy bits: | ||
- The update check promise is reused by electron-updater. | ||
- We don't want more than one event emitted. | ||
- There's no sorting or de-duplication of the sources, on purpose. | ||
*/ | ||
__classPrivateFieldGet(this, _pendingCheckSources).push(source); | ||
this._log("debug", "_check called", { | ||
source, | ||
pendingCheckSources: __classPrivateFieldGet(this, _pendingCheckSources), | ||
}); | ||
} | ||
catch (e) { | ||
this._log("error", e); | ||
// Ignore. Auto-check on interval and programmatic API still supported | ||
} | ||
} | ||
async _check({ source, }) { | ||
/* | ||
We collect the sources and emit them with the update-downloaded event. | ||
Other noteworthy bits: | ||
- The update check promise is reused by electron-updater. | ||
- We don't want more than one event emitted. | ||
- There's no sorting or de-duplication of the sources, on purpose. | ||
*/ | ||
__classPrivateFieldGet(this, _pendingCheckSources).push(source); | ||
this._log("debug", "_check called", { | ||
source, | ||
pendingCheckSources: __classPrivateFieldGet(this, _pendingCheckSources), | ||
if (__classPrivateFieldGet(this, _pendingUpdateCheckPromise)) { | ||
return __classPrivateFieldGet(this, _pendingUpdateCheckPromise); | ||
} | ||
const onEnd = () => { | ||
__classPrivateFieldSet(this, _pendingUpdateCheckPromise, null); | ||
}; | ||
__classPrivateFieldSet(this, _pendingUpdateCheckPromise, this._actuallyPerformCheck() | ||
.then((updateCheckResult) => { | ||
onEnd(); | ||
return updateCheckResult; | ||
}) | ||
.catch((e) => { | ||
onEnd(); | ||
throw e; | ||
})); | ||
return __classPrivateFieldGet(this, _pendingUpdateCheckPromise); | ||
}); | ||
if (__classPrivateFieldGet(this, _pendingUpdateCheckPromise)) { | ||
return __classPrivateFieldGet(this, _pendingUpdateCheckPromise); | ||
} | ||
const onEnd = () => { | ||
__classPrivateFieldSet(this, _pendingUpdateCheckPromise, null); | ||
}; | ||
__classPrivateFieldSet(this, _pendingUpdateCheckPromise, this._actuallyPerformCheck() | ||
.then((updateCheckResult) => { | ||
onEnd(); | ||
return updateCheckResult; | ||
}) | ||
.catch((e) => { | ||
onEnd(); | ||
throw e; | ||
})); | ||
return __classPrivateFieldGet(this, _pendingUpdateCheckPromise); | ||
} | ||
@@ -255,0 +272,0 @@ /* |
@@ -1,1 +0,11 @@ | ||
export {}; | ||
import AutoUpdater from "./AutoUpdater"; | ||
import { LoggerInterface } from "./Logger"; | ||
declare class ToDesktop { | ||
autoUpdater: AutoUpdater; | ||
init({ autoUpdater, customLogger, }?: { | ||
autoUpdater?: boolean; | ||
customLogger?: LoggerInterface; | ||
}): void; | ||
} | ||
declare const _default: ToDesktop; | ||
export default _default; |
@@ -34,2 +34,3 @@ "use strict"; | ||
} | ||
exports.default = todesktop; | ||
module.exports = todesktop; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) { | ||
@@ -37,33 +46,35 @@ if (!privateMap.has(receiver)) { | ||
} | ||
async checkAndDownload() { | ||
try { | ||
electronUpdater.autoUpdater.autoDownload = false; | ||
const updateCheckResult = await electronUpdater.autoUpdater.checkForUpdates(); | ||
// No update available? | ||
if (!updateCheckResult || !updateCheckResult.updateInfo) { | ||
return null; | ||
checkAndDownload() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
electronUpdater.autoUpdater.autoDownload = false; | ||
const updateCheckResult = yield electronUpdater.autoUpdater.checkForUpdates(); | ||
// No update available? | ||
if (!updateCheckResult || !updateCheckResult.updateInfo) { | ||
return null; | ||
} | ||
// We need to make sure the version is actually newer | ||
const currentVersion = electron.app.getVersion(); | ||
const newVersion = updateCheckResult.updateInfo.version; | ||
__classPrivateFieldGet(this, _log).call(this, "debug", "Analysing autoUpdater.checkForUpdates result", { | ||
currentVersion, | ||
newVersion, | ||
updateInfo: updateCheckResult.updateInfo, | ||
}); | ||
if (!semver.gt(newVersion, currentVersion)) { | ||
return null; | ||
} | ||
yield electronUpdater.autoUpdater.downloadUpdate(updateCheckResult.cancellationToken); | ||
return { | ||
releaseDate: updateCheckResult.updateInfo.releaseDate, | ||
version: updateCheckResult.updateInfo.version, | ||
}; | ||
} | ||
// We need to make sure the version is actually newer | ||
const currentVersion = electron.app.getVersion(); | ||
const newVersion = updateCheckResult.updateInfo.version; | ||
__classPrivateFieldGet(this, _log).call(this, "debug", "Analysing autoUpdater.checkForUpdates result", { | ||
currentVersion, | ||
newVersion, | ||
updateInfo: updateCheckResult.updateInfo, | ||
}); | ||
if (!semver.gt(newVersion, currentVersion)) { | ||
return null; | ||
catch (e) { | ||
if (e.name === "CancellationError") { | ||
throw new Error("Update check cancelled"); | ||
} | ||
throw e; | ||
} | ||
await electronUpdater.autoUpdater.downloadUpdate(updateCheckResult.cancellationToken); | ||
return { | ||
releaseDate: updateCheckResult.updateInfo.releaseDate, | ||
version: updateCheckResult.updateInfo.version, | ||
}; | ||
} | ||
catch (e) { | ||
if (e.name === "CancellationError") { | ||
throw new Error("Update check cancelled"); | ||
} | ||
throw e; | ||
} | ||
}); | ||
} | ||
@@ -122,49 +133,51 @@ restartAndInstall() { | ||
*/ | ||
async checkAndDownload() { | ||
return new Promise((resolve, reject) => { | ||
const onError = (err) => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
removeListeners(); | ||
reject(err); | ||
}; | ||
const onUpdateDownloaded = (event, releaseNotes, releaseName, releaseDate, updateURL) => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
removeListeners(); | ||
/* | ||
If we can't grab the version from the URL, then just return a fake | ||
version that's higher than the current version. | ||
The server will only respond if the update is actually newer, so the | ||
only issue is if the API user decides to show the version in the UI, | ||
it'll be incorrect. | ||
That's not so bad compared to auto-updates failing because the URL | ||
pattern changed and our runtime library broke (nothing the user can | ||
see / control). | ||
*/ | ||
const version = this._getVersionFromNupkgUrl(updateURL) || | ||
semver.inc(electron.app.getVersion(), "patch"); | ||
resolve({ | ||
releaseDate: releaseDate.toString(), | ||
version, | ||
checkAndDownload() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return new Promise((resolve, reject) => { | ||
const onError = (err) => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
removeListeners(); | ||
reject(err); | ||
}; | ||
const onUpdateDownloaded = (event, releaseNotes, releaseName, releaseDate, updateURL) => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
removeListeners(); | ||
/* | ||
If we can't grab the version from the URL, then just return a fake | ||
version that's higher than the current version. | ||
The server will only respond if the update is actually newer, so the | ||
only issue is if the API user decides to show the version in the UI, | ||
it'll be incorrect. | ||
That's not so bad compared to auto-updates failing because the URL | ||
pattern changed and our runtime library broke (nothing the user can | ||
see / control). | ||
*/ | ||
const version = this._getVersionFromNupkgUrl(updateURL) || | ||
semver.inc(electron.app.getVersion(), "patch"); | ||
resolve({ | ||
releaseDate: releaseDate.toString(), | ||
version, | ||
}); | ||
}; | ||
const onUpdateNotAvailable = () => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
removeListeners(); | ||
resolve(null); | ||
}; | ||
const removeListeners = once(() => { | ||
electron.autoUpdater.off("error", onError); | ||
electron.autoUpdater.off("update-downloaded", onUpdateDownloaded); | ||
electron.autoUpdater.off("update-not-available", onUpdateNotAvailable); | ||
}); | ||
}; | ||
const onUpdateNotAvailable = () => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
removeListeners(); | ||
resolve(null); | ||
}; | ||
const removeListeners = once(() => { | ||
electron.autoUpdater.off("error", onError); | ||
electron.autoUpdater.off("update-downloaded", onUpdateDownloaded); | ||
electron.autoUpdater.off("update-not-available", onUpdateNotAvailable); | ||
electron.autoUpdater.once("error", onError); | ||
electron.autoUpdater.once("update-downloaded", onUpdateDownloaded); | ||
electron.autoUpdater.once("update-not-available", onUpdateNotAvailable); | ||
try { | ||
// This auto-downloads (and the version comparison is automatic) | ||
electron.autoUpdater.checkForUpdates(); | ||
} | ||
catch (e) { | ||
onError(e); | ||
} | ||
}); | ||
electron.autoUpdater.once("error", onError); | ||
electron.autoUpdater.once("update-downloaded", onUpdateDownloaded); | ||
electron.autoUpdater.once("update-not-available", onUpdateNotAvailable); | ||
try { | ||
// This auto-downloads (and the version comparison is automatic) | ||
electron.autoUpdater.checkForUpdates(); | ||
} | ||
catch (e) { | ||
onError(e); | ||
} | ||
}); | ||
@@ -171,0 +184,0 @@ } |
@@ -7,3 +7,3 @@ { | ||
"name": "@todesktop/runtime", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"license": "MIT", | ||
@@ -17,3 +17,3 @@ "author": "Adam Lynch <contact@adamlynch.com> (http://adamlynch.com/)", | ||
"scripts": { | ||
"build": "tsc --skipLibCheck --declaration -p .", | ||
"build": "tsc", | ||
"clear-test-cache": "ava reset-cache", | ||
@@ -20,0 +20,0 @@ "lint": "tsc --noEmit --skipLibCheck && eslint . --ext .js,.ts,.tsx", |
45001
794