@wildwinter/app-shell 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES.md +62 -0
- package/README.md +8 -0
- package/dist/about.css +17 -38
- package/dist/about.css.map +1 -1
- package/dist/anchored.css +9 -9
- package/dist/anchored.css.map +1 -1
- package/dist/app-store.cjs +15 -0
- package/dist/app-store.cjs.map +1 -1
- package/dist/app-store.d.cts +25 -1
- package/dist/app-store.d.ts +25 -1
- package/dist/app-store.js +14 -1
- package/dist/app-store.js.map +1 -1
- package/dist/chunk-2ECJUTSX.js +97 -0
- package/dist/chunk-2ECJUTSX.js.map +1 -0
- package/dist/chunk-DS4XXAZS.js +56 -0
- package/dist/chunk-DS4XXAZS.js.map +1 -0
- package/dist/chunk-EHR2KBZC.js +82 -0
- package/dist/chunk-EHR2KBZC.js.map +1 -0
- package/dist/chunk-IU7EY6BI.js +26 -0
- package/dist/chunk-IU7EY6BI.js.map +1 -0
- package/dist/chunk-OPZPFOPS.js +66 -0
- package/dist/chunk-OPZPFOPS.js.map +1 -0
- package/dist/chunk-P4G4ERG6.js +60 -0
- package/dist/chunk-P4G4ERG6.js.map +1 -0
- package/dist/chunk-P6WLAEKZ.js +71 -0
- package/dist/chunk-P6WLAEKZ.js.map +1 -0
- package/dist/chunk-R3NXPHRZ.js +534 -0
- package/dist/chunk-R3NXPHRZ.js.map +1 -0
- package/dist/chunk-SDOKZTAR.js +87 -0
- package/dist/chunk-SDOKZTAR.js.map +1 -0
- package/dist/chunk-SEWJLUOD.js +70 -0
- package/dist/chunk-SEWJLUOD.js.map +1 -0
- package/dist/chunk-UG2P777F.js +587 -0
- package/dist/chunk-UG2P777F.js.map +1 -0
- package/dist/chunk-YRFJLJUH.js +55 -0
- package/dist/chunk-YRFJLJUH.js.map +1 -0
- package/dist/chunk-YWMSQFXX.js +146 -0
- package/dist/chunk-YWMSQFXX.js.map +1 -0
- package/dist/comments.css +16 -14
- package/dist/comments.css.map +1 -1
- package/dist/confirm.css +2 -55
- package/dist/confirm.css.map +1 -1
- package/dist/context-menu.cjs +250 -0
- package/dist/context-menu.cjs.map +1 -0
- package/dist/context-menu.css +64 -0
- package/dist/context-menu.css.map +1 -0
- package/dist/context-menu.d.cts +25 -0
- package/dist/context-menu.d.ts +25 -0
- package/dist/context-menu.js +5 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/controls.css +214 -0
- package/dist/controls.css.map +1 -0
- package/dist/dialog.cjs +222 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.css +56 -0
- package/dist/dialog.css.map +1 -0
- package/dist/dialog.d.cts +28 -0
- package/dist/dialog.d.ts +28 -0
- package/dist/dialog.js +5 -0
- package/dist/dialog.js.map +1 -0
- package/dist/floating.cjs +83 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.d.cts +25 -0
- package/dist/floating.d.ts +25 -0
- package/dist/floating.js +4 -0
- package/dist/floating.js.map +1 -0
- package/dist/identity.css +15 -50
- package/dist/identity.css.map +1 -1
- package/dist/index.cjs +1553 -260
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +385 -152
- package/dist/index.d.ts +385 -152
- package/dist/index.js +264 -823
- package/dist/index.js.map +1 -1
- package/dist/job.css +24 -1
- package/dist/job.css.map +1 -1
- package/dist/keys.cjs +155 -0
- package/dist/keys.cjs.map +1 -0
- package/dist/keys.css +41 -0
- package/dist/keys.css.map +1 -0
- package/dist/keys.d.cts +57 -0
- package/dist/keys.d.ts +57 -0
- package/dist/keys.js +3 -0
- package/dist/keys.js.map +1 -0
- package/dist/link-status.cjs +430 -0
- package/dist/link-status.cjs.map +1 -0
- package/dist/link-status.css +76 -0
- package/dist/link-status.css.map +1 -0
- package/dist/link-status.d.cts +53 -0
- package/dist/link-status.d.ts +53 -0
- package/dist/link-status.js +5 -0
- package/dist/link-status.js.map +1 -0
- package/dist/menu.cjs +62 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.d.cts +88 -1
- package/dist/menu.d.ts +88 -1
- package/dist/menu.js +1 -1
- package/dist/notes-editor.css +9 -52
- package/dist/notes-editor.css.map +1 -1
- package/dist/pane-shell.css +23 -5
- package/dist/pane-shell.css.map +1 -1
- package/dist/problems.cjs +61 -0
- package/dist/problems.cjs.map +1 -0
- package/dist/problems.d.cts +57 -0
- package/dist/problems.d.ts +57 -0
- package/dist/problems.js +3 -0
- package/dist/problems.js.map +1 -0
- package/dist/property-list-D3VZCJzi.d.cts +162 -0
- package/dist/property-list-D3VZCJzi.d.ts +162 -0
- package/dist/property-list.cjs +784 -0
- package/dist/property-list.cjs.map +1 -0
- package/dist/property-list.d.cts +1 -0
- package/dist/property-list.d.ts +1 -0
- package/dist/property-list.js +4 -0
- package/dist/property-list.js.map +1 -0
- package/dist/save.css +1 -1
- package/dist/save.css.map +1 -1
- package/dist/settings.css +62 -18
- package/dist/settings.css.map +1 -1
- package/dist/stale.css +4 -1
- package/dist/stale.css.map +1 -1
- package/dist/stepper.css +47 -3
- package/dist/stepper.css.map +1 -1
- package/dist/toast.css +5 -5
- package/dist/toast.css.map +1 -1
- package/dist/tokens.css +20 -0
- package/dist/tokens.css.map +1 -1
- package/dist/tool-window.cjs +63 -0
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.css +8 -4
- package/dist/tool-window.css.map +1 -1
- package/dist/tool-window.d.cts +67 -1
- package/dist/tool-window.d.ts +67 -1
- package/dist/tool-window.js +63 -1
- package/dist/tool-window.js.map +1 -1
- package/dist/tooltip.css +3 -3
- package/dist/tooltip.css.map +1 -1
- package/dist/updater-view.cjs +283 -0
- package/dist/updater-view.cjs.map +1 -0
- package/dist/updater-view.d.cts +23 -0
- package/dist/updater-view.d.ts +23 -0
- package/dist/updater-view.js +6 -0
- package/dist/updater-view.js.map +1 -0
- package/dist/updater.cjs +12 -16
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.css +44 -0
- package/dist/updater.css.map +1 -0
- package/dist/updater.js +12 -16
- package/dist/updater.js.map +1 -1
- package/dist/util.cjs +78 -0
- package/dist/util.cjs.map +1 -0
- package/dist/util.d.cts +37 -0
- package/dist/util.d.ts +37 -0
- package/dist/util.js +3 -0
- package/dist/util.js.map +1 -0
- package/dist/vc.css +4 -3
- package/dist/vc.css.map +1 -1
- package/dist/welcome.cjs +215 -0
- package/dist/welcome.cjs.map +1 -0
- package/dist/welcome.css +112 -0
- package/dist/welcome.css.map +1 -0
- package/dist/welcome.d.cts +55 -0
- package/dist/welcome.d.ts +55 -0
- package/dist/welcome.js +4 -0
- package/dist/welcome.js.map +1 -0
- package/package.json +72 -2
- package/dist/chunk-QDPFU4WX.js +0 -43
- package/dist/chunk-QDPFU4WX.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"updater-view.js"}
|
package/dist/updater.cjs
CHANGED
|
@@ -95,7 +95,7 @@ function beginDownload(info, attempts) {
|
|
|
95
95
|
if (!download && !updateDownloaded) beginDownload(info, state.attempts);
|
|
96
96
|
}, RETRY_DELAY_MS);
|
|
97
97
|
} else {
|
|
98
|
-
lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times
|
|
98
|
+
lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times, most recently with "${why}". It will be tried again on the next check.`;
|
|
99
99
|
writeLog("error", [`download: giving up on ${info.version} this cycle - ${why}`]);
|
|
100
100
|
}
|
|
101
101
|
});
|
|
@@ -178,7 +178,7 @@ async function quitAndInstallSafely() {
|
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
const response = await themedPrompt(win, {
|
|
181
|
-
buttons: ["Save and
|
|
181
|
+
buttons: ["Save and restart", "Discard and restart", "Cancel"],
|
|
182
182
|
defaultId: 0,
|
|
183
183
|
cancelId: 2,
|
|
184
184
|
message: "You have unsaved changes.",
|
|
@@ -206,11 +206,11 @@ autoUpdater.on("update-downloaded", (info) => {
|
|
|
206
206
|
const win = activeWindow();
|
|
207
207
|
if (!win) return;
|
|
208
208
|
void themedPrompt(win, {
|
|
209
|
-
buttons: ["Restart
|
|
209
|
+
buttons: ["Restart now", "Later"],
|
|
210
210
|
defaultId: 0,
|
|
211
211
|
cancelId: 1,
|
|
212
212
|
message: "Update ready to install",
|
|
213
|
-
detail: `${appName} ${info.version} has been downloaded. Restart now to
|
|
213
|
+
detail: `${appName} ${info.version} has been downloaded. Restart now to install it, or it installs the next time you quit.`
|
|
214
214
|
}).then((response) => {
|
|
215
215
|
if (response === 0) void quitAndInstallSafely();
|
|
216
216
|
});
|
|
@@ -230,21 +230,19 @@ async function manualCheckForUpdates(win) {
|
|
|
230
230
|
const parent = win || activeWindow();
|
|
231
231
|
if (!electron.app.isPackaged) {
|
|
232
232
|
await themedPrompt(parent, {
|
|
233
|
-
message: "Updates unavailable in development build",
|
|
234
|
-
detail: `Auto-update only runs in packaged
|
|
235
|
-
|
|
236
|
-
Current version: ${electron.app.getVersion()}`,
|
|
233
|
+
message: "Updates unavailable in a development build",
|
|
234
|
+
detail: `Auto-update only runs in a packaged build. This is version ${electron.app.getVersion()}.`,
|
|
237
235
|
buttons: ["OK"]
|
|
238
236
|
});
|
|
239
237
|
return;
|
|
240
238
|
}
|
|
241
239
|
if (updateDownloaded) {
|
|
242
240
|
const response = await themedPrompt(parent, {
|
|
243
|
-
buttons: ["Restart
|
|
241
|
+
buttons: ["Restart now", "Later"],
|
|
244
242
|
defaultId: 0,
|
|
245
243
|
cancelId: 1,
|
|
246
244
|
message: "Update ready to install",
|
|
247
|
-
detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to
|
|
245
|
+
detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to install it.`
|
|
248
246
|
});
|
|
249
247
|
if (response === 0) await quitAndInstallSafely();
|
|
250
248
|
return;
|
|
@@ -272,19 +270,17 @@ Current version: ${electron.app.getVersion()}`,
|
|
|
272
270
|
lastBackgroundError = null;
|
|
273
271
|
} else if (ready) {
|
|
274
272
|
const response = await themedPrompt(parent, {
|
|
275
|
-
buttons: ["Restart
|
|
273
|
+
buttons: ["Restart now", "Later"],
|
|
276
274
|
defaultId: 0,
|
|
277
275
|
cancelId: 1,
|
|
278
276
|
message: "Update ready to install",
|
|
279
|
-
detail: `${appName} ${ready.version} has been downloaded. Restart now to
|
|
277
|
+
detail: `${appName} ${ready.version} has been downloaded. Restart now to install it.`
|
|
280
278
|
});
|
|
281
279
|
if (response === 0) await quitAndInstallSafely();
|
|
282
280
|
} else if (lastBackgroundError) {
|
|
283
281
|
await themedPrompt(parent, {
|
|
284
|
-
message: "You're on the latest version
|
|
285
|
-
detail:
|
|
286
|
-
|
|
287
|
-
Last update error:
|
|
282
|
+
message: "You're on the latest version.",
|
|
283
|
+
detail: `${appName} ${electron.app.getVersion()} is up to date. The last update check reported an error:
|
|
288
284
|
${lastBackgroundError}`,
|
|
289
285
|
buttons: ["OK"]
|
|
290
286
|
});
|
package/dist/updater.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/updater.ts"],"names":["BrowserWindow","electronUpdater","join","app","appendFileSync","ipcMain"],"mappings":"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","file":"updater.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// In-app auto-update over electron-updater: the family's, lifted from Patterpad\n// where it was hardened in the field.\n//\n// This is not a wrapper anybody should write twice. Every rule in it is a bug\n// that reached a user:\n//\n// - Downloads are driven HERE rather than by electron-updater's autoDownload,\n// because holding the CancellationToken is what lets a stall watchdog kill\n// and retry a download that hangs. A hung stream emits neither progress nor\n// `error`, so anything built on events alone sits \"downloading\" for ever\n// (Patterpad #33: a Windows user's download did exactly that, all day).\n// - Differential download is off. On Windows it stalled silently across an\n// Electron major bump, where almost no blocks match anyway.\n// - There is a PERSISTENT LOG in userData, because electron-updater is silent\n// by default and a stalled download otherwise leaves nothing to diagnose.\n// - Installing asks the renderer whether there is unsaved work, and offers\n// Save / Discard / Cancel. \"Restart Now\" must never silently drop an\n// author's afternoon.\n// - Every prompt is THEMED, in the renderer, never a stock OS dialog.\n//\n// Only runs in a packaged build; in development the manual check says so rather\n// than failing obscurely.\n//\n// THE SEAMS: the app supplies its NAME (it appears in every message), a way to\n// find the ACTIVE WINDOW, and a renderer that answers four channels - the\n// contract below. Everything else is the same in every app.\n// ---------------------------------------------------------------------------\n\nimport { app, BrowserWindow, ipcMain } from \"electron\";\nimport { appendFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ProgressInfo, UpdateDownloadedEvent, UpdateInfo } from \"electron-updater\";\nimport electronUpdater from \"electron-updater\";\n\n/** Download progress, broadcast to every window so a live dialog can show it. */\nexport interface UpdaterDownloadProgress {\n percent: number;\n transferred: number;\n total: number;\n bytesPerSecond: number;\n /** Which version is coming, so a dialog can name it. */\n version: string;\n}\n\n/** A themed prompt, answered by the renderer with the chosen button index -\n * the same contract as `dialog.showMessageBox`'s `response`. */\nexport interface UpdaterPromptOptions {\n message: string;\n detail?: string;\n buttons: string[];\n defaultId?: number;\n cancelId?: number;\n /** Show live download progress in the dialog. */\n progress?: boolean;\n}\n\n/** The channels the app's renderer must answer. Named here so both sides agree. */\nexport const UPDATER_CHANNELS = {\n /** main -> renderer: \"are there unsaved edits?\"; reply on `dirtyReply`. */\n checkDirty: \"updater:check-dirty\",\n dirtyReply: \"updater:dirty-reply\",\n /** main -> renderer: \"save now\"; reply `{ ok }` on `saveDone`. */\n saveBeforeInstall: \"updater:save-before-install\",\n saveDone: \"updater:save-done\",\n /** main -> renderer: show this prompt; reply with the button index. */\n prompt: \"updater:prompt\",\n promptReply: \"updater:prompt-reply\",\n /** main -> renderer: live download progress. */\n progress: \"updater:download-progress\",\n} as const;\n\nexport interface UpdaterOptions {\n /** The product name, as it should read in every message (\"Patterpad\"). */\n appName: string;\n /** Which window the prompts belong to. */\n activeWindow: () => BrowserWindow | null;\n /** The log file inside userData. */\n logFile?: string;\n}\n\nlet appName = \"This app\";\nlet activeWindowFn: () => BrowserWindow | null = () =>\n BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] ?? null;\nconst activeWindow = (): BrowserWindow | null => activeWindowFn();\n\nconst { autoUpdater, CancellationToken } = electronUpdater;\n\nlet updateDownloaded: UpdateDownloadedEvent | null = null;\n// Remember the last background error so the manual \"Check for Updates\" dialog can\n// surface it - otherwise a broken feed (network policy, expired cert, GitHub outage)\n// stays invisible and the user has no idea why updates never arrive.\nlet lastBackgroundError: string | null = null;\n\n// Persistent updater log (userData/updater.log). electron-updater is silent by default; without a log a\n// Windows download that stalls without emitting `error` (found in 0.1.5) leaves nothing to diagnose.\nlet logPath = join(app.getPath(\"userData\"), \"updater.log\");\nconst writeLog = (level: string, args: unknown[]): void => {\n try { appendFileSync(logPath, `${new Date().toISOString()} [${level}] ${args.map((a) => (a instanceof Error ? a.stack || a.message : String(a))).join(\" \")}\\n`); } catch { /* logging must never throw */ }\n};\nautoUpdater.logger = {\n info: (...a: unknown[]) => writeLog(\"info\", a),\n warn: (...a: unknown[]) => writeLog(\"warn\", a),\n error: (...a: unknown[]) => writeLog(\"error\", a),\n debug: (...a: unknown[]) => writeLog(\"debug\", a),\n};\n\n// Downloads are driven by the download manager below, NOT electron-updater's autoDownload: holding\n// the CancellationToken ourselves is what lets the stall watchdog kill and retry a download that\n// hangs without ever erroring (#33 - a Windows user's download sat \"in the background\" all day; a\n// hung stream emits neither progress nor `error`, so nothing built on events alone can recover it).\nautoUpdater.autoDownload = false;\nautoUpdater.autoInstallOnAppQuit = true;\n// Force a plain full download instead of the block-by-block differential. On Windows the differential\n// path stalled silently (0.1.5): across an Electron-major bump almost no blocks match, so it downloads\n// nearly everything anyway, and it's fragile for unsigned installers. Full download is one reliable\n// stream. No-op on macOS (Squirrel.Mac always fetches the whole zip).\nautoUpdater.disableDifferentialDownload = true;\n\nautoUpdater.on(\"error\", (err) => {\n console.error(\"AutoUpdater error:\", err?.message || err);\n lastBackgroundError = err?.message || String(err);\n});\n\n// ---------------------------------------------------------------------------\n// Download manager: start, watch, retry. Platform-agnostic - it rides\n// electron-updater's shared events, identical for NSIS (win), zip (mac), and\n// AppImage (linux) feeds.\n// ---------------------------------------------------------------------------\n\nconst STALL_MS = 3 * 60 * 1000; // no progress event for this long = the download is hung\nconst WATCH_EVERY_MS = 30 * 1000; // how often the watchdog looks at the clock\nconst RETRY_DELAY_MS = 15 * 1000; // pause before re-attempting a killed/failed download\nconst MAX_ATTEMPTS = 3; // per check cycle; the 6-hourly background check starts a fresh cycle\n\ntype DownloadState = {\n info: UpdateInfo;\n token: InstanceType<typeof CancellationToken>;\n attempts: number; // attempts consumed this cycle, including the running one\n lastProgressAt: number;\n cancelledByWatchdog: boolean;\n watchdog: NodeJS.Timeout;\n};\nlet download: DownloadState | null = null;\n\nfunction broadcastProgress(p: UpdaterDownloadProgress): void {\n for (const w of BrowserWindow.getAllWindows()) {\n if (!w.isDestroyed() && !w.webContents.isDestroyed()) w.webContents.send(UPDATER_CHANNELS.progress, p);\n }\n}\n\nfunction clearDownload(): void {\n if (download) clearInterval(download.watchdog);\n download = null;\n}\n\n/** Begin (or re-attempt) downloading `info`. One state machine for every platform. */\nfunction beginDownload(info: UpdateInfo, attempts: number): void {\n clearDownload();\n const token = new CancellationToken();\n const state: DownloadState = {\n info, token, attempts: attempts + 1, lastProgressAt: Date.now(), cancelledByWatchdog: false,\n watchdog: setInterval(() => {\n if (download !== state) return;\n const quiet = Date.now() - state.lastProgressAt;\n if (quiet < STALL_MS) return;\n // Hung: no progress and no error for STALL_MS. Kill it; the download promise's catch retries.\n writeLog(\"warn\", [`watchdog: no download progress for ${Math.round(quiet / 1000)}s - cancelling (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n state.cancelledByWatchdog = true;\n state.token.cancel();\n }, WATCH_EVERY_MS),\n };\n download = state;\n writeLog(\"info\", [`download: starting ${info.version} (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n\n autoUpdater.downloadUpdate(token).then(() => {\n // Success is reported via the update-downloaded event; it clears the state.\n }).catch((err: unknown) => {\n if (download !== state) return; // superseded by a newer attempt/cycle\n const why = state.cancelledByWatchdog ? \"stalled (killed by the watchdog)\"\n : (err instanceof Error ? err.message : String(err));\n clearDownload();\n if (state.attempts < MAX_ATTEMPTS) {\n writeLog(\"warn\", [`download: attempt ${state.attempts} failed - ${why}; retrying in ${RETRY_DELAY_MS / 1000}s`]);\n setTimeout(() => { if (!download && !updateDownloaded) beginDownload(info, state.attempts); }, RETRY_DELAY_MS);\n } else {\n // Out of attempts for this cycle. Record it where the manual check surfaces it; the next\n // background check (6-hourly, or Help > Check for Updates) starts a fresh cycle.\n lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times (last: ${why}). Will retry on the next check.`;\n writeLog(\"error\", [`download: giving up on ${info.version} this cycle - ${why}`]);\n }\n });\n}\n\nautoUpdater.on(\"update-available\", (info: UpdateInfo) => {\n if (download || updateDownloaded) return; // already downloading it, or already have it\n beginDownload(info, 0);\n});\n\nautoUpdater.on(\"download-progress\", (p: ProgressInfo) => {\n if (download) download.lastProgressAt = Date.now();\n broadcastProgress({\n percent: p.percent, transferred: p.transferred, total: p.total,\n bytesPerSecond: p.bytesPerSecond, version: download?.info.version ?? \"\",\n });\n});\n\n/** Ask the renderer (one-shot, timeout-guarded) whether the open scene has unsaved edits.\n * Resolves false on any error/timeout - the renderer's dirty flag is the only truth here. */\nfunction askRendererIsDirty(win: BrowserWindow | null): Promise<boolean> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(false); return; }\n const onReply = (_e: unknown, isDirty: boolean) => { clearTimeout(timeout); resolve(!!isDirty); };\n ipcMain.once(UPDATER_CHANNELS.dirtyReply, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.dirtyReply, onReply);\n console.warn(\"Updater dirty-check timed out; assuming clean.\");\n resolve(false);\n }, 2000);\n win.webContents.send(UPDATER_CHANNELS.checkDirty);\n });\n}\n\n/** Trigger a save in the renderer and wait for completion. Saves run the lock-aware VC write path,\n * so the timeout is generous; if it elapses we abort rather than install over a half-saved project. */\nfunction triggerRendererSave(win: BrowserWindow | null): Promise<{ ok: boolean }> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve({ ok: false }); return; }\n const onReply = (_e: unknown, result: { ok: boolean }) => { clearTimeout(timeout); resolve(result || { ok: false }); };\n ipcMain.once(UPDATER_CHANNELS.saveDone, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.saveDone, onReply);\n console.warn(\"Updater save timed out.\");\n resolve({ ok: false });\n }, 30000);\n win.webContents.send(UPDATER_CHANNELS.saveBeforeInstall);\n });\n}\n\n/** Show a THEMED prompt in the renderer (never a stock OS dialog; design-language \"coherent to the edges\")\n * and resolve the chosen button index - the same contract as dialog.showMessageBox's `response`. Falls\n * back to cancelId/defaultId if there's no live renderer to answer (so an install is never blocked on it). */\nfunction themedPrompt(win: BrowserWindow | null, opts: UpdaterPromptOptions): Promise<number> {\n return new Promise((resolve) => {\n const fallback = opts.cancelId ?? opts.defaultId ?? 0;\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(fallback); return; }\n const onReply = (_e: unknown, idx: number) => { clearTimeout(timeout); resolve(typeof idx === \"number\" ? idx : fallback); };\n ipcMain.once(UPDATER_CHANNELS.promptReply, onReply);\n // Generous: these are user-facing prompts. If the renderer never answers (gone / hung), fall back safely.\n const timeout = setTimeout(() => { ipcMain.removeListener(UPDATER_CHANNELS.promptReply, onReply); resolve(fallback); }, 300000);\n win.webContents.send(UPDATER_CHANNELS.prompt, opts);\n });\n}\n\n/** quitAndInstall, guarded by the unsaved-edits check. autoInstallOnAppQuit routes through the normal\n * window-close save path already, so only this explicit \"Restart Now\" needs the guard. */\nasync function quitAndInstallSafely(): Promise<void> {\n const win = activeWindow();\n if (!(await askRendererIsDirty(win))) { autoUpdater.quitAndInstall(); return; }\n\n const response = await themedPrompt(win, {\n buttons: [\"Save and Restart\", \"Discard and Restart\", \"Cancel\"],\n defaultId: 0,\n cancelId: 2,\n message: \"You have unsaved changes.\",\n detail: `Save them before restarting to install ${appName} ${updateDownloaded?.version || \"\"}?`,\n });\n\n if (response === 2) return; // Cancel - abort the install\n if (response === 0) {\n const result = await triggerRendererSave(win);\n if (!result.ok) {\n await themedPrompt(win, {\n message: \"Save failed\",\n detail: \"Your changes could not be saved, so the update was not installed. Save manually, then try again.\",\n buttons: [\"OK\"],\n });\n return;\n }\n }\n // response === 1 (Discard) falls through.\n autoUpdater.quitAndInstall();\n}\n\nautoUpdater.on(\"update-downloaded\", (info: UpdateDownloadedEvent) => {\n clearDownload();\n updateDownloaded = info;\n lastBackgroundError = null; // it got here in the end; stale retry noise would only mislead\n writeLog(\"info\", [`download: ${info.version} downloaded and ready to install`]);\n const win = activeWindow();\n if (!win) return;\n void themedPrompt(win, {\n buttons: [\"Restart Now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${info.version} has been downloaded. Restart now to apply, or it will install automatically next time you quit.`,\n }).then((response) => { if (response === 0) void quitAndInstallSafely(); });\n});\n\n/** Name the app and say how to find its window. Call once, at startup. */\nexport function configureUpdater(opts: UpdaterOptions): void {\n appName = opts.appName;\n activeWindowFn = opts.activeWindow;\n if (opts.logFile !== undefined) logPath = join(app.getPath(\"userData\"), opts.logFile);\n}\n\n/** Fire-and-forget background check; periodic + on launch. No-op in dev. */\nexport function startBackgroundUpdateCheck(): void {\n if (!app.isPackaged) return;\n autoUpdater.checkForUpdates().catch((err) => {\n console.error(\"AutoUpdater background check failed:\", err?.message || err);\n });\n}\n\n/** The Help ▸ Check for Updates… handler: always gives the user feedback (downloading / up to date /\n * error), unlike the silent background check. */\nexport async function manualCheckForUpdates(win?: BrowserWindow | null): Promise<void> {\n const parent = win || activeWindow();\n\n if (!app.isPackaged) {\n await themedPrompt(parent, {\n message: \"Updates unavailable in development build\",\n detail: `Auto-update only runs in packaged builds.\\n\\nCurrent version: ${app.getVersion()}`,\n buttons: [\"OK\"],\n });\n return;\n }\n\n if (updateDownloaded) {\n const response = await themedPrompt(parent, {\n buttons: [\"Restart Now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to apply.`,\n });\n if (response === 0) await quitAndInstallSafely();\n return;\n }\n\n if (download) {\n // A download is already in flight: show it, live (the dialog subscribes to progress events).\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${download.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n return;\n }\n\n try {\n // A manual check starts a fresh retry cycle: if the last one gave up, this is the user asking again.\n await autoUpdater.checkForUpdates();\n // With autoDownload off, `update-available` (fired during the await) starts the managed download,\n // so by here `download` is set iff the feed had something newer. (Read through locals: TS narrows the\n // module-level `download` to null after the guard above and cannot see the event handler mutate it.)\n const started = download as DownloadState | null;\n const ready = updateDownloaded as UpdateDownloadedEvent | null;\n if (started) {\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${started.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n lastBackgroundError = null;\n } else if (ready) {\n // The check completed a download between our earlier guard and now (tiny window, but free to handle).\n const response = await themedPrompt(parent, {\n buttons: [\"Restart Now\", \"Later\"], defaultId: 0, cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${ready.version} has been downloaded. Restart now to apply.`,\n });\n if (response === 0) await quitAndInstallSafely();\n } else if (lastBackgroundError) {\n await themedPrompt(parent, {\n message: \"You're on the latest version, but updates have had errors.\",\n detail: `Current version: ${appName} ${app.getVersion()}\\n\\nLast update error:\\n${lastBackgroundError}`,\n buttons: [\"OK\"],\n });\n lastBackgroundError = null;\n } else {\n await themedPrompt(parent, {\n message: \"You're on the latest version.\",\n detail: `${appName} ${app.getVersion()} is up to date.`,\n buttons: [\"OK\"],\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const detail = lastBackgroundError && lastBackgroundError !== message\n ? `${message}\\n\\nA previous background check also failed with:\\n${lastBackgroundError}`\n : message;\n await themedPrompt(parent, { message: \"Update check failed\", detail, buttons: [\"OK\"] });\n lastBackgroundError = null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/updater.ts"],"names":["BrowserWindow","electronUpdater","join","app","appendFileSync","ipcMain"],"mappings":"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","file":"updater.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// In-app auto-update over electron-updater: the family's, lifted from Patterpad\n// where it was hardened in the field.\n//\n// This is not a wrapper anybody should write twice. Every rule in it is a bug\n// that reached a user:\n//\n// - Downloads are driven HERE rather than by electron-updater's autoDownload,\n// because holding the CancellationToken is what lets a stall watchdog kill\n// and retry a download that hangs. A hung stream emits neither progress nor\n// `error`, so anything built on events alone sits \"downloading\" for ever\n// (Patterpad #33: a Windows user's download did exactly that, all day).\n// - Differential download is off. On Windows it stalled silently across an\n// Electron major bump, where almost no blocks match anyway.\n// - There is a PERSISTENT LOG in userData, because electron-updater is silent\n// by default and a stalled download otherwise leaves nothing to diagnose.\n// - Installing asks the renderer whether there is unsaved work, and offers\n// Save / Discard / Cancel. \"Restart Now\" must never silently drop an\n// author's afternoon.\n// - Every prompt is THEMED, in the renderer, never a stock OS dialog.\n//\n// Only runs in a packaged build; in development the manual check says so rather\n// than failing obscurely.\n//\n// THE SEAMS: the app supplies its NAME (it appears in every message), a way to\n// find the ACTIVE WINDOW, and a renderer that answers four channels - the\n// contract below. Everything else is the same in every app.\n// ---------------------------------------------------------------------------\n\nimport { app, BrowserWindow, ipcMain } from \"electron\";\nimport { appendFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ProgressInfo, UpdateDownloadedEvent, UpdateInfo } from \"electron-updater\";\nimport electronUpdater from \"electron-updater\";\n\n/** Download progress, broadcast to every window so a live dialog can show it. */\nexport interface UpdaterDownloadProgress {\n percent: number;\n transferred: number;\n total: number;\n bytesPerSecond: number;\n /** Which version is coming, so a dialog can name it. */\n version: string;\n}\n\n/** A themed prompt, answered by the renderer with the chosen button index -\n * the same contract as `dialog.showMessageBox`'s `response`. */\nexport interface UpdaterPromptOptions {\n message: string;\n detail?: string;\n buttons: string[];\n defaultId?: number;\n cancelId?: number;\n /** Show live download progress in the dialog. */\n progress?: boolean;\n}\n\n/** The channels the app's renderer must answer. Named here so both sides agree. */\nexport const UPDATER_CHANNELS = {\n /** main -> renderer: \"are there unsaved edits?\"; reply on `dirtyReply`. */\n checkDirty: \"updater:check-dirty\",\n dirtyReply: \"updater:dirty-reply\",\n /** main -> renderer: \"save now\"; reply `{ ok }` on `saveDone`. */\n saveBeforeInstall: \"updater:save-before-install\",\n saveDone: \"updater:save-done\",\n /** main -> renderer: show this prompt; reply with the button index. */\n prompt: \"updater:prompt\",\n promptReply: \"updater:prompt-reply\",\n /** main -> renderer: live download progress. */\n progress: \"updater:download-progress\",\n} as const;\n\nexport interface UpdaterOptions {\n /** The product name, as it should read in every message (\"Patterpad\"). */\n appName: string;\n /** Which window the prompts belong to. */\n activeWindow: () => BrowserWindow | null;\n /** The log file inside userData. */\n logFile?: string;\n}\n\nlet appName = \"This app\";\nlet activeWindowFn: () => BrowserWindow | null = () =>\n BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] ?? null;\nconst activeWindow = (): BrowserWindow | null => activeWindowFn();\n\nconst { autoUpdater, CancellationToken } = electronUpdater;\n\nlet updateDownloaded: UpdateDownloadedEvent | null = null;\n// Remember the last background error so the manual \"Check for Updates\" dialog can\n// surface it - otherwise a broken feed (network policy, expired cert, GitHub outage)\n// stays invisible and the user has no idea why updates never arrive.\nlet lastBackgroundError: string | null = null;\n\n// Persistent updater log (userData/updater.log). electron-updater is silent by default; without a log a\n// Windows download that stalls without emitting `error` (found in 0.1.5) leaves nothing to diagnose.\nlet logPath = join(app.getPath(\"userData\"), \"updater.log\");\nconst writeLog = (level: string, args: unknown[]): void => {\n try { appendFileSync(logPath, `${new Date().toISOString()} [${level}] ${args.map((a) => (a instanceof Error ? a.stack || a.message : String(a))).join(\" \")}\\n`); } catch { /* logging must never throw */ }\n};\nautoUpdater.logger = {\n info: (...a: unknown[]) => writeLog(\"info\", a),\n warn: (...a: unknown[]) => writeLog(\"warn\", a),\n error: (...a: unknown[]) => writeLog(\"error\", a),\n debug: (...a: unknown[]) => writeLog(\"debug\", a),\n};\n\n// Downloads are driven by the download manager below, NOT electron-updater's autoDownload: holding\n// the CancellationToken ourselves is what lets the stall watchdog kill and retry a download that\n// hangs without ever erroring (#33 - a Windows user's download sat \"in the background\" all day; a\n// hung stream emits neither progress nor `error`, so nothing built on events alone can recover it).\nautoUpdater.autoDownload = false;\nautoUpdater.autoInstallOnAppQuit = true;\n// Force a plain full download instead of the block-by-block differential. On Windows the differential\n// path stalled silently (0.1.5): across an Electron-major bump almost no blocks match, so it downloads\n// nearly everything anyway, and it's fragile for unsigned installers. Full download is one reliable\n// stream. No-op on macOS (Squirrel.Mac always fetches the whole zip).\nautoUpdater.disableDifferentialDownload = true;\n\nautoUpdater.on(\"error\", (err) => {\n console.error(\"AutoUpdater error:\", err?.message || err);\n lastBackgroundError = err?.message || String(err);\n});\n\n// ---------------------------------------------------------------------------\n// Download manager: start, watch, retry. Platform-agnostic - it rides\n// electron-updater's shared events, identical for NSIS (win), zip (mac), and\n// AppImage (linux) feeds.\n// ---------------------------------------------------------------------------\n\nconst STALL_MS = 3 * 60 * 1000; // no progress event for this long = the download is hung\nconst WATCH_EVERY_MS = 30 * 1000; // how often the watchdog looks at the clock\nconst RETRY_DELAY_MS = 15 * 1000; // pause before re-attempting a killed/failed download\nconst MAX_ATTEMPTS = 3; // per check cycle; the 6-hourly background check starts a fresh cycle\n\ntype DownloadState = {\n info: UpdateInfo;\n token: InstanceType<typeof CancellationToken>;\n attempts: number; // attempts consumed this cycle, including the running one\n lastProgressAt: number;\n cancelledByWatchdog: boolean;\n watchdog: NodeJS.Timeout;\n};\nlet download: DownloadState | null = null;\n\nfunction broadcastProgress(p: UpdaterDownloadProgress): void {\n for (const w of BrowserWindow.getAllWindows()) {\n if (!w.isDestroyed() && !w.webContents.isDestroyed()) w.webContents.send(UPDATER_CHANNELS.progress, p);\n }\n}\n\nfunction clearDownload(): void {\n if (download) clearInterval(download.watchdog);\n download = null;\n}\n\n/** Begin (or re-attempt) downloading `info`. One state machine for every platform. */\nfunction beginDownload(info: UpdateInfo, attempts: number): void {\n clearDownload();\n const token = new CancellationToken();\n const state: DownloadState = {\n info, token, attempts: attempts + 1, lastProgressAt: Date.now(), cancelledByWatchdog: false,\n watchdog: setInterval(() => {\n if (download !== state) return;\n const quiet = Date.now() - state.lastProgressAt;\n if (quiet < STALL_MS) return;\n // Hung: no progress and no error for STALL_MS. Kill it; the download promise's catch retries.\n writeLog(\"warn\", [`watchdog: no download progress for ${Math.round(quiet / 1000)}s - cancelling (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n state.cancelledByWatchdog = true;\n state.token.cancel();\n }, WATCH_EVERY_MS),\n };\n download = state;\n writeLog(\"info\", [`download: starting ${info.version} (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n\n autoUpdater.downloadUpdate(token).then(() => {\n // Success is reported via the update-downloaded event; it clears the state.\n }).catch((err: unknown) => {\n if (download !== state) return; // superseded by a newer attempt/cycle\n const why = state.cancelledByWatchdog ? \"stalled (killed by the watchdog)\"\n : (err instanceof Error ? err.message : String(err));\n clearDownload();\n if (state.attempts < MAX_ATTEMPTS) {\n writeLog(\"warn\", [`download: attempt ${state.attempts} failed - ${why}; retrying in ${RETRY_DELAY_MS / 1000}s`]);\n setTimeout(() => { if (!download && !updateDownloaded) beginDownload(info, state.attempts); }, RETRY_DELAY_MS);\n } else {\n // Out of attempts for this cycle. Record it where the manual check surfaces it; the next\n // background check (6-hourly, or Help > Check for Updates) starts a fresh cycle.\n lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times, most recently with \"${why}\". It will be tried again on the next check.`;\n writeLog(\"error\", [`download: giving up on ${info.version} this cycle - ${why}`]);\n }\n });\n}\n\nautoUpdater.on(\"update-available\", (info: UpdateInfo) => {\n if (download || updateDownloaded) return; // already downloading it, or already have it\n beginDownload(info, 0);\n});\n\nautoUpdater.on(\"download-progress\", (p: ProgressInfo) => {\n if (download) download.lastProgressAt = Date.now();\n broadcastProgress({\n percent: p.percent, transferred: p.transferred, total: p.total,\n bytesPerSecond: p.bytesPerSecond, version: download?.info.version ?? \"\",\n });\n});\n\n/** Ask the renderer (one-shot, timeout-guarded) whether the open scene has unsaved edits.\n * Resolves false on any error/timeout - the renderer's dirty flag is the only truth here. */\nfunction askRendererIsDirty(win: BrowserWindow | null): Promise<boolean> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(false); return; }\n const onReply = (_e: unknown, isDirty: boolean) => { clearTimeout(timeout); resolve(!!isDirty); };\n ipcMain.once(UPDATER_CHANNELS.dirtyReply, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.dirtyReply, onReply);\n console.warn(\"Updater dirty-check timed out; assuming clean.\");\n resolve(false);\n }, 2000);\n win.webContents.send(UPDATER_CHANNELS.checkDirty);\n });\n}\n\n/** Trigger a save in the renderer and wait for completion. Saves run the lock-aware VC write path,\n * so the timeout is generous; if it elapses we abort rather than install over a half-saved project. */\nfunction triggerRendererSave(win: BrowserWindow | null): Promise<{ ok: boolean }> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve({ ok: false }); return; }\n const onReply = (_e: unknown, result: { ok: boolean }) => { clearTimeout(timeout); resolve(result || { ok: false }); };\n ipcMain.once(UPDATER_CHANNELS.saveDone, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.saveDone, onReply);\n console.warn(\"Updater save timed out.\");\n resolve({ ok: false });\n }, 30000);\n win.webContents.send(UPDATER_CHANNELS.saveBeforeInstall);\n });\n}\n\n/** Show a THEMED prompt in the renderer (never a stock OS dialog; design-language \"coherent to the edges\")\n * and resolve the chosen button index - the same contract as dialog.showMessageBox's `response`. Falls\n * back to cancelId/defaultId if there's no live renderer to answer (so an install is never blocked on it). */\nfunction themedPrompt(win: BrowserWindow | null, opts: UpdaterPromptOptions): Promise<number> {\n return new Promise((resolve) => {\n const fallback = opts.cancelId ?? opts.defaultId ?? 0;\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(fallback); return; }\n const onReply = (_e: unknown, idx: number) => { clearTimeout(timeout); resolve(typeof idx === \"number\" ? idx : fallback); };\n ipcMain.once(UPDATER_CHANNELS.promptReply, onReply);\n // Generous: these are user-facing prompts. If the renderer never answers (gone / hung), fall back safely.\n const timeout = setTimeout(() => { ipcMain.removeListener(UPDATER_CHANNELS.promptReply, onReply); resolve(fallback); }, 300000);\n win.webContents.send(UPDATER_CHANNELS.prompt, opts);\n });\n}\n\n/** quitAndInstall, guarded by the unsaved-edits check. autoInstallOnAppQuit routes through the normal\n * window-close save path already, so only this explicit \"Restart Now\" needs the guard. */\nasync function quitAndInstallSafely(): Promise<void> {\n const win = activeWindow();\n if (!(await askRendererIsDirty(win))) { autoUpdater.quitAndInstall(); return; }\n\n const response = await themedPrompt(win, {\n buttons: [\"Save and restart\", \"Discard and restart\", \"Cancel\"],\n defaultId: 0,\n cancelId: 2,\n message: \"You have unsaved changes.\",\n detail: `Save them before restarting to install ${appName} ${updateDownloaded?.version || \"\"}?`,\n });\n\n if (response === 2) return; // Cancel - abort the install\n if (response === 0) {\n const result = await triggerRendererSave(win);\n if (!result.ok) {\n await themedPrompt(win, {\n message: \"Save failed\",\n detail: \"Your changes could not be saved, so the update was not installed. Save manually, then try again.\",\n buttons: [\"OK\"],\n });\n return;\n }\n }\n // response === 1 (Discard) falls through.\n autoUpdater.quitAndInstall();\n}\n\nautoUpdater.on(\"update-downloaded\", (info: UpdateDownloadedEvent) => {\n clearDownload();\n updateDownloaded = info;\n lastBackgroundError = null; // it got here in the end; stale retry noise would only mislead\n writeLog(\"info\", [`download: ${info.version} downloaded and ready to install`]);\n const win = activeWindow();\n if (!win) return;\n void themedPrompt(win, {\n buttons: [\"Restart now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${info.version} has been downloaded. Restart now to install it, or it installs the next time you quit.`,\n }).then((response) => { if (response === 0) void quitAndInstallSafely(); });\n});\n\n/** Name the app and say how to find its window. Call once, at startup. */\nexport function configureUpdater(opts: UpdaterOptions): void {\n appName = opts.appName;\n activeWindowFn = opts.activeWindow;\n if (opts.logFile !== undefined) logPath = join(app.getPath(\"userData\"), opts.logFile);\n}\n\n/** Fire-and-forget background check; periodic + on launch. No-op in dev. */\nexport function startBackgroundUpdateCheck(): void {\n if (!app.isPackaged) return;\n autoUpdater.checkForUpdates().catch((err) => {\n console.error(\"AutoUpdater background check failed:\", err?.message || err);\n });\n}\n\n/** The Help ▸ Check for Updates… handler: always gives the user feedback (downloading / up to date /\n * error), unlike the silent background check. */\nexport async function manualCheckForUpdates(win?: BrowserWindow | null): Promise<void> {\n const parent = win || activeWindow();\n\n if (!app.isPackaged) {\n await themedPrompt(parent, {\n message: \"Updates unavailable in a development build\",\n detail: `Auto-update only runs in a packaged build. This is version ${app.getVersion()}.`,\n buttons: [\"OK\"],\n });\n return;\n }\n\n if (updateDownloaded) {\n const response = await themedPrompt(parent, {\n buttons: [\"Restart now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to install it.`,\n });\n if (response === 0) await quitAndInstallSafely();\n return;\n }\n\n if (download) {\n // A download is already in flight: show it, live (the dialog subscribes to progress events).\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${download.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n return;\n }\n\n try {\n // A manual check starts a fresh retry cycle: if the last one gave up, this is the user asking again.\n await autoUpdater.checkForUpdates();\n // With autoDownload off, `update-available` (fired during the await) starts the managed download,\n // so by here `download` is set iff the feed had something newer. (Read through locals: TS narrows the\n // module-level `download` to null after the guard above and cannot see the event handler mutate it.)\n const started = download as DownloadState | null;\n const ready = updateDownloaded as UpdateDownloadedEvent | null;\n if (started) {\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${started.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n lastBackgroundError = null;\n } else if (ready) {\n // The check completed a download between our earlier guard and now (tiny window, but free to handle).\n const response = await themedPrompt(parent, {\n buttons: [\"Restart now\", \"Later\"], defaultId: 0, cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${ready.version} has been downloaded. Restart now to install it.`,\n });\n if (response === 0) await quitAndInstallSafely();\n } else if (lastBackgroundError) {\n await themedPrompt(parent, {\n message: \"You're on the latest version.\",\n detail: `${appName} ${app.getVersion()} is up to date. The last update check reported an error:\\n${lastBackgroundError}`,\n buttons: [\"OK\"],\n });\n lastBackgroundError = null;\n } else {\n await themedPrompt(parent, {\n message: \"You're on the latest version.\",\n detail: `${appName} ${app.getVersion()} is up to date.`,\n buttons: [\"OK\"],\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const detail = lastBackgroundError && lastBackgroundError !== message\n ? `${message}\\n\\nA previous background check also failed with:\\n${lastBackgroundError}`\n : message;\n await themedPrompt(parent, { message: \"Update check failed\", detail, buttons: [\"OK\"] });\n lastBackgroundError = null;\n }\n}\n"]}
|
package/dist/updater.css
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* src/updater.css */
|
|
2
|
+
.updater-dialog {
|
|
3
|
+
--dialog-width: 416px;
|
|
4
|
+
}
|
|
5
|
+
.updater-detail {
|
|
6
|
+
margin: 0;
|
|
7
|
+
font: 13px/1.45 var(--font-ui);
|
|
8
|
+
color: var(--muted);
|
|
9
|
+
white-space: pre-line;
|
|
10
|
+
}
|
|
11
|
+
.updater-progress-track {
|
|
12
|
+
height: 6px;
|
|
13
|
+
margin-top: 4px;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
border-radius: var(--radius-sm);
|
|
16
|
+
background: color-mix(in oklab, var(--ink) 12%, transparent);
|
|
17
|
+
}
|
|
18
|
+
.updater-progress-bar {
|
|
19
|
+
height: 100%;
|
|
20
|
+
width: 0%;
|
|
21
|
+
border-radius: inherit;
|
|
22
|
+
background: var(--accent);
|
|
23
|
+
transition: width var(--dur-quick) var(--ease-standard);
|
|
24
|
+
}
|
|
25
|
+
.updater-progress-label {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font: 12.5px var(--font-mono);
|
|
28
|
+
color: var(--muted);
|
|
29
|
+
}
|
|
30
|
+
.updater-links {
|
|
31
|
+
margin: 0;
|
|
32
|
+
display: flex;
|
|
33
|
+
gap: 16px;
|
|
34
|
+
font: 13.5px var(--font-ui);
|
|
35
|
+
}
|
|
36
|
+
.updater-links a {
|
|
37
|
+
color: var(--accent);
|
|
38
|
+
text-decoration: underline;
|
|
39
|
+
text-underline-offset: 2px;
|
|
40
|
+
}
|
|
41
|
+
.updater-links a:hover {
|
|
42
|
+
color: var(--ink);
|
|
43
|
+
}
|
|
44
|
+
/*# sourceMappingURL=updater.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/updater.css"],"sourcesContent":["/* ---------------------------------------------------------------------------\n The updater prompt (updater-view.ts): what is the updater's own. The panel,\n the scrim, the motion and the actions row are the dialog frame's\n (dialog.css); the buttons are controls.css's `.btn`. Import those two\n beside this file.\n --------------------------------------------------------------------------- */\n\n.updater-dialog { --dialog-width: 416px; }\n.updater-detail { margin: 0; font: 13px/1.45 var(--font-ui); color: var(--muted); white-space: pre-line; }\n\n/* Live download progress: a track, a bar that interpolates to each tick, and\n the numbers under it in the mono face. */\n.updater-progress-track {\n height: 6px; margin-top: 4px; overflow: hidden;\n border-radius: var(--radius-sm);\n background: color-mix(in oklab, var(--ink) 12%, transparent);\n}\n.updater-progress-bar {\n height: 100%; width: 0%; border-radius: inherit; background: var(--accent);\n transition: width var(--dur-quick) var(--ease-standard);\n}\n.updater-progress-label { margin: 0; font: 12.5px var(--font-mono); color: var(--muted); }\n\n/* Web links under the detail: links, so underlined and coloured. */\n.updater-links { margin: 0; display: flex; gap: 16px; font: 13.5px var(--font-ui); }\n.updater-links a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }\n.updater-links a:hover { color: var(--ink); }\n"],"mappings":";AAOA,CAAC;AAAiB,kBAAgB;AAAO;AACzC,CAAC;AAAiB,UAAQ;AAAG,QAAM,IAAI,CAAC,KAAK,IAAI;AAAY,SAAO,IAAI;AAAU,eAAa;AAAU;AAIzG,CAAC;AACC,UAAQ;AAAK,cAAY;AAAK,YAAU;AACxC,iBAAe,IAAI;AACnB,cAAY,UAAU,GAAG,KAAK,EAAE,IAAI,OAAO,GAAG,EAAE;AAClD;AACA,CAAC;AACC,UAAQ;AAAM,SAAO;AAAI,iBAAe;AAAS,cAAY,IAAI;AACjE,cAAY,MAAM,IAAI,aAAa,IAAI;AACzC;AACA,CAAC;AAAyB,UAAQ;AAAG,QAAM,OAAO,IAAI;AAAc,SAAO,IAAI;AAAU;AAGzF,CAAC;AAAgB,UAAQ;AAAG,WAAS;AAAM,OAAK;AAAM,QAAM,OAAO,IAAI;AAAY;AACnF,CADC,cACc;AAAI,SAAO,IAAI;AAAW,mBAAiB;AAAW,yBAAuB;AAAK;AACjG,CAFC,cAEc,CAAC;AAAS,SAAO,IAAI;AAAQ;","names":[]}
|
package/dist/updater.js
CHANGED
|
@@ -89,7 +89,7 @@ function beginDownload(info, attempts) {
|
|
|
89
89
|
if (!download && !updateDownloaded) beginDownload(info, state.attempts);
|
|
90
90
|
}, RETRY_DELAY_MS);
|
|
91
91
|
} else {
|
|
92
|
-
lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times
|
|
92
|
+
lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times, most recently with "${why}". It will be tried again on the next check.`;
|
|
93
93
|
writeLog("error", [`download: giving up on ${info.version} this cycle - ${why}`]);
|
|
94
94
|
}
|
|
95
95
|
});
|
|
@@ -172,7 +172,7 @@ async function quitAndInstallSafely() {
|
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
174
|
const response = await themedPrompt(win, {
|
|
175
|
-
buttons: ["Save and
|
|
175
|
+
buttons: ["Save and restart", "Discard and restart", "Cancel"],
|
|
176
176
|
defaultId: 0,
|
|
177
177
|
cancelId: 2,
|
|
178
178
|
message: "You have unsaved changes.",
|
|
@@ -200,11 +200,11 @@ autoUpdater.on("update-downloaded", (info) => {
|
|
|
200
200
|
const win = activeWindow();
|
|
201
201
|
if (!win) return;
|
|
202
202
|
void themedPrompt(win, {
|
|
203
|
-
buttons: ["Restart
|
|
203
|
+
buttons: ["Restart now", "Later"],
|
|
204
204
|
defaultId: 0,
|
|
205
205
|
cancelId: 1,
|
|
206
206
|
message: "Update ready to install",
|
|
207
|
-
detail: `${appName} ${info.version} has been downloaded. Restart now to
|
|
207
|
+
detail: `${appName} ${info.version} has been downloaded. Restart now to install it, or it installs the next time you quit.`
|
|
208
208
|
}).then((response) => {
|
|
209
209
|
if (response === 0) void quitAndInstallSafely();
|
|
210
210
|
});
|
|
@@ -224,21 +224,19 @@ async function manualCheckForUpdates(win) {
|
|
|
224
224
|
const parent = win || activeWindow();
|
|
225
225
|
if (!app.isPackaged) {
|
|
226
226
|
await themedPrompt(parent, {
|
|
227
|
-
message: "Updates unavailable in development build",
|
|
228
|
-
detail: `Auto-update only runs in packaged
|
|
229
|
-
|
|
230
|
-
Current version: ${app.getVersion()}`,
|
|
227
|
+
message: "Updates unavailable in a development build",
|
|
228
|
+
detail: `Auto-update only runs in a packaged build. This is version ${app.getVersion()}.`,
|
|
231
229
|
buttons: ["OK"]
|
|
232
230
|
});
|
|
233
231
|
return;
|
|
234
232
|
}
|
|
235
233
|
if (updateDownloaded) {
|
|
236
234
|
const response = await themedPrompt(parent, {
|
|
237
|
-
buttons: ["Restart
|
|
235
|
+
buttons: ["Restart now", "Later"],
|
|
238
236
|
defaultId: 0,
|
|
239
237
|
cancelId: 1,
|
|
240
238
|
message: "Update ready to install",
|
|
241
|
-
detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to
|
|
239
|
+
detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to install it.`
|
|
242
240
|
});
|
|
243
241
|
if (response === 0) await quitAndInstallSafely();
|
|
244
242
|
return;
|
|
@@ -266,19 +264,17 @@ Current version: ${app.getVersion()}`,
|
|
|
266
264
|
lastBackgroundError = null;
|
|
267
265
|
} else if (ready) {
|
|
268
266
|
const response = await themedPrompt(parent, {
|
|
269
|
-
buttons: ["Restart
|
|
267
|
+
buttons: ["Restart now", "Later"],
|
|
270
268
|
defaultId: 0,
|
|
271
269
|
cancelId: 1,
|
|
272
270
|
message: "Update ready to install",
|
|
273
|
-
detail: `${appName} ${ready.version} has been downloaded. Restart now to
|
|
271
|
+
detail: `${appName} ${ready.version} has been downloaded. Restart now to install it.`
|
|
274
272
|
});
|
|
275
273
|
if (response === 0) await quitAndInstallSafely();
|
|
276
274
|
} else if (lastBackgroundError) {
|
|
277
275
|
await themedPrompt(parent, {
|
|
278
|
-
message: "You're on the latest version
|
|
279
|
-
detail:
|
|
280
|
-
|
|
281
|
-
Last update error:
|
|
276
|
+
message: "You're on the latest version.",
|
|
277
|
+
detail: `${appName} ${app.getVersion()} is up to date. The last update check reported an error:
|
|
282
278
|
${lastBackgroundError}`,
|
|
283
279
|
buttons: ["OK"]
|
|
284
280
|
});
|
package/dist/updater.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/updater.ts"],"names":[],"mappings":"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","file":"updater.js","sourcesContent":["// ---------------------------------------------------------------------------\n// In-app auto-update over electron-updater: the family's, lifted from Patterpad\n// where it was hardened in the field.\n//\n// This is not a wrapper anybody should write twice. Every rule in it is a bug\n// that reached a user:\n//\n// - Downloads are driven HERE rather than by electron-updater's autoDownload,\n// because holding the CancellationToken is what lets a stall watchdog kill\n// and retry a download that hangs. A hung stream emits neither progress nor\n// `error`, so anything built on events alone sits \"downloading\" for ever\n// (Patterpad #33: a Windows user's download did exactly that, all day).\n// - Differential download is off. On Windows it stalled silently across an\n// Electron major bump, where almost no blocks match anyway.\n// - There is a PERSISTENT LOG in userData, because electron-updater is silent\n// by default and a stalled download otherwise leaves nothing to diagnose.\n// - Installing asks the renderer whether there is unsaved work, and offers\n// Save / Discard / Cancel. \"Restart Now\" must never silently drop an\n// author's afternoon.\n// - Every prompt is THEMED, in the renderer, never a stock OS dialog.\n//\n// Only runs in a packaged build; in development the manual check says so rather\n// than failing obscurely.\n//\n// THE SEAMS: the app supplies its NAME (it appears in every message), a way to\n// find the ACTIVE WINDOW, and a renderer that answers four channels - the\n// contract below. Everything else is the same in every app.\n// ---------------------------------------------------------------------------\n\nimport { app, BrowserWindow, ipcMain } from \"electron\";\nimport { appendFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ProgressInfo, UpdateDownloadedEvent, UpdateInfo } from \"electron-updater\";\nimport electronUpdater from \"electron-updater\";\n\n/** Download progress, broadcast to every window so a live dialog can show it. */\nexport interface UpdaterDownloadProgress {\n percent: number;\n transferred: number;\n total: number;\n bytesPerSecond: number;\n /** Which version is coming, so a dialog can name it. */\n version: string;\n}\n\n/** A themed prompt, answered by the renderer with the chosen button index -\n * the same contract as `dialog.showMessageBox`'s `response`. */\nexport interface UpdaterPromptOptions {\n message: string;\n detail?: string;\n buttons: string[];\n defaultId?: number;\n cancelId?: number;\n /** Show live download progress in the dialog. */\n progress?: boolean;\n}\n\n/** The channels the app's renderer must answer. Named here so both sides agree. */\nexport const UPDATER_CHANNELS = {\n /** main -> renderer: \"are there unsaved edits?\"; reply on `dirtyReply`. */\n checkDirty: \"updater:check-dirty\",\n dirtyReply: \"updater:dirty-reply\",\n /** main -> renderer: \"save now\"; reply `{ ok }` on `saveDone`. */\n saveBeforeInstall: \"updater:save-before-install\",\n saveDone: \"updater:save-done\",\n /** main -> renderer: show this prompt; reply with the button index. */\n prompt: \"updater:prompt\",\n promptReply: \"updater:prompt-reply\",\n /** main -> renderer: live download progress. */\n progress: \"updater:download-progress\",\n} as const;\n\nexport interface UpdaterOptions {\n /** The product name, as it should read in every message (\"Patterpad\"). */\n appName: string;\n /** Which window the prompts belong to. */\n activeWindow: () => BrowserWindow | null;\n /** The log file inside userData. */\n logFile?: string;\n}\n\nlet appName = \"This app\";\nlet activeWindowFn: () => BrowserWindow | null = () =>\n BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] ?? null;\nconst activeWindow = (): BrowserWindow | null => activeWindowFn();\n\nconst { autoUpdater, CancellationToken } = electronUpdater;\n\nlet updateDownloaded: UpdateDownloadedEvent | null = null;\n// Remember the last background error so the manual \"Check for Updates\" dialog can\n// surface it - otherwise a broken feed (network policy, expired cert, GitHub outage)\n// stays invisible and the user has no idea why updates never arrive.\nlet lastBackgroundError: string | null = null;\n\n// Persistent updater log (userData/updater.log). electron-updater is silent by default; without a log a\n// Windows download that stalls without emitting `error` (found in 0.1.5) leaves nothing to diagnose.\nlet logPath = join(app.getPath(\"userData\"), \"updater.log\");\nconst writeLog = (level: string, args: unknown[]): void => {\n try { appendFileSync(logPath, `${new Date().toISOString()} [${level}] ${args.map((a) => (a instanceof Error ? a.stack || a.message : String(a))).join(\" \")}\\n`); } catch { /* logging must never throw */ }\n};\nautoUpdater.logger = {\n info: (...a: unknown[]) => writeLog(\"info\", a),\n warn: (...a: unknown[]) => writeLog(\"warn\", a),\n error: (...a: unknown[]) => writeLog(\"error\", a),\n debug: (...a: unknown[]) => writeLog(\"debug\", a),\n};\n\n// Downloads are driven by the download manager below, NOT electron-updater's autoDownload: holding\n// the CancellationToken ourselves is what lets the stall watchdog kill and retry a download that\n// hangs without ever erroring (#33 - a Windows user's download sat \"in the background\" all day; a\n// hung stream emits neither progress nor `error`, so nothing built on events alone can recover it).\nautoUpdater.autoDownload = false;\nautoUpdater.autoInstallOnAppQuit = true;\n// Force a plain full download instead of the block-by-block differential. On Windows the differential\n// path stalled silently (0.1.5): across an Electron-major bump almost no blocks match, so it downloads\n// nearly everything anyway, and it's fragile for unsigned installers. Full download is one reliable\n// stream. No-op on macOS (Squirrel.Mac always fetches the whole zip).\nautoUpdater.disableDifferentialDownload = true;\n\nautoUpdater.on(\"error\", (err) => {\n console.error(\"AutoUpdater error:\", err?.message || err);\n lastBackgroundError = err?.message || String(err);\n});\n\n// ---------------------------------------------------------------------------\n// Download manager: start, watch, retry. Platform-agnostic - it rides\n// electron-updater's shared events, identical for NSIS (win), zip (mac), and\n// AppImage (linux) feeds.\n// ---------------------------------------------------------------------------\n\nconst STALL_MS = 3 * 60 * 1000; // no progress event for this long = the download is hung\nconst WATCH_EVERY_MS = 30 * 1000; // how often the watchdog looks at the clock\nconst RETRY_DELAY_MS = 15 * 1000; // pause before re-attempting a killed/failed download\nconst MAX_ATTEMPTS = 3; // per check cycle; the 6-hourly background check starts a fresh cycle\n\ntype DownloadState = {\n info: UpdateInfo;\n token: InstanceType<typeof CancellationToken>;\n attempts: number; // attempts consumed this cycle, including the running one\n lastProgressAt: number;\n cancelledByWatchdog: boolean;\n watchdog: NodeJS.Timeout;\n};\nlet download: DownloadState | null = null;\n\nfunction broadcastProgress(p: UpdaterDownloadProgress): void {\n for (const w of BrowserWindow.getAllWindows()) {\n if (!w.isDestroyed() && !w.webContents.isDestroyed()) w.webContents.send(UPDATER_CHANNELS.progress, p);\n }\n}\n\nfunction clearDownload(): void {\n if (download) clearInterval(download.watchdog);\n download = null;\n}\n\n/** Begin (or re-attempt) downloading `info`. One state machine for every platform. */\nfunction beginDownload(info: UpdateInfo, attempts: number): void {\n clearDownload();\n const token = new CancellationToken();\n const state: DownloadState = {\n info, token, attempts: attempts + 1, lastProgressAt: Date.now(), cancelledByWatchdog: false,\n watchdog: setInterval(() => {\n if (download !== state) return;\n const quiet = Date.now() - state.lastProgressAt;\n if (quiet < STALL_MS) return;\n // Hung: no progress and no error for STALL_MS. Kill it; the download promise's catch retries.\n writeLog(\"warn\", [`watchdog: no download progress for ${Math.round(quiet / 1000)}s - cancelling (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n state.cancelledByWatchdog = true;\n state.token.cancel();\n }, WATCH_EVERY_MS),\n };\n download = state;\n writeLog(\"info\", [`download: starting ${info.version} (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n\n autoUpdater.downloadUpdate(token).then(() => {\n // Success is reported via the update-downloaded event; it clears the state.\n }).catch((err: unknown) => {\n if (download !== state) return; // superseded by a newer attempt/cycle\n const why = state.cancelledByWatchdog ? \"stalled (killed by the watchdog)\"\n : (err instanceof Error ? err.message : String(err));\n clearDownload();\n if (state.attempts < MAX_ATTEMPTS) {\n writeLog(\"warn\", [`download: attempt ${state.attempts} failed - ${why}; retrying in ${RETRY_DELAY_MS / 1000}s`]);\n setTimeout(() => { if (!download && !updateDownloaded) beginDownload(info, state.attempts); }, RETRY_DELAY_MS);\n } else {\n // Out of attempts for this cycle. Record it where the manual check surfaces it; the next\n // background check (6-hourly, or Help > Check for Updates) starts a fresh cycle.\n lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times (last: ${why}). Will retry on the next check.`;\n writeLog(\"error\", [`download: giving up on ${info.version} this cycle - ${why}`]);\n }\n });\n}\n\nautoUpdater.on(\"update-available\", (info: UpdateInfo) => {\n if (download || updateDownloaded) return; // already downloading it, or already have it\n beginDownload(info, 0);\n});\n\nautoUpdater.on(\"download-progress\", (p: ProgressInfo) => {\n if (download) download.lastProgressAt = Date.now();\n broadcastProgress({\n percent: p.percent, transferred: p.transferred, total: p.total,\n bytesPerSecond: p.bytesPerSecond, version: download?.info.version ?? \"\",\n });\n});\n\n/** Ask the renderer (one-shot, timeout-guarded) whether the open scene has unsaved edits.\n * Resolves false on any error/timeout - the renderer's dirty flag is the only truth here. */\nfunction askRendererIsDirty(win: BrowserWindow | null): Promise<boolean> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(false); return; }\n const onReply = (_e: unknown, isDirty: boolean) => { clearTimeout(timeout); resolve(!!isDirty); };\n ipcMain.once(UPDATER_CHANNELS.dirtyReply, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.dirtyReply, onReply);\n console.warn(\"Updater dirty-check timed out; assuming clean.\");\n resolve(false);\n }, 2000);\n win.webContents.send(UPDATER_CHANNELS.checkDirty);\n });\n}\n\n/** Trigger a save in the renderer and wait for completion. Saves run the lock-aware VC write path,\n * so the timeout is generous; if it elapses we abort rather than install over a half-saved project. */\nfunction triggerRendererSave(win: BrowserWindow | null): Promise<{ ok: boolean }> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve({ ok: false }); return; }\n const onReply = (_e: unknown, result: { ok: boolean }) => { clearTimeout(timeout); resolve(result || { ok: false }); };\n ipcMain.once(UPDATER_CHANNELS.saveDone, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.saveDone, onReply);\n console.warn(\"Updater save timed out.\");\n resolve({ ok: false });\n }, 30000);\n win.webContents.send(UPDATER_CHANNELS.saveBeforeInstall);\n });\n}\n\n/** Show a THEMED prompt in the renderer (never a stock OS dialog; design-language \"coherent to the edges\")\n * and resolve the chosen button index - the same contract as dialog.showMessageBox's `response`. Falls\n * back to cancelId/defaultId if there's no live renderer to answer (so an install is never blocked on it). */\nfunction themedPrompt(win: BrowserWindow | null, opts: UpdaterPromptOptions): Promise<number> {\n return new Promise((resolve) => {\n const fallback = opts.cancelId ?? opts.defaultId ?? 0;\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(fallback); return; }\n const onReply = (_e: unknown, idx: number) => { clearTimeout(timeout); resolve(typeof idx === \"number\" ? idx : fallback); };\n ipcMain.once(UPDATER_CHANNELS.promptReply, onReply);\n // Generous: these are user-facing prompts. If the renderer never answers (gone / hung), fall back safely.\n const timeout = setTimeout(() => { ipcMain.removeListener(UPDATER_CHANNELS.promptReply, onReply); resolve(fallback); }, 300000);\n win.webContents.send(UPDATER_CHANNELS.prompt, opts);\n });\n}\n\n/** quitAndInstall, guarded by the unsaved-edits check. autoInstallOnAppQuit routes through the normal\n * window-close save path already, so only this explicit \"Restart Now\" needs the guard. */\nasync function quitAndInstallSafely(): Promise<void> {\n const win = activeWindow();\n if (!(await askRendererIsDirty(win))) { autoUpdater.quitAndInstall(); return; }\n\n const response = await themedPrompt(win, {\n buttons: [\"Save and Restart\", \"Discard and Restart\", \"Cancel\"],\n defaultId: 0,\n cancelId: 2,\n message: \"You have unsaved changes.\",\n detail: `Save them before restarting to install ${appName} ${updateDownloaded?.version || \"\"}?`,\n });\n\n if (response === 2) return; // Cancel - abort the install\n if (response === 0) {\n const result = await triggerRendererSave(win);\n if (!result.ok) {\n await themedPrompt(win, {\n message: \"Save failed\",\n detail: \"Your changes could not be saved, so the update was not installed. Save manually, then try again.\",\n buttons: [\"OK\"],\n });\n return;\n }\n }\n // response === 1 (Discard) falls through.\n autoUpdater.quitAndInstall();\n}\n\nautoUpdater.on(\"update-downloaded\", (info: UpdateDownloadedEvent) => {\n clearDownload();\n updateDownloaded = info;\n lastBackgroundError = null; // it got here in the end; stale retry noise would only mislead\n writeLog(\"info\", [`download: ${info.version} downloaded and ready to install`]);\n const win = activeWindow();\n if (!win) return;\n void themedPrompt(win, {\n buttons: [\"Restart Now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${info.version} has been downloaded. Restart now to apply, or it will install automatically next time you quit.`,\n }).then((response) => { if (response === 0) void quitAndInstallSafely(); });\n});\n\n/** Name the app and say how to find its window. Call once, at startup. */\nexport function configureUpdater(opts: UpdaterOptions): void {\n appName = opts.appName;\n activeWindowFn = opts.activeWindow;\n if (opts.logFile !== undefined) logPath = join(app.getPath(\"userData\"), opts.logFile);\n}\n\n/** Fire-and-forget background check; periodic + on launch. No-op in dev. */\nexport function startBackgroundUpdateCheck(): void {\n if (!app.isPackaged) return;\n autoUpdater.checkForUpdates().catch((err) => {\n console.error(\"AutoUpdater background check failed:\", err?.message || err);\n });\n}\n\n/** The Help ▸ Check for Updates… handler: always gives the user feedback (downloading / up to date /\n * error), unlike the silent background check. */\nexport async function manualCheckForUpdates(win?: BrowserWindow | null): Promise<void> {\n const parent = win || activeWindow();\n\n if (!app.isPackaged) {\n await themedPrompt(parent, {\n message: \"Updates unavailable in development build\",\n detail: `Auto-update only runs in packaged builds.\\n\\nCurrent version: ${app.getVersion()}`,\n buttons: [\"OK\"],\n });\n return;\n }\n\n if (updateDownloaded) {\n const response = await themedPrompt(parent, {\n buttons: [\"Restart Now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to apply.`,\n });\n if (response === 0) await quitAndInstallSafely();\n return;\n }\n\n if (download) {\n // A download is already in flight: show it, live (the dialog subscribes to progress events).\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${download.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n return;\n }\n\n try {\n // A manual check starts a fresh retry cycle: if the last one gave up, this is the user asking again.\n await autoUpdater.checkForUpdates();\n // With autoDownload off, `update-available` (fired during the await) starts the managed download,\n // so by here `download` is set iff the feed had something newer. (Read through locals: TS narrows the\n // module-level `download` to null after the guard above and cannot see the event handler mutate it.)\n const started = download as DownloadState | null;\n const ready = updateDownloaded as UpdateDownloadedEvent | null;\n if (started) {\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${started.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n lastBackgroundError = null;\n } else if (ready) {\n // The check completed a download between our earlier guard and now (tiny window, but free to handle).\n const response = await themedPrompt(parent, {\n buttons: [\"Restart Now\", \"Later\"], defaultId: 0, cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${ready.version} has been downloaded. Restart now to apply.`,\n });\n if (response === 0) await quitAndInstallSafely();\n } else if (lastBackgroundError) {\n await themedPrompt(parent, {\n message: \"You're on the latest version, but updates have had errors.\",\n detail: `Current version: ${appName} ${app.getVersion()}\\n\\nLast update error:\\n${lastBackgroundError}`,\n buttons: [\"OK\"],\n });\n lastBackgroundError = null;\n } else {\n await themedPrompt(parent, {\n message: \"You're on the latest version.\",\n detail: `${appName} ${app.getVersion()} is up to date.`,\n buttons: [\"OK\"],\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const detail = lastBackgroundError && lastBackgroundError !== message\n ? `${message}\\n\\nA previous background check also failed with:\\n${lastBackgroundError}`\n : message;\n await themedPrompt(parent, { message: \"Update check failed\", detail, buttons: [\"OK\"] });\n lastBackgroundError = null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/updater.ts"],"names":[],"mappings":"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","file":"updater.js","sourcesContent":["// ---------------------------------------------------------------------------\n// In-app auto-update over electron-updater: the family's, lifted from Patterpad\n// where it was hardened in the field.\n//\n// This is not a wrapper anybody should write twice. Every rule in it is a bug\n// that reached a user:\n//\n// - Downloads are driven HERE rather than by electron-updater's autoDownload,\n// because holding the CancellationToken is what lets a stall watchdog kill\n// and retry a download that hangs. A hung stream emits neither progress nor\n// `error`, so anything built on events alone sits \"downloading\" for ever\n// (Patterpad #33: a Windows user's download did exactly that, all day).\n// - Differential download is off. On Windows it stalled silently across an\n// Electron major bump, where almost no blocks match anyway.\n// - There is a PERSISTENT LOG in userData, because electron-updater is silent\n// by default and a stalled download otherwise leaves nothing to diagnose.\n// - Installing asks the renderer whether there is unsaved work, and offers\n// Save / Discard / Cancel. \"Restart Now\" must never silently drop an\n// author's afternoon.\n// - Every prompt is THEMED, in the renderer, never a stock OS dialog.\n//\n// Only runs in a packaged build; in development the manual check says so rather\n// than failing obscurely.\n//\n// THE SEAMS: the app supplies its NAME (it appears in every message), a way to\n// find the ACTIVE WINDOW, and a renderer that answers four channels - the\n// contract below. Everything else is the same in every app.\n// ---------------------------------------------------------------------------\n\nimport { app, BrowserWindow, ipcMain } from \"electron\";\nimport { appendFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ProgressInfo, UpdateDownloadedEvent, UpdateInfo } from \"electron-updater\";\nimport electronUpdater from \"electron-updater\";\n\n/** Download progress, broadcast to every window so a live dialog can show it. */\nexport interface UpdaterDownloadProgress {\n percent: number;\n transferred: number;\n total: number;\n bytesPerSecond: number;\n /** Which version is coming, so a dialog can name it. */\n version: string;\n}\n\n/** A themed prompt, answered by the renderer with the chosen button index -\n * the same contract as `dialog.showMessageBox`'s `response`. */\nexport interface UpdaterPromptOptions {\n message: string;\n detail?: string;\n buttons: string[];\n defaultId?: number;\n cancelId?: number;\n /** Show live download progress in the dialog. */\n progress?: boolean;\n}\n\n/** The channels the app's renderer must answer. Named here so both sides agree. */\nexport const UPDATER_CHANNELS = {\n /** main -> renderer: \"are there unsaved edits?\"; reply on `dirtyReply`. */\n checkDirty: \"updater:check-dirty\",\n dirtyReply: \"updater:dirty-reply\",\n /** main -> renderer: \"save now\"; reply `{ ok }` on `saveDone`. */\n saveBeforeInstall: \"updater:save-before-install\",\n saveDone: \"updater:save-done\",\n /** main -> renderer: show this prompt; reply with the button index. */\n prompt: \"updater:prompt\",\n promptReply: \"updater:prompt-reply\",\n /** main -> renderer: live download progress. */\n progress: \"updater:download-progress\",\n} as const;\n\nexport interface UpdaterOptions {\n /** The product name, as it should read in every message (\"Patterpad\"). */\n appName: string;\n /** Which window the prompts belong to. */\n activeWindow: () => BrowserWindow | null;\n /** The log file inside userData. */\n logFile?: string;\n}\n\nlet appName = \"This app\";\nlet activeWindowFn: () => BrowserWindow | null = () =>\n BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0] ?? null;\nconst activeWindow = (): BrowserWindow | null => activeWindowFn();\n\nconst { autoUpdater, CancellationToken } = electronUpdater;\n\nlet updateDownloaded: UpdateDownloadedEvent | null = null;\n// Remember the last background error so the manual \"Check for Updates\" dialog can\n// surface it - otherwise a broken feed (network policy, expired cert, GitHub outage)\n// stays invisible and the user has no idea why updates never arrive.\nlet lastBackgroundError: string | null = null;\n\n// Persistent updater log (userData/updater.log). electron-updater is silent by default; without a log a\n// Windows download that stalls without emitting `error` (found in 0.1.5) leaves nothing to diagnose.\nlet logPath = join(app.getPath(\"userData\"), \"updater.log\");\nconst writeLog = (level: string, args: unknown[]): void => {\n try { appendFileSync(logPath, `${new Date().toISOString()} [${level}] ${args.map((a) => (a instanceof Error ? a.stack || a.message : String(a))).join(\" \")}\\n`); } catch { /* logging must never throw */ }\n};\nautoUpdater.logger = {\n info: (...a: unknown[]) => writeLog(\"info\", a),\n warn: (...a: unknown[]) => writeLog(\"warn\", a),\n error: (...a: unknown[]) => writeLog(\"error\", a),\n debug: (...a: unknown[]) => writeLog(\"debug\", a),\n};\n\n// Downloads are driven by the download manager below, NOT electron-updater's autoDownload: holding\n// the CancellationToken ourselves is what lets the stall watchdog kill and retry a download that\n// hangs without ever erroring (#33 - a Windows user's download sat \"in the background\" all day; a\n// hung stream emits neither progress nor `error`, so nothing built on events alone can recover it).\nautoUpdater.autoDownload = false;\nautoUpdater.autoInstallOnAppQuit = true;\n// Force a plain full download instead of the block-by-block differential. On Windows the differential\n// path stalled silently (0.1.5): across an Electron-major bump almost no blocks match, so it downloads\n// nearly everything anyway, and it's fragile for unsigned installers. Full download is one reliable\n// stream. No-op on macOS (Squirrel.Mac always fetches the whole zip).\nautoUpdater.disableDifferentialDownload = true;\n\nautoUpdater.on(\"error\", (err) => {\n console.error(\"AutoUpdater error:\", err?.message || err);\n lastBackgroundError = err?.message || String(err);\n});\n\n// ---------------------------------------------------------------------------\n// Download manager: start, watch, retry. Platform-agnostic - it rides\n// electron-updater's shared events, identical for NSIS (win), zip (mac), and\n// AppImage (linux) feeds.\n// ---------------------------------------------------------------------------\n\nconst STALL_MS = 3 * 60 * 1000; // no progress event for this long = the download is hung\nconst WATCH_EVERY_MS = 30 * 1000; // how often the watchdog looks at the clock\nconst RETRY_DELAY_MS = 15 * 1000; // pause before re-attempting a killed/failed download\nconst MAX_ATTEMPTS = 3; // per check cycle; the 6-hourly background check starts a fresh cycle\n\ntype DownloadState = {\n info: UpdateInfo;\n token: InstanceType<typeof CancellationToken>;\n attempts: number; // attempts consumed this cycle, including the running one\n lastProgressAt: number;\n cancelledByWatchdog: boolean;\n watchdog: NodeJS.Timeout;\n};\nlet download: DownloadState | null = null;\n\nfunction broadcastProgress(p: UpdaterDownloadProgress): void {\n for (const w of BrowserWindow.getAllWindows()) {\n if (!w.isDestroyed() && !w.webContents.isDestroyed()) w.webContents.send(UPDATER_CHANNELS.progress, p);\n }\n}\n\nfunction clearDownload(): void {\n if (download) clearInterval(download.watchdog);\n download = null;\n}\n\n/** Begin (or re-attempt) downloading `info`. One state machine for every platform. */\nfunction beginDownload(info: UpdateInfo, attempts: number): void {\n clearDownload();\n const token = new CancellationToken();\n const state: DownloadState = {\n info, token, attempts: attempts + 1, lastProgressAt: Date.now(), cancelledByWatchdog: false,\n watchdog: setInterval(() => {\n if (download !== state) return;\n const quiet = Date.now() - state.lastProgressAt;\n if (quiet < STALL_MS) return;\n // Hung: no progress and no error for STALL_MS. Kill it; the download promise's catch retries.\n writeLog(\"warn\", [`watchdog: no download progress for ${Math.round(quiet / 1000)}s - cancelling (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n state.cancelledByWatchdog = true;\n state.token.cancel();\n }, WATCH_EVERY_MS),\n };\n download = state;\n writeLog(\"info\", [`download: starting ${info.version} (attempt ${state.attempts}/${MAX_ATTEMPTS})`]);\n\n autoUpdater.downloadUpdate(token).then(() => {\n // Success is reported via the update-downloaded event; it clears the state.\n }).catch((err: unknown) => {\n if (download !== state) return; // superseded by a newer attempt/cycle\n const why = state.cancelledByWatchdog ? \"stalled (killed by the watchdog)\"\n : (err instanceof Error ? err.message : String(err));\n clearDownload();\n if (state.attempts < MAX_ATTEMPTS) {\n writeLog(\"warn\", [`download: attempt ${state.attempts} failed - ${why}; retrying in ${RETRY_DELAY_MS / 1000}s`]);\n setTimeout(() => { if (!download && !updateDownloaded) beginDownload(info, state.attempts); }, RETRY_DELAY_MS);\n } else {\n // Out of attempts for this cycle. Record it where the manual check surfaces it; the next\n // background check (6-hourly, or Help > Check for Updates) starts a fresh cycle.\n lastBackgroundError = `Downloading ${info.version} failed ${MAX_ATTEMPTS} times, most recently with \"${why}\". It will be tried again on the next check.`;\n writeLog(\"error\", [`download: giving up on ${info.version} this cycle - ${why}`]);\n }\n });\n}\n\nautoUpdater.on(\"update-available\", (info: UpdateInfo) => {\n if (download || updateDownloaded) return; // already downloading it, or already have it\n beginDownload(info, 0);\n});\n\nautoUpdater.on(\"download-progress\", (p: ProgressInfo) => {\n if (download) download.lastProgressAt = Date.now();\n broadcastProgress({\n percent: p.percent, transferred: p.transferred, total: p.total,\n bytesPerSecond: p.bytesPerSecond, version: download?.info.version ?? \"\",\n });\n});\n\n/** Ask the renderer (one-shot, timeout-guarded) whether the open scene has unsaved edits.\n * Resolves false on any error/timeout - the renderer's dirty flag is the only truth here. */\nfunction askRendererIsDirty(win: BrowserWindow | null): Promise<boolean> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(false); return; }\n const onReply = (_e: unknown, isDirty: boolean) => { clearTimeout(timeout); resolve(!!isDirty); };\n ipcMain.once(UPDATER_CHANNELS.dirtyReply, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.dirtyReply, onReply);\n console.warn(\"Updater dirty-check timed out; assuming clean.\");\n resolve(false);\n }, 2000);\n win.webContents.send(UPDATER_CHANNELS.checkDirty);\n });\n}\n\n/** Trigger a save in the renderer and wait for completion. Saves run the lock-aware VC write path,\n * so the timeout is generous; if it elapses we abort rather than install over a half-saved project. */\nfunction triggerRendererSave(win: BrowserWindow | null): Promise<{ ok: boolean }> {\n return new Promise((resolve) => {\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve({ ok: false }); return; }\n const onReply = (_e: unknown, result: { ok: boolean }) => { clearTimeout(timeout); resolve(result || { ok: false }); };\n ipcMain.once(UPDATER_CHANNELS.saveDone, onReply);\n const timeout = setTimeout(() => {\n ipcMain.removeListener(UPDATER_CHANNELS.saveDone, onReply);\n console.warn(\"Updater save timed out.\");\n resolve({ ok: false });\n }, 30000);\n win.webContents.send(UPDATER_CHANNELS.saveBeforeInstall);\n });\n}\n\n/** Show a THEMED prompt in the renderer (never a stock OS dialog; design-language \"coherent to the edges\")\n * and resolve the chosen button index - the same contract as dialog.showMessageBox's `response`. Falls\n * back to cancelId/defaultId if there's no live renderer to answer (so an install is never blocked on it). */\nfunction themedPrompt(win: BrowserWindow | null, opts: UpdaterPromptOptions): Promise<number> {\n return new Promise((resolve) => {\n const fallback = opts.cancelId ?? opts.defaultId ?? 0;\n if (!win || win.isDestroyed() || win.webContents.isDestroyed()) { resolve(fallback); return; }\n const onReply = (_e: unknown, idx: number) => { clearTimeout(timeout); resolve(typeof idx === \"number\" ? idx : fallback); };\n ipcMain.once(UPDATER_CHANNELS.promptReply, onReply);\n // Generous: these are user-facing prompts. If the renderer never answers (gone / hung), fall back safely.\n const timeout = setTimeout(() => { ipcMain.removeListener(UPDATER_CHANNELS.promptReply, onReply); resolve(fallback); }, 300000);\n win.webContents.send(UPDATER_CHANNELS.prompt, opts);\n });\n}\n\n/** quitAndInstall, guarded by the unsaved-edits check. autoInstallOnAppQuit routes through the normal\n * window-close save path already, so only this explicit \"Restart Now\" needs the guard. */\nasync function quitAndInstallSafely(): Promise<void> {\n const win = activeWindow();\n if (!(await askRendererIsDirty(win))) { autoUpdater.quitAndInstall(); return; }\n\n const response = await themedPrompt(win, {\n buttons: [\"Save and restart\", \"Discard and restart\", \"Cancel\"],\n defaultId: 0,\n cancelId: 2,\n message: \"You have unsaved changes.\",\n detail: `Save them before restarting to install ${appName} ${updateDownloaded?.version || \"\"}?`,\n });\n\n if (response === 2) return; // Cancel - abort the install\n if (response === 0) {\n const result = await triggerRendererSave(win);\n if (!result.ok) {\n await themedPrompt(win, {\n message: \"Save failed\",\n detail: \"Your changes could not be saved, so the update was not installed. Save manually, then try again.\",\n buttons: [\"OK\"],\n });\n return;\n }\n }\n // response === 1 (Discard) falls through.\n autoUpdater.quitAndInstall();\n}\n\nautoUpdater.on(\"update-downloaded\", (info: UpdateDownloadedEvent) => {\n clearDownload();\n updateDownloaded = info;\n lastBackgroundError = null; // it got here in the end; stale retry noise would only mislead\n writeLog(\"info\", [`download: ${info.version} downloaded and ready to install`]);\n const win = activeWindow();\n if (!win) return;\n void themedPrompt(win, {\n buttons: [\"Restart now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${info.version} has been downloaded. Restart now to install it, or it installs the next time you quit.`,\n }).then((response) => { if (response === 0) void quitAndInstallSafely(); });\n});\n\n/** Name the app and say how to find its window. Call once, at startup. */\nexport function configureUpdater(opts: UpdaterOptions): void {\n appName = opts.appName;\n activeWindowFn = opts.activeWindow;\n if (opts.logFile !== undefined) logPath = join(app.getPath(\"userData\"), opts.logFile);\n}\n\n/** Fire-and-forget background check; periodic + on launch. No-op in dev. */\nexport function startBackgroundUpdateCheck(): void {\n if (!app.isPackaged) return;\n autoUpdater.checkForUpdates().catch((err) => {\n console.error(\"AutoUpdater background check failed:\", err?.message || err);\n });\n}\n\n/** The Help ▸ Check for Updates… handler: always gives the user feedback (downloading / up to date /\n * error), unlike the silent background check. */\nexport async function manualCheckForUpdates(win?: BrowserWindow | null): Promise<void> {\n const parent = win || activeWindow();\n\n if (!app.isPackaged) {\n await themedPrompt(parent, {\n message: \"Updates unavailable in a development build\",\n detail: `Auto-update only runs in a packaged build. This is version ${app.getVersion()}.`,\n buttons: [\"OK\"],\n });\n return;\n }\n\n if (updateDownloaded) {\n const response = await themedPrompt(parent, {\n buttons: [\"Restart now\", \"Later\"],\n defaultId: 0,\n cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${updateDownloaded.version} has been downloaded. Restart now to install it.`,\n });\n if (response === 0) await quitAndInstallSafely();\n return;\n }\n\n if (download) {\n // A download is already in flight: show it, live (the dialog subscribes to progress events).\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${download.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n return;\n }\n\n try {\n // A manual check starts a fresh retry cycle: if the last one gave up, this is the user asking again.\n await autoUpdater.checkForUpdates();\n // With autoDownload off, `update-available` (fired during the await) starts the managed download,\n // so by here `download` is set iff the feed had something newer. (Read through locals: TS narrows the\n // module-level `download` to null after the guard above and cannot see the event handler mutate it.)\n const started = download as DownloadState | null;\n const ready = updateDownloaded as UpdateDownloadedEvent | null;\n if (started) {\n await themedPrompt(parent, {\n message: \"Update available\",\n detail: `${appName} ${started.info.version} is downloading. You'll be prompted to restart when it's ready.`,\n buttons: [\"OK\"],\n progress: true,\n });\n lastBackgroundError = null;\n } else if (ready) {\n // The check completed a download between our earlier guard and now (tiny window, but free to handle).\n const response = await themedPrompt(parent, {\n buttons: [\"Restart now\", \"Later\"], defaultId: 0, cancelId: 1,\n message: \"Update ready to install\",\n detail: `${appName} ${ready.version} has been downloaded. Restart now to install it.`,\n });\n if (response === 0) await quitAndInstallSafely();\n } else if (lastBackgroundError) {\n await themedPrompt(parent, {\n message: \"You're on the latest version.\",\n detail: `${appName} ${app.getVersion()} is up to date. The last update check reported an error:\\n${lastBackgroundError}`,\n buttons: [\"OK\"],\n });\n lastBackgroundError = null;\n } else {\n await themedPrompt(parent, {\n message: \"You're on the latest version.\",\n detail: `${appName} ${app.getVersion()} is up to date.`,\n buttons: [\"OK\"],\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const detail = lastBackgroundError && lastBackgroundError !== message\n ? `${message}\\n\\nA previous background check also failed with:\\n${lastBackgroundError}`\n : message;\n await themedPrompt(parent, { message: \"Update check failed\", detail, buttons: [\"OK\"] });\n lastBackgroundError = null;\n }\n}\n"]}
|
package/dist/util.cjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/util.ts
|
|
4
|
+
function plural(n, noun, pluralForm = `${noun}s`) {
|
|
5
|
+
return `${n} ${n === 1 ? noun : pluralForm}`;
|
|
6
|
+
}
|
|
7
|
+
function debounce(fn, ms) {
|
|
8
|
+
let timer;
|
|
9
|
+
let pending;
|
|
10
|
+
const run = () => {
|
|
11
|
+
timer = void 0;
|
|
12
|
+
if (pending === void 0) return;
|
|
13
|
+
const args = pending;
|
|
14
|
+
pending = void 0;
|
|
15
|
+
fn(...args);
|
|
16
|
+
};
|
|
17
|
+
const debounced = ((...args) => {
|
|
18
|
+
pending = args;
|
|
19
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
20
|
+
timer = setTimeout(run, ms);
|
|
21
|
+
});
|
|
22
|
+
debounced.cancel = () => {
|
|
23
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
24
|
+
timer = void 0;
|
|
25
|
+
pending = void 0;
|
|
26
|
+
};
|
|
27
|
+
debounced.flush = () => {
|
|
28
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
29
|
+
run();
|
|
30
|
+
};
|
|
31
|
+
return debounced;
|
|
32
|
+
}
|
|
33
|
+
function isEditableTarget(t) {
|
|
34
|
+
if (!(t instanceof Element)) return false;
|
|
35
|
+
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) return true;
|
|
36
|
+
if (t instanceof HTMLElement && t.isContentEditable === true) return true;
|
|
37
|
+
const attr = t.getAttribute("contenteditable");
|
|
38
|
+
return attr !== null && attr !== "false";
|
|
39
|
+
}
|
|
40
|
+
function formatCount(n) {
|
|
41
|
+
return n.toLocaleString();
|
|
42
|
+
}
|
|
43
|
+
var copyTimers = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
function copyWithFeedback(el, text, ms = 1e3) {
|
|
45
|
+
const write = typeof navigator !== "undefined" && navigator.clipboard ? navigator.clipboard.writeText(text).catch(() => {
|
|
46
|
+
}) : Promise.resolve();
|
|
47
|
+
el.classList.add("copied");
|
|
48
|
+
const prior = copyTimers.get(el);
|
|
49
|
+
if (prior !== void 0) clearTimeout(prior);
|
|
50
|
+
copyTimers.set(el, setTimeout(() => {
|
|
51
|
+
el.classList.remove("copied");
|
|
52
|
+
copyTimers.delete(el);
|
|
53
|
+
}, ms));
|
|
54
|
+
return write;
|
|
55
|
+
}
|
|
56
|
+
function relativeTime(date, now = Date.now()) {
|
|
57
|
+
const then = date instanceof Date ? date.getTime() : new Date(date).getTime();
|
|
58
|
+
if (!Number.isFinite(then)) return "";
|
|
59
|
+
const mins = Math.floor((now - then) / 6e4);
|
|
60
|
+
if (mins < 1) return "just now";
|
|
61
|
+
if (mins < 60) return `${plural(mins, "minute")} ago`;
|
|
62
|
+
const hours = Math.floor(mins / 60);
|
|
63
|
+
if (hours === 1) return "an hour ago";
|
|
64
|
+
if (hours < 24) return `${hours} hours ago`;
|
|
65
|
+
const days = Math.floor(hours / 24);
|
|
66
|
+
if (days === 1) return "yesterday";
|
|
67
|
+
if (days < 7) return `${days} days ago`;
|
|
68
|
+
return new Date(then).toLocaleDateString(void 0, { day: "numeric", month: "short", year: "numeric" });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
exports.copyWithFeedback = copyWithFeedback;
|
|
72
|
+
exports.debounce = debounce;
|
|
73
|
+
exports.formatCount = formatCount;
|
|
74
|
+
exports.isEditableTarget = isEditableTarget;
|
|
75
|
+
exports.plural = plural;
|
|
76
|
+
exports.relativeTime = relativeTime;
|
|
77
|
+
//# sourceMappingURL=util.cjs.map
|
|
78
|
+
//# sourceMappingURL=util.cjs.map
|