git-watchtower 1.14.2 → 1.14.4
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/bin/git-watchtower.js +35 -30
- package/package.json +1 -1
- package/src/git/remote.js +1 -1
- package/src/server/coordinator.js +18 -7
- package/src/server/web-ui/js.js +8 -8
- package/src/server/web.js +4 -4
- package/src/utils/monitor-lock.js +1 -1
- package/src/utils/version-check.js +1 -1
package/bin/git-watchtower.js
CHANGED
|
@@ -654,7 +654,7 @@ async function loadAsyncActionData(branch, currentData) {
|
|
|
654
654
|
try {
|
|
655
655
|
const host = new URL(env.webUrlBase).hostname;
|
|
656
656
|
webUrl = buildBranchUrl(env.webUrlBase, host, branch.name);
|
|
657
|
-
} catch (e) { /*
|
|
657
|
+
} catch (e) { /* invalid webUrlBase — leave webUrl null, modal hides the link */ }
|
|
658
658
|
}
|
|
659
659
|
|
|
660
660
|
// Fetch session URL (local git, fast but async)
|
|
@@ -1963,7 +1963,7 @@ async function pollGitChanges() {
|
|
|
1963
1963
|
}
|
|
1964
1964
|
|
|
1965
1965
|
// Background ahead/behind fetch for visible branches
|
|
1966
|
-
fetchAheadBehindForBranches(pollFilteredBranches).catch(() => {});
|
|
1966
|
+
fetchAheadBehindForBranches(pollFilteredBranches).catch(() => { /* transient git/network error — next poll will retry */ });
|
|
1967
1967
|
|
|
1968
1968
|
// AUTO-PULL: If current branch has remote updates, pull automatically (if enabled)
|
|
1969
1969
|
const autoPullBranchName = store.get('currentBranch');
|
|
@@ -2207,7 +2207,12 @@ function createStaticServer() {
|
|
|
2207
2207
|
let realStaticDir;
|
|
2208
2208
|
try {
|
|
2209
2209
|
realStaticDir = fs.realpathSync(resolvedStaticDir);
|
|
2210
|
-
} catch {
|
|
2210
|
+
} catch (e) {
|
|
2211
|
+
// STATIC_DIR comes from our own package layout, so a realpath failure
|
|
2212
|
+
// means the install is broken (missing dir, permissions, etc.) — worth
|
|
2213
|
+
// diagnosing. Fall back to the unresolved path so the request still
|
|
2214
|
+
// gets its 403 rather than crashing.
|
|
2215
|
+
telemetry.captureError(e);
|
|
2211
2216
|
realStaticDir = resolvedStaticDir;
|
|
2212
2217
|
}
|
|
2213
2218
|
if (!resolvedPath.startsWith(realStaticDir + path.sep) && resolvedPath !== realStaticDir) {
|
|
@@ -2447,7 +2452,7 @@ function setupKeyboardInput() {
|
|
|
2447
2452
|
store.setState({ actionData: fullData, actionLoading: false });
|
|
2448
2453
|
render();
|
|
2449
2454
|
}
|
|
2450
|
-
}).catch(() => {});
|
|
2455
|
+
}).catch(() => { /* PR was created; modal refresh is a nice-to-have, user can reopen */ });
|
|
2451
2456
|
} catch (e) {
|
|
2452
2457
|
const msg = (e && e.stderr) || (e && e.message) || String(e);
|
|
2453
2458
|
addLog(`Failed to create ${prLabel}: ${msg.split('\n')[0]}`, 'error');
|
|
@@ -2930,7 +2935,7 @@ function setupKeyboardInput() {
|
|
|
2930
2935
|
} else {
|
|
2931
2936
|
startWebDashboard(true).then(() => {
|
|
2932
2937
|
showFlash(`Web dashboard on :${WEB_PORT}`);
|
|
2933
|
-
}).catch(() => {});
|
|
2938
|
+
}).catch(() => { /* startWebDashboard surfaces its own errors via addLog/showErrorToast */ });
|
|
2934
2939
|
}
|
|
2935
2940
|
break;
|
|
2936
2941
|
}
|
|
@@ -3190,7 +3195,7 @@ async function startWebDashboard(openBrowser) {
|
|
|
3190
3195
|
// Resolve and cache the repo web URL for link building in the web UI
|
|
3191
3196
|
getRemoteWebUrl(null).then((url) => {
|
|
3192
3197
|
if (url) webDashboard.setRepoWebUrl(url);
|
|
3193
|
-
}).catch(() => {});
|
|
3198
|
+
}).catch(() => { /* no remote or unreachable — web UI falls back to branch names without links */ });
|
|
3194
3199
|
|
|
3195
3200
|
// Atomically try to claim the coordinator role. If another live instance
|
|
3196
3201
|
// already owns the lock, connect as a worker instead. This prevents a
|
|
@@ -3317,10 +3322,10 @@ async function startWebDashboard(openBrowser) {
|
|
|
3317
3322
|
webStateInterval = null;
|
|
3318
3323
|
}
|
|
3319
3324
|
if (webDashboard) {
|
|
3320
|
-
try { webDashboard.stop(); } catch (_) { /*
|
|
3325
|
+
try { webDashboard.stop(); } catch (_) { /* web server may not have bound yet — nothing to stop */ }
|
|
3321
3326
|
}
|
|
3322
3327
|
if (coordinator) {
|
|
3323
|
-
try { coordinator.stop(); } catch (_) { /*
|
|
3328
|
+
try { coordinator.stop(); } catch (_) { /* coordinator may not have started its IPC server */ }
|
|
3324
3329
|
}
|
|
3325
3330
|
removeLock();
|
|
3326
3331
|
removeSocket();
|
|
@@ -3387,7 +3392,7 @@ function restartProcess() {
|
|
|
3387
3392
|
// child can acquire it. The parent stays alive waiting on child.on('close'),
|
|
3388
3393
|
// so without this the child sees the parent as an active owner and refuses.
|
|
3389
3394
|
if (monitorLockFile) {
|
|
3390
|
-
try { monitorLock.release(monitorLockFile); } catch (_) { /*
|
|
3395
|
+
try { monitorLock.release(monitorLockFile); } catch (_) { /* lock file may have already been unlinked */ }
|
|
3391
3396
|
monitorLockFile = null;
|
|
3392
3397
|
}
|
|
3393
3398
|
|
|
@@ -3433,23 +3438,23 @@ function cleanupResources() {
|
|
|
3433
3438
|
|
|
3434
3439
|
// Restore terminal first so the user sees a clean prompt even if a
|
|
3435
3440
|
// later step throws.
|
|
3436
|
-
try { write(ansi.showCursor); } catch (_) { /*
|
|
3437
|
-
try { write(ansi.restoreScreen); } catch (_) { /*
|
|
3438
|
-
try { restoreTerminalTitle(); } catch (_) { /*
|
|
3439
|
-
try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch (_) { /*
|
|
3440
|
-
try { process.stdin.pause(); } catch (_) { /*
|
|
3441
|
+
try { write(ansi.showCursor); } catch (_) { /* stdout may be closed during crash cleanup */ }
|
|
3442
|
+
try { write(ansi.restoreScreen); } catch (_) { /* stdout may be closed during crash cleanup */ }
|
|
3443
|
+
try { restoreTerminalTitle(); } catch (_) { /* stdout may be closed during crash cleanup */ }
|
|
3444
|
+
try { if (process.stdin.isTTY) process.stdin.setRawMode(false); } catch (_) { /* stdin may already be unraw or detached */ }
|
|
3445
|
+
try { process.stdin.pause(); } catch (_) { /* stdin may already be paused or destroyed */ }
|
|
3441
3446
|
|
|
3442
3447
|
if (pollIntervalId) {
|
|
3443
|
-
try { clearTimeout(pollIntervalId); } catch (_) { /*
|
|
3448
|
+
try { clearTimeout(pollIntervalId); } catch (_) { /* defensive — clearTimeout normally won't throw */ }
|
|
3444
3449
|
pollIntervalId = null;
|
|
3445
3450
|
}
|
|
3446
3451
|
|
|
3447
3452
|
if (periodicUpdateCheck) {
|
|
3448
|
-
try { periodicUpdateCheck.stop(); } catch (_) { /*
|
|
3453
|
+
try { periodicUpdateCheck.stop(); } catch (_) { /* interval handle may already be cleared */ }
|
|
3449
3454
|
}
|
|
3450
3455
|
|
|
3451
3456
|
if (fileWatcher) {
|
|
3452
|
-
try { fileWatcher.close(); } catch (_) { /*
|
|
3457
|
+
try { fileWatcher.close(); } catch (_) { /* watcher may already be closed by OS or previous cleanup */ }
|
|
3453
3458
|
fileWatcher = null;
|
|
3454
3459
|
}
|
|
3455
3460
|
|
|
@@ -3457,24 +3462,24 @@ function cleanupResources() {
|
|
|
3457
3462
|
if (SERVER_MODE === 'static') {
|
|
3458
3463
|
try {
|
|
3459
3464
|
clients.forEach((client) => {
|
|
3460
|
-
try { client.end(); } catch (_) { /*
|
|
3465
|
+
try { client.end(); } catch (_) { /* SSE client socket already closed */ }
|
|
3461
3466
|
});
|
|
3462
3467
|
clients.clear();
|
|
3463
|
-
} catch (_) { /*
|
|
3468
|
+
} catch (_) { /* clients set may have mutated mid-iteration during shutdown */ }
|
|
3464
3469
|
}
|
|
3465
3470
|
|
|
3466
3471
|
// User's dev-server process (command mode)
|
|
3467
3472
|
if (SERVER_MODE === 'command') {
|
|
3468
|
-
try { stopServerProcess(); } catch (_) { /*
|
|
3473
|
+
try { stopServerProcess(); } catch (_) { /* dev-server child may already be gone */ }
|
|
3469
3474
|
}
|
|
3470
3475
|
|
|
3471
3476
|
// Web dashboard + worker/coordinator (unlinks lock file + IPC socket)
|
|
3472
|
-
try { stopWebDashboard(); } catch (_) { /*
|
|
3477
|
+
try { stopWebDashboard(); } catch (_) { /* web dashboard may never have been started */ }
|
|
3473
3478
|
|
|
3474
3479
|
// Per-repo monitor lock — release last so the slot stays reserved for the
|
|
3475
3480
|
// entire lifetime of this process, including any errors in the steps above.
|
|
3476
3481
|
if (monitorLockFile) {
|
|
3477
|
-
try { monitorLock.release(monitorLockFile); } catch (_) { /*
|
|
3482
|
+
try { monitorLock.release(monitorLockFile); } catch (_) { /* lock file may have been unlinked externally */ }
|
|
3478
3483
|
monitorLockFile = null;
|
|
3479
3484
|
}
|
|
3480
3485
|
}
|
|
@@ -3523,9 +3528,9 @@ process.on('uncaughtException', async (err) => {
|
|
|
3523
3528
|
// if telemetry shutdown hangs or throws.
|
|
3524
3529
|
cleanupResources();
|
|
3525
3530
|
|
|
3526
|
-
try { telemetry.captureError(err); } catch (_) { /*
|
|
3531
|
+
try { telemetry.captureError(err); } catch (_) { /* telemetry must never prevent crash cleanup */ }
|
|
3527
3532
|
console.error('Uncaught exception:', err);
|
|
3528
|
-
try { await telemetry.shutdown(); } catch (_) { /*
|
|
3533
|
+
try { await telemetry.shutdown(); } catch (_) { /* telemetry must never prevent crash cleanup */ }
|
|
3529
3534
|
process.exit(1);
|
|
3530
3535
|
});
|
|
3531
3536
|
|
|
@@ -3652,11 +3657,11 @@ async function start() {
|
|
|
3652
3657
|
|
|
3653
3658
|
// Detect default branch for ahead/behind counts, then fetch initial data
|
|
3654
3659
|
detectDefaultBranch().then(() => {
|
|
3655
|
-
fetchAheadBehindForBranches(initBranches).catch(() => {});
|
|
3656
|
-
}).catch(() => {});
|
|
3660
|
+
fetchAheadBehindForBranches(initBranches).catch(() => { /* ahead/behind is background-only — stale counts are better than a noisy startup */ });
|
|
3661
|
+
}).catch(() => { /* no default branch detectable (no remote refs yet) — ahead/behind stays hidden */ });
|
|
3657
3662
|
|
|
3658
3663
|
// Load sparklines and action cache in background
|
|
3659
|
-
refreshAllSparklines().catch(() => {});
|
|
3664
|
+
refreshAllSparklines().catch(() => { /* sparkline cache stays empty — activity column just renders blank */ });
|
|
3660
3665
|
initActionCache().then(() => {
|
|
3661
3666
|
// Once env is known, kick off initial PR status fetch
|
|
3662
3667
|
fetchAllPrStatuses().then(map => {
|
|
@@ -3665,8 +3670,8 @@ async function start() {
|
|
|
3665
3670
|
lastPrStatusFetch = Date.now();
|
|
3666
3671
|
render();
|
|
3667
3672
|
}
|
|
3668
|
-
}).catch(() => {});
|
|
3669
|
-
}).catch(() => {});
|
|
3673
|
+
}).catch(() => { /* gh/glab unreachable — inline PR indicators stay hidden, poller will retry */ });
|
|
3674
|
+
}).catch(() => { /* cliEnv detection failed — PR actions fall back to web links where possible */ });
|
|
3670
3675
|
|
|
3671
3676
|
// Start server based on mode
|
|
3672
3677
|
const startBranchName = store.get('currentBranch');
|
|
@@ -3737,7 +3742,7 @@ async function start() {
|
|
|
3737
3742
|
addLog(`New version available: ${latestVersion} \u2192 npm i -g git-watchtower`, 'update');
|
|
3738
3743
|
render();
|
|
3739
3744
|
}
|
|
3740
|
-
}).catch(() => {});
|
|
3745
|
+
}).catch(() => { /* npm registry unreachable — periodic check will try again in 4h */ });
|
|
3741
3746
|
|
|
3742
3747
|
// Re-check for updates periodically (every 4 hours) while running.
|
|
3743
3748
|
// Assigned to module scope so the top-level exit handler can stop it.
|
package/package.json
CHANGED
package/src/git/remote.js
CHANGED
|
@@ -77,7 +77,7 @@ function detectPlatform(webUrl) {
|
|
|
77
77
|
if (host === 'gitlab.com' || parts.includes('gitlab')) return 'gitlab';
|
|
78
78
|
if (host === 'bitbucket.org' || parts.includes('bitbucket')) return 'bitbucket';
|
|
79
79
|
if (host === 'dev.azure.com' || host.endsWith('.visualstudio.com')) return 'azure';
|
|
80
|
-
} catch (e) { /*
|
|
80
|
+
} catch (e) { /* webUrl isn't a valid URL — fall through to the self-hosted default */ }
|
|
81
81
|
return 'github'; // default assumption for self-hosted
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -20,6 +20,7 @@ const fs = require('fs');
|
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const os = require('os');
|
|
22
22
|
const crypto = require('crypto');
|
|
23
|
+
const telemetry = require('../telemetry');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Directory for watchtower runtime files
|
|
@@ -161,14 +162,14 @@ function finalizeLock(pid, port, socketPath) {
|
|
|
161
162
|
* Remove the lock file.
|
|
162
163
|
*/
|
|
163
164
|
function removeLock() {
|
|
164
|
-
try { fs.unlinkSync(LOCK_FILE); } catch (e) { /*
|
|
165
|
+
try { fs.unlinkSync(LOCK_FILE); } catch (e) { /* lock file may not exist */ }
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
/**
|
|
168
169
|
* Remove stale socket file.
|
|
169
170
|
*/
|
|
170
171
|
function removeSocket() {
|
|
171
|
-
try { fs.unlinkSync(SOCKET_PATH); } catch (e) { /*
|
|
172
|
+
try { fs.unlinkSync(SOCKET_PATH); } catch (e) { /* socket file may not exist */ }
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
/**
|
|
@@ -255,7 +256,7 @@ class Coordinator {
|
|
|
255
256
|
stop() {
|
|
256
257
|
// Close all worker sockets
|
|
257
258
|
for (const socket of this.workerSockets.values()) {
|
|
258
|
-
try { socket.destroy(); } catch (e) { /*
|
|
259
|
+
try { socket.destroy(); } catch (e) { /* socket may already be destroyed */ }
|
|
259
260
|
}
|
|
260
261
|
this.workerSockets.clear();
|
|
261
262
|
this.projects.clear();
|
|
@@ -359,7 +360,12 @@ class Coordinator {
|
|
|
359
360
|
try {
|
|
360
361
|
const msg = JSON.parse(line);
|
|
361
362
|
this._handleWorkerMessage(socket, msg, (id) => { workerId = id; }, () => workerId);
|
|
362
|
-
} catch (e) {
|
|
363
|
+
} catch (e) {
|
|
364
|
+
// Both sides of this socket are our own code, so a JSON-parse
|
|
365
|
+
// failure indicates a protocol/version bug worth diagnosing.
|
|
366
|
+
telemetry.captureError(e);
|
|
367
|
+
/* skip malformed frame and keep reading */
|
|
368
|
+
}
|
|
363
369
|
}
|
|
364
370
|
}
|
|
365
371
|
});
|
|
@@ -445,7 +451,7 @@ class Coordinator {
|
|
|
445
451
|
_sendMessage(socket, msg) {
|
|
446
452
|
try {
|
|
447
453
|
socket.write(JSON.stringify(msg) + '\n');
|
|
448
|
-
} catch (e) { /*
|
|
454
|
+
} catch (e) { /* peer socket closed between iteration and write — peer will reconnect if it recovers */ }
|
|
449
455
|
}
|
|
450
456
|
|
|
451
457
|
/**
|
|
@@ -517,7 +523,12 @@ class Worker {
|
|
|
517
523
|
try {
|
|
518
524
|
const msg = JSON.parse(line);
|
|
519
525
|
this._handleMessage(msg);
|
|
520
|
-
} catch (e) {
|
|
526
|
+
} catch (e) {
|
|
527
|
+
// Both sides of this socket are our own code, so a JSON-parse
|
|
528
|
+
// failure indicates a protocol/version bug worth diagnosing.
|
|
529
|
+
telemetry.captureError(e);
|
|
530
|
+
/* skip malformed frame and keep reading */
|
|
531
|
+
}
|
|
521
532
|
}
|
|
522
533
|
}
|
|
523
534
|
});
|
|
@@ -574,7 +585,7 @@ class Worker {
|
|
|
574
585
|
if (this.socket && this._connected) {
|
|
575
586
|
try {
|
|
576
587
|
this.socket.write(JSON.stringify(msg) + '\n');
|
|
577
|
-
} catch (e) { /*
|
|
588
|
+
} catch (e) { /* coordinator socket closed between isConnected() check and write */ }
|
|
578
589
|
}
|
|
579
590
|
}
|
|
580
591
|
|
package/src/server/web-ui/js.js
CHANGED
|
@@ -70,7 +70,7 @@ function getDashboardJs() {
|
|
|
70
70
|
function savePrefs(updates) {
|
|
71
71
|
const prefs = loadPrefs();
|
|
72
72
|
Object.keys(updates).forEach((k) => { prefs[k] = updates[k]; });
|
|
73
|
-
try { localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)); } catch (e) { /*
|
|
73
|
+
try { localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)); } catch (e) { /* localStorage quota exceeded or disabled (private mode) — prefs are best-effort */ }
|
|
74
74
|
return prefs;
|
|
75
75
|
}
|
|
76
76
|
const prefs = loadPrefs();
|
|
@@ -122,7 +122,7 @@ function getDashboardJs() {
|
|
|
122
122
|
try {
|
|
123
123
|
const n = new Notification(title, { body, tag: tag || 'git-watchtower', icon: '', silent: false });
|
|
124
124
|
setTimeout(() => n.close(), 8000);
|
|
125
|
-
} catch (e) { /*
|
|
125
|
+
} catch (e) { /* Notification constructor can throw on some browsers (e.g. permission revoked mid-session) */ }
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
function diffBranchesForNotifications(oldBranches, newBranches) {
|
|
@@ -232,14 +232,14 @@ function getDashboardJs() {
|
|
|
232
232
|
}
|
|
233
233
|
renderTabs();
|
|
234
234
|
render();
|
|
235
|
-
} catch (err) { /*
|
|
235
|
+
} catch (err) { /* malformed SSE state frame — skip this push, next one will re-render */ }
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
evtSource.addEventListener('flash', (e) => {
|
|
239
239
|
try {
|
|
240
240
|
const data = JSON.parse(e.data);
|
|
241
241
|
showFlash(data.text, data.type);
|
|
242
|
-
} catch (err) { /*
|
|
242
|
+
} catch (err) { /* malformed flash payload — not worth surfacing, skip */ }
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
evtSource.addEventListener('actionResult', (e) => {
|
|
@@ -251,7 +251,7 @@ function getDashboardJs() {
|
|
|
251
251
|
} else {
|
|
252
252
|
showToast(data.message, data.success ? 'success' : 'error');
|
|
253
253
|
}
|
|
254
|
-
} catch (err) { /*
|
|
254
|
+
} catch (err) { /* malformed actionResult payload — skip (the action already ran server-side) */ }
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
evtSource.onerror = () => {
|
|
@@ -434,7 +434,7 @@ function getDashboardJs() {
|
|
|
434
434
|
state.serverMode = pState.serverMode || 'none';
|
|
435
435
|
state.repoWebUrl = pState.repoWebUrl || null;
|
|
436
436
|
render();
|
|
437
|
-
} catch (err) { /*
|
|
437
|
+
} catch (err) { /* malformed per-project state response — keep current view until the next poll */ }
|
|
438
438
|
}
|
|
439
439
|
};
|
|
440
440
|
xhr.send();
|
|
@@ -804,8 +804,8 @@ ${pureFnBlock}
|
|
|
804
804
|
const btn = e.target.closest('.action-item');
|
|
805
805
|
if (!btn) return;
|
|
806
806
|
const key = btn.getAttribute('data-action-key');
|
|
807
|
-
|
|
808
|
-
try { data = JSON.parse(btn.getAttribute('data-action-data') || '{}'); } catch (err) { /*
|
|
807
|
+
let data = {};
|
|
808
|
+
try { data = JSON.parse(btn.getAttribute('data-action-data') || '{}'); } catch (err) { /* malformed data-action-data — fall through with empty object */ }
|
|
809
809
|
|
|
810
810
|
hideBranchActions();
|
|
811
811
|
|
package/src/server/web.js
CHANGED
|
@@ -272,7 +272,7 @@ class WebDashboardServer {
|
|
|
272
272
|
|
|
273
273
|
// Close all SSE connections
|
|
274
274
|
for (const client of this.clients) {
|
|
275
|
-
try { client.end(); } catch (e) { /*
|
|
275
|
+
try { client.end(); } catch (e) { /* SSE client may already be disconnected */ }
|
|
276
276
|
}
|
|
277
277
|
this.clients.clear();
|
|
278
278
|
|
|
@@ -293,7 +293,7 @@ class WebDashboardServer {
|
|
|
293
293
|
try {
|
|
294
294
|
client.write('event: flash\n');
|
|
295
295
|
client.write('data: ' + data + '\n\n');
|
|
296
|
-
} catch (e) { /*
|
|
296
|
+
} catch (e) { /* SSE client disconnected — will be pruned when its response closes */ }
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
|
|
@@ -307,7 +307,7 @@ class WebDashboardServer {
|
|
|
307
307
|
try {
|
|
308
308
|
client.write('event: preview\n');
|
|
309
309
|
client.write('data: ' + json + '\n\n');
|
|
310
|
-
} catch (e) { /*
|
|
310
|
+
} catch (e) { /* SSE client disconnected — will be pruned when its response closes */ }
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
|
@@ -321,7 +321,7 @@ class WebDashboardServer {
|
|
|
321
321
|
try {
|
|
322
322
|
client.write('event: actionResult\n');
|
|
323
323
|
client.write('data: ' + json + '\n\n');
|
|
324
|
-
} catch (e) { /*
|
|
324
|
+
} catch (e) { /* SSE client disconnected — will be pruned when its response closes */ }
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
|
|
@@ -100,7 +100,7 @@ function startPeriodicUpdateCheck(onUpdateFound, interval = UPDATE_CHECK_INTERVA
|
|
|
100
100
|
.then((latestVersion) => {
|
|
101
101
|
if (latestVersion) onUpdateFound(latestVersion);
|
|
102
102
|
})
|
|
103
|
-
.catch(() => {});
|
|
103
|
+
.catch(() => { /* npm registry unreachable — next scheduled tick will try again */ });
|
|
104
104
|
}, interval);
|
|
105
105
|
|
|
106
106
|
return { stop: () => clearInterval(timerId) };
|