@fungi.computer/oyster 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1134 -2
- package/dist/agents.d.ts +114 -0
- package/dist/agents.js +322 -0
- package/dist/app-builds.d.ts +210 -0
- package/dist/app-builds.js +499 -0
- package/dist/app-sandbox.d.ts +2 -0
- package/dist/app-sandbox.js +2 -0
- package/dist/app-sources.d.ts +31 -0
- package/dist/app-sources.js +370 -0
- package/dist/app-storage.d.ts +141 -0
- package/dist/app-storage.js +281 -0
- package/dist/apps/gate.d.ts +105 -0
- package/dist/apps/gate.js +273 -0
- package/dist/apps/index.d.ts +5 -0
- package/dist/apps/index.js +5 -0
- package/dist/apps/installation.d.ts +248 -0
- package/dist/apps/installation.js +307 -0
- package/dist/apps/operations.d.ts +8 -0
- package/dist/apps/operations.js +98 -0
- package/dist/apps/tools.d.ts +33 -0
- package/dist/apps/tools.js +17 -0
- package/dist/auth.d.ts +11 -0
- package/dist/auth.js +93 -0
- package/dist/backend-request.d.ts +3 -0
- package/dist/backend-request.js +53 -0
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.d.ts +128 -0
- package/dist/build/build-jobs.js +203 -0
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +37 -0
- package/dist/build/build-step.js +28 -0
- package/dist/build/bundled-html.d.ts +7 -0
- package/dist/build/bundled-html.js +64 -0
- package/dist/build/candidate-builder.d.ts +68 -0
- package/dist/build/candidate-builder.js +91 -0
- package/dist/build/candidate-cli.d.ts +1 -0
- package/dist/build/candidate-cli.js +119 -0
- package/dist/build/candidate-cli.mjs +28170 -0
- package/dist/build/content-objects.d.ts +37 -0
- package/dist/build/content-objects.js +53 -0
- package/dist/build/definition-emission.d.ts +34 -0
- package/dist/build/definition-emission.js +201 -0
- package/dist/build/effect.d.ts +249 -0
- package/dist/build/effect.js +436 -0
- package/dist/build/git-environment.d.ts +9 -0
- package/dist/build/git-environment.js +60 -0
- package/dist/build/html-recipe.d.ts +20 -0
- package/dist/build/html-recipe.js +51 -0
- package/dist/build/local-build.d.ts +67 -0
- package/dist/build/local-build.js +1128 -0
- package/dist/build/snapshot-package-store.d.ts +12 -0
- package/dist/build/snapshot-package-store.js +36 -0
- package/dist/build/snapshot-program-scripts.d.ts +6 -0
- package/dist/build/snapshot-program-scripts.js +2 -0
- package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
- package/dist/build/snapshot-programs/snapshot-path.js +6 -0
- package/dist/build/submission-contract.d.ts +828 -0
- package/dist/build/submission-contract.js +180 -0
- package/dist/build/workspace-build.d.ts +55 -0
- package/dist/build/workspace-build.js +200 -0
- package/dist/build-contract.d.ts +7 -0
- package/dist/build-contract.js +7 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build-jobs.d.ts +2 -0
- package/dist/build-jobs.js +2 -0
- package/dist/build.d.ts +12 -0
- package/dist/build.js +14 -0
- package/dist/capability-descriptor.d.ts +46 -0
- package/dist/capability-descriptor.js +51 -0
- package/dist/catalog-frame.d.ts +39 -0
- package/dist/catalog-frame.js +72 -0
- package/dist/cause-class.d.ts +19 -0
- package/dist/cause-class.js +54 -0
- package/dist/cloudflare.d.ts +123 -0
- package/dist/cloudflare.js +391 -0
- package/dist/computer-terminal.d.ts +38 -0
- package/dist/computer-terminal.js +410 -0
- package/dist/computer.d.ts +60 -0
- package/dist/computer.js +33 -0
- package/dist/event-recipients.d.ts +57 -0
- package/dist/event-recipients.js +144 -0
- package/dist/facet-invocation.d.ts +4 -0
- package/dist/facet-invocation.js +45 -0
- package/dist/files.d.ts +42 -0
- package/dist/files.js +123 -0
- package/dist/frame-binding-wire-script.d.ts +2 -0
- package/dist/frame-binding-wire-script.js +1 -0
- package/dist/frame-binding-wire.d.ts +28 -0
- package/dist/frame-binding-wire.js +54 -0
- package/dist/git-admission.d.ts +52 -0
- package/dist/git-admission.js +188 -0
- package/dist/git-auth.d.ts +11 -0
- package/dist/git-auth.js +27 -0
- package/dist/git-door.d.ts +35 -0
- package/dist/git-door.js +108 -0
- package/dist/git-journal.d.ts +31 -0
- package/dist/git-journal.js +188 -0
- package/dist/git.d.ts +31 -0
- package/dist/git.js +64 -0
- package/dist/guest-policy.d.ts +9 -0
- package/dist/guest-policy.js +13 -0
- package/dist/host.d.ts +244 -0
- package/dist/host.js +1043 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +320 -0
- package/dist/installation-consent.d.ts +79 -0
- package/dist/installation-consent.js +82 -0
- package/dist/internal/background-origins.d.ts +3 -0
- package/dist/internal/background-origins.js +8 -0
- package/dist/internal/delivery.d.ts +12 -0
- package/dist/internal/delivery.js +167 -0
- package/dist/internal/host-gate.d.ts +14 -0
- package/dist/internal/host-gate.js +62 -0
- package/dist/internal/registry-installations.d.ts +6 -0
- package/dist/internal/registry-installations.js +12 -0
- package/dist/invocation.d.ts +251 -0
- package/dist/invocation.js +377 -0
- package/dist/keys.d.ts +48 -0
- package/dist/keys.js +52 -0
- package/dist/mcp-owner.d.ts +27 -0
- package/dist/mcp-owner.js +155 -0
- package/dist/mcp.d.ts +89 -0
- package/dist/mcp.js +429 -0
- package/dist/media.d.ts +23 -0
- package/dist/media.js +12 -0
- package/dist/pack.d.ts +79 -0
- package/dist/pack.js +151 -0
- package/dist/page/capabilities.d.ts +23 -0
- package/dist/page/capabilities.js +256 -0
- package/dist/page/computer-handles.d.ts +79 -0
- package/dist/page/computer-handles.js +44 -0
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/lifetime.d.ts +6 -0
- package/dist/page/lifetime.js +19 -0
- package/dist/page/mount.d.ts +163 -0
- package/dist/page/mount.js +390 -0
- package/dist/page/storage.d.ts +3 -0
- package/dist/page/storage.js +82 -0
- package/dist/page/terminal.d.ts +5 -0
- package/dist/page/terminal.js +7 -0
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.d.ts +14 -0
- package/dist/page-script.js +5 -0
- package/dist/r2-files.d.ts +3 -0
- package/dist/r2-files.js +79 -0
- package/dist/release.d.ts +83 -0
- package/dist/release.js +167 -0
- package/dist/root-data.d.ts +86 -0
- package/dist/root-data.js +69 -0
- package/dist/shell/app-directory.d.ts +542 -0
- package/dist/shell/app-directory.js +199 -0
- package/dist/shell/browser-actions.d.ts +7 -0
- package/dist/shell/browser-actions.js +56 -0
- package/dist/shell/command-host.d.ts +112 -0
- package/dist/shell/command-host.js +502 -0
- package/dist/shell/commands.d.ts +25 -0
- package/dist/shell/commands.js +78 -0
- package/dist/shell/index.d.ts +8 -0
- package/dist/shell/index.js +8 -0
- package/dist/shell/keys.d.ts +41 -0
- package/dist/shell/keys.js +134 -0
- package/dist/shell/shell-module.d.ts +55 -0
- package/dist/shell/shell-module.js +86 -0
- package/dist/shell/window-manager.d.ts +124 -0
- package/dist/shell/window-manager.js +711 -0
- package/dist/shell/workbench-contract.d.ts +68 -0
- package/dist/shell/workbench-contract.js +1 -0
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/shell/workbench-host-types.d.ts +96 -0
- package/dist/shell/workbench-host-types.js +1 -0
- package/dist/shell/workbench-host.d.ts +34 -0
- package/dist/shell/workbench-host.js +347 -0
- package/dist/shell/workbench-model.d.ts +192 -0
- package/dist/shell/workbench-model.js +53 -0
- package/dist/shell/workbench.d.ts +4 -0
- package/dist/shell/workbench.js +2 -0
- package/dist/source/fork.d.ts +27 -0
- package/dist/source/fork.js +46 -0
- package/dist/source/index.d.ts +10 -0
- package/dist/source/index.js +10 -0
- package/dist/source/seed-receipt.d.ts +53 -0
- package/dist/source/seed-receipt.js +18 -0
- package/dist/source/team-source.d.ts +32 -0
- package/dist/source/team-source.js +42 -0
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +4 -0
- package/dist/source/template-repository.js +21 -0
- package/dist/source/template-seed.d.ts +68 -0
- package/dist/source/template-seed.js +80 -0
- package/dist/standalone/agent-directory.d.ts +4 -0
- package/dist/standalone/agent-directory.js +4 -0
- package/dist/standalone/agents.d.ts +220 -0
- package/dist/standalone/agents.js +795 -0
- package/dist/standalone/app-builds-binding.d.ts +36 -0
- package/dist/standalone/app-builds-binding.js +119 -0
- package/dist/standalone/app-set.d.ts +91 -0
- package/dist/standalone/app-set.js +395 -0
- package/dist/standalone/app-source.d.ts +41 -0
- package/dist/standalone/app-source.js +59 -0
- package/dist/standalone/builds.d.ts +18 -0
- package/dist/standalone/builds.js +101 -0
- package/dist/standalone/computer-egress.d.ts +37 -0
- package/dist/standalone/computer-egress.js +111 -0
- package/dist/standalone/computer.d.ts +122 -0
- package/dist/standalone/computer.js +714 -0
- package/dist/standalone/install-apps-cli.d.ts +2 -0
- package/dist/standalone/install-apps-cli.js +126 -0
- package/dist/standalone/instance.d.ts +35 -0
- package/dist/standalone/instance.js +74 -0
- package/dist/standalone/media.d.ts +76 -0
- package/dist/standalone/media.js +227 -0
- package/dist/standalone/preview.d.ts +51 -0
- package/dist/standalone/preview.js +167 -0
- package/dist/standalone/registry.d.ts +102 -0
- package/dist/standalone/registry.js +193 -0
- package/dist/standalone/tenancy.d.ts +42 -0
- package/dist/standalone/tenancy.js +241 -0
- package/dist/standalone/worker.d.ts +55 -0
- package/dist/standalone/worker.js +147 -0
- package/dist/standalone/workflow-authority-readers.d.ts +16 -0
- package/dist/standalone/workflow-authority-readers.js +35 -0
- package/dist/standalone/workflow-binding.d.ts +216 -0
- package/dist/standalone/workflow-binding.js +217 -0
- package/dist/standalone/workflow-events.d.ts +46 -0
- package/dist/standalone/workflow-events.js +39 -0
- package/dist/standalone/workflow-git-capture.d.ts +7 -0
- package/dist/standalone/workflow-git-capture.js +62 -0
- package/dist/standalone/workflow-host.d.ts +931 -0
- package/dist/standalone/workflow-host.js +751 -0
- package/dist/standalone/workflow-node-capture.d.ts +13 -0
- package/dist/standalone/workflow-node-capture.js +72 -0
- package/dist/standalone/workflow-root-authority.d.ts +27 -0
- package/dist/standalone/workflow-root-authority.js +87 -0
- package/dist/standalone/workflow-schedules.d.ts +52 -0
- package/dist/standalone/workflow-schedules.js +40 -0
- package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
- package/dist/standalone/workflow-trigger-host.js +52 -0
- package/dist/standalone/workflows.d.ts +657 -0
- package/dist/standalone/workflows.js +554 -0
- package/dist/storage-frame.d.ts +64 -0
- package/dist/storage-frame.js +28 -0
- package/dist/tenancy/auth0.d.ts +64 -0
- package/dist/tenancy/auth0.js +198 -0
- package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
- package/dist/tenancy/cloudflare/rate-limit.js +19 -0
- package/dist/tenancy/cloudflare/registry.d.ts +50 -0
- package/dist/tenancy/cloudflare/registry.js +301 -0
- package/dist/tenancy/cloudflare.d.ts +2 -0
- package/dist/tenancy/cloudflare.js +2 -0
- package/dist/tenancy/cold-directory.d.ts +13 -0
- package/dist/tenancy/cold-directory.js +43 -0
- package/dist/tenancy/contracts.d.ts +195 -0
- package/dist/tenancy/contracts.js +1 -0
- package/dist/tenancy/index.d.ts +19 -0
- package/dist/tenancy/index.js +156 -0
- package/dist/tenancy/owner-key.d.ts +26 -0
- package/dist/tenancy/owner-key.js +63 -0
- package/dist/tenancy/oyster.d.ts +4 -0
- package/dist/tenancy/oyster.js +25 -0
- package/dist/tenancy/registry-record.d.ts +7 -0
- package/dist/tenancy/registry-record.js +44 -0
- package/dist/tenancy/registry.d.ts +27 -0
- package/dist/tenancy/registry.js +138 -0
- package/dist/tenancy/resolve.d.ts +7 -0
- package/dist/tenancy/resolve.js +64 -0
- package/dist/tenant.d.ts +81 -0
- package/dist/tenant.js +71 -0
- package/dist/testing/capabilities.d.ts +29 -0
- package/dist/testing/capabilities.js +130 -0
- package/dist/testing/catalog.d.ts +3 -0
- package/dist/testing/catalog.js +53 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/index.js +10 -0
- package/dist/testing/installation.d.ts +11 -0
- package/dist/testing/installation.js +25 -0
- package/dist/testing/native-worker.d.ts +21 -0
- package/dist/testing/native-worker.js +106 -0
- package/dist/testing/worker.d.ts +18 -0
- package/dist/testing/worker.js +67 -0
- package/dist/testing/workers-runtime.d.ts +24 -0
- package/dist/testing/workers-runtime.js +33 -0
- package/dist/webhook-delivery.d.ts +14 -0
- package/dist/webhook-delivery.js +17 -0
- package/dist/workers-git.d.ts +285 -0
- package/dist/workers-git.js +1174 -0
- package/dist/workers.d.ts +352 -0
- package/dist/workers.js +868 -0
- package/docs/api/README.md +82 -0
- package/docs/api/agents.md +332 -0
- package/docs/api/app-storage.md +209 -0
- package/docs/api/apps.md +1375 -0
- package/docs/api/auth.md +63 -0
- package/docs/api/build-contract.md +1304 -0
- package/docs/api/build.md +1712 -0
- package/docs/api/cloudflare.md +421 -0
- package/docs/api/computer.md +203 -0
- package/docs/api/files.md +117 -0
- package/docs/api/git.md +470 -0
- package/docs/api/host.md +1328 -0
- package/docs/api/index.md +430 -0
- package/docs/api/invocation.md +738 -0
- package/docs/api/keys.md +175 -0
- package/docs/api/mcp.md +301 -0
- package/docs/api/media.md +40 -0
- package/docs/api/modules.md +50 -0
- package/docs/api/pack.md +141 -0
- package/docs/api/page/mount.md +298 -0
- package/docs/api/root-data.md +111 -0
- package/docs/api/shell/app-directory.md +599 -0
- package/docs/api/shell/command-host.md +224 -0
- package/docs/api/shell/commands.md +197 -0
- package/docs/api/shell/keys.md +183 -0
- package/docs/api/shell/shell-module.md +204 -0
- package/docs/api/shell/window-manager.md +267 -0
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +205 -0
- package/docs/api/shell.md +1915 -0
- package/docs/api/tenancy/auth0.md +110 -0
- package/docs/api/tenancy/cloudflare.md +221 -0
- package/docs/api/tenancy/cold-directory.md +46 -0
- package/docs/api/tenancy/owner-key.md +63 -0
- package/docs/api/tenancy/registry.md +77 -0
- package/docs/api/tenancy/resolve.md +69 -0
- package/docs/api/tenancy.md +430 -0
- package/docs/api/tenant.md +194 -0
- package/docs/api/workers-git.md +1646 -0
- package/docs/api/workers.md +1691 -0
- package/docs/site/agent-quickstart.mdx +346 -0
- package/examples/standalone/worker.ts +7 -0
- package/examples/standalone/wrangler.jsonc +82 -0
- package/package.json +300 -3
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import * as Redacted from "effect/Redacted";
|
|
2
|
+
import { AppError, statusActionAllowed, } from "@fungi.computer/agar";
|
|
3
|
+
/**
|
|
4
|
+
* Shared window lifetime owner for browser and terminal shells.
|
|
5
|
+
*
|
|
6
|
+
* This module knows how a window is mounted and disposed. It deliberately does
|
|
7
|
+
* not know how an App is selected, admitted, or transported; those decisions
|
|
8
|
+
* are made by the owner issuing the launch descriptor.
|
|
9
|
+
*/
|
|
10
|
+
/** Mount failures may outlive their window; keep their cause safe and observable. */
|
|
11
|
+
function defaultFailure(windowId, generation, phase, cause) {
|
|
12
|
+
console.error(`App window ${phase} failed`, { windowId, generation, phase }, Redacted.make(cause));
|
|
13
|
+
}
|
|
14
|
+
function sameStatusItems(current, next) {
|
|
15
|
+
// Decoded status items are plain data, so their serialization is their value.
|
|
16
|
+
return JSON.stringify(current) === JSON.stringify(next);
|
|
17
|
+
}
|
|
18
|
+
function sameNavigation(current, next) {
|
|
19
|
+
if (current === null || next === null)
|
|
20
|
+
return current === next;
|
|
21
|
+
return current.canGoBack === next.canGoBack && current.title === next.title;
|
|
22
|
+
}
|
|
23
|
+
/** Whether any projected App window is still visible in the workspace. */
|
|
24
|
+
export function hasVisibleWindows(projection) {
|
|
25
|
+
return projection.windows.some((window) => window.status !== "minimized");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Owns App-window generations while leaving selection and App authority to the
|
|
29
|
+
* caller. A stale mount is always disposed when it eventually resolves.
|
|
30
|
+
*/
|
|
31
|
+
export function createWindowManager(options) {
|
|
32
|
+
const reportFailure = (windowId, generation, phase, cause) => {
|
|
33
|
+
if (options.onFailure !== undefined)
|
|
34
|
+
options.onFailure({ windowId, generation, phase, cause });
|
|
35
|
+
else
|
|
36
|
+
defaultFailure(windowId, generation, phase, cause);
|
|
37
|
+
};
|
|
38
|
+
const records = new Map();
|
|
39
|
+
const hiddenForDesktop = new Set();
|
|
40
|
+
const listeners = new Set();
|
|
41
|
+
const operations = new Map();
|
|
42
|
+
let managerDisposed = false;
|
|
43
|
+
let disposalInFlight;
|
|
44
|
+
const enqueueMutation = (windowId, task) => {
|
|
45
|
+
const generation = records.get(windowId)?.generation ?? null;
|
|
46
|
+
const previous = operations.get(windowId) ?? Promise.resolve();
|
|
47
|
+
const next = previous.then(task);
|
|
48
|
+
const settled = next.then(() => {
|
|
49
|
+
if (operations.get(windowId) === settled)
|
|
50
|
+
operations.delete(windowId);
|
|
51
|
+
}, (cause) => {
|
|
52
|
+
reportFailure(windowId, generation, "mutation", cause);
|
|
53
|
+
if (operations.get(windowId) === settled)
|
|
54
|
+
operations.delete(windowId);
|
|
55
|
+
});
|
|
56
|
+
operations.set(windowId, settled);
|
|
57
|
+
return next;
|
|
58
|
+
};
|
|
59
|
+
const project = () => ({
|
|
60
|
+
windows: Array.from(records.values(), (record) => {
|
|
61
|
+
const projected = {
|
|
62
|
+
windowId: record.launch.windowId,
|
|
63
|
+
launch: record.launch,
|
|
64
|
+
container: record.container,
|
|
65
|
+
status: record.status,
|
|
66
|
+
generation: record.generation,
|
|
67
|
+
// A loading, hidden or failed App offers no Back.
|
|
68
|
+
navigation: record.status === "ready" ? record.navigation : null,
|
|
69
|
+
statusItems: record.statusItems,
|
|
70
|
+
};
|
|
71
|
+
const withError = record.error === undefined
|
|
72
|
+
? projected
|
|
73
|
+
: { ...projected, error: record.error };
|
|
74
|
+
return record.cleanupError === undefined
|
|
75
|
+
? withError
|
|
76
|
+
: { ...withError, cleanupError: record.cleanupError };
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
let projection = project();
|
|
80
|
+
const notify = () => {
|
|
81
|
+
projection = project();
|
|
82
|
+
for (const listener of Array.from(listeners))
|
|
83
|
+
listener();
|
|
84
|
+
};
|
|
85
|
+
const read = () => projection;
|
|
86
|
+
const isCurrent = (record) => records.get(record.launch.windowId) === record;
|
|
87
|
+
const isActive = (record) => isCurrent(record) &&
|
|
88
|
+
!record.controller.signal.aborted &&
|
|
89
|
+
!record.retiring &&
|
|
90
|
+
!record.disposed;
|
|
91
|
+
const hasCloseRequest = (record) => record.closeRequested;
|
|
92
|
+
const isVisibilityCurrent = (record, version) => isCurrent(record) &&
|
|
93
|
+
!record.disposed &&
|
|
94
|
+
record.visibilityVersion === version;
|
|
95
|
+
const reconfigurationWasCancelled = (record, controller) => controller.signal.aborted || hasCloseRequest(record) || !isActive(record);
|
|
96
|
+
const retire = (record) => {
|
|
97
|
+
hiddenForDesktop.delete(record);
|
|
98
|
+
if (!record.retiring) {
|
|
99
|
+
record.retiring = true;
|
|
100
|
+
record.controller.abort();
|
|
101
|
+
}
|
|
102
|
+
for (const resolve of record.attachmentWaiters)
|
|
103
|
+
resolve(false);
|
|
104
|
+
record.attachmentWaiters.clear();
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Dispose the host currently owned by a record. The disposal promise is
|
|
108
|
+
* shared, so close, replacement, and setup failure cannot call a host's
|
|
109
|
+
* disposer twice. A host operation is allowed to settle before disposal;
|
|
110
|
+
* otherwise visibility and disposal would race on the same connection.
|
|
111
|
+
*/
|
|
112
|
+
const disposeRecordHost = async (record) => {
|
|
113
|
+
if (record.disposed)
|
|
114
|
+
return;
|
|
115
|
+
if (record.disposal) {
|
|
116
|
+
await record.disposal;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const resolve of record.attachmentWaiters)
|
|
120
|
+
resolve(false);
|
|
121
|
+
record.attachmentWaiters.clear();
|
|
122
|
+
const disposal = (async () => {
|
|
123
|
+
// A retiring record may still be awaiting attachment or its async mount.
|
|
124
|
+
// `mount` disposes any host that arrives after retirement; waiting here
|
|
125
|
+
// makes close/dispose mean that cleanup has actually completed.
|
|
126
|
+
if (!record.host && record.mountPromise)
|
|
127
|
+
await record.mountPromise;
|
|
128
|
+
const host = record.host;
|
|
129
|
+
if (!host) {
|
|
130
|
+
record.disposed = true;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const operation = record.hostOperation;
|
|
134
|
+
if (operation) {
|
|
135
|
+
try {
|
|
136
|
+
await operation;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
// The visibility owner records this failure as the primary error
|
|
140
|
+
// when it handles the operation. This branch covers close winning
|
|
141
|
+
// the race and therefore records it here as well.
|
|
142
|
+
if (record.error === undefined) {
|
|
143
|
+
record.status = "error";
|
|
144
|
+
record.error = error;
|
|
145
|
+
notify();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
await host.dispose();
|
|
150
|
+
if (record.host === host)
|
|
151
|
+
record.host = undefined;
|
|
152
|
+
record.disposed = true;
|
|
153
|
+
})();
|
|
154
|
+
record.disposal = disposal;
|
|
155
|
+
try {
|
|
156
|
+
await disposal;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
record.disposal = undefined;
|
|
160
|
+
record.cleanupError = error;
|
|
161
|
+
notify();
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const disposeLateHost = async (record, host) => {
|
|
166
|
+
try {
|
|
167
|
+
await host.dispose();
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
record.cleanupError = error;
|
|
171
|
+
if (isCurrent(record) && record.error === undefined) {
|
|
172
|
+
record.status = "error";
|
|
173
|
+
record.error = error;
|
|
174
|
+
notify();
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const setHostVisibility = async (record, host, visibility) => {
|
|
180
|
+
const operation = Promise.resolve().then(() => host.setVisibility(visibility));
|
|
181
|
+
record.hostOperation = operation;
|
|
182
|
+
try {
|
|
183
|
+
await operation;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
if (record.hostOperation === operation)
|
|
187
|
+
record.hostOperation = undefined;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const applyVisibility = async (record, version) => {
|
|
191
|
+
if (!isVisibilityCurrent(record, version) || record.retiring)
|
|
192
|
+
return;
|
|
193
|
+
const host = record.host;
|
|
194
|
+
if (!host)
|
|
195
|
+
return;
|
|
196
|
+
const requested = record.visibility;
|
|
197
|
+
try {
|
|
198
|
+
await setHostVisibility(record, host, requested);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (!isVisibilityCurrent(record, version))
|
|
202
|
+
return;
|
|
203
|
+
record.status = "error";
|
|
204
|
+
record.error = error;
|
|
205
|
+
notify();
|
|
206
|
+
try {
|
|
207
|
+
await disposeRecordHost(record);
|
|
208
|
+
}
|
|
209
|
+
catch (cleanupError) {
|
|
210
|
+
// Keep the visibility error in `error`; cleanupError makes the second
|
|
211
|
+
// failure observable without changing the first failure's identity.
|
|
212
|
+
record.cleanupError = cleanupError;
|
|
213
|
+
notify();
|
|
214
|
+
throw cleanupError;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const mount = async (record) => {
|
|
219
|
+
performance.mark("fungi.app-open.window-mount-start");
|
|
220
|
+
if (options.waitForAttachment && !options.isAttached?.(record.container)) {
|
|
221
|
+
performance.mark("fungi.app-open.window-wait-for-attachment");
|
|
222
|
+
const attached = await new Promise((resolve) => {
|
|
223
|
+
record.attachmentWaiters.add(resolve);
|
|
224
|
+
});
|
|
225
|
+
if (!attached)
|
|
226
|
+
return;
|
|
227
|
+
performance.mark("fungi.app-open.window-attached");
|
|
228
|
+
}
|
|
229
|
+
let host;
|
|
230
|
+
try {
|
|
231
|
+
performance.mark("fungi.app-open.window-host-mount-start");
|
|
232
|
+
host = await options.mount(record.container, record.launch, record.controller.signal, {
|
|
233
|
+
onComputerChoice: () => Promise.resolve().then(() => {
|
|
234
|
+
if (!isActive(record) ||
|
|
235
|
+
record.status !== "ready" ||
|
|
236
|
+
record.computerChoice === undefined)
|
|
237
|
+
throw new AppError("Computer choice is unavailable for this window", { code: "CAPABILITY_DENIED", retryable: false });
|
|
238
|
+
record.computerChoice();
|
|
239
|
+
}),
|
|
240
|
+
onNavigationState: (state) => {
|
|
241
|
+
// A retired generation never speaks for the window again.
|
|
242
|
+
if (!isActive(record) || sameNavigation(record.navigation, state))
|
|
243
|
+
return;
|
|
244
|
+
record.navigation = state;
|
|
245
|
+
notify();
|
|
246
|
+
},
|
|
247
|
+
onStatusItems: (items) => {
|
|
248
|
+
if (!isActive(record) || sameStatusItems(record.statusItems, items))
|
|
249
|
+
return;
|
|
250
|
+
record.statusItems = items;
|
|
251
|
+
notify();
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
performance.mark("fungi.app-open.window-host-mount-ready");
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (!isActive(record)) {
|
|
258
|
+
if (error !== record.controller.signal.reason)
|
|
259
|
+
reportFailure(record.launch.windowId, record.generation, "mount", error);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
record.status = record.visibility === "hidden" ? "minimized" : "error";
|
|
263
|
+
record.error = error;
|
|
264
|
+
notify();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!isActive(record)) {
|
|
268
|
+
await disposeLateHost(record, host);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
record.host = host;
|
|
272
|
+
if (record.visibility === "hidden") {
|
|
273
|
+
await applyVisibility(record, record.visibilityVersion);
|
|
274
|
+
}
|
|
275
|
+
if (!isActive(record)) {
|
|
276
|
+
await disposeRecordHost(record);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (record.status !== "error") {
|
|
280
|
+
record.status = record.visibility === "hidden" ? "minimized" : "ready";
|
|
281
|
+
record.error = undefined;
|
|
282
|
+
record.cleanupError = undefined;
|
|
283
|
+
notify();
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const startMount = (record) => {
|
|
287
|
+
const mounted = mount(record);
|
|
288
|
+
record.mountPromise = mounted;
|
|
289
|
+
// Callers such as React effect cleanup can intentionally ignore open's
|
|
290
|
+
// promise. Keep that ordinary use from creating an unhandled rejection;
|
|
291
|
+
// the original promise still reports failures to callers that await it.
|
|
292
|
+
void mounted.catch((cause) => {
|
|
293
|
+
reportFailure(record.launch.windowId, record.generation, "mount", cause);
|
|
294
|
+
return undefined;
|
|
295
|
+
});
|
|
296
|
+
return mounted;
|
|
297
|
+
};
|
|
298
|
+
const createRecord = (launch, generation) => ({
|
|
299
|
+
launch,
|
|
300
|
+
container: options.createContainer(launch),
|
|
301
|
+
generation,
|
|
302
|
+
status: "loading",
|
|
303
|
+
controller: new AbortController(),
|
|
304
|
+
host: undefined,
|
|
305
|
+
disposed: false,
|
|
306
|
+
retiring: false,
|
|
307
|
+
disposal: undefined,
|
|
308
|
+
mountPromise: undefined,
|
|
309
|
+
hostOperation: undefined,
|
|
310
|
+
reconfigureController: undefined,
|
|
311
|
+
closeRequested: false,
|
|
312
|
+
error: undefined,
|
|
313
|
+
cleanupError: undefined,
|
|
314
|
+
visibility: "visible",
|
|
315
|
+
attachmentWaiters: new Set(),
|
|
316
|
+
visibilityVersion: 0,
|
|
317
|
+
navigation: null,
|
|
318
|
+
statusItems: [],
|
|
319
|
+
computerChoice: undefined,
|
|
320
|
+
});
|
|
321
|
+
const open = (launch) => {
|
|
322
|
+
const existingAtCall = records.get(launch.windowId);
|
|
323
|
+
const created = !managerDisposed && !existingAtCall ? createRecord(launch, 1) : undefined;
|
|
324
|
+
let mounted;
|
|
325
|
+
if (created) {
|
|
326
|
+
records.set(launch.windowId, created);
|
|
327
|
+
notify();
|
|
328
|
+
mounted = startMount(created);
|
|
329
|
+
}
|
|
330
|
+
const operation = enqueueMutation(launch.windowId, async () => {
|
|
331
|
+
if (managerDisposed) {
|
|
332
|
+
if (created && records.get(launch.windowId) === created) {
|
|
333
|
+
retire(created);
|
|
334
|
+
await disposeRecordHost(created);
|
|
335
|
+
records.delete(launch.windowId);
|
|
336
|
+
notify();
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const existing = records.get(launch.windowId);
|
|
341
|
+
if (created) {
|
|
342
|
+
if (existing !== created)
|
|
343
|
+
return;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (existing) {
|
|
347
|
+
mounted = existing.mountPromise;
|
|
348
|
+
if (existing.visibility === "hidden" && !existing.retiring) {
|
|
349
|
+
const version = ++existing.visibilityVersion;
|
|
350
|
+
existing.visibility = "visible";
|
|
351
|
+
existing.status =
|
|
352
|
+
existing.error !== undefined
|
|
353
|
+
? "error"
|
|
354
|
+
: existing.host
|
|
355
|
+
? "ready"
|
|
356
|
+
: "loading";
|
|
357
|
+
notify();
|
|
358
|
+
if (existing.error === undefined)
|
|
359
|
+
await applyVisibility(existing, version);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
notify();
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const record = createRecord(launch, 1);
|
|
367
|
+
records.set(launch.windowId, record);
|
|
368
|
+
notify();
|
|
369
|
+
mounted = startMount(record);
|
|
370
|
+
});
|
|
371
|
+
return operation.then(async () => {
|
|
372
|
+
if (mounted)
|
|
373
|
+
await mounted;
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
const attach = (windowId) => {
|
|
377
|
+
if (managerDisposed)
|
|
378
|
+
return;
|
|
379
|
+
const record = records.get(windowId);
|
|
380
|
+
if (!record || record.disposed || record.retiring)
|
|
381
|
+
return;
|
|
382
|
+
for (const resolve of record.attachmentWaiters)
|
|
383
|
+
resolve(true);
|
|
384
|
+
record.attachmentWaiters.clear();
|
|
385
|
+
};
|
|
386
|
+
const requestVisibility = (windowId, visibility) => {
|
|
387
|
+
const record = records.get(windowId);
|
|
388
|
+
if (managerDisposed)
|
|
389
|
+
return Promise.resolve();
|
|
390
|
+
// A failed App still has a visible error window, even after its host was
|
|
391
|
+
// disposed. Hide that presentation without discarding the failure.
|
|
392
|
+
if (record && !record.retiring && record.error !== undefined) {
|
|
393
|
+
record.visibility = visibility;
|
|
394
|
+
record.status = visibility === "hidden" ? "minimized" : "error";
|
|
395
|
+
notify();
|
|
396
|
+
return Promise.resolve();
|
|
397
|
+
}
|
|
398
|
+
if (!record ||
|
|
399
|
+
record.disposed ||
|
|
400
|
+
record.retiring ||
|
|
401
|
+
record.status === "error") {
|
|
402
|
+
return enqueueMutation(windowId, async () => {
|
|
403
|
+
const next = records.get(windowId);
|
|
404
|
+
if (!next || next.disposed || next.retiring || next.status === "error")
|
|
405
|
+
return;
|
|
406
|
+
const nextVersion = ++next.visibilityVersion;
|
|
407
|
+
next.visibility = visibility;
|
|
408
|
+
next.status =
|
|
409
|
+
visibility === "hidden"
|
|
410
|
+
? "minimized"
|
|
411
|
+
: next.host
|
|
412
|
+
? "ready"
|
|
413
|
+
: "loading";
|
|
414
|
+
notify();
|
|
415
|
+
await applyVisibility(next, nextVersion);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const version = ++record.visibilityVersion;
|
|
419
|
+
record.visibility = visibility;
|
|
420
|
+
record.status =
|
|
421
|
+
visibility === "hidden" ? "minimized" : record.host ? "ready" : "loading";
|
|
422
|
+
notify();
|
|
423
|
+
return enqueueMutation(windowId, () => applyVisibility(record, version));
|
|
424
|
+
};
|
|
425
|
+
const minimize = (windowId) => requestVisibility(windowId, "hidden");
|
|
426
|
+
const restore = (windowId) => requestVisibility(windowId, "visible");
|
|
427
|
+
const hideAll = async () => {
|
|
428
|
+
if (managerDisposed)
|
|
429
|
+
return;
|
|
430
|
+
const visible = [...records.values()].filter((record) => !record.retiring && record.visibility === "visible");
|
|
431
|
+
for (const record of visible)
|
|
432
|
+
hiddenForDesktop.add(record);
|
|
433
|
+
await Promise.all(visible.map((record) => minimize(record.launch.windowId)));
|
|
434
|
+
};
|
|
435
|
+
const restoreHidden = async () => {
|
|
436
|
+
const hidden = [...hiddenForDesktop];
|
|
437
|
+
hiddenForDesktop.clear();
|
|
438
|
+
await Promise.all(hidden
|
|
439
|
+
.filter((record) => records.get(record.launch.windowId) === record)
|
|
440
|
+
.map((record) => restore(record.launch.windowId)));
|
|
441
|
+
};
|
|
442
|
+
const consultClose = async (record) => {
|
|
443
|
+
// Manager disposal is the host/process teardown path; it cannot wait for
|
|
444
|
+
// an interactive dirty-buffer decision.
|
|
445
|
+
if (managerDisposed)
|
|
446
|
+
return true;
|
|
447
|
+
const requestClose = record.host?.requestClose;
|
|
448
|
+
if (!requestClose)
|
|
449
|
+
return true;
|
|
450
|
+
try {
|
|
451
|
+
return await requestClose();
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
if (isCurrent(record)) {
|
|
455
|
+
record.status = "error";
|
|
456
|
+
record.error = error;
|
|
457
|
+
notify();
|
|
458
|
+
}
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
const close = (windowId) => {
|
|
463
|
+
const current = records.get(windowId);
|
|
464
|
+
const hadRecordAtCall = current !== undefined;
|
|
465
|
+
// Invalidate an in-flight reconfiguration immediately. The serialized
|
|
466
|
+
// close still performs the dirty-buffer consultation before disposal, but
|
|
467
|
+
// a late reconfiguration result must not become the committed launch.
|
|
468
|
+
current?.reconfigureController?.abort();
|
|
469
|
+
if (current)
|
|
470
|
+
current.closeRequested = true;
|
|
471
|
+
return enqueueMutation(windowId, async () => {
|
|
472
|
+
if (!hadRecordAtCall)
|
|
473
|
+
return;
|
|
474
|
+
const record = records.get(windowId);
|
|
475
|
+
if (!record)
|
|
476
|
+
return;
|
|
477
|
+
// Consultation belongs inside the serialized mutation. A replacement
|
|
478
|
+
// or second close must not race an App's dirty-buffer decision.
|
|
479
|
+
let mayClose;
|
|
480
|
+
try {
|
|
481
|
+
mayClose = await consultClose(record);
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
record.closeRequested = false;
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
if (!mayClose) {
|
|
488
|
+
record.closeRequested = false;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
retire(record);
|
|
492
|
+
try {
|
|
493
|
+
await disposeRecordHost(record);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
if (isCurrent(record)) {
|
|
497
|
+
record.status = "error";
|
|
498
|
+
// An earlier mount/visibility error remains the primary projection
|
|
499
|
+
// error; this cleanup failure is exposed beside it.
|
|
500
|
+
if (record.error === undefined)
|
|
501
|
+
record.error = error;
|
|
502
|
+
record.cleanupError = error;
|
|
503
|
+
notify();
|
|
504
|
+
}
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
if (!isCurrent(record))
|
|
508
|
+
return;
|
|
509
|
+
records.delete(windowId);
|
|
510
|
+
notify();
|
|
511
|
+
});
|
|
512
|
+
};
|
|
513
|
+
const update = (windowId, derive) => {
|
|
514
|
+
const operation = enqueueMutation(windowId, async () => {
|
|
515
|
+
if (managerDisposed)
|
|
516
|
+
return;
|
|
517
|
+
const record = records.get(windowId);
|
|
518
|
+
if (!record || record.disposed || record.retiring)
|
|
519
|
+
return;
|
|
520
|
+
if (hasCloseRequest(record))
|
|
521
|
+
return;
|
|
522
|
+
const next = { ...derive(record.launch), windowId };
|
|
523
|
+
options.validateReconfiguration?.(record.launch, next);
|
|
524
|
+
if (options.isChromeOnlyChange?.(record.launch, next) === true) {
|
|
525
|
+
record.launch = next;
|
|
526
|
+
notify();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const controller = new AbortController();
|
|
530
|
+
record.reconfigureController = controller;
|
|
531
|
+
try {
|
|
532
|
+
if (!record.host && record.mountPromise)
|
|
533
|
+
await record.mountPromise;
|
|
534
|
+
if (reconfigurationWasCancelled(record, controller))
|
|
535
|
+
return;
|
|
536
|
+
const host = record.host;
|
|
537
|
+
if (!host)
|
|
538
|
+
throw new Error("The App host is not ready for reconfiguration.");
|
|
539
|
+
const hostOperation = Promise.resolve().then(() => host.reconfigure(next, controller.signal));
|
|
540
|
+
record.hostOperation = hostOperation;
|
|
541
|
+
try {
|
|
542
|
+
await hostOperation;
|
|
543
|
+
}
|
|
544
|
+
finally {
|
|
545
|
+
if (record.hostOperation === hostOperation)
|
|
546
|
+
record.hostOperation = undefined;
|
|
547
|
+
}
|
|
548
|
+
if (reconfigurationWasCancelled(record, controller))
|
|
549
|
+
return;
|
|
550
|
+
// The old descriptor remains authoritative until the mounted host has
|
|
551
|
+
// accepted its new configuration. Reconfiguration keeps the same
|
|
552
|
+
// generation and container, so only the descriptor is committed here.
|
|
553
|
+
record.launch = next;
|
|
554
|
+
record.status = record.visibility === "hidden" ? "minimized" : "ready";
|
|
555
|
+
record.error = undefined;
|
|
556
|
+
record.cleanupError = undefined;
|
|
557
|
+
notify();
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
if (reconfigurationWasCancelled(record, controller))
|
|
561
|
+
return;
|
|
562
|
+
// Keep the existing host and descriptor mounted so the caller can
|
|
563
|
+
// report the failure and retry without creating a second iframe. The
|
|
564
|
+
// rejected operation is the failure surface; changing the projection
|
|
565
|
+
// to "error" would incorrectly hide that the old App is still usable.
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
finally {
|
|
569
|
+
if (record.reconfigureController === controller)
|
|
570
|
+
record.reconfigureController = undefined;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
return operation;
|
|
574
|
+
};
|
|
575
|
+
const reconfigure = (windowId, launch) => update(windowId, () => launch);
|
|
576
|
+
const replace = (windowId, launch) => {
|
|
577
|
+
let mounted;
|
|
578
|
+
const operation = enqueueMutation(windowId, async () => {
|
|
579
|
+
if (managerDisposed)
|
|
580
|
+
return;
|
|
581
|
+
const previous = records.get(windowId);
|
|
582
|
+
let restoreWithDesktop = false;
|
|
583
|
+
if (previous) {
|
|
584
|
+
if (!(await consultClose(previous)))
|
|
585
|
+
return;
|
|
586
|
+
restoreWithDesktop = hiddenForDesktop.has(previous);
|
|
587
|
+
retire(previous);
|
|
588
|
+
try {
|
|
589
|
+
await disposeRecordHost(previous);
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
if (isCurrent(previous)) {
|
|
593
|
+
previous.status = "error";
|
|
594
|
+
if (previous.error === undefined)
|
|
595
|
+
previous.error = error;
|
|
596
|
+
previous.cleanupError = error;
|
|
597
|
+
notify();
|
|
598
|
+
}
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const descriptor = { ...launch, windowId };
|
|
603
|
+
const record = createRecord(descriptor, previous ? previous.generation + 1 : 1);
|
|
604
|
+
if (previous?.visibility === "hidden") {
|
|
605
|
+
record.visibility = "hidden";
|
|
606
|
+
record.status = "minimized";
|
|
607
|
+
}
|
|
608
|
+
if (restoreWithDesktop)
|
|
609
|
+
hiddenForDesktop.add(record);
|
|
610
|
+
records.set(windowId, record);
|
|
611
|
+
notify();
|
|
612
|
+
mounted = startMount(record);
|
|
613
|
+
});
|
|
614
|
+
return operation.then(async () => {
|
|
615
|
+
if (mounted)
|
|
616
|
+
await mounted;
|
|
617
|
+
});
|
|
618
|
+
};
|
|
619
|
+
const back = async (windowId) => {
|
|
620
|
+
const record = records.get(windowId);
|
|
621
|
+
if (!record ||
|
|
622
|
+
!isActive(record) ||
|
|
623
|
+
record.status !== "ready" ||
|
|
624
|
+
record.navigation?.canGoBack !== true)
|
|
625
|
+
return false;
|
|
626
|
+
const requestBack = record.host?.requestBack;
|
|
627
|
+
return requestBack ? await requestBack() : false;
|
|
628
|
+
};
|
|
629
|
+
const activateStatus = async (windowId, action) => {
|
|
630
|
+
const record = records.get(windowId);
|
|
631
|
+
if (!record || !isActive(record) || record.status !== "ready")
|
|
632
|
+
return false;
|
|
633
|
+
// Only an enabled item (and option) the App currently declares.
|
|
634
|
+
if (!statusActionAllowed(record.statusItems, action))
|
|
635
|
+
return false;
|
|
636
|
+
const deliver = record.host?.activateStatus;
|
|
637
|
+
return deliver ? await deliver(action) : false;
|
|
638
|
+
};
|
|
639
|
+
const registerComputerChoice = (windowId, handler) => {
|
|
640
|
+
const record = records.get(windowId);
|
|
641
|
+
if (!record || !isActive(record))
|
|
642
|
+
throw new AppError("App window is unavailable", {
|
|
643
|
+
code: "DISCONNECTED",
|
|
644
|
+
retryable: false,
|
|
645
|
+
});
|
|
646
|
+
record.computerChoice = handler;
|
|
647
|
+
return () => {
|
|
648
|
+
if (record.computerChoice === handler)
|
|
649
|
+
record.computerChoice = undefined;
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
const disposeAll = async () => {
|
|
653
|
+
managerDisposed = true;
|
|
654
|
+
for (const record of records.values()) {
|
|
655
|
+
record.closeRequested = true;
|
|
656
|
+
record.reconfigureController?.abort();
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
// Finish already-enqueued setup/state work before taking the record
|
|
660
|
+
// snapshot. New public work is rejected by managerDisposed meanwhile.
|
|
661
|
+
await Promise.allSettled(operations.values());
|
|
662
|
+
const failures = [];
|
|
663
|
+
const results = await Promise.allSettled([...records.keys()].map((windowId) => close(windowId)));
|
|
664
|
+
for (const result of results) {
|
|
665
|
+
if (result.status === "rejected")
|
|
666
|
+
failures.push(result.reason);
|
|
667
|
+
}
|
|
668
|
+
if (failures.length > 0) {
|
|
669
|
+
throw new AggregateError(failures, "Failed to dispose App windows");
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
finally {
|
|
673
|
+
listeners.clear();
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
const dispose = () => {
|
|
677
|
+
if (disposalInFlight)
|
|
678
|
+
return disposalInFlight;
|
|
679
|
+
const pending = disposeAll();
|
|
680
|
+
disposalInFlight = pending;
|
|
681
|
+
void pending.then(() => {
|
|
682
|
+
if (disposalInFlight === pending)
|
|
683
|
+
disposalInFlight = undefined;
|
|
684
|
+
}, () => {
|
|
685
|
+
if (disposalInFlight === pending)
|
|
686
|
+
disposalInFlight = undefined;
|
|
687
|
+
});
|
|
688
|
+
return pending;
|
|
689
|
+
};
|
|
690
|
+
return {
|
|
691
|
+
open,
|
|
692
|
+
attach,
|
|
693
|
+
minimize,
|
|
694
|
+
restore,
|
|
695
|
+
hideAll,
|
|
696
|
+
restoreHidden,
|
|
697
|
+
close,
|
|
698
|
+
reconfigure,
|
|
699
|
+
update,
|
|
700
|
+
activateStatus,
|
|
701
|
+
registerComputerChoice,
|
|
702
|
+
replace,
|
|
703
|
+
back,
|
|
704
|
+
dispose,
|
|
705
|
+
read,
|
|
706
|
+
subscribe: (listener) => {
|
|
707
|
+
listeners.add(listener);
|
|
708
|
+
return () => listeners.delete(listener);
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
}
|