livedesk 0.1.607 → 0.1.609
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/electron/installer-quit-gate.mjs +183 -0
- package/electron/main.mjs +249 -102
- package/electron/runtime-window-load-owner.mjs +153 -0
- package/electron/update-manager.mjs +762 -144
- package/package.json +1 -1
- package/web/dist/app.webmanifest +1 -1
- package/web/dist/assets/LiveDeskApp-Dr77Bkmu.js +179 -0
- package/web/dist/assets/{icons-JxbtiHLQ.js → icons-tFaWqIIe.js} +1 -1
- package/web/dist/assets/{index-C4QRbhif.js → index-Dzx6WcDO.js} +4 -4
- package/web/dist/assets/{react-hDor0Pi9.js → react-BQos8uGS.js} +1 -1
- package/web/dist/assets/supabase-C7qjtLN3.js +29 -0
- package/web/dist/index.html +3 -3
- package/web/dist/livedesk-build-evidence.json +25 -25
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/LiveDeskApp-Dw86X0VP.js +0 -179
- package/web/dist/assets/supabase-C0Z0FS3K.js +0 -29
|
@@ -1,23 +1,63 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { EventEmitter } from 'node:events';
|
|
5
|
+
import { resolveDesktopUpdateFeed } from './update-feed-resolver.mjs';
|
|
5
6
|
|
|
6
|
-
const require = createRequire(import.meta.url);
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CHECK_DEADLINE_MS = 30_000;
|
|
10
|
+
const DEFAULT_DOWNLOAD_STALL_TIMEOUT_MS = 120_000;
|
|
11
|
+
const DEFAULT_RETIRED_EVENT_QUIET_MS = 100;
|
|
12
|
+
const DEFAULT_UPDATER_CLEANUP_RESET_MS = 5_000;
|
|
13
|
+
const MINIMUM_DEADLINE_MS = 10;
|
|
14
|
+
|
|
15
|
+
export function createPlatformProductUpdater(platform = process.platform) {
|
|
16
|
+
const module = require('electron-updater');
|
|
17
|
+
if (platform === 'win32') return new module.NsisUpdater();
|
|
18
|
+
if (platform === 'darwin') return new module.MacUpdater();
|
|
19
|
+
try {
|
|
20
|
+
const packageType = String(readFileSync(join(process.resourcesPath, 'package-type'), 'utf8')).trim();
|
|
21
|
+
if (packageType === 'deb') return new module.DebUpdater();
|
|
22
|
+
if (packageType === 'rpm') return new module.RpmUpdater();
|
|
23
|
+
if (packageType === 'pacman') return new module.PacmanUpdater();
|
|
24
|
+
} catch {
|
|
25
|
+
// AppImage is the Linux fallback used by electron-updater itself.
|
|
26
|
+
}
|
|
27
|
+
return new module.AppImageUpdater();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeDeadline(value, fallback) {
|
|
31
|
+
const milliseconds = Number(value);
|
|
32
|
+
return Number.isFinite(milliseconds) && milliseconds >= MINIMUM_DEADLINE_MS
|
|
33
|
+
? Math.round(milliseconds)
|
|
34
|
+
: fallback;
|
|
35
|
+
}
|
|
7
36
|
|
|
8
37
|
export function createProductUpdateManager({
|
|
9
38
|
app,
|
|
10
39
|
onStatus = () => undefined,
|
|
11
40
|
beforeInstall = async () => undefined,
|
|
12
|
-
onInstallError = async () => undefined,
|
|
13
|
-
updater = null,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
onInstallError = async () => undefined,
|
|
42
|
+
updater = null,
|
|
43
|
+
updaterFactory = null,
|
|
44
|
+
platform = process.platform,
|
|
45
|
+
arch = process.arch,
|
|
46
|
+
startupCheckTimeoutMs = 2_500,
|
|
47
|
+
checkDeadlineMs = DEFAULT_CHECK_DEADLINE_MS,
|
|
48
|
+
downloadStallTimeoutMs = DEFAULT_DOWNLOAD_STALL_TIMEOUT_MS,
|
|
49
|
+
retiredEventQuietMs = DEFAULT_RETIRED_EVENT_QUIET_MS,
|
|
50
|
+
updaterCleanupResetMs = DEFAULT_UPDATER_CLEANUP_RESET_MS,
|
|
51
|
+
setTimer = setTimeout,
|
|
52
|
+
clearTimer = clearTimeout,
|
|
53
|
+
now = Date.now,
|
|
54
|
+
updateFeedConfig = null,
|
|
55
|
+
updateChannel = ''
|
|
56
|
+
} = {}) {
|
|
57
|
+
if (!updater) {
|
|
58
|
+
updaterFactory ||= () => createPlatformProductUpdater(platform);
|
|
59
|
+
updater = updaterFactory();
|
|
60
|
+
}
|
|
21
61
|
let feedConfig = updateFeedConfig;
|
|
22
62
|
let packageMetadata = {};
|
|
23
63
|
try {
|
|
@@ -43,109 +83,549 @@ export function createProductUpdateManager({
|
|
|
43
83
|
let status = {
|
|
44
84
|
state: 'idle',
|
|
45
85
|
currentVersion: app.getVersion(),
|
|
46
|
-
availableVersion: '',
|
|
47
|
-
downloaded: false,
|
|
48
|
-
|
|
49
|
-
|
|
86
|
+
availableVersion: '',
|
|
87
|
+
downloaded: false,
|
|
88
|
+
downloadedVersion: '',
|
|
89
|
+
progress: 0,
|
|
90
|
+
error: '',
|
|
91
|
+
errorCode: '',
|
|
50
92
|
configured: feed.configured,
|
|
51
93
|
feedUrl: feed.url,
|
|
52
94
|
channel: feed.channel,
|
|
53
95
|
platform: feed.platform,
|
|
54
|
-
arch: feed.arch,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
arch: feed.arch,
|
|
97
|
+
lastCheckStartedAt: '',
|
|
98
|
+
lastCheckCompletedAt: '',
|
|
99
|
+
cleanupPending: false,
|
|
100
|
+
reopenRequired: false,
|
|
101
|
+
startupCheck: 'idle'
|
|
102
|
+
};
|
|
103
|
+
let installPromise = null;
|
|
104
|
+
let installOperationPending = false;
|
|
58
105
|
let checkPromise = null;
|
|
59
106
|
let downloadPromise = null;
|
|
60
|
-
let cyclePromise = null;
|
|
61
|
-
let startupPromise = null;
|
|
62
|
-
let
|
|
63
|
-
let
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
let cyclePromise = null;
|
|
108
|
+
let startupPromise = null;
|
|
109
|
+
let ownerGeneration = 0;
|
|
110
|
+
let activeOwner = null;
|
|
111
|
+
let availableDownload = null;
|
|
112
|
+
let installRuntimePrepared = false;
|
|
113
|
+
let installRecoveryPromise = null;
|
|
114
|
+
const checkDeadline = normalizeDeadline(checkDeadlineMs, DEFAULT_CHECK_DEADLINE_MS);
|
|
115
|
+
const downloadStallTimeout = normalizeDeadline(downloadStallTimeoutMs, DEFAULT_DOWNLOAD_STALL_TIMEOUT_MS);
|
|
116
|
+
const retiredEventQuiet = normalizeDeadline(retiredEventQuietMs, DEFAULT_RETIRED_EVENT_QUIET_MS);
|
|
117
|
+
const updaterCleanupReset = normalizeDeadline(updaterCleanupResetMs, DEFAULT_UPDATER_CLEANUP_RESET_MS);
|
|
118
|
+
const updaterEvents = new EventEmitter();
|
|
119
|
+
const forwardedUpdaterEvents = [
|
|
120
|
+
'checking-for-update',
|
|
121
|
+
'update-available',
|
|
122
|
+
'update-not-available',
|
|
123
|
+
'download-progress',
|
|
124
|
+
'update-downloaded',
|
|
125
|
+
'error'
|
|
126
|
+
];
|
|
127
|
+
const readNow = () => {
|
|
128
|
+
const value = Number(now());
|
|
129
|
+
return Number.isFinite(value) ? value : Date.now();
|
|
130
|
+
};
|
|
131
|
+
const nowIso = () => new Date(readNow()).toISOString();
|
|
132
|
+
const publish = patch => {
|
|
133
|
+
status = { ...status, ...patch };
|
|
134
|
+
onStatus({ ...status });
|
|
135
|
+
};
|
|
136
|
+
const snapshot = () => ({ ...status, packaged: Boolean(app.isPackaged) });
|
|
137
|
+
const releaseAvailableDownload = record => {
|
|
138
|
+
const owned = record || availableDownload;
|
|
139
|
+
if (!owned) return;
|
|
140
|
+
if (availableDownload === owned) availableDownload = null;
|
|
141
|
+
owned.cancellationToken?.dispose?.();
|
|
142
|
+
};
|
|
143
|
+
const beginOwner = (phase, expectedVersion = '') => {
|
|
144
|
+
if (activeOwner) throw new Error('desktop-update-operation-already-active');
|
|
145
|
+
const owner = {
|
|
146
|
+
generation: ++ownerGeneration,
|
|
147
|
+
phase,
|
|
148
|
+
expectedVersion: String(expectedVersion || ''),
|
|
149
|
+
acceptsEvents: true,
|
|
150
|
+
outcome: phase,
|
|
151
|
+
startedAt: readNow(),
|
|
152
|
+
lastActivityAt: readNow(),
|
|
153
|
+
deadline: null,
|
|
154
|
+
closeRequested: false,
|
|
155
|
+
cancelRequested: false,
|
|
156
|
+
downloadEventReceived: false,
|
|
157
|
+
versionMismatch: false,
|
|
158
|
+
lastProgressPercent: Number.NEGATIVE_INFINITY,
|
|
159
|
+
lastTransferredBytes: -1,
|
|
160
|
+
vendorSettled: false,
|
|
161
|
+
retiredQuietTimer: null,
|
|
162
|
+
cleanupResetTimer: null,
|
|
163
|
+
updaterReplaced: false,
|
|
164
|
+
onRetiredRelease: null
|
|
165
|
+
};
|
|
166
|
+
activeOwner = owner;
|
|
167
|
+
return owner;
|
|
168
|
+
};
|
|
169
|
+
const ownerFor = phase => (
|
|
170
|
+
activeOwner?.acceptsEvents === true && activeOwner.phase === phase
|
|
171
|
+
? activeOwner
|
|
172
|
+
: null
|
|
173
|
+
);
|
|
174
|
+
const publishOwned = (owner, patch) => {
|
|
175
|
+
if (activeOwner !== owner || !owner.acceptsEvents) return false;
|
|
176
|
+
publish(patch);
|
|
177
|
+
return true;
|
|
178
|
+
};
|
|
179
|
+
const publishOwnedTerminal = (owner, patch) => {
|
|
180
|
+
if (activeOwner !== owner) return false;
|
|
181
|
+
publish(patch);
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
const createOwnedDeadline = (owner, milliseconds) => {
|
|
185
|
+
let timer = null;
|
|
186
|
+
let resolved = false;
|
|
187
|
+
let resolveDeadline;
|
|
188
|
+
const promise = new Promise(resolve => {
|
|
189
|
+
resolveDeadline = resolve;
|
|
190
|
+
});
|
|
191
|
+
const clear = () => {
|
|
192
|
+
if (timer !== null) clearTimer(timer);
|
|
193
|
+
timer = null;
|
|
194
|
+
};
|
|
195
|
+
const schedule = dueAt => {
|
|
196
|
+
clear();
|
|
197
|
+
owner.lastActivityAt = readNow();
|
|
198
|
+
const fire = () => {
|
|
199
|
+
timer = null;
|
|
200
|
+
if (resolved || activeOwner !== owner || !owner.acceptsEvents) return;
|
|
201
|
+
const remaining = dueAt - readNow();
|
|
202
|
+
if (remaining > 0) {
|
|
203
|
+
timer = setTimer(fire, remaining);
|
|
204
|
+
timer?.unref?.();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
resolved = true;
|
|
208
|
+
resolveDeadline({ generation: owner.generation, at: readNow() });
|
|
209
|
+
};
|
|
210
|
+
timer = setTimer(fire, Math.max(0, dueAt - readNow()));
|
|
211
|
+
timer?.unref?.();
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
promise,
|
|
215
|
+
restart() {
|
|
216
|
+
if (!resolved && activeOwner === owner && owner.acceptsEvents) {
|
|
217
|
+
schedule(readNow() + milliseconds);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
clear() {
|
|
221
|
+
resolved = true;
|
|
222
|
+
clear();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
const retireOwner = owner => {
|
|
227
|
+
if (activeOwner !== owner) return;
|
|
228
|
+
owner.acceptsEvents = false;
|
|
229
|
+
owner.deadline?.clear();
|
|
230
|
+
};
|
|
231
|
+
const clearRetiredQuietTimer = owner => {
|
|
232
|
+
if (!owner || owner.retiredQuietTimer === null) return;
|
|
233
|
+
clearTimer(owner.retiredQuietTimer);
|
|
234
|
+
owner.retiredQuietTimer = null;
|
|
235
|
+
};
|
|
236
|
+
const clearCleanupResetTimer = owner => {
|
|
237
|
+
if (!owner || owner.cleanupResetTimer === null) return;
|
|
238
|
+
clearTimer(owner.cleanupResetTimer);
|
|
239
|
+
owner.cleanupResetTimer = null;
|
|
240
|
+
};
|
|
241
|
+
const finishOwner = owner => {
|
|
242
|
+
owner.deadline?.clear();
|
|
243
|
+
clearRetiredQuietTimer(owner);
|
|
244
|
+
clearCleanupResetTimer(owner);
|
|
245
|
+
owner.acceptsEvents = false;
|
|
246
|
+
if (activeOwner === owner) activeOwner = null;
|
|
247
|
+
};
|
|
248
|
+
const completeRetiredOwner = owner => {
|
|
249
|
+
if (activeOwner !== owner || owner.acceptsEvents) return;
|
|
250
|
+
const onRelease = owner.onRetiredRelease;
|
|
251
|
+
owner.onRetiredRelease = null;
|
|
252
|
+
try {
|
|
253
|
+
onRelease?.();
|
|
254
|
+
} finally {
|
|
255
|
+
publishOwnedTerminal(owner, { cleanupPending: false });
|
|
256
|
+
finishOwner(owner);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const scheduleRetiredOwnerRelease = owner => {
|
|
260
|
+
if (activeOwner !== owner || owner.acceptsEvents || !owner.vendorSettled) return;
|
|
261
|
+
clearRetiredQuietTimer(owner);
|
|
262
|
+
owner.retiredQuietTimer = setTimer(() => {
|
|
263
|
+
owner.retiredQuietTimer = null;
|
|
264
|
+
completeRetiredOwner(owner);
|
|
265
|
+
}, retiredEventQuiet);
|
|
266
|
+
owner.retiredQuietTimer?.unref?.();
|
|
267
|
+
};
|
|
268
|
+
const replaceCurrentUpdater = () => {
|
|
269
|
+
const retiredUpdater = updater;
|
|
270
|
+
retiredUpdater.autoInstallOnAppQuit = false;
|
|
271
|
+
if (typeof updaterFactory !== 'function') return false;
|
|
272
|
+
const replacement = updaterFactory();
|
|
273
|
+
if (!replacement || replacement === retiredUpdater) {
|
|
274
|
+
throw new Error('desktop-update-updater-factory-did-not-replace-owner');
|
|
275
|
+
}
|
|
276
|
+
connectUpdater(replacement);
|
|
277
|
+
// electron-updater installs downloaded artifacts from an app 'quit'
|
|
278
|
+
// listener. A retired instance must never launch its older cached file.
|
|
279
|
+
updater = replacement;
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
282
|
+
const replaceRetiredUpdater = (owner, { required = false } = {}) => {
|
|
283
|
+
if (owner.updaterReplaced) return true;
|
|
284
|
+
try {
|
|
285
|
+
owner.updaterReplaced = replaceCurrentUpdater();
|
|
286
|
+
return owner.updaterReplaced;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (required) {
|
|
289
|
+
publishOwnedTerminal(owner, {
|
|
290
|
+
error: String(error?.message || error),
|
|
291
|
+
errorCode: 'desktop-update-owner-reset-failed',
|
|
292
|
+
reopenRequired: true
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const deferRetiredOwnerRelease = (owner, rawVendorPromise, onRelease = null) => {
|
|
299
|
+
updater.autoInstallOnAppQuit = false;
|
|
300
|
+
owner.onRetiredRelease = onRelease;
|
|
301
|
+
publishOwnedTerminal(owner, { cleanupPending: true });
|
|
302
|
+
owner.cleanupResetTimer = setTimer(() => {
|
|
303
|
+
owner.cleanupResetTimer = null;
|
|
304
|
+
if (activeOwner !== owner || owner.acceptsEvents || owner.vendorSettled) return;
|
|
305
|
+
if (typeof updaterFactory !== 'function') {
|
|
306
|
+
publishOwnedTerminal(owner, {
|
|
307
|
+
error: 'desktop-update-owner-cleanup-stuck',
|
|
308
|
+
errorCode: 'desktop-update-owner-cleanup-stuck',
|
|
309
|
+
reopenRequired: true
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (replaceRetiredUpdater(owner, { required: true })) {
|
|
314
|
+
owner.vendorSettled = true;
|
|
315
|
+
scheduleRetiredOwnerRelease(owner);
|
|
316
|
+
}
|
|
317
|
+
}, updaterCleanupReset);
|
|
318
|
+
owner.cleanupResetTimer?.unref?.();
|
|
319
|
+
void Promise.resolve(rawVendorPromise)
|
|
320
|
+
.catch(() => undefined)
|
|
321
|
+
.finally(() => {
|
|
322
|
+
if (activeOwner !== owner || owner.acceptsEvents) return;
|
|
323
|
+
// A production timeout always retires the whole emitter instance.
|
|
324
|
+
// Any event that old electron-updater schedules later is then absorbed
|
|
325
|
+
// by its forwarding listener and cannot enter a new generation.
|
|
326
|
+
replaceRetiredUpdater(owner);
|
|
327
|
+
owner.vendorSettled = true;
|
|
328
|
+
clearCleanupResetTimer(owner);
|
|
329
|
+
scheduleRetiredOwnerRelease(owner);
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const ignoreRetiredUpdaterEvent = () => {
|
|
333
|
+
const owner = activeOwner;
|
|
334
|
+
if (!owner || owner.acceptsEvents) return false;
|
|
335
|
+
if (owner.vendorSettled) scheduleRetiredOwnerRelease(owner);
|
|
336
|
+
return true;
|
|
337
|
+
};
|
|
338
|
+
const closeUpdaterConnections = async owner => {
|
|
339
|
+
if (owner.closeRequested) return;
|
|
340
|
+
owner.closeRequested = true;
|
|
341
|
+
const netSession = updater.netSession;
|
|
342
|
+
if (typeof netSession?.closeAllConnections !== 'function') {
|
|
343
|
+
throw new Error('desktop-update-session-close-unavailable');
|
|
344
|
+
}
|
|
345
|
+
await netSession.closeAllConnections();
|
|
346
|
+
};
|
|
347
|
+
const recoverPreparedRuntime = error => {
|
|
348
|
+
if (!installRuntimePrepared) return Promise.resolve();
|
|
349
|
+
installRuntimePrepared = false;
|
|
350
|
+
// electron-updater keeps both a one-shot quitAndInstall lock and a cached
|
|
351
|
+
// installer-on-quit handler on its instance. Disable that instance before
|
|
352
|
+
// runtime recovery does anything that may allow the app to quit.
|
|
353
|
+
updater.autoInstallOnAppQuit = false;
|
|
354
|
+
releaseAvailableDownload();
|
|
355
|
+
publish({
|
|
356
|
+
downloaded: false,
|
|
357
|
+
downloadedVersion: '',
|
|
358
|
+
progress: 0
|
|
359
|
+
});
|
|
360
|
+
if (!installRecoveryPromise) {
|
|
361
|
+
installRecoveryPromise = Promise.resolve(onInstallError(error))
|
|
362
|
+
.then(() => {
|
|
363
|
+
if (typeof updaterFactory === 'function') {
|
|
364
|
+
try {
|
|
365
|
+
if (!replaceCurrentUpdater()) {
|
|
366
|
+
throw new Error('desktop-update-updater-owner-reset-unavailable');
|
|
367
|
+
}
|
|
368
|
+
} catch (resetError) {
|
|
369
|
+
const wrapped = new Error(
|
|
370
|
+
`desktop-update-updater-owner-reset-failed:${String(resetError?.message || resetError)}`
|
|
371
|
+
);
|
|
372
|
+
wrapped.code = 'desktop-update-owner-reset-failed';
|
|
373
|
+
throw wrapped;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
})
|
|
377
|
+
.catch(recoveryError => {
|
|
378
|
+
const message = String(recoveryError?.message || recoveryError);
|
|
379
|
+
publish({
|
|
380
|
+
state: 'error',
|
|
381
|
+
error: message,
|
|
382
|
+
errorCode: recoveryError?.code === 'desktop-update-owner-reset-failed'
|
|
383
|
+
? 'desktop-update-owner-reset-failed'
|
|
384
|
+
: 'desktop-update-runtime-recovery-failed',
|
|
385
|
+
reopenRequired: true
|
|
386
|
+
});
|
|
387
|
+
})
|
|
388
|
+
.finally(() => {
|
|
389
|
+
installRecoveryPromise = null;
|
|
390
|
+
if (status.state === 'error' && !installOperationPending) installPromise = null;
|
|
391
|
+
});
|
|
80
392
|
}
|
|
81
|
-
return installRecoveryPromise;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
393
|
+
return installRecoveryPromise;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const connectedUpdaters = new WeakSet();
|
|
397
|
+
const connectUpdater = candidate => {
|
|
398
|
+
if (!candidate || connectedUpdaters.has(candidate)) return;
|
|
399
|
+
candidate.autoDownload = false;
|
|
400
|
+
candidate.autoInstallOnAppQuit = true;
|
|
401
|
+
if (feed.url) candidate.setFeedURL({ provider: 'generic', url: feed.url });
|
|
402
|
+
for (const eventName of forwardedUpdaterEvents) {
|
|
403
|
+
candidate.on(eventName, (...args) => {
|
|
404
|
+
// A force-retired updater keeps this harmless listener so a late
|
|
405
|
+
// EventEmitter 'error' cannot terminate Electron. Only the current
|
|
406
|
+
// instance can reach the manager owner.
|
|
407
|
+
if (candidate !== updater) return;
|
|
408
|
+
updaterEvents.emit(eventName, ...args);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
connectedUpdaters.add(candidate);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
updaterEvents.on('checking-for-update', () => {
|
|
415
|
+
if (ignoreRetiredUpdaterEvent()) return;
|
|
416
|
+
const owner = ownerFor('checking');
|
|
417
|
+
if (!owner || owner.outcome === 'error') return;
|
|
418
|
+
owner.outcome = 'checking';
|
|
419
|
+
owner.lastActivityAt = readNow();
|
|
420
|
+
publishOwned(owner, { state: 'checking', error: '', errorCode: '' });
|
|
421
|
+
});
|
|
422
|
+
updaterEvents.on('update-available', info => {
|
|
423
|
+
if (ignoreRetiredUpdaterEvent()) return;
|
|
424
|
+
const owner = ownerFor('checking');
|
|
425
|
+
if (!owner || owner.outcome === 'error') return;
|
|
426
|
+
owner.outcome = 'available';
|
|
427
|
+
owner.expectedVersion = String(info?.version || owner.expectedVersion || '');
|
|
428
|
+
owner.lastActivityAt = readNow();
|
|
429
|
+
const downloaded = Boolean(
|
|
430
|
+
status.downloaded
|
|
431
|
+
&& status.downloadedVersion
|
|
432
|
+
&& status.downloadedVersion === owner.expectedVersion
|
|
433
|
+
);
|
|
434
|
+
publishOwned(owner, {
|
|
435
|
+
state: 'available',
|
|
436
|
+
availableVersion: owner.expectedVersion,
|
|
437
|
+
downloaded,
|
|
438
|
+
downloadedVersion: downloaded ? status.downloadedVersion : '',
|
|
439
|
+
progress: downloaded ? 100 : 0,
|
|
440
|
+
error: '',
|
|
441
|
+
errorCode: ''
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
updaterEvents.on('update-not-available', () => {
|
|
445
|
+
if (ignoreRetiredUpdaterEvent()) return;
|
|
446
|
+
const owner = ownerFor('checking');
|
|
447
|
+
if (!owner || owner.outcome === 'error') return;
|
|
448
|
+
owner.outcome = 'current';
|
|
449
|
+
owner.lastActivityAt = readNow();
|
|
450
|
+
publishOwned(owner, { state: 'current', availableVersion: '', downloaded: false, downloadedVersion: '', progress: 0, error: '', errorCode: '' });
|
|
451
|
+
});
|
|
452
|
+
updaterEvents.on('download-progress', progress => {
|
|
453
|
+
if (ignoreRetiredUpdaterEvent()) return;
|
|
454
|
+
const owner = ownerFor('downloading');
|
|
455
|
+
if (!owner || owner.outcome === 'error' || owner.downloadEventReceived) return;
|
|
456
|
+
const percent = Number(progress?.percent || 0);
|
|
457
|
+
const transferredBytes = Number(progress?.transferred || 0);
|
|
458
|
+
const advanced = percent > owner.lastProgressPercent || transferredBytes > owner.lastTransferredBytes;
|
|
459
|
+
owner.lastProgressPercent = Math.max(owner.lastProgressPercent, percent);
|
|
460
|
+
owner.lastTransferredBytes = Math.max(owner.lastTransferredBytes, transferredBytes);
|
|
461
|
+
owner.lastActivityAt = readNow();
|
|
462
|
+
if (advanced) owner.deadline?.restart();
|
|
463
|
+
publishOwned(owner, { state: 'downloading', progress: percent, error: '', errorCode: '' });
|
|
464
|
+
});
|
|
465
|
+
updaterEvents.on('update-downloaded', info => {
|
|
466
|
+
if (ignoreRetiredUpdaterEvent()) return;
|
|
467
|
+
const owner = ownerFor('downloading');
|
|
468
|
+
if (!owner || owner.outcome === 'error') return;
|
|
469
|
+
const receivedVersion = String(info?.version || '');
|
|
470
|
+
if (owner.expectedVersion && receivedVersion && receivedVersion !== owner.expectedVersion) {
|
|
471
|
+
owner.versionMismatch = true;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (!owner.expectedVersion) owner.expectedVersion = receivedVersion;
|
|
475
|
+
owner.outcome = 'downloaded';
|
|
476
|
+
owner.downloadEventReceived = true;
|
|
477
|
+
owner.lastActivityAt = readNow();
|
|
478
|
+
publishOwned(owner, {
|
|
479
|
+
state: 'downloaded',
|
|
480
|
+
downloaded: true,
|
|
481
|
+
downloadedVersion: receivedVersion || owner.expectedVersion || status.availableVersion || '',
|
|
482
|
+
progress: 100,
|
|
483
|
+
availableVersion: receivedVersion || owner.expectedVersion || status.availableVersion || '',
|
|
484
|
+
error: '',
|
|
485
|
+
errorCode: ''
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
updaterEvents.on('error', error => {
|
|
489
|
+
if (ignoreRetiredUpdaterEvent()) return;
|
|
490
|
+
const recoverRuntime = () => recoverPreparedRuntime(error);
|
|
491
|
+
const owner = activeOwner?.acceptsEvents === true ? activeOwner : null;
|
|
492
|
+
if (!owner) {
|
|
493
|
+
if (!installRuntimePrepared) return;
|
|
494
|
+
publish({
|
|
495
|
+
state: 'error',
|
|
496
|
+
error: String(error?.message || error),
|
|
497
|
+
errorCode: 'desktop-update-installer-launch-failed'
|
|
498
|
+
});
|
|
499
|
+
void recoverRuntime();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
owner.outcome = 'error';
|
|
503
|
+
owner.lastActivityAt = readNow();
|
|
504
|
+
publishOwned(owner, {
|
|
505
|
+
state: 'error',
|
|
506
|
+
error: String(error?.message || error),
|
|
507
|
+
errorCode: owner.phase === 'checking'
|
|
508
|
+
? 'desktop-update-check-failed'
|
|
509
|
+
: 'desktop-update-download-failed'
|
|
510
|
+
});
|
|
511
|
+
void recoverRuntime();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
connectUpdater(updater);
|
|
114
515
|
|
|
115
516
|
const unavailableStatus = () => ({
|
|
116
|
-
...snapshot(),
|
|
117
|
-
state: 'unavailable',
|
|
118
|
-
error: feed.error || 'desktop-update-feed-not-configured'
|
|
517
|
+
...snapshot(),
|
|
518
|
+
state: 'unavailable',
|
|
519
|
+
error: feed.error || 'desktop-update-feed-not-configured',
|
|
520
|
+
errorCode: feed.error || 'desktop-update-feed-not-configured'
|
|
119
521
|
});
|
|
120
522
|
|
|
121
|
-
const checkForUpdates = async () => {
|
|
122
|
-
if (
|
|
523
|
+
const checkForUpdates = async () => {
|
|
524
|
+
if (status.reopenRequired) return snapshot();
|
|
525
|
+
if (!app.isPackaged) {
|
|
123
526
|
return { ...status, state: 'development', error: 'desktop-updates-require-packaged-app' };
|
|
124
527
|
}
|
|
125
|
-
if (!feed.url) return unavailableStatus();
|
|
126
|
-
if (checkPromise) return checkPromise;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
528
|
+
if (!feed.url) return unavailableStatus();
|
|
529
|
+
if (checkPromise) return checkPromise;
|
|
530
|
+
if (activeOwner) return snapshot();
|
|
531
|
+
const operation = (async () => {
|
|
532
|
+
let owner = null;
|
|
533
|
+
let ownerReleaseDeferred = false;
|
|
534
|
+
try {
|
|
535
|
+
owner = beginOwner('checking');
|
|
536
|
+
releaseAvailableDownload();
|
|
537
|
+
owner.deadline = createOwnedDeadline(owner, checkDeadline);
|
|
538
|
+
owner.deadline.restart();
|
|
539
|
+
publishOwned(owner, {
|
|
540
|
+
state: 'checking',
|
|
541
|
+
error: '',
|
|
542
|
+
errorCode: '',
|
|
543
|
+
lastCheckStartedAt: nowIso(),
|
|
544
|
+
lastCheckCompletedAt: ''
|
|
545
|
+
});
|
|
546
|
+
const rawVendorPromise = Promise.resolve().then(() => updater.checkForUpdates());
|
|
547
|
+
const settledVendorPromise = rawVendorPromise.then(
|
|
548
|
+
result => ({ kind: 'result', result }),
|
|
549
|
+
error => ({ kind: 'error', error })
|
|
550
|
+
);
|
|
551
|
+
const outcome = await Promise.race([
|
|
552
|
+
settledVendorPromise,
|
|
553
|
+
owner.deadline.promise.then(() => ({ kind: 'deadline' }))
|
|
554
|
+
]);
|
|
555
|
+
if (outcome.kind === 'deadline') {
|
|
556
|
+
retireOwner(owner);
|
|
557
|
+
publishOwnedTerminal(owner, {
|
|
558
|
+
state: 'error',
|
|
559
|
+
availableVersion: '',
|
|
560
|
+
downloaded: false,
|
|
561
|
+
downloadedVersion: '',
|
|
562
|
+
progress: 0,
|
|
563
|
+
error: 'desktop-update-check-deadline-exceeded',
|
|
564
|
+
errorCode: 'desktop-update-check-deadline-exceeded',
|
|
565
|
+
lastCheckCompletedAt: nowIso()
|
|
566
|
+
});
|
|
567
|
+
void closeUpdaterConnections(owner).catch(() => undefined);
|
|
568
|
+
deferRetiredOwnerRelease(owner, rawVendorPromise);
|
|
569
|
+
ownerReleaseDeferred = true;
|
|
570
|
+
return snapshot();
|
|
571
|
+
}
|
|
572
|
+
owner.deadline.clear();
|
|
573
|
+
if (outcome.kind === 'error') {
|
|
574
|
+
owner.outcome = 'error';
|
|
575
|
+
if (status.state !== 'error') {
|
|
576
|
+
publishOwned(owner, {
|
|
577
|
+
state: 'error',
|
|
578
|
+
error: String(outcome.error?.message || outcome.error),
|
|
579
|
+
errorCode: 'desktop-update-check-failed'
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return snapshot();
|
|
583
|
+
}
|
|
584
|
+
const result = outcome.result;
|
|
585
|
+
if (owner.outcome === 'checking') {
|
|
586
|
+
const availableVersion = String(result?.updateInfo?.version || status.availableVersion || '');
|
|
587
|
+
const updateAvailable = Boolean(availableVersion && availableVersion !== app.getVersion());
|
|
588
|
+
const downloaded = Boolean(
|
|
589
|
+
updateAvailable
|
|
590
|
+
&& status.downloaded
|
|
591
|
+
&& status.downloadedVersion
|
|
592
|
+
&& status.downloadedVersion === availableVersion
|
|
593
|
+
);
|
|
594
|
+
owner.outcome = updateAvailable ? 'available' : 'current';
|
|
595
|
+
owner.expectedVersion = updateAvailable ? availableVersion : '';
|
|
596
|
+
publishOwned(owner, {
|
|
597
|
+
state: owner.outcome,
|
|
598
|
+
availableVersion: updateAvailable ? availableVersion : '',
|
|
599
|
+
downloaded,
|
|
600
|
+
downloadedVersion: downloaded ? status.downloadedVersion : '',
|
|
601
|
+
progress: downloaded ? 100 : 0,
|
|
602
|
+
error: '',
|
|
603
|
+
errorCode: ''
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (owner.outcome === 'available') {
|
|
607
|
+
availableDownload = {
|
|
608
|
+
generation: owner.generation,
|
|
609
|
+
version: String(owner.expectedVersion || result?.updateInfo?.version || status.availableVersion || ''),
|
|
610
|
+
cancellationToken: result?.cancellationToken || null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (owner) owner.outcome = 'error';
|
|
615
|
+
if (!owner || activeOwner === owner) {
|
|
616
|
+
publishOwnedTerminal(owner, {
|
|
617
|
+
state: 'error',
|
|
618
|
+
error: String(error?.message || error),
|
|
619
|
+
errorCode: 'desktop-update-check-failed'
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
} finally {
|
|
623
|
+
if (owner && !ownerReleaseDeferred) {
|
|
624
|
+
publishOwnedTerminal(owner, { lastCheckCompletedAt: nowIso() });
|
|
625
|
+
finishOwner(owner);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return { ...status, packaged: Boolean(app.isPackaged) };
|
|
149
629
|
})();
|
|
150
630
|
checkPromise = operation;
|
|
151
631
|
try {
|
|
@@ -155,22 +635,123 @@ export function createProductUpdateManager({
|
|
|
155
635
|
}
|
|
156
636
|
};
|
|
157
637
|
|
|
158
|
-
const downloadUpdate = async () => {
|
|
159
|
-
if (
|
|
638
|
+
const downloadUpdate = async () => {
|
|
639
|
+
if (status.reopenRequired) return snapshot();
|
|
640
|
+
if (!app.isPackaged) {
|
|
160
641
|
return { ...status, state: 'development', error: 'desktop-updates-require-packaged-app' };
|
|
161
642
|
}
|
|
162
643
|
if (!feed.url) return unavailableStatus();
|
|
163
|
-
if (
|
|
164
|
-
if (
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
644
|
+
if (downloadPromise) return downloadPromise;
|
|
645
|
+
if (activeOwner) return snapshot();
|
|
646
|
+
const requestedVersion = String(availableDownload?.version || status.availableVersion || '');
|
|
647
|
+
if (status.downloaded && status.downloadedVersion && status.downloadedVersion === requestedVersion) {
|
|
648
|
+
releaseAvailableDownload();
|
|
649
|
+
return { ...status, packaged: true };
|
|
650
|
+
}
|
|
651
|
+
const operation = (async () => {
|
|
652
|
+
let owner = null;
|
|
653
|
+
let ownerReleaseDeferred = false;
|
|
654
|
+
const downloadRecord = availableDownload;
|
|
655
|
+
try {
|
|
656
|
+
owner = beginOwner('downloading', downloadRecord?.version || status.availableVersion);
|
|
657
|
+
owner.deadline = createOwnedDeadline(owner, downloadStallTimeout);
|
|
658
|
+
owner.deadline.restart();
|
|
659
|
+
publishOwned(owner, {
|
|
660
|
+
state: 'downloading',
|
|
661
|
+
progress: Math.max(0, Number(status.progress || 0)),
|
|
662
|
+
error: '',
|
|
663
|
+
errorCode: ''
|
|
664
|
+
});
|
|
665
|
+
const cancellationToken = downloadRecord?.cancellationToken;
|
|
666
|
+
const rawVendorPromise = Promise.resolve().then(() => updater.downloadUpdate(cancellationToken));
|
|
667
|
+
const settledVendorPromise = rawVendorPromise.then(
|
|
668
|
+
result => ({ kind: 'result', result }),
|
|
669
|
+
error => ({ kind: 'error', error })
|
|
670
|
+
);
|
|
671
|
+
const outcome = await Promise.race([
|
|
672
|
+
settledVendorPromise,
|
|
673
|
+
owner.deadline.promise.then(() => ({ kind: 'deadline' }))
|
|
674
|
+
]);
|
|
675
|
+
if (outcome.kind === 'deadline') {
|
|
676
|
+
retireOwner(owner);
|
|
677
|
+
publishOwnedTerminal(owner, {
|
|
678
|
+
state: 'error',
|
|
679
|
+
downloaded: false,
|
|
680
|
+
downloadedVersion: '',
|
|
681
|
+
error: 'desktop-update-download-stalled',
|
|
682
|
+
errorCode: 'desktop-update-download-stalled'
|
|
683
|
+
});
|
|
684
|
+
if (!owner.cancelRequested) {
|
|
685
|
+
owner.cancelRequested = true;
|
|
686
|
+
let cancelled = false;
|
|
687
|
+
if (typeof cancellationToken?.cancel === 'function') {
|
|
688
|
+
try {
|
|
689
|
+
cancellationToken.cancel();
|
|
690
|
+
cancelled = true;
|
|
691
|
+
} catch {
|
|
692
|
+
cancelled = false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (!cancelled) {
|
|
696
|
+
void closeUpdaterConnections(owner).catch(() => undefined);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
deferRetiredOwnerRelease(owner, rawVendorPromise, () => releaseAvailableDownload(downloadRecord));
|
|
700
|
+
ownerReleaseDeferred = true;
|
|
701
|
+
return snapshot();
|
|
702
|
+
}
|
|
703
|
+
owner.deadline.clear();
|
|
704
|
+
if (outcome.kind === 'error') {
|
|
705
|
+
if (status.state !== 'error') {
|
|
706
|
+
publishOwned(owner, {
|
|
707
|
+
state: 'error',
|
|
708
|
+
downloaded: false,
|
|
709
|
+
downloadedVersion: '',
|
|
710
|
+
error: String(outcome.error?.message || outcome.error),
|
|
711
|
+
errorCode: 'desktop-update-download-failed'
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return snapshot();
|
|
715
|
+
}
|
|
716
|
+
if (owner.outcome === 'error') return snapshot();
|
|
717
|
+
if (owner.versionMismatch) {
|
|
718
|
+
publishOwned(owner, {
|
|
719
|
+
state: 'error',
|
|
720
|
+
downloaded: false,
|
|
721
|
+
downloadedVersion: '',
|
|
722
|
+
error: 'desktop-update-downloaded-version-mismatch',
|
|
723
|
+
errorCode: 'desktop-update-downloaded-version-mismatch'
|
|
724
|
+
});
|
|
725
|
+
return snapshot();
|
|
726
|
+
}
|
|
727
|
+
if (!owner.downloadEventReceived) {
|
|
728
|
+
publishOwned(owner, {
|
|
729
|
+
state: 'downloaded',
|
|
730
|
+
downloaded: true,
|
|
731
|
+
downloadedVersion: owner.expectedVersion || status.availableVersion || '',
|
|
732
|
+
progress: 100,
|
|
733
|
+
availableVersion: owner.expectedVersion || status.availableVersion || '',
|
|
734
|
+
error: '',
|
|
735
|
+
errorCode: ''
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (!owner || activeOwner === owner) {
|
|
740
|
+
publishOwnedTerminal(owner, {
|
|
741
|
+
state: 'error',
|
|
742
|
+
downloaded: false,
|
|
743
|
+
downloadedVersion: '',
|
|
744
|
+
error: String(error?.message || error),
|
|
745
|
+
errorCode: 'desktop-update-download-failed'
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
} finally {
|
|
749
|
+
if (!ownerReleaseDeferred) {
|
|
750
|
+
if (owner) finishOwner(owner);
|
|
751
|
+
releaseAvailableDownload(downloadRecord);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return { ...status, packaged: Boolean(app.isPackaged) };
|
|
174
755
|
})();
|
|
175
756
|
downloadPromise = operation;
|
|
176
757
|
try {
|
|
@@ -211,12 +792,12 @@ export function createProductUpdateManager({
|
|
|
211
792
|
publish({ startupCheck: 'complete' });
|
|
212
793
|
});
|
|
213
794
|
let timeoutId;
|
|
214
|
-
const timeout = new Promise(resolve => {
|
|
215
|
-
timeoutId =
|
|
216
|
-
timeoutId.unref?.();
|
|
217
|
-
});
|
|
218
|
-
const result = await Promise.race([background, timeout]);
|
|
219
|
-
|
|
795
|
+
const timeout = new Promise(resolve => {
|
|
796
|
+
timeoutId = setTimer(() => resolve('timeout'), Math.max(10, Number(startupCheckTimeoutMs) || 2_500));
|
|
797
|
+
timeoutId.unref?.();
|
|
798
|
+
});
|
|
799
|
+
const result = await Promise.race([background, timeout]);
|
|
800
|
+
clearTimer(timeoutId);
|
|
220
801
|
if (result === 'timeout') {
|
|
221
802
|
publish({ startupCheck: 'timed-out' });
|
|
222
803
|
return snapshot();
|
|
@@ -230,14 +811,33 @@ export function createProductUpdateManager({
|
|
|
230
811
|
if (startupPromise === operation) startupPromise = null;
|
|
231
812
|
}
|
|
232
813
|
},
|
|
233
|
-
async install() {
|
|
234
|
-
if (!app.isPackaged) return { ...snapshot(), state: 'development', error: 'desktop-updates-require-packaged-app' };
|
|
235
|
-
if (!feed.url) return unavailableStatus();
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
814
|
+
async install() {
|
|
815
|
+
if (!app.isPackaged) return { ...snapshot(), state: 'development', error: 'desktop-updates-require-packaged-app' };
|
|
816
|
+
if (!feed.url) return unavailableStatus();
|
|
817
|
+
if (status.reopenRequired) return snapshot();
|
|
818
|
+
if (installRecoveryPromise) {
|
|
819
|
+
await installRecoveryPromise;
|
|
820
|
+
return snapshot();
|
|
821
|
+
}
|
|
822
|
+
if (installPromise) return installPromise;
|
|
823
|
+
installOperationPending = true;
|
|
824
|
+
const operation = (async () => {
|
|
825
|
+
try {
|
|
826
|
+
if (status.downloaded
|
|
827
|
+
&& (!status.downloadedVersion || status.downloadedVersion !== status.availableVersion)) {
|
|
828
|
+
publish({
|
|
829
|
+
state: 'error',
|
|
830
|
+
downloaded: false,
|
|
831
|
+
downloadedVersion: '',
|
|
832
|
+
error: 'desktop-update-downloaded-version-mismatch',
|
|
833
|
+
errorCode: 'desktop-update-downloaded-version-mismatch'
|
|
834
|
+
});
|
|
835
|
+
return snapshot();
|
|
836
|
+
}
|
|
837
|
+
if (!status.downloaded) {
|
|
838
|
+
const downloadResult = availableDownload
|
|
839
|
+
? await downloadUpdate()
|
|
840
|
+
: await checkAndDownload();
|
|
241
841
|
if (!downloadResult.downloaded) return downloadResult;
|
|
242
842
|
}
|
|
243
843
|
// The updater may launch its installer synchronously from
|
|
@@ -245,24 +845,42 @@ export function createProductUpdateManager({
|
|
|
245
845
|
// zero before allowing that replacement process to exist.
|
|
246
846
|
await beforeInstall();
|
|
247
847
|
installRuntimePrepared = true;
|
|
248
|
-
publish({ state: 'installing', error: '' });
|
|
848
|
+
publish({ state: 'installing', error: '', errorCode: '' });
|
|
249
849
|
// Long-running desktop Clients must not stop at the assisted NSIS
|
|
250
|
-
// wizard after the runtime has already shut down for replacement.
|
|
251
|
-
updater.quitAndInstall(true, true);
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
850
|
+
// wizard after the runtime has already shut down for replacement.
|
|
851
|
+
updater.quitAndInstall(true, true);
|
|
852
|
+
// BaseUpdater can emit a synchronous error and return instead of
|
|
853
|
+
// throwing. Keep this install owner locked until runtime recovery
|
|
854
|
+
// completes, so Retry cannot begin a second pre-install drain.
|
|
855
|
+
if (installRecoveryPromise) await installRecoveryPromise;
|
|
856
|
+
return snapshot();
|
|
857
|
+
} catch (error) {
|
|
858
|
+
await recoverPreparedRuntime(error);
|
|
859
|
+
if (!status.reopenRequired) {
|
|
860
|
+
const message = String(error?.message || error);
|
|
861
|
+
const preinstallRecoveryFailed = message.includes('desktop-update-preinstall-recovery-failed');
|
|
862
|
+
publish({
|
|
863
|
+
state: 'error',
|
|
864
|
+
error: message,
|
|
865
|
+
errorCode: preinstallRecoveryFailed
|
|
866
|
+
? 'desktop-update-runtime-recovery-failed'
|
|
867
|
+
: 'desktop-update-installer-launch-failed',
|
|
868
|
+
reopenRequired: preinstallRecoveryFailed
|
|
869
|
+
});
|
|
870
|
+
}
|
|
256
871
|
return snapshot();
|
|
257
872
|
}
|
|
258
873
|
})();
|
|
259
874
|
installPromise = operation;
|
|
260
|
-
try {
|
|
261
|
-
return await operation;
|
|
262
|
-
} finally {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
875
|
+
try {
|
|
876
|
+
return await operation;
|
|
877
|
+
} finally {
|
|
878
|
+
installOperationPending = false;
|
|
879
|
+
if (installPromise === operation
|
|
880
|
+
&& status.state !== 'installing'
|
|
881
|
+
&& !installRecoveryPromise) {
|
|
882
|
+
installPromise = null;
|
|
883
|
+
}
|
|
266
884
|
}
|
|
267
885
|
}
|
|
268
886
|
};
|