livedesk 0.1.606 → 0.1.608
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/client/bin/livedesk-client.js +95 -49
- package/client/package.json +1 -1
- package/client/src/runtime/client-runtime-server.js +16 -8
- package/electron/installer-quit-gate.mjs +183 -0
- package/electron/main.mjs +197 -95
- package/electron/update-manager.mjs +762 -144
- package/package.json +2 -2
- 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-BGB-rcGS.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-BoS9GSoS.js +0 -179
- package/web/dist/assets/supabase-C0Z0FS3K.js +0 -29
package/electron/main.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, nativeImage, powerMonitor, safeStorage, shell, Tray } from 'electron';
|
|
1
|
+
import { app, autoUpdater as electronAutoUpdater, BrowserWindow, clipboard, dialog, ipcMain, Menu, nativeImage, powerMonitor, safeStorage, shell, Tray } from 'electron';
|
|
2
2
|
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
-
import { createProductUpdateManager } from './update-manager.mjs';
|
|
8
|
-
import { createRecurringProductUpdateOwner } from './recurring-update-owner.mjs';
|
|
9
|
-
import {
|
|
7
|
+
import { createProductUpdateManager } from './update-manager.mjs';
|
|
8
|
+
import { createRecurringProductUpdateOwner } from './recurring-update-owner.mjs';
|
|
9
|
+
import { createInstallerQuitGate, InstallerQuitDecision } from './installer-quit-gate.mjs';
|
|
10
|
+
import { AUTH_REQUEST_TIMEOUT_MS, createPkceAuthorizationUrl, exchangePkceCode, fetchSupabaseUser, parsePkceCallbackUrl, refreshSupabaseSession } from './oauth-pkce.mjs';
|
|
10
11
|
import { getRuntimeAuthEnvironment, resolveDesktopAuthConfig, resolveDesktopBuildFlavor } from './auth-config.mjs';
|
|
11
12
|
import { createBoundedDesktopLogger } from './bounded-desktop-log.mjs';
|
|
12
13
|
import {
|
|
@@ -155,10 +156,10 @@ let runtimeRestartWindowStartedAt = 0;
|
|
|
155
156
|
let runtimeRestartTimer = null;
|
|
156
157
|
let runtimeRestartPromise = null;
|
|
157
158
|
let runtimeLaunchStartedAt = 0;
|
|
158
|
-
let runtimeRoleTakeoverPending = process.env.LIVEDESK_ROLE_TAKEOVER === '1';
|
|
159
|
-
let quitSequenceStarted = false;
|
|
160
|
-
let updateInstallSequenceStarted = false;
|
|
161
|
-
let productUpdates = null;
|
|
159
|
+
let runtimeRoleTakeoverPending = process.env.LIVEDESK_ROLE_TAKEOVER === '1';
|
|
160
|
+
let quitSequenceStarted = false;
|
|
161
|
+
let updateInstallSequenceStarted = false;
|
|
162
|
+
let productUpdates = null;
|
|
162
163
|
let recurringProductUpdates = null;
|
|
163
164
|
let pendingOAuth = null;
|
|
164
165
|
let pendingProtocolUrl = '';
|
|
@@ -169,7 +170,10 @@ let lastDesktopResumeRecoveryAt = 0;
|
|
|
169
170
|
let desktopSessionEpoch = 0;
|
|
170
171
|
let lastKnownDesktopSession = null;
|
|
171
172
|
let desktopClipboardOwner = null;
|
|
172
|
-
const desktopAuthInvalidationGuard = createDesktopAuthInvalidationGuard();
|
|
173
|
+
const desktopAuthInvalidationGuard = createDesktopAuthInvalidationGuard();
|
|
174
|
+
const installerQuitGate = createInstallerQuitGate({
|
|
175
|
+
requestQuit: () => app.quit()
|
|
176
|
+
});
|
|
173
177
|
const FALLBACK_ICON_DATA_URL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none"><rect x="1" y="1" width="30" height="30" rx="7" fill="#111827"/><g transform="translate(5 5) scale(.785714)"><rect x="2.5" y="4" width="14" height="9" rx="1.5" stroke="#F8FAFC" stroke-width="1.7" opacity=".42"/><path d="M7 16v1.5h5" stroke="#F8FAFC" stroke-width="1.7" stroke-linecap="round" opacity=".42"/><rect x="10.5" y="2.5" width="14" height="9" rx="1.5" stroke="#F8FAFC" stroke-width="1.7" opacity=".62"/><path d="M15 14.5V16h5" stroke="#F8FAFC" stroke-width="1.7" stroke-linecap="round" opacity=".62"/><rect x="6.5" y="8.5" width="15" height="10" rx="1.7" fill="#F8FAFC" fill-opacity=".12" stroke="#F8FAFC" stroke-width="1.9"/><path d="M12 21v1.5H9.5M16 21v1.5h2.5M9.5 22.5h9" stroke="#F8FAFC" stroke-width="1.9" stroke-linecap="round"/></g></svg>')}`;
|
|
174
178
|
const desktopLogger = createBoundedDesktopLogger({ logPath });
|
|
175
179
|
|
|
@@ -615,12 +619,18 @@ async function stopRuntime() {
|
|
|
615
619
|
child.livedeskRuntimeStopDeadline = deadline;
|
|
616
620
|
const operation = (async () => {
|
|
617
621
|
await requestRuntimeShutdown(deadline);
|
|
618
|
-
const gracefulDeadline = Math.max(
|
|
619
|
-
Date.now(),
|
|
620
|
-
deadline - DEFAULT_RUNTIME_FORCED_CLEANUP_RESERVE_MS
|
|
621
|
-
);
|
|
622
|
-
|
|
623
|
-
|
|
622
|
+
const gracefulDeadline = Math.max(
|
|
623
|
+
Date.now(),
|
|
624
|
+
deadline - DEFAULT_RUNTIME_FORCED_CLEANUP_RESERVE_MS
|
|
625
|
+
);
|
|
626
|
+
try {
|
|
627
|
+
await waitForOwnedRuntimeTreeExit(child, { deadline: gracefulDeadline });
|
|
628
|
+
} catch (error) {
|
|
629
|
+
// Preserve the forced-cleanup reserve. A stale ChildProcess terminal
|
|
630
|
+
// notification must not prevent the exact registered tree drain below.
|
|
631
|
+
log(`runtime graceful exit wait expired; forcing exact drain: ${error?.message || error}`);
|
|
632
|
+
}
|
|
633
|
+
await drainRuntimeTreeUntilStopped(child, deadline);
|
|
624
634
|
if (runtimeChild === child) runtimeChild = null;
|
|
625
635
|
return true;
|
|
626
636
|
})();
|
|
@@ -669,48 +679,85 @@ function requestRuntimeRestart(source = 'manual') {
|
|
|
669
679
|
async function prepareRuntimeForUpdateInstall() {
|
|
670
680
|
if (updateInstallSequenceStarted) {
|
|
671
681
|
throw new Error('desktop-update-install-already-started');
|
|
672
|
-
}
|
|
673
|
-
updateInstallSequenceStarted = true;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
682
|
+
}
|
|
683
|
+
updateInstallSequenceStarted = true;
|
|
684
|
+
installerQuitGate.beginInstall();
|
|
685
|
+
isQuitting = true;
|
|
686
|
+
recurringProductUpdates?.stop();
|
|
687
|
+
try {
|
|
688
|
+
await quiesceDesktopAuthOwner('desktop-update-install');
|
|
689
|
+
await stopRuntime();
|
|
690
|
+
} catch (error) {
|
|
691
|
+
installerQuitGate.failInstall(error);
|
|
692
|
+
updateInstallSequenceStarted = false;
|
|
693
|
+
isQuitting = false;
|
|
694
|
+
scheduleDesktopAuthRefresh(readEncryptedSession());
|
|
695
|
+
let recoveryError = null;
|
|
696
|
+
try {
|
|
697
|
+
const ready = await restartRuntimeProcess();
|
|
698
|
+
if (!ready) throw new Error('desktop-update-runtime-recovery-not-ready');
|
|
699
|
+
} catch (cause) {
|
|
700
|
+
recoveryError = cause;
|
|
701
|
+
log(`desktop update preparation recovery failed: ${cause?.message || cause}`);
|
|
702
|
+
}
|
|
703
|
+
if (!recoveryError) {
|
|
704
|
+
recurringProductUpdates?.start();
|
|
705
|
+
}
|
|
706
|
+
if (recoveryError) {
|
|
707
|
+
throw new Error(
|
|
708
|
+
`desktop-update-preinstall-recovery-failed:${recoveryError?.message || recoveryError}`,
|
|
709
|
+
{ cause: error }
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function recoverRuntimeAfterUpdateInstallError(error) {
|
|
717
|
+
// electron-updater can report a Windows installer spawn failure just before
|
|
718
|
+
// its queued app.quit(). Seal that exact update quit before restarting.
|
|
719
|
+
installerQuitGate.failInstall(error);
|
|
720
|
+
log(`desktop update install launch failed after runtime drain: ${error?.message || error}`);
|
|
721
|
+
updateInstallSequenceStarted = false;
|
|
722
|
+
isQuitting = false;
|
|
723
|
+
scheduleDesktopAuthRefresh(readEncryptedSession());
|
|
724
|
+
if (!runtimeChild) startRuntime();
|
|
725
|
+
const ready = await waitForRuntime();
|
|
726
|
+
if (!ready) {
|
|
727
|
+
throw new Error('desktop-update-runtime-recovery-not-ready', { cause: error });
|
|
728
|
+
}
|
|
729
|
+
mainWindow?.loadURL(LOCAL_RUNTIME_URL);
|
|
730
|
+
recurringProductUpdates?.start();
|
|
731
|
+
}
|
|
695
732
|
|
|
696
|
-
async function syncSessionToRuntime(session) {
|
|
697
|
-
const normalized = normalizeSession(session);
|
|
698
|
-
if (!normalized) throw new Error('oauth-session-invalid');
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
733
|
+
async function syncSessionToRuntime(session) {
|
|
734
|
+
const normalized = normalizeSession(session);
|
|
735
|
+
if (!normalized) throw new Error('oauth-session-invalid');
|
|
736
|
+
const deadline = Date.now() + AUTH_REQUEST_TIMEOUT_MS + 2_500;
|
|
737
|
+
const csrfToken = await getRuntimeCsrfToken(Math.min(deadline, Date.now() + 2_500));
|
|
738
|
+
const response = await fetchBeforeRuntimeDeadline(
|
|
739
|
+
fetch,
|
|
740
|
+
`${LOCAL_RUNTIME_URL}api/auth/session`,
|
|
741
|
+
{
|
|
742
|
+
method: 'POST',
|
|
743
|
+
headers: {
|
|
744
|
+
'Content-Type': 'application/json',
|
|
745
|
+
...(csrfToken ? { 'X-LiveDesk-CSRF': csrfToken } : {})
|
|
746
|
+
},
|
|
747
|
+
body: JSON.stringify({
|
|
748
|
+
accessToken: normalized.access_token,
|
|
749
|
+
refreshToken: normalized.refresh_token,
|
|
750
|
+
expiresAt: normalized.expires_at,
|
|
751
|
+
userId: normalized.user?.id,
|
|
752
|
+
user: { id: normalized.user?.id, email: normalized.user?.email }
|
|
753
|
+
})
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
deadline,
|
|
757
|
+
maxDurationMs: Math.min(AUTH_REQUEST_TIMEOUT_MS, remainingRuntimeDeadlineMs(deadline)),
|
|
758
|
+
label: 'runtime-auth-sync-deadline'
|
|
759
|
+
}
|
|
760
|
+
);
|
|
714
761
|
if (!response.ok) {
|
|
715
762
|
const payload = await response.json().catch(() => ({}));
|
|
716
763
|
throw new Error(String(payload?.error || `runtime-session-sync-failed:${response.status}`));
|
|
@@ -854,18 +901,29 @@ async function restoreEncryptedSessionOwned({ syncRuntime = true } = {}) {
|
|
|
854
901
|
return session;
|
|
855
902
|
}
|
|
856
903
|
|
|
857
|
-
async function restoreEncryptedSession(options = {}) {
|
|
858
|
-
if (desktopSessionRestorePromise) return desktopSessionRestorePromise;
|
|
859
|
-
|
|
904
|
+
async function restoreEncryptedSession(options = {}) {
|
|
905
|
+
if (desktopSessionRestorePromise) return desktopSessionRestorePromise;
|
|
906
|
+
if (isQuitting) return readEncryptedSession();
|
|
907
|
+
const operation = restoreEncryptedSessionOwned(options);
|
|
860
908
|
desktopSessionRestorePromise = operation;
|
|
861
909
|
try {
|
|
862
910
|
return await operation;
|
|
863
911
|
} finally {
|
|
864
912
|
if (desktopSessionRestorePromise === operation) desktopSessionRestorePromise = null;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
async function
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function quiesceDesktopAuthOwner(reason = 'application-exit') {
|
|
917
|
+
clearDesktopAuthRefreshTimer();
|
|
918
|
+
while (desktopSessionRestorePromise) {
|
|
919
|
+
const pending = desktopSessionRestorePromise;
|
|
920
|
+
log(`waiting for secure auth owner before ${reason}`);
|
|
921
|
+
await pending;
|
|
922
|
+
if (desktopSessionRestorePromise === pending) break;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function signOutDesktopSession() {
|
|
869
927
|
desktopSessionEpoch += 1;
|
|
870
928
|
desktopAuthInvalidationGuard.observeSuccess();
|
|
871
929
|
clearEncryptedSession();
|
|
@@ -1071,19 +1129,21 @@ function registerIpc() {
|
|
|
1071
1129
|
if (quitSequenceStarted || updateInstallSequenceStarted) {
|
|
1072
1130
|
return { ok: false, error: 'application-exit-already-started' };
|
|
1073
1131
|
}
|
|
1074
|
-
quitSequenceStarted = true;
|
|
1075
|
-
isQuitting = true;
|
|
1076
|
-
try {
|
|
1077
|
-
await
|
|
1132
|
+
quitSequenceStarted = true;
|
|
1133
|
+
isQuitting = true;
|
|
1134
|
+
try {
|
|
1135
|
+
await quiesceDesktopAuthOwner('application-restart');
|
|
1136
|
+
await stopRuntime();
|
|
1078
1137
|
await desktopClipboardOwner?.close();
|
|
1079
1138
|
await closeDesktopLog();
|
|
1080
1139
|
app.relaunch();
|
|
1081
1140
|
app.exit(0);
|
|
1082
1141
|
return { ok: true };
|
|
1083
|
-
} catch (error) {
|
|
1084
|
-
isQuitting = false;
|
|
1085
|
-
quitSequenceStarted = false;
|
|
1086
|
-
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
isQuitting = false;
|
|
1144
|
+
quitSequenceStarted = false;
|
|
1145
|
+
scheduleDesktopAuthRefresh(readEncryptedSession());
|
|
1146
|
+
updateTray('Runtime cleanup blocked');
|
|
1087
1147
|
log(`runtime cleanup blocked application restart: ${error?.message || error}`);
|
|
1088
1148
|
if (!runtimeChild) {
|
|
1089
1149
|
startRuntime();
|
|
@@ -1195,12 +1255,18 @@ function createTray() {
|
|
|
1195
1255
|
updateTray('Starting');
|
|
1196
1256
|
}
|
|
1197
1257
|
|
|
1198
|
-
app.on('open-url', (event, url) => {
|
|
1258
|
+
app.on('open-url', (event, url) => {
|
|
1199
1259
|
event.preventDefault();
|
|
1200
1260
|
if (!isLiveDeskAuthCallbackUrl(url)) return;
|
|
1201
1261
|
if (app.isReady()) void handleProtocolUrl(url);
|
|
1202
1262
|
else pendingProtocolUrl = url;
|
|
1203
|
-
});
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
electronAutoUpdater.on('before-quit-for-update', () => {
|
|
1266
|
+
// On Windows this is updater intent, not proof that the asynchronous NSIS
|
|
1267
|
+
// spawn succeeded. The first updater-owned quit is confirmed below.
|
|
1268
|
+
installerQuitGate.noteBeforeQuitForUpdate();
|
|
1269
|
+
});
|
|
1204
1270
|
|
|
1205
1271
|
if (!app.requestSingleInstanceLock()) {
|
|
1206
1272
|
app.quit();
|
|
@@ -1211,19 +1277,43 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1211
1277
|
showWindow();
|
|
1212
1278
|
});
|
|
1213
1279
|
app.whenReady().then(async () => {
|
|
1214
|
-
app.setAsDefaultProtocolClient('livedesk');
|
|
1215
|
-
removeLegacyStartupEntries();
|
|
1216
|
-
migrateLegacyPlaintextSession();
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1280
|
+
app.setAsDefaultProtocolClient('livedesk');
|
|
1281
|
+
removeLegacyStartupEntries();
|
|
1282
|
+
migrateLegacyPlaintextSession();
|
|
1283
|
+
let lastProductUpdateLogSignature = '';
|
|
1284
|
+
productUpdates = createProductUpdateManager({
|
|
1285
|
+
app,
|
|
1286
|
+
onStatus: status => {
|
|
1220
1287
|
if (mainWindow && !mainWindow.isDestroyed() && !mainWindow.webContents.isDestroyed()) {
|
|
1221
1288
|
mainWindow.webContents.send('updates:status', status);
|
|
1222
1289
|
}
|
|
1223
|
-
if (status.state === 'available') updateTray(`Update ${status.availableVersion} available`);
|
|
1224
|
-
else if (status.state === 'downloading') updateTray(`Downloading update ${Math.round(status.progress || 0)}%`);
|
|
1225
|
-
else if (status.state === 'downloaded') updateTray(`Update ${status.availableVersion} ready`);
|
|
1226
|
-
|
|
1290
|
+
if (status.state === 'available') updateTray(`Update ${status.availableVersion} available`);
|
|
1291
|
+
else if (status.state === 'downloading') updateTray(`Downloading update ${Math.round(status.progress || 0)}%`);
|
|
1292
|
+
else if (status.state === 'downloaded') updateTray(`Update ${status.availableVersion} ready`);
|
|
1293
|
+
const progressBucket = Math.floor(Math.max(0, Number(status.progress || 0)) / 10) * 10;
|
|
1294
|
+
const signature = [
|
|
1295
|
+
status.state,
|
|
1296
|
+
status.channel,
|
|
1297
|
+
status.currentVersion,
|
|
1298
|
+
status.availableVersion,
|
|
1299
|
+
status.downloadedVersion,
|
|
1300
|
+
progressBucket,
|
|
1301
|
+
status.cleanupPending,
|
|
1302
|
+
status.reopenRequired,
|
|
1303
|
+
status.errorCode,
|
|
1304
|
+
status.error,
|
|
1305
|
+
status.lastCheckStartedAt,
|
|
1306
|
+
status.lastCheckCompletedAt,
|
|
1307
|
+
status.startupCheck
|
|
1308
|
+
].join('|');
|
|
1309
|
+
if (signature !== lastProductUpdateLogSignature) {
|
|
1310
|
+
lastProductUpdateLogSignature = signature;
|
|
1311
|
+
log(
|
|
1312
|
+
`desktop update state=${status.state || 'unknown'} channel=${status.channel || 'unknown'} current=${status.currentVersion || 'unknown'} available=${status.availableVersion || 'none'} downloaded=${status.downloadedVersion || 'none'} progress=${progressBucket} cleanupPending=${Boolean(status.cleanupPending)} reopenRequired=${Boolean(status.reopenRequired)} feed=${status.feedUrl || 'none'} checkStarted=${status.lastCheckStartedAt || 'none'} checkCompleted=${status.lastCheckCompletedAt || 'none'} errorCode=${status.errorCode || 'none'} error=${status.error || 'none'}`,
|
|
1313
|
+
{ priority: status.state === 'error' || status.state === 'installing' ? 'normal' : 'low' }
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1227
1317
|
beforeInstall: prepareRuntimeForUpdateInstall,
|
|
1228
1318
|
onInstallError: recoverRuntimeAfterUpdateInstallError
|
|
1229
1319
|
});
|
|
@@ -1272,24 +1362,36 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1272
1362
|
pendingProtocolUrl = '';
|
|
1273
1363
|
if (initialProtocolUrl) void handleProtocolUrl(initialProtocolUrl);
|
|
1274
1364
|
});
|
|
1275
|
-
app.on('window-all-closed', () => { /* tray-owned desktop stays alive */ });
|
|
1276
|
-
app.on('before-quit', event => {
|
|
1277
|
-
if (quitSequenceStarted) return;
|
|
1278
|
-
|
|
1365
|
+
app.on('window-all-closed', () => { /* tray-owned desktop stays alive */ });
|
|
1366
|
+
app.on('before-quit', event => {
|
|
1367
|
+
if (quitSequenceStarted) return;
|
|
1368
|
+
const installerQuitDecision = installerQuitGate.handleBeforeQuit();
|
|
1369
|
+
if (installerQuitDecision === InstallerQuitDecision.HOLD
|
|
1370
|
+
|| installerQuitDecision === InstallerQuitDecision.BLOCK) {
|
|
1371
|
+
event.preventDefault();
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (installerQuitDecision === InstallerQuitDecision.ALLOW) return;
|
|
1375
|
+
event.preventDefault();
|
|
1279
1376
|
recurringProductUpdates?.stop();
|
|
1280
|
-
quitSequenceStarted = true;
|
|
1281
|
-
isQuitting = true;
|
|
1282
|
-
void
|
|
1283
|
-
return
|
|
1377
|
+
quitSequenceStarted = true;
|
|
1378
|
+
isQuitting = true;
|
|
1379
|
+
void quiesceDesktopAuthOwner('application-quit').then(() => {
|
|
1380
|
+
return stopRuntime();
|
|
1381
|
+
}).then(() => {
|
|
1382
|
+
return desktopClipboardOwner?.close();
|
|
1284
1383
|
}).then(() => {
|
|
1285
1384
|
return closeDesktopLog();
|
|
1286
1385
|
}).then(() => {
|
|
1287
1386
|
app.quit();
|
|
1288
1387
|
}).catch(error => {
|
|
1289
|
-
isQuitting = false;
|
|
1290
|
-
quitSequenceStarted = false;
|
|
1291
|
-
updateInstallSequenceStarted = false;
|
|
1292
|
-
|
|
1388
|
+
isQuitting = false;
|
|
1389
|
+
quitSequenceStarted = false;
|
|
1390
|
+
updateInstallSequenceStarted = false;
|
|
1391
|
+
installerQuitGate.failInstall(error);
|
|
1392
|
+
scheduleDesktopAuthRefresh(readEncryptedSession());
|
|
1393
|
+
recurringProductUpdates?.start();
|
|
1394
|
+
updateTray('Runtime cleanup blocked');
|
|
1293
1395
|
log(`runtime cleanup blocked application quit: ${error?.message || error}`);
|
|
1294
1396
|
});
|
|
1295
1397
|
});
|