@cats-inc/cats-platform 0.1.1 → 0.1.7
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/.env.example +2 -2
- package/README.md +6 -0
- package/build/desktop/bootstrapPage.d.ts.map +1 -1
- package/build/desktop/bootstrapPage.js +81 -4
- package/build/desktop/bootstrapPage.js.map +1 -1
- package/build/desktop/config.d.ts +11 -1
- package/build/desktop/config.d.ts.map +1 -1
- package/build/desktop/config.js +5 -2
- package/build/desktop/config.js.map +1 -1
- package/build/desktop/contracts.d.ts +68 -11
- package/build/desktop/contracts.d.ts.map +1 -1
- package/build/desktop/contracts.js +52 -1
- package/build/desktop/contracts.js.map +1 -1
- package/build/desktop/hostState.d.ts +4 -3
- package/build/desktop/hostState.d.ts.map +1 -1
- package/build/desktop/hostState.js +22 -30
- package/build/desktop/hostState.js.map +1 -1
- package/build/desktop/hostVersion.d.ts +1 -0
- package/build/desktop/hostVersion.d.ts.map +1 -1
- package/build/desktop/hostVersion.js +29 -5
- package/build/desktop/hostVersion.js.map +1 -1
- package/build/desktop/ipcSecurity.d.ts +6 -0
- package/build/desktop/ipcSecurity.d.ts.map +1 -0
- package/build/desktop/ipcSecurity.js +10 -0
- package/build/desktop/ipcSecurity.js.map +1 -0
- package/build/desktop/main.js +272 -59
- package/build/desktop/main.js.map +1 -1
- package/build/desktop/packaging.d.ts.map +1 -1
- package/build/desktop/packaging.js +10 -4
- package/build/desktop/packaging.js.map +1 -1
- package/build/desktop/preload.cjs +39 -0
- package/build/desktop/preload.cjs.map +1 -1
- package/build/desktop/processSupervisor.d.ts +2 -0
- package/build/desktop/processSupervisor.d.ts.map +1 -1
- package/build/desktop/processSupervisor.js +28 -13
- package/build/desktop/processSupervisor.js.map +1 -1
- package/build/desktop/readiness.d.ts +2 -2
- package/build/desktop/readiness.d.ts.map +1 -1
- package/build/desktop/readiness.js +2 -2
- package/build/desktop/readiness.js.map +1 -1
- package/build/desktop/releaseDescriptor.d.ts +85 -0
- package/build/desktop/releaseDescriptor.d.ts.map +1 -0
- package/build/desktop/releaseDescriptor.js +246 -0
- package/build/desktop/releaseDescriptor.js.map +1 -0
- package/build/desktop/screenshotCapture.d.ts.map +1 -1
- package/build/desktop/screenshotCapture.js +3 -7
- package/build/desktop/screenshotCapture.js.map +1 -1
- package/build/desktop/security.d.ts +8 -0
- package/build/desktop/security.d.ts.map +1 -1
- package/build/desktop/security.js +54 -1
- package/build/desktop/security.js.map +1 -1
- package/build/desktop/tray.d.ts +1 -0
- package/build/desktop/tray.d.ts.map +1 -1
- package/build/desktop/tray.js +26 -0
- package/build/desktop/tray.js.map +1 -1
- package/build/desktop/trayMenu.d.ts +22 -1
- package/build/desktop/trayMenu.d.ts.map +1 -1
- package/build/desktop/trayMenu.js +61 -0
- package/build/desktop/trayMenu.js.map +1 -1
- package/build/desktop/updateInstallHandoff.d.ts +30 -0
- package/build/desktop/updateInstallHandoff.d.ts.map +1 -0
- package/build/desktop/updateInstallHandoff.js +39 -0
- package/build/desktop/updateInstallHandoff.js.map +1 -0
- package/build/desktop/updateIpc.d.ts +42 -0
- package/build/desktop/updateIpc.d.ts.map +1 -0
- package/build/desktop/updateIpc.js +71 -0
- package/build/desktop/updateIpc.js.map +1 -0
- package/build/desktop/updateManager.d.ts +76 -0
- package/build/desktop/updateManager.d.ts.map +1 -0
- package/build/desktop/updateManager.js +342 -0
- package/build/desktop/updateManager.js.map +1 -0
- package/build/desktop/updateNotifications.d.ts +64 -0
- package/build/desktop/updateNotifications.d.ts.map +1 -0
- package/build/desktop/updateNotifications.js +143 -0
- package/build/desktop/updateNotifications.js.map +1 -0
- package/build/desktop/updaterAdapter.d.ts +72 -0
- package/build/desktop/updaterAdapter.d.ts.map +1 -0
- package/build/desktop/updaterAdapter.js +198 -0
- package/build/desktop/updaterAdapter.js.map +1 -0
- package/build/desktop/voiceCapture.d.ts.map +1 -1
- package/build/desktop/voiceCapture.js +3 -7
- package/build/desktop/voiceCapture.js.map +1 -1
- package/build/renderer/assets/ActiveSessionPermissionChip-BRMLwkvf.js +1 -0
- package/build/renderer/assets/{App-BbuTWT6Y.js → App-BM2nzo6s.js} +1 -1
- package/build/renderer/assets/{App-n-RtGh42.js → App-CVQAwCMi.js} +1 -1
- package/build/renderer/assets/{App-B13AzNko.js → App-Dl5FLaR_.js} +1 -1
- package/build/renderer/assets/{WorkspaceAppRoutes-BktWrQK-.js → WorkspaceAppRoutes-C3DUzdbj.js} +1 -1
- package/build/renderer/assets/app-hP5h2jAt.js +43 -0
- package/build/renderer/assets/{client-DREGSZqQ.js → client-BdGHAUoK.js} +1 -1
- package/build/renderer/assets/{conversationSidebarRecentEntries-BNPz3X1V.js → conversationSidebarRecentEntries-B-mNvfiy.js} +1 -1
- package/build/renderer/assets/{screenshotOverlay-EF07cXvw.js → screenshotOverlay-DfBcolX-.js} +1 -1
- package/build/renderer/assets/{useDraftSessionChips-3z_Je4ju.js → useDraftSessionChips-DuXIoptW.js} +1 -1
- package/build/renderer/desktop/overlay/index.html +2 -2
- package/build/renderer/index.html +2 -2
- package/build/server/app/renderer/settings/settingsDesktopErrorLabels.d.ts +5 -0
- package/build/server/app/renderer/settings/settingsDesktopErrorLabels.d.ts.map +1 -0
- package/build/server/app/renderer/settings/settingsDesktopErrorLabels.js +16 -0
- package/build/server/app/renderer/settings/settingsDesktopErrorLabels.js.map +1 -0
- package/build/server/app/renderer/settings/settingsDesktopPreferences.d.ts +11 -0
- package/build/server/app/renderer/settings/settingsDesktopPreferences.d.ts.map +1 -0
- package/build/server/app/renderer/settings/settingsDesktopPreferences.js +30 -0
- package/build/server/app/renderer/settings/settingsDesktopPreferences.js.map +1 -0
- package/build/server/app/renderer/settings/settingsDesktopUpdateLabels.d.ts +31 -0
- package/build/server/app/renderer/settings/settingsDesktopUpdateLabels.d.ts.map +1 -0
- package/build/server/app/renderer/settings/settingsDesktopUpdateLabels.js +101 -0
- package/build/server/app/renderer/settings/settingsDesktopUpdateLabels.js.map +1 -0
- package/build/server/app/server/authGatePolicy.js +14 -2
- package/build/server/app/server/authGatePolicy.js.map +1 -1
- package/build/server/app/server/authRoutes.d.ts +2 -1
- package/build/server/app/server/authRoutes.d.ts.map +1 -1
- package/build/server/app/server/authRoutes.js +260 -4
- package/build/server/app/server/authRoutes.js.map +1 -1
- package/build/server/app/server/contracts.d.ts +3 -1
- package/build/server/app/server/contracts.d.ts.map +1 -1
- package/build/server/app/server/dependencies.d.ts.map +1 -1
- package/build/server/app/server/dependencies.js +4 -1
- package/build/server/app/server/dependencies.js.map +1 -1
- package/build/server/app/server/index.js +1 -1
- package/build/server/app/server/index.js.map +1 -1
- package/build/server/app/server/platformSetupRoutes.d.ts.map +1 -1
- package/build/server/app/server/platformSetupRoutes.js +42 -5
- package/build/server/app/server/platformSetupRoutes.js.map +1 -1
- package/build/server/app/server/requestRouter.d.ts.map +1 -1
- package/build/server/app/server/requestRouter.js +3 -0
- package/build/server/app/server/requestRouter.js.map +1 -1
- package/build/server/design/components/runtimeChips.d.ts +1 -1
- package/build/server/design/components/runtimeChips.d.ts.map +1 -1
- package/build/server/design/components/runtimeChips.js +4 -1
- package/build/server/design/components/runtimeChips.js.map +1 -1
- package/build/server/index.js +12 -1
- package/build/server/index.js.map +1 -1
- package/build/server/platform/auth/browserHandoff.d.ts +50 -0
- package/build/server/platform/auth/browserHandoff.d.ts.map +1 -0
- package/build/server/platform/auth/browserHandoff.js +94 -0
- package/build/server/platform/auth/browserHandoff.js.map +1 -0
- package/build/server/platform/auth/index.d.ts +1 -0
- package/build/server/platform/auth/index.d.ts.map +1 -1
- package/build/server/platform/auth/index.js +1 -0
- package/build/server/platform/auth/index.js.map +1 -1
- package/build/server/platform/auth/principal.js +2 -2
- package/build/server/platform/auth/principal.js.map +1 -1
- package/build/server/platform/auth/session.d.ts +3 -0
- package/build/server/platform/auth/session.d.ts.map +1 -1
- package/build/server/platform/auth/session.js +41 -0
- package/build/server/platform/auth/session.js.map +1 -1
- package/build/server/platform/auth/sessionSecretProvisioning.d.ts +23 -0
- package/build/server/platform/auth/sessionSecretProvisioning.d.ts.map +1 -0
- package/build/server/platform/auth/sessionSecretProvisioning.js +291 -0
- package/build/server/platform/auth/sessionSecretProvisioning.js.map +1 -0
- package/build/server/platform/auth/state.js +4 -0
- package/build/server/platform/auth/state.js.map +1 -1
- package/build/server/platform/auth/types.d.ts +1 -0
- package/build/server/platform/auth/types.d.ts.map +1 -1
- package/build/server/products/code/livePreview/realProcessAdapter.d.ts.map +1 -1
- package/build/server/products/code/livePreview/realProcessAdapter.js +7 -1
- package/build/server/products/code/livePreview/realProcessAdapter.js.map +1 -1
- package/build/server/products/code/state/appExport.d.ts.map +1 -1
- package/build/server/products/code/state/appExport.js +2 -0
- package/build/server/products/code/state/appExport.js.map +1 -1
- package/build/server/shared/browserHandoff.d.ts +11 -0
- package/build/server/shared/browserHandoff.d.ts.map +1 -0
- package/build/server/shared/browserHandoff.js +2 -0
- package/build/server/shared/browserHandoff.js.map +1 -0
- package/build/server/shared/desktopRecoveryBridge.d.ts +47 -0
- package/build/server/shared/desktopRecoveryBridge.d.ts.map +1 -1
- package/build/server/shared/desktopRecoveryBridge.js +20 -0
- package/build/server/shared/desktopRecoveryBridge.js.map +1 -1
- package/build/server/shared/i18n/catalogs/en.d.ts.map +1 -1
- package/build/server/shared/i18n/catalogs/en.js +38 -0
- package/build/server/shared/i18n/catalogs/en.js.map +1 -1
- package/build/server/shared/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/build/server/shared/i18n/catalogs/zh-TW.js +36 -0
- package/build/server/shared/i18n/catalogs/zh-TW.js.map +1 -1
- package/build/server/shared/i18n/index.d.ts +36 -0
- package/build/server/shared/i18n/index.d.ts.map +1 -1
- package/build/server/shared/i18n/messageKeys.d.ts +36 -0
- package/build/server/shared/i18n/messageKeys.d.ts.map +1 -1
- package/build/server/shared/i18n/messageKeys.js +36 -0
- package/build/server/shared/i18n/messageKeys.js.map +1 -1
- package/build/server/shared/loadProjectEnvFile.d.ts.map +1 -1
- package/build/server/shared/loadProjectEnvFile.js +18 -19
- package/build/server/shared/loadProjectEnvFile.js.map +1 -1
- package/build/server/shared/platformChildProcessEnv.d.ts +2 -0
- package/build/server/shared/platformChildProcessEnv.d.ts.map +1 -0
- package/build/server/shared/platformChildProcessEnv.js +22 -0
- package/build/server/shared/platformChildProcessEnv.js.map +1 -0
- package/package.json +19 -27
- package/scripts/build-desktop-installer.mjs +438 -37
- package/scripts/build-test-ui.mjs +13 -2
- package/scripts/generate-desktop-release-descriptor.mjs +258 -0
- package/scripts/validate-release-assets.mjs +422 -0
- package/scripts/validate-release-version.mjs +217 -0
- package/scripts/windows/Test-WindowsInstallerSmoke.ps1 +55 -1
- package/build/desktop/update.d.ts +0 -24
- package/build/desktop/update.d.ts.map +0 -1
- package/build/desktop/update.js +0 -173
- package/build/desktop/update.js.map +0 -1
- package/build/renderer/assets/ActiveSessionPermissionChip-BYr8cDpb.js +0 -1
- package/build/renderer/assets/app-DnVQMpW_.js +0 -43
- package/build/server/app/renderer/settings/settingsDesktopStartupErrorLabels.d.ts +0 -5
- package/build/server/app/renderer/settings/settingsDesktopStartupErrorLabels.d.ts.map +0 -1
- package/build/server/app/renderer/settings/settingsDesktopStartupErrorLabels.js +0 -11
- package/build/server/app/renderer/settings/settingsDesktopStartupErrorLabels.js.map +0 -1
package/build/desktop/main.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path';
|
|
2
|
-
import { app, BrowserWindow, ipcMain, session, shell, systemPreferences } from 'electron';
|
|
2
|
+
import { app, BrowserWindow, ipcMain, Notification, session, shell, systemPreferences, } from 'electron';
|
|
3
3
|
import { buildDesktopBootstrapPage } from './bootstrapPage.js';
|
|
4
4
|
import { resolveDesktopBootstrapNavigation, shouldRevealDesktopBootstrapRecovery, shouldNavigateDesktopBootstrap, resolveDesktopWindowRevealNavigation, shouldAllowDesktopBootstrapWindowNavigation, } from './bootstrapNavigation.js';
|
|
5
5
|
import { resolveCatsHomeDir, resolveDesktopHostConfig, resolveDesktopUserDataDir, } from './config.js';
|
|
6
|
+
import { DESKTOP_BROWSER_HANDOFF_OPEN_CHANNEL } from './contracts.js';
|
|
6
7
|
import { appendHostEvent, appendRuntimeEvent, buildDesktopAggregationBundle, createBootstrapAttemptId, createDesktopBootstrapEvent, createEmptyDesktopDiagnosticsState, toDesktopBootstrapError, toDesktopBootstrapStatus, updateServiceLogs, } from './bootstrapDiagnostics.js';
|
|
7
|
-
import { createDesktopBackgroundState, DesktopHostStateStore } from './hostState.js';
|
|
8
|
+
import { createDesktopBackgroundState, DesktopHostStateStore, readPersistedUpdateLastCheckedAt, } from './hostState.js';
|
|
8
9
|
import { createDesktopPackagingPlan } from './packaging.js';
|
|
9
10
|
import { ManagedServiceSupervisor } from './processSupervisor.js';
|
|
10
11
|
import { buildDesktopBootstrapSnapshot, fetchJson, } from './readiness.js';
|
|
11
|
-
import { isDesktopHostActionId, validateDesktopUrl } from './security.js';
|
|
12
|
+
import { isDesktopAppOriginUrl, isDesktopHostActionId, resolveDesktopBrowserHandoffUrl, validateDesktopUrl, } from './security.js';
|
|
13
|
+
import { assertMainWindowIpcSender } from './ipcSecurity.js';
|
|
12
14
|
import { readPersistedSetupCompletionState, } from './persistedSetupState.js';
|
|
13
15
|
import { buildDesktopSetupSnapshot, createEmptyDesktopSetupState, runDesktopSetupHelper, shouldAutoRunSetupAudit, } from './setupBridge.js';
|
|
14
16
|
import { isDesktopBootstrapLoadingPhase, resolveDesktopBootstrapError, shouldAttemptDesktopLateReadyRecovery, } from './startupRecovery.js';
|
|
@@ -18,7 +20,13 @@ import { createDesktopTrayController, } from './tray.js';
|
|
|
18
20
|
import { buildDesktopTrayMenuState as buildElectronTrayMenuState, buildDesktopTrayQuittingMenuState, } from './trayMenu.js';
|
|
19
21
|
import { applyDesktopHostPlatformShellUpdate, normalizePlatformShellSetupState, parseDesktopHostPlatformShellUpdate, } from './platformShellUpdate.js';
|
|
20
22
|
import { enableDesktopMobilePairingEnv } from './mobilePairingEnv.js';
|
|
21
|
-
import {
|
|
23
|
+
import { DESKTOP_APP_USER_MODEL_ID, DESKTOP_HOST_VERSION } from './hostVersion.js';
|
|
24
|
+
import { loadDesktopReleaseDescriptor, resolveDesktopDistributionIdentity, } from './releaseDescriptor.js';
|
|
25
|
+
import { createDesktopUpdateCapability, createDesktopUpdateManager, createUnavailableDesktopUpdateSnapshot, } from './updateManager.js';
|
|
26
|
+
import { configureElectronUpdater, createElectronUpdaterAdapter, resolveAutoUpdaterExport, } from './updaterAdapter.js';
|
|
27
|
+
import { createDesktopUpdateIpcHandlers, createDesktopUpdateSnapshotBroadcast, } from './updateIpc.js';
|
|
28
|
+
import { withDesktopInstallHandoff } from './updateInstallHandoff.js';
|
|
29
|
+
import { DESKTOP_UPDATE_SETTINGS_PATH, resolveDesktopUpdateAnnouncement, } from './updateNotifications.js';
|
|
22
30
|
import { applyDesktopWindowChrome, resolveDesktopWindowChromeOptions, } from './windowChrome.js';
|
|
23
31
|
import { resolveDesktopWindowIconPath } from './windowIcon.js';
|
|
24
32
|
import { readDesktopStartupPreferences, resolveDesktopStartupLaunchContext, syncDesktopStartupPreferences, updateDesktopStartupPreferences, DESKTOP_LAUNCH_AT_LOGIN_ARG, } from './desktopStartup.js';
|
|
@@ -64,6 +72,8 @@ let trayController = null;
|
|
|
64
72
|
let stateStore = null;
|
|
65
73
|
let backgroundState = null;
|
|
66
74
|
let updateState = null;
|
|
75
|
+
let updateManager = null;
|
|
76
|
+
let detachUpdateBroadcast = null;
|
|
67
77
|
let packagingState = null;
|
|
68
78
|
let setupState = null;
|
|
69
79
|
let diagnosticsState = null;
|
|
@@ -229,16 +239,29 @@ function resolveAllowedAppHosts(config) {
|
|
|
229
239
|
return Array.from(new Set([config.appHost, appBaseHost]));
|
|
230
240
|
}
|
|
231
241
|
function shouldAllowInAppNavigation(rawUrl, config) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
return isDesktopAppOriginUrl(rawUrl, {
|
|
243
|
+
appBaseUrl: config.appBaseUrl,
|
|
244
|
+
allowedHosts: resolveAllowedAppHosts(config),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function configureDesktopWindowNavigation(window, config) {
|
|
248
|
+
window.webContents.setWindowOpenHandler(({ url }) => {
|
|
249
|
+
if (shouldAllowInAppNavigation(url, config)) {
|
|
250
|
+
return { action: 'allow' };
|
|
251
|
+
}
|
|
252
|
+
void openExternalDesktopUrl(url).catch(reportExternalUrlOpenFailure);
|
|
253
|
+
return { action: 'deny' };
|
|
254
|
+
});
|
|
255
|
+
window.webContents.on('will-navigate', (event, url) => {
|
|
256
|
+
if (shouldAllowInAppNavigation(url, config)) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
void openExternalDesktopUrl(url).catch(reportExternalUrlOpenFailure);
|
|
261
|
+
});
|
|
262
|
+
window.webContents.on('did-create-window', (childWindow) => {
|
|
263
|
+
configureDesktopWindowNavigation(childWindow, config);
|
|
264
|
+
});
|
|
242
265
|
}
|
|
243
266
|
async function ensureBootstrapPageVisible() {
|
|
244
267
|
if (!mainWindow) {
|
|
@@ -582,6 +605,7 @@ function resolveDesktopTrayMenuState(snapshot) {
|
|
|
582
605
|
actions: snapshot.actions,
|
|
583
606
|
products: latestAppShellPayload?.products,
|
|
584
607
|
locale: app.getLocale(),
|
|
608
|
+
updates: updateState,
|
|
585
609
|
});
|
|
586
610
|
}
|
|
587
611
|
function isSystemTrayEnabled() {
|
|
@@ -707,6 +731,14 @@ function buildTrayControllerOptions() {
|
|
|
707
731
|
void shutdownHost();
|
|
708
732
|
},
|
|
709
733
|
canInteract: () => !shuttingDown,
|
|
734
|
+
onCheckForUpdates: async () => {
|
|
735
|
+
if (shuttingDown) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
// The menu closes on click, so this result has to reach the user through
|
|
739
|
+
// a notification or by opening Settings.
|
|
740
|
+
await refreshUpdateState('tray');
|
|
741
|
+
},
|
|
710
742
|
};
|
|
711
743
|
}
|
|
712
744
|
async function syncTrayController() {
|
|
@@ -1197,24 +1229,151 @@ async function maybeRecoverFromLateReadyStateChange() {
|
|
|
1197
1229
|
})();
|
|
1198
1230
|
await lateReadyRecoveryPromise;
|
|
1199
1231
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* Builds the update manager for this launch.
|
|
1234
|
+
*
|
|
1235
|
+
* Capability is derived from the embedded release descriptor plus the running
|
|
1236
|
+
* package, never from the environment. When the build is not an official
|
|
1237
|
+
* release the adapter is omitted entirely, so no provider is ever configured.
|
|
1238
|
+
*/
|
|
1239
|
+
async function createUpdateManagerForLaunch(config, initialLastCheckedAt = null) {
|
|
1240
|
+
const descriptor = await loadDesktopReleaseDescriptor();
|
|
1241
|
+
const identity = resolveDesktopDistributionIdentity({
|
|
1242
|
+
isPackaged: config.packaged,
|
|
1243
|
+
currentVersion: DESKTOP_HOST_VERSION,
|
|
1244
|
+
nodePlatform: process.platform,
|
|
1245
|
+
descriptor,
|
|
1246
|
+
});
|
|
1247
|
+
const capability = createDesktopUpdateCapability({
|
|
1248
|
+
identity,
|
|
1249
|
+
nodePlatform: process.platform,
|
|
1250
|
+
});
|
|
1251
|
+
let adapter = null;
|
|
1252
|
+
if (capability.canCheck && identity.repository !== null) {
|
|
1253
|
+
// Self-update is a feature, not a startup requirement. If the provider
|
|
1254
|
+
// cannot be constructed the app must still open with update capability
|
|
1255
|
+
// switched off, rather than failing to launch at all.
|
|
1256
|
+
try {
|
|
1257
|
+
const autoUpdater = resolveAutoUpdaterExport(await import('electron-updater'));
|
|
1258
|
+
configureElectronUpdater(autoUpdater, {
|
|
1259
|
+
repository: identity.repository,
|
|
1260
|
+
channel: capability.channel,
|
|
1261
|
+
distribution: capability.distribution,
|
|
1262
|
+
});
|
|
1263
|
+
adapter = withDesktopInstallHandoff(createElectronUpdaterAdapter(autoUpdater), {
|
|
1264
|
+
drainManagedServices: async () => {
|
|
1265
|
+
// The tray says "Quitting" for the same reason shutdown does: from
|
|
1266
|
+
// here the app is on its way out, and a menu offering to open Cats
|
|
1267
|
+
// would be a lie while the sidecars are stopping. This also locks the
|
|
1268
|
+
// menu against repeated interaction without touching `shuttingDown`,
|
|
1269
|
+
// which gates far more behaviour and is never designed to be reset.
|
|
1270
|
+
trayController?.updateMenu(buildDesktopTrayQuittingMenuState(app.getLocale()));
|
|
1271
|
+
await drainManagedServices();
|
|
1272
|
+
// electron-updater quits the app itself. Without this the before-quit
|
|
1273
|
+
// handler would cancel that quit and run a second drain, delaying the
|
|
1274
|
+
// exit the installer is waiting on. The drain above already satisfied
|
|
1275
|
+
// the contract that flag stands for.
|
|
1276
|
+
exitingAfterShutdown = true;
|
|
1277
|
+
},
|
|
1278
|
+
restartManagedServices: async () => {
|
|
1279
|
+
// The handoff failed but the process survived, so the exit is no
|
|
1280
|
+
// longer prepared, the sidecars have to come back, and the tray has to
|
|
1281
|
+
// stop claiming we are quitting. startAll() intentionally reuses the
|
|
1282
|
+
// supervisor-lifetime environment snapshot populated during the first
|
|
1283
|
+
// bootstrap, including the Desktop-provisioned platform auth secret.
|
|
1284
|
+
exitingAfterShutdown = false;
|
|
1285
|
+
await supervisor?.startAll();
|
|
1286
|
+
await syncTrayController();
|
|
1287
|
+
},
|
|
1288
|
+
logger: (message) => {
|
|
1289
|
+
process.stdout.write(`${message}\n`);
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
catch (error) {
|
|
1294
|
+
process.stderr.write('[desktop-update] updater unavailable, continuing without self-update: '
|
|
1295
|
+
+ `${error instanceof Error ? error.message : String(error)}\n`);
|
|
1296
|
+
adapter = null;
|
|
1297
|
+
}
|
|
1203
1298
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1299
|
+
return createDesktopUpdateManager({
|
|
1300
|
+
capability,
|
|
1301
|
+
adapter,
|
|
1302
|
+
initialLastCheckedAt,
|
|
1303
|
+
logger: (message) => {
|
|
1304
|
+
process.stdout.write(`${message}\n`);
|
|
1305
|
+
},
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
async function openDesktopUpdateSettings() {
|
|
1309
|
+
if (shuttingDown || !hostConfig) {
|
|
1310
|
+
return;
|
|
1213
1311
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1312
|
+
await showMainWindow(`${hostConfig.appBaseUrl}${DESKTOP_UPDATE_SETTINGS_PATH}`);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Shows a native notification, reporting whether the user will actually see it.
|
|
1316
|
+
*
|
|
1317
|
+
* A false return sends the caller to the navigation fallback. Notifying is not
|
|
1318
|
+
* worth failing a check over, so a throwing platform is treated the same as one
|
|
1319
|
+
* without notification support.
|
|
1320
|
+
*/
|
|
1321
|
+
function showDesktopUpdateNotification(notification) {
|
|
1322
|
+
try {
|
|
1323
|
+
const native = new Notification({
|
|
1324
|
+
title: notification.title,
|
|
1325
|
+
body: notification.body,
|
|
1326
|
+
});
|
|
1327
|
+
native.on('click', () => {
|
|
1328
|
+
void openDesktopUpdateSettings();
|
|
1329
|
+
});
|
|
1330
|
+
native.show();
|
|
1331
|
+
return true;
|
|
1332
|
+
}
|
|
1333
|
+
catch (error) {
|
|
1334
|
+
process.stderr.write('[desktop-update] native notification unavailable: '
|
|
1335
|
+
+ `${error instanceof Error ? error.message : String(error)}\n`);
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function areNativeNotificationsSupported() {
|
|
1340
|
+
try {
|
|
1341
|
+
return Notification.isSupported();
|
|
1342
|
+
}
|
|
1343
|
+
catch {
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
async function announceDesktopUpdateResult(origin, snapshot) {
|
|
1348
|
+
if (!snapshot || shuttingDown) {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const announcement = resolveDesktopUpdateAnnouncement({
|
|
1352
|
+
origin,
|
|
1353
|
+
snapshot,
|
|
1354
|
+
locale: app.getLocale(),
|
|
1355
|
+
notificationsSupported: areNativeNotificationsSupported(),
|
|
1356
|
+
});
|
|
1357
|
+
if (announcement.notification && showDesktopUpdateNotification(announcement.notification)) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
if (announcement.fallbackNavigatePath) {
|
|
1361
|
+
await openDesktopUpdateSettings();
|
|
1217
1362
|
}
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Runs a check and reports its result to whoever asked for it.
|
|
1366
|
+
*
|
|
1367
|
+
* The origin is required rather than defaulted: it decides whether the result
|
|
1368
|
+
* is announced at all, and a new caller silently inheriting another surface's
|
|
1369
|
+
* policy is the failure this signature exists to prevent.
|
|
1370
|
+
*/
|
|
1371
|
+
async function refreshUpdateState(origin) {
|
|
1372
|
+
if (!updateManager) {
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
updateState = await updateManager.checkForUpdates();
|
|
1376
|
+
await announceDesktopUpdateResult(origin, updateState);
|
|
1218
1377
|
return updateState;
|
|
1219
1378
|
}
|
|
1220
1379
|
async function bootstrapDesktopHost(restartServices = false) {
|
|
@@ -1448,17 +1607,7 @@ async function createMainWindow(config, options) {
|
|
|
1448
1607
|
sandbox: true,
|
|
1449
1608
|
},
|
|
1450
1609
|
});
|
|
1451
|
-
window
|
|
1452
|
-
void openExternalDesktopUrl(url).catch(reportExternalUrlOpenFailure);
|
|
1453
|
-
return { action: 'deny' };
|
|
1454
|
-
});
|
|
1455
|
-
window.webContents.on('will-navigate', (event, url) => {
|
|
1456
|
-
if (shouldAllowInAppNavigation(url, config)) {
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
event.preventDefault();
|
|
1460
|
-
void openExternalDesktopUrl(url).catch(reportExternalUrlOpenFailure);
|
|
1461
|
-
});
|
|
1610
|
+
configureDesktopWindowNavigation(window, config);
|
|
1462
1611
|
applyDesktopWindowChrome(window);
|
|
1463
1612
|
const showBootstrapWindow = () => {
|
|
1464
1613
|
if (!options.showWindowOnStartup || window.isDestroyed() || window.isVisible()) {
|
|
@@ -1488,6 +1637,33 @@ function waitForShutdownDeadline(ms) {
|
|
|
1488
1637
|
timer.unref?.();
|
|
1489
1638
|
});
|
|
1490
1639
|
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Stops managed services, forcing them down if the graceful path overruns.
|
|
1642
|
+
*
|
|
1643
|
+
* Shared by host shutdown and the update installer handoff, because both need
|
|
1644
|
+
* the sidecars actually gone — the installer for the same reason the exit path
|
|
1645
|
+
* does, plus the Windows upgrade running the old uninstaller over files a
|
|
1646
|
+
* surviving sidecar may still hold.
|
|
1647
|
+
*/
|
|
1648
|
+
async function drainManagedServices() {
|
|
1649
|
+
const drain = supervisor?.stopAll() ?? Promise.resolve();
|
|
1650
|
+
const shutdownWatchdogMs = hostConfig
|
|
1651
|
+
? resolveShutdownWatchdogMs(hostConfig, supervisor?.getManagedServiceCount() ?? 0)
|
|
1652
|
+
: SHUTDOWN_WATCHDOG_BUFFER_MS;
|
|
1653
|
+
const result = await Promise.race([
|
|
1654
|
+
drain.then(() => 'drained'),
|
|
1655
|
+
waitForShutdownDeadline(shutdownWatchdogMs),
|
|
1656
|
+
]);
|
|
1657
|
+
if (result !== 'timed_out') {
|
|
1658
|
+
return { timedOut: false };
|
|
1659
|
+
}
|
|
1660
|
+
process.stderr.write(`Desktop host shutdown watchdog tripped after ${shutdownWatchdogMs}ms; forcing managed services down.\n`);
|
|
1661
|
+
await supervisor?.forceStopAll();
|
|
1662
|
+
void drain.catch((error) => {
|
|
1663
|
+
process.stderr.write(`Desktop host graceful shutdown failed after watchdog: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
1664
|
+
});
|
|
1665
|
+
return { timedOut: true };
|
|
1666
|
+
}
|
|
1491
1667
|
async function shutdownHost() {
|
|
1492
1668
|
if (shutdownPromise) {
|
|
1493
1669
|
return shutdownPromise;
|
|
@@ -1502,22 +1678,10 @@ async function shutdownHost() {
|
|
|
1502
1678
|
voiceCaptureController?.dispose();
|
|
1503
1679
|
voiceCaptureController = null;
|
|
1504
1680
|
activeTrayController?.updateMenu(buildDesktopTrayQuittingMenuState(app.getLocale()));
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
? resolveShutdownWatchdogMs(hostConfig, supervisor?.getManagedServiceCount() ?? 0)
|
|
1508
|
-
: SHUTDOWN_WATCHDOG_BUFFER_MS;
|
|
1509
|
-
const result = await Promise.race([
|
|
1510
|
-
drain.then(() => 'drained'),
|
|
1511
|
-
waitForShutdownDeadline(shutdownWatchdogMs),
|
|
1512
|
-
]);
|
|
1513
|
-
if (result === 'timed_out') {
|
|
1681
|
+
const { timedOut } = await drainManagedServices();
|
|
1682
|
+
if (timedOut) {
|
|
1514
1683
|
shutdownExitCode = 1;
|
|
1515
|
-
process.stderr.write(`Desktop host shutdown watchdog tripped after ${shutdownWatchdogMs}ms; forcing managed services down.\n`);
|
|
1516
1684
|
forcedStopAttempted = true;
|
|
1517
|
-
await supervisor?.forceStopAll();
|
|
1518
|
-
void drain.catch((error) => {
|
|
1519
|
-
process.stderr.write(`Desktop host graceful shutdown failed after watchdog: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
1520
|
-
});
|
|
1521
1685
|
}
|
|
1522
1686
|
}
|
|
1523
1687
|
catch (error) {
|
|
@@ -1551,6 +1715,14 @@ async function main() {
|
|
|
1551
1715
|
return;
|
|
1552
1716
|
}
|
|
1553
1717
|
app.setPath('userData', resolveDesktopUserDataDir(app.getPath('appData')));
|
|
1718
|
+
// Windows routes a notification through the Start Menu shortcut whose
|
|
1719
|
+
// Application User Model ID matches the running process. The installer writes
|
|
1720
|
+
// that shortcut with the electron-builder appId, so claiming it here is what
|
|
1721
|
+
// makes update notifications visible at all. Unpackaged runs have no such
|
|
1722
|
+
// shortcut, so the claim would buy nothing.
|
|
1723
|
+
if (process.platform === 'win32' && app.isPackaged && DESKTOP_APP_USER_MODEL_ID) {
|
|
1724
|
+
app.setAppUserModelId(DESKTOP_APP_USER_MODEL_ID);
|
|
1725
|
+
}
|
|
1554
1726
|
await app.whenReady();
|
|
1555
1727
|
const nodeProcess = process;
|
|
1556
1728
|
hostConfig = resolveDesktopHostConfig({
|
|
@@ -1580,12 +1752,14 @@ async function main() {
|
|
|
1580
1752
|
stateStore = new DesktopHostStateStore(hostConfig.paths.hostStatePath);
|
|
1581
1753
|
{
|
|
1582
1754
|
const defaultBackground = applyEffectiveBackgroundPreferences(createDesktopBackgroundState(hostConfig));
|
|
1583
|
-
|
|
1755
|
+
// Built once with no restored history so the state file can be read; the
|
|
1756
|
+
// manager is rebuilt below with the restored last-check time.
|
|
1757
|
+
const provisionalUpdates = createUnavailableDesktopUpdateSnapshot(DESKTOP_HOST_VERSION);
|
|
1584
1758
|
const defaultPackaging = buildHostPackagingPlan(hostConfig);
|
|
1585
1759
|
const defaultSetup = createEmptyDesktopSetupState();
|
|
1586
1760
|
const restoredState = await stateStore.load(hostConfig, {
|
|
1587
1761
|
background: defaultBackground,
|
|
1588
|
-
updates:
|
|
1762
|
+
updates: provisionalUpdates,
|
|
1589
1763
|
packaging: defaultPackaging,
|
|
1590
1764
|
setup: defaultSetup,
|
|
1591
1765
|
});
|
|
@@ -1598,7 +1772,21 @@ async function main() {
|
|
|
1598
1772
|
lastHiddenAt: new Date().toISOString(),
|
|
1599
1773
|
};
|
|
1600
1774
|
}
|
|
1601
|
-
|
|
1775
|
+
// One owner: the manager holds update state and the host snapshot mirrors
|
|
1776
|
+
// it. Only the last-check time crosses a restart, because every status is
|
|
1777
|
+
// provider-dependent and cannot be acted on without checking again.
|
|
1778
|
+
updateManager = await createUpdateManagerForLaunch(hostConfig, readPersistedUpdateLastCheckedAt(restoredState?.updates));
|
|
1779
|
+
updateState = updateManager.getSnapshot();
|
|
1780
|
+
detachUpdateBroadcast = createDesktopUpdateSnapshotBroadcast({
|
|
1781
|
+
manager: updateManager,
|
|
1782
|
+
resolveMainWindowWebContents: () => mainWindow?.webContents ?? null,
|
|
1783
|
+
});
|
|
1784
|
+
updateManager.subscribe((snapshot) => {
|
|
1785
|
+
updateState = snapshot;
|
|
1786
|
+
if (latestSnapshot) {
|
|
1787
|
+
publishSnapshot(latestSnapshot);
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1602
1790
|
packagingState = defaultPackaging;
|
|
1603
1791
|
setupState = normalizePlatformShellSetupState(restoredState?.setup ?? defaultSetup, Boolean(latestPersistedSetupState.setupCompleteAt
|
|
1604
1792
|
|| latestPersistedSetupState.productSetupCompleted));
|
|
@@ -1626,6 +1814,18 @@ async function main() {
|
|
|
1626
1814
|
ipcMain.handle('cats-host:get-snapshot', async () => {
|
|
1627
1815
|
return latestSnapshot ?? buildSnapshot(null);
|
|
1628
1816
|
});
|
|
1817
|
+
if (updateManager) {
|
|
1818
|
+
const updateHandlers = createDesktopUpdateIpcHandlers({
|
|
1819
|
+
manager: updateManager,
|
|
1820
|
+
resolveMainWindowWebContents: () => mainWindow?.webContents ?? null,
|
|
1821
|
+
logger: (message) => {
|
|
1822
|
+
process.stdout.write(`${message}\n`);
|
|
1823
|
+
},
|
|
1824
|
+
});
|
|
1825
|
+
for (const [channel, handler] of Object.entries(updateHandlers)) {
|
|
1826
|
+
ipcMain.handle(channel, async (event) => handler(event));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1629
1829
|
ipcMain.handle('cats-host:get-setup-snapshot', async () => {
|
|
1630
1830
|
return await getSetupSnapshot();
|
|
1631
1831
|
});
|
|
@@ -1663,6 +1863,17 @@ async function main() {
|
|
|
1663
1863
|
ipcMain.handle('cats-host:resume-setup', async () => {
|
|
1664
1864
|
return await resumeSetupAction();
|
|
1665
1865
|
});
|
|
1866
|
+
ipcMain.handle(DESKTOP_BROWSER_HANDOFF_OPEN_CHANNEL, async (event, launchPath) => {
|
|
1867
|
+
assertMainWindowIpcSender(event, mainWindow, 'Browser handoff is only available to the main Cats window.');
|
|
1868
|
+
if (!hostConfig || typeof launchPath !== 'string') {
|
|
1869
|
+
throw new Error('Desktop browser handoff is not initialized.');
|
|
1870
|
+
}
|
|
1871
|
+
const url = resolveDesktopBrowserHandoffUrl(launchPath, {
|
|
1872
|
+
appBaseUrl: hostConfig.appBaseUrl,
|
|
1873
|
+
allowedHosts: resolveAllowedAppHosts(hostConfig),
|
|
1874
|
+
});
|
|
1875
|
+
await openExternalDesktopUrl(url);
|
|
1876
|
+
});
|
|
1666
1877
|
ipcMain.handle(DESKTOP_SCREENSHOT_IPC_CHANNEL, async (event, payload) => {
|
|
1667
1878
|
assertMainWindowScreenshotIpcSender(event, mainWindow);
|
|
1668
1879
|
return await captureScreenshotRegion({
|
|
@@ -1808,8 +2019,10 @@ async function main() {
|
|
|
1808
2019
|
void showMainWindow();
|
|
1809
2020
|
});
|
|
1810
2021
|
await bootstrapDesktopHost(false);
|
|
1811
|
-
|
|
1812
|
-
|
|
2022
|
+
// SPEC-111 section 6: at most one silent check per launch, and only when the
|
|
2023
|
+
// build actually has update capability.
|
|
2024
|
+
if (hostConfig.update.checkOnStartup && updateManager?.getSnapshot().capability.canCheck) {
|
|
2025
|
+
void refreshUpdateState('startup');
|
|
1813
2026
|
}
|
|
1814
2027
|
}
|
|
1815
2028
|
void main().catch((error) => {
|