@astrale-os/shell 0.1.1 → 0.3.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 +202 -0
- package/dist/application/apps.d.ts +18 -0
- package/dist/application/apps.d.ts.map +1 -0
- package/dist/application/apps.js +35 -0
- package/dist/application/namespaces.d.ts +21 -0
- package/dist/application/namespaces.d.ts.map +1 -0
- package/dist/application/namespaces.js +12 -0
- package/dist/application/session/open.d.ts +19 -0
- package/dist/application/session/open.d.ts.map +1 -0
- package/dist/application/session/open.js +46 -0
- package/dist/application/session/session.d.ts +41 -0
- package/dist/application/session/session.d.ts.map +1 -0
- package/dist/application/session/session.js +9 -0
- package/dist/application/session/token.d.ts +24 -0
- package/dist/application/session/token.d.ts.map +1 -0
- package/dist/application/session/token.js +51 -0
- package/dist/application/spaces.d.ts +17 -0
- package/dist/application/spaces.d.ts.map +1 -0
- package/dist/application/spaces.js +30 -0
- package/dist/application/user.d.ts +20 -0
- package/dist/application/user.d.ts.map +1 -0
- package/dist/application/user.js +26 -0
- package/dist/application/view.d.ts +20 -0
- package/dist/application/view.d.ts.map +1 -0
- package/dist/application/view.js +18 -0
- package/dist/{application/channels/children-channel.d.ts → channel/children.d.ts} +4 -4
- package/dist/channel/children.d.ts.map +1 -0
- package/dist/channel/listeners.d.ts +8 -0
- package/dist/channel/listeners.d.ts.map +1 -0
- package/dist/{adapters/message-port.transport.d.ts → channel/message-port.d.ts} +2 -2
- package/dist/channel/message-port.d.ts.map +1 -0
- package/dist/{application/channels → channel}/observer.d.ts +2 -1
- package/dist/channel/observer.d.ts.map +1 -0
- package/dist/{application/channels/parent-channel.d.ts → channel/parent.d.ts} +4 -4
- package/dist/channel/parent.d.ts.map +1 -0
- package/dist/{application/channels/parent-channel.js → channel/parent.js} +1 -1
- package/dist/channel/transport.d.ts +8 -0
- package/dist/channel/transport.d.ts.map +1 -0
- package/dist/index.d.ts +56 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -20
- package/dist/{domain/intent → intent}/middleware/capabilities.d.ts +2 -2
- package/dist/intent/middleware/capabilities.d.ts.map +1 -0
- package/dist/intent/middleware/logging.d.ts.map +1 -0
- package/dist/{domain/intent → intent}/middleware/rate-limit.d.ts +1 -1
- package/dist/intent/middleware/rate-limit.d.ts.map +1 -0
- package/dist/{application/intent/pipeline-runner.d.ts → intent/pipeline.d.ts} +3 -3
- package/dist/intent/pipeline.d.ts.map +1 -0
- package/dist/intent/policy.d.ts +40 -0
- package/dist/intent/policy.d.ts.map +1 -0
- package/dist/intent/policy.js +15 -0
- package/dist/{application/intent → intent}/request.d.ts +5 -14
- package/dist/intent/request.d.ts.map +1 -0
- package/dist/{application/intent → intent}/request.js +6 -20
- package/dist/intent/router.d.ts +31 -0
- package/dist/intent/router.d.ts.map +1 -0
- package/dist/{application/intent → intent}/router.js +1 -15
- package/dist/kernel/client.d.ts +33 -0
- package/dist/kernel/client.d.ts.map +1 -0
- package/dist/kernel/credential.d.ts +35 -0
- package/dist/kernel/credential.d.ts.map +1 -0
- package/dist/kernel/credential.js +43 -0
- package/dist/kernel/delegated.d.ts +17 -0
- package/dist/kernel/delegated.d.ts.map +1 -0
- package/dist/kernel/delegated.js +29 -0
- package/dist/{application/windowing/token-refresh.d.ts → kernel/delegation/refresh.d.ts} +2 -2
- package/dist/kernel/delegation/refresh.d.ts.map +1 -0
- package/dist/{application/windowing/token-refresh.js → kernel/delegation/refresh.js} +1 -1
- package/dist/{domain/identity/delegation.d.ts → kernel/delegation/token.d.ts} +1 -1
- package/dist/kernel/delegation/token.d.ts.map +1 -0
- package/dist/{adapters/kernel-client.adapter.d.ts → kernel/session.d.ts} +15 -12
- package/dist/kernel/session.d.ts.map +1 -0
- package/dist/{adapters/kernel-client.adapter.js → kernel/session.js} +10 -15
- package/dist/message/ctrl.d.ts +34 -0
- package/dist/message/ctrl.d.ts.map +1 -0
- package/dist/message/error.d.ts +9 -0
- package/dist/message/error.d.ts.map +1 -0
- package/dist/{application/windowing/protocol.d.ts → message/init.d.ts} +1 -1
- package/dist/message/init.d.ts.map +1 -0
- package/dist/{domain/message/registries.d.ts → message/intent.d.ts} +45 -37
- package/dist/message/intent.d.ts.map +1 -0
- package/dist/message/message.d.ts +10 -0
- package/dist/message/message.d.ts.map +1 -0
- package/dist/shell/access.d.ts +10 -0
- package/dist/shell/access.d.ts.map +1 -0
- package/dist/shell/access.js +19 -0
- package/dist/shell/build.d.ts +28 -0
- package/dist/shell/build.d.ts.map +1 -0
- package/dist/shell/build.js +58 -0
- package/dist/shell/config.d.ts +29 -0
- package/dist/shell/config.d.ts.map +1 -0
- package/dist/shell/create.d.ts +4 -0
- package/dist/shell/create.d.ts.map +1 -0
- package/dist/shell/create.js +5 -0
- package/dist/{application/windowing/child-init.d.ts → shell/sandboxed/boot.d.ts} +5 -5
- package/dist/shell/sandboxed/boot.d.ts.map +1 -0
- package/dist/{application/windowing/child-init.js → shell/sandboxed/boot.js} +5 -6
- package/dist/shell/sandboxed/guarded.d.ts +13 -0
- package/dist/shell/sandboxed/guarded.d.ts.map +1 -0
- package/dist/shell/sandboxed/guarded.js +28 -0
- package/dist/shell/sandboxed/pending.d.ts +14 -0
- package/dist/shell/sandboxed/pending.d.ts.map +1 -0
- package/dist/shell/sandboxed/pending.js +30 -0
- package/dist/shell/sandboxed/sandboxed.d.ts +4 -0
- package/dist/shell/sandboxed/sandboxed.d.ts.map +1 -0
- package/dist/shell/sandboxed/sandboxed.js +100 -0
- package/dist/shell/shell.d.ts +36 -0
- package/dist/shell/shell.d.ts.map +1 -0
- package/dist/shell/standalone.d.ts +4 -0
- package/dist/shell/standalone.d.ts.map +1 -0
- package/dist/shell/standalone.js +85 -0
- package/dist/{domain/window/config.d.ts → window/capabilities.d.ts} +3 -3
- package/dist/window/capabilities.d.ts.map +1 -0
- package/dist/{ports/shell-adapter.d.ts → window/embed/adapter.d.ts} +3 -2
- package/dist/window/embed/adapter.d.ts.map +1 -0
- package/dist/{adapters/iframe.shell-adapter.d.ts → window/embed/iframe.d.ts} +2 -2
- package/dist/window/embed/iframe.d.ts.map +1 -0
- package/dist/{application/windowing/lifecycle-controller.d.ts → window/lifecycle/states.d.ts} +4 -3
- package/dist/window/lifecycle/states.d.ts.map +1 -0
- package/dist/{application/windowing/lifecycle-controller.js → window/lifecycle/states.js} +1 -1
- package/dist/{domain/window/lifecycle.d.ts → window/lifecycle/transition.d.ts} +3 -3
- package/dist/window/lifecycle/transition.d.ts.map +1 -0
- package/dist/{domain/window/lifecycle.js → window/lifecycle/transition.js} +1 -1
- package/dist/window/mount/close.d.ts +27 -0
- package/dist/window/mount/close.d.ts.map +1 -0
- package/dist/window/mount/close.js +74 -0
- package/dist/window/mount/credential.d.ts +41 -0
- package/dist/window/mount/credential.d.ts.map +1 -0
- package/dist/window/mount/credential.js +74 -0
- package/dist/window/mount/handshake.d.ts.map +1 -0
- package/dist/{application/windowing → window/mount}/handshake.js +1 -1
- package/dist/{application/windowing/mount-controller.d.ts → window/mount/mount.d.ts} +7 -7
- package/dist/window/mount/mount.d.ts.map +1 -0
- package/dist/window/mount/mount.js +143 -0
- package/dist/window/mount/params.d.ts +73 -0
- package/dist/window/mount/params.d.ts.map +1 -0
- package/dist/window/mount/params.js +1 -0
- package/dist/window/mount/plain.d.ts +20 -0
- package/dist/window/mount/plain.d.ts.map +1 -0
- package/dist/window/mount/plain.js +46 -0
- package/dist/{domain/window → window}/window.d.ts +1 -1
- package/dist/window/window.d.ts.map +1 -0
- package/dist/window/window.js +1 -0
- package/package.json +15 -7
- package/src/application/apps.ts +60 -0
- package/src/application/namespaces.ts +38 -0
- package/src/application/session/open.ts +73 -0
- package/src/application/session/session.ts +46 -0
- package/src/application/session/token.ts +87 -0
- package/src/application/spaces.ts +52 -0
- package/src/application/user.ts +54 -0
- package/src/application/view.ts +42 -0
- package/src/{application/channels/children-channel.ts → channel/children.ts} +3 -3
- package/src/{application/channels/types.ts → channel/listeners.ts} +2 -7
- package/src/{adapters/message-port.transport.ts → channel/message-port.ts} +2 -2
- package/src/{application/channels → channel}/observer.ts +2 -1
- package/src/{application/channels/parent-channel.ts → channel/parent.ts} +4 -4
- package/src/{ports → channel}/transport.ts +2 -1
- package/src/index.ts +80 -88
- package/src/{domain/intent → intent}/middleware/capabilities.ts +2 -3
- package/src/{domain/intent → intent}/middleware/logging.ts +1 -1
- package/src/{domain/intent → intent}/middleware/rate-limit.ts +1 -1
- package/src/{application/intent/pipeline-runner.ts → intent/pipeline.ts} +3 -2
- package/src/intent/policy.ts +46 -0
- package/src/{application/intent → intent}/request.ts +14 -34
- package/src/{application/intent → intent}/router.ts +12 -49
- package/src/kernel/client.ts +33 -0
- package/src/kernel/credential.ts +78 -0
- package/src/kernel/delegated.ts +49 -0
- package/src/{application/windowing/token-refresh.ts → kernel/delegation/refresh.ts} +2 -2
- package/src/{adapters/kernel-client.adapter.ts → kernel/session.ts} +26 -33
- package/src/message/ctrl.ts +25 -0
- package/src/{domain/message/errors.ts → message/error.ts} +8 -0
- package/src/message/intent.ts +98 -0
- package/src/message/message.ts +11 -0
- package/src/shell/access.ts +26 -0
- package/src/shell/build.ts +95 -0
- package/src/shell/config.ts +32 -0
- package/src/shell/create.ts +9 -0
- package/src/{application/windowing/child-init.ts → shell/sandboxed/boot.ts} +10 -11
- package/src/shell/sandboxed/guarded.ts +32 -0
- package/src/shell/sandboxed/pending.ts +52 -0
- package/src/shell/sandboxed/sandboxed.ts +131 -0
- package/src/shell/shell.ts +37 -0
- package/src/shell/standalone.ts +97 -0
- package/src/{domain/window/config.ts → window/capabilities.ts} +2 -2
- package/src/{ports/shell-adapter.ts → window/embed/adapter.ts} +2 -1
- package/src/{adapters/iframe.shell-adapter.ts → window/embed/iframe.ts} +2 -2
- package/src/{application/windowing/lifecycle-controller.ts → window/lifecycle/states.ts} +3 -2
- package/src/{domain/window/lifecycle.ts → window/lifecycle/transition.ts} +2 -2
- package/src/window/mount/close.ts +111 -0
- package/src/window/mount/credential.ts +101 -0
- package/src/{application/windowing → window/mount}/handshake.ts +3 -2
- package/src/window/mount/mount.ts +185 -0
- package/src/window/mount/params.ts +64 -0
- package/src/window/mount/plain.ts +68 -0
- package/src/{domain/window → window}/window.ts +1 -1
- package/dist/adapters/iframe.shell-adapter.d.ts.map +0 -1
- package/dist/adapters/kernel-client.adapter.d.ts.map +0 -1
- package/dist/adapters/message-port.transport.d.ts.map +0 -1
- package/dist/application/channels/children-channel.d.ts.map +0 -1
- package/dist/application/channels/observer.d.ts.map +0 -1
- package/dist/application/channels/parent-channel.d.ts.map +0 -1
- package/dist/application/channels/types.d.ts +0 -12
- package/dist/application/channels/types.d.ts.map +0 -1
- package/dist/application/intent/pipeline-runner.d.ts.map +0 -1
- package/dist/application/intent/request.d.ts.map +0 -1
- package/dist/application/intent/router.d.ts +0 -56
- package/dist/application/intent/router.d.ts.map +0 -1
- package/dist/application/kernel-facade.d.ts +0 -33
- package/dist/application/kernel-facade.d.ts.map +0 -1
- package/dist/application/kernel-facade.js +0 -77
- package/dist/application/shell.d.ts +0 -100
- package/dist/application/shell.d.ts.map +0 -1
- package/dist/application/shell.js +0 -256
- package/dist/application/windowing/child-init.d.ts.map +0 -1
- package/dist/application/windowing/handshake.d.ts.map +0 -1
- package/dist/application/windowing/lifecycle-controller.d.ts.map +0 -1
- package/dist/application/windowing/mount-controller.d.ts.map +0 -1
- package/dist/application/windowing/mount-controller.js +0 -277
- package/dist/application/windowing/protocol.d.ts.map +0 -1
- package/dist/application/windowing/token-refresh.d.ts.map +0 -1
- package/dist/domain/identity/delegation.d.ts.map +0 -1
- package/dist/domain/identity/path.d.ts +0 -11
- package/dist/domain/identity/path.d.ts.map +0 -1
- package/dist/domain/identity/path.js +0 -13
- package/dist/domain/identity/resolve-self.d.ts +0 -21
- package/dist/domain/identity/resolve-self.d.ts.map +0 -1
- package/dist/domain/identity/resolve-self.js +0 -36
- package/dist/domain/intent/middleware/capabilities.d.ts.map +0 -1
- package/dist/domain/intent/middleware/logging.d.ts.map +0 -1
- package/dist/domain/intent/middleware/rate-limit.d.ts.map +0 -1
- package/dist/domain/intent/pipeline.d.ts +0 -3
- package/dist/domain/intent/pipeline.d.ts.map +0 -1
- package/dist/domain/intent/router.d.ts +0 -6
- package/dist/domain/intent/router.d.ts.map +0 -1
- package/dist/domain/message/errors.d.ts +0 -2
- package/dist/domain/message/errors.d.ts.map +0 -1
- package/dist/domain/message/registries.d.ts.map +0 -1
- package/dist/domain/message/types.d.ts +0 -30
- package/dist/domain/message/types.d.ts.map +0 -1
- package/dist/domain/window/config.d.ts.map +0 -1
- package/dist/domain/window/lifecycle.d.ts.map +0 -1
- package/dist/domain/window/window.d.ts.map +0 -1
- package/dist/ports/kernel-client.d.ts +0 -33
- package/dist/ports/kernel-client.d.ts.map +0 -1
- package/dist/ports/shell-adapter.d.ts.map +0 -1
- package/dist/ports/transport.d.ts +0 -7
- package/dist/ports/transport.d.ts.map +0 -1
- package/src/application/kernel-facade.ts +0 -143
- package/src/application/shell.ts +0 -433
- package/src/application/windowing/mount-controller.ts +0 -335
- package/src/domain/identity/path.ts +0 -14
- package/src/domain/identity/resolve-self.ts +0 -38
- package/src/domain/intent/pipeline.ts +0 -3
- package/src/domain/intent/router.ts +0 -6
- package/src/domain/message/registries.ts +0 -76
- package/src/domain/message/types.ts +0 -32
- package/src/ports/kernel-client.ts +0 -32
- /package/dist/{application/channels/children-channel.js → channel/children.js} +0 -0
- /package/dist/{application/channels/types.js → channel/listeners.js} +0 -0
- /package/dist/{adapters/message-port.transport.js → channel/message-port.js} +0 -0
- /package/dist/{application/channels → channel}/observer.js +0 -0
- /package/dist/{ports → channel}/transport.js +0 -0
- /package/dist/{domain/intent → intent}/middleware/capabilities.js +0 -0
- /package/dist/{domain/intent → intent}/middleware/logging.d.ts +0 -0
- /package/dist/{domain/intent → intent}/middleware/logging.js +0 -0
- /package/dist/{domain/intent → intent}/middleware/rate-limit.js +0 -0
- /package/dist/{application/intent/pipeline-runner.js → intent/pipeline.js} +0 -0
- /package/dist/{domain/intent/pipeline.js → kernel/client.js} +0 -0
- /package/dist/{domain/identity/delegation.js → kernel/delegation/token.js} +0 -0
- /package/dist/{domain/intent/router.js → message/ctrl.js} +0 -0
- /package/dist/{domain/message/errors.js → message/error.js} +0 -0
- /package/dist/{application/windowing/protocol.js → message/init.js} +0 -0
- /package/dist/{domain/message/registries.js → message/intent.js} +0 -0
- /package/dist/{domain/message/types.js → message/message.js} +0 -0
- /package/dist/{domain/window → shell}/config.js +0 -0
- /package/dist/{domain/window/window.js → shell/shell.js} +0 -0
- /package/dist/{ports/kernel-client.js → window/capabilities.js} +0 -0
- /package/dist/{ports/shell-adapter.js → window/embed/adapter.js} +0 -0
- /package/dist/{adapters/iframe.shell-adapter.js → window/embed/iframe.js} +0 -0
- /package/dist/{application/windowing → window/mount}/handshake.d.ts +0 -0
- /package/src/{domain/identity/delegation.ts → kernel/delegation/token.ts} +0 -0
- /package/src/{application/windowing/protocol.ts → message/init.ts} +0 -0
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
type FnMap,
|
|
5
5
|
type FunctionSchema,
|
|
6
6
|
} from '@astrale-os/kernel-client'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import {
|
|
8
|
+
type BoundClientSessionView,
|
|
9
|
+
ClientSession,
|
|
10
|
+
type ClientSessionConfig,
|
|
11
|
+
delegationMintVia,
|
|
12
|
+
} from '@astrale-os/kernel-client/session'
|
|
11
13
|
|
|
12
14
|
// Untyped FnMap — the shell uses string paths, not typed method keys.
|
|
13
15
|
type AnyFnMap = FnMap
|
|
@@ -16,8 +18,8 @@ type AnyFnMap = FnMap
|
|
|
16
18
|
* Low-level kernel adapter — wraps a `ClientSession` (the post-v2 orchestration
|
|
17
19
|
* layer above `KernelClient`).
|
|
18
20
|
*
|
|
19
|
-
* Methods take an explicit credential. The
|
|
20
|
-
*
|
|
21
|
+
* Methods take an explicit credential. The credential facades manage which
|
|
22
|
+
* credential to use.
|
|
21
23
|
*
|
|
22
24
|
* Remote-domain redirects are followed transparently: when the kernel
|
|
23
25
|
* responds with a `redirect` result (pointing at an external worker URL),
|
|
@@ -34,6 +36,14 @@ export type KernelClientAdapter = {
|
|
|
34
36
|
credential: CredentialInput,
|
|
35
37
|
opts?: CallOptions,
|
|
36
38
|
): Promise<unknown>
|
|
39
|
+
/**
|
|
40
|
+
* Credential-bound view over the underlying `ClientSession` — the flattened
|
|
41
|
+
* `BoundClientSessionView` surface (`.get/.node/.children/.query/.mutate/
|
|
42
|
+
* .createNode/…` graph sugar beside `.call/.stream/.as/…`). `getCredential`
|
|
43
|
+
* is a thunk invoked per dispatch, so the view tracks the active identity the
|
|
44
|
+
* same way the per-call `credential` injection did.
|
|
45
|
+
*/
|
|
46
|
+
boundView(getCredential: () => CredentialInput): BoundClientSessionView<AnyFnMap>
|
|
37
47
|
load(schemas: FunctionSchema[]): void
|
|
38
48
|
disconnect(): void
|
|
39
49
|
readonly url: string
|
|
@@ -52,14 +62,6 @@ export type CreateKernelClientAdapterOpts = {
|
|
|
52
62
|
* with `Credential audience mismatch`.
|
|
53
63
|
*/
|
|
54
64
|
getCredential?: () => CredentialInput
|
|
55
|
-
/**
|
|
56
|
-
* Explicit identity node/path to use as the delegation mint subject. When
|
|
57
|
-
* omitted, the authenticated caller is resolved via `whoami` (so a cookie-fronted
|
|
58
|
-
* SPA mints as the logged-in user). Set this only to pin a subject; if set it
|
|
59
|
-
* MUST match the principal `getCredential()` authenticates as, or
|
|
60
|
-
* `mintDelegationCredential`'s self-invariant rejects the mint.
|
|
61
|
-
*/
|
|
62
|
-
mintIdentity?: string
|
|
63
65
|
/**
|
|
64
66
|
* Wire-layer `fetch` shim, forwarded to the underlying `KernelClient`s.
|
|
65
67
|
* Lets a same-origin SPA inject `credentials:'include'` + refresh-on-401 so
|
|
@@ -85,27 +87,14 @@ export function createKernelClientAdapter(
|
|
|
85
87
|
if (opts.fetch) config.fetch = opts.fetch
|
|
86
88
|
if (opts.getCredential) {
|
|
87
89
|
const getCred = opts.getCredential
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
// Cross-origin redirects (kernel → worker) mint a fresh worker-scoped
|
|
91
|
+
// delegation from the active identity — the blessed self-mint adapter over
|
|
92
|
+
// `auth.delegate`, with the `skipDelegation` recursion guard baked in.
|
|
90
93
|
config.delegation = {
|
|
91
|
-
mint:
|
|
94
|
+
mint: delegationMintVia(() => {
|
|
92
95
|
if (!session) throw new Error('ClientSession not initialised')
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
explicitMintIdentity ??
|
|
96
|
-
(await resolveSelf(() =>
|
|
97
|
-
s.call(WHOAMI_PATH, {}, { credential: getCred(), skipDelegation: true }),
|
|
98
|
-
))
|
|
99
|
-
const result = await s.call(
|
|
100
|
-
identityMethod(subject, 'mintDelegationCredential'),
|
|
101
|
-
{ audience, delegation, ttl: 3600 },
|
|
102
|
-
{ credential: getCred(), skipDelegation: true },
|
|
103
|
-
)
|
|
104
|
-
if (typeof result !== 'string') {
|
|
105
|
-
throw new Error(`mintDelegationCredential returned non-string: ${typeof result}`)
|
|
106
|
-
}
|
|
107
|
-
return { credential: result, ttl: 3600 }
|
|
108
|
-
},
|
|
96
|
+
return session
|
|
97
|
+
}, getCred),
|
|
109
98
|
}
|
|
110
99
|
}
|
|
111
100
|
session = new ClientSession<AnyFnMap>(config)
|
|
@@ -114,6 +103,10 @@ export function createKernelClientAdapter(
|
|
|
114
103
|
return {
|
|
115
104
|
url: opts.url,
|
|
116
105
|
|
|
106
|
+
boundView(getCredential) {
|
|
107
|
+
return ready.as(getCredential)
|
|
108
|
+
},
|
|
109
|
+
|
|
117
110
|
call(method, params, credential, callOpts) {
|
|
118
111
|
const sessionOpts: Parameters<typeof ready.call>[2] = { credential }
|
|
119
112
|
if (callOpts?.timeout !== undefined) sessionOpts.timeout = callOpts.timeout
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control message registry — extensible via declaration merging.
|
|
3
|
+
*/
|
|
4
|
+
export interface CtrlRegistry {
|
|
5
|
+
handshake: {
|
|
6
|
+
data: {
|
|
7
|
+
delegationToken: string
|
|
8
|
+
tokenExpiresAt: number
|
|
9
|
+
windowId: string
|
|
10
|
+
kernelUrl: string
|
|
11
|
+
functionId: string
|
|
12
|
+
targetNodeId?: string
|
|
13
|
+
mintIdentity?: string
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
handshakeAck: { data: { windowId: string } }
|
|
17
|
+
tokenRefresh: { data: { delegationToken: string; tokenExpiresAt: number } }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type CtrlMessage<K extends keyof CtrlRegistry = keyof CtrlRegistry> = {
|
|
21
|
+
readonly type: 'ctrl'
|
|
22
|
+
readonly version: 1
|
|
23
|
+
readonly action: K
|
|
24
|
+
readonly data: CtrlRegistry[K]['data']
|
|
25
|
+
}
|
|
@@ -7,3 +7,11 @@ export type ErrorCode =
|
|
|
7
7
|
| 'DELIVERY_FAILED'
|
|
8
8
|
| 'TOKEN_REFRESH_FAILED'
|
|
9
9
|
| 'NO_VIEW_AVAILABLE'
|
|
10
|
+
|
|
11
|
+
export type ErrorMessage = {
|
|
12
|
+
readonly type: 'error'
|
|
13
|
+
readonly version: 1
|
|
14
|
+
readonly code: ErrorCode
|
|
15
|
+
readonly message: string
|
|
16
|
+
readonly context?: unknown
|
|
17
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export interface ViewRef {
|
|
2
|
+
readonly viewId: string
|
|
3
|
+
readonly functionId: string
|
|
4
|
+
readonly label?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Intent registry — extensible via declaration merging.
|
|
9
|
+
*
|
|
10
|
+
* An entry is EITHER `{ payload; reply }` (the reply-typed convention) OR a bare
|
|
11
|
+
* payload type (legacy). Read entries through {@link PayloadOf} / {@link ReplyOf}
|
|
12
|
+
* — never index `[K]['payload']` directly, so bare augmentations keep compiling.
|
|
13
|
+
*
|
|
14
|
+
* Consumers extend via:
|
|
15
|
+
* declare module '@astrale-os/shell' {
|
|
16
|
+
* interface IntentRegistry { ... }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
export interface IntentRegistry {
|
|
20
|
+
open: { payload: { nodeId: string; viewId?: string }; reply: { windowId: string } }
|
|
21
|
+
close: { payload: { windowId: string }; reply: void }
|
|
22
|
+
focus: { payload: { nodeId: string }; reply: void }
|
|
23
|
+
willClose: { payload: Record<string, never>; reply: void }
|
|
24
|
+
closeAck: { payload: Record<string, never>; reply: void }
|
|
25
|
+
closeRefuse: { payload: { reason: string }; reply: void }
|
|
26
|
+
pickView: { payload: { views: ViewRef[] }; reply: { viewId: string } }
|
|
27
|
+
receive: { payload: { data: unknown; sourceIntent: string }; reply: void }
|
|
28
|
+
/**
|
|
29
|
+
* Hot-swap — parent pushes a new target node to an already-mounted view
|
|
30
|
+
* without remounting. The child reads `targetNodeId` at handshake time
|
|
31
|
+
* for the initial value; `setTarget` replaces it for the lifetime of the
|
|
32
|
+
* window.
|
|
33
|
+
*/
|
|
34
|
+
setTarget: { payload: { nodeId: string }; reply: void }
|
|
35
|
+
/**
|
|
36
|
+
* Child → parent: a view asks the user to grant `perms` (a permission
|
|
37
|
+
* bitmask) on `node` to `identity` (the beneficiary subject). Both `node`
|
|
38
|
+
* and `identity` are bare node ids (e.g. the view's `targetNodeId`, or
|
|
39
|
+
* `whoami().id` for "grant to myself") — the root shell addresses them as
|
|
40
|
+
* `@<id>`. The root shell (GUI) surfaces a modal, computes the delta vs.
|
|
41
|
+
* what the subject already holds, and — on approval — grants only the
|
|
42
|
+
* missing bits under the GUI user's own credential. `reason` is shown to
|
|
43
|
+
* the user. `reply.perms` is the bitmask actually granted (0 if
|
|
44
|
+
* declined / nothing was missing).
|
|
45
|
+
*
|
|
46
|
+
* Reaches a handler only from a view mounted as a DIRECT child of the root
|
|
47
|
+
* shell — the default intent policy does not bubble past one hop.
|
|
48
|
+
*/
|
|
49
|
+
requestAccess: {
|
|
50
|
+
payload: { node: string; identity: string; perms: number; reason?: string }
|
|
51
|
+
reply: { granted: boolean; perms: number }
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generic correlated reply channel. The default intent transport is
|
|
55
|
+
* fire-and-forget; `requestIntent()` sends an intent carrying a
|
|
56
|
+
* `correlationId` and awaits an `intentReply` whose payload echoes it back
|
|
57
|
+
* with the handler's `result`. Routed to the resolver purely by
|
|
58
|
+
* `correlationId` — it intentionally matches no local handler.
|
|
59
|
+
*/
|
|
60
|
+
intentReply: { payload: { correlationId: string; result: unknown }; reply: void }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Payload type for intent `K`. A `{ payload; reply }` entry projects its
|
|
65
|
+
* `payload`; a bare-payload (legacy) entry IS the payload. Distributes over `K`
|
|
66
|
+
* (the outer `extends` makes `K` naked) so `PayloadOf<keyof IntentRegistry>` is
|
|
67
|
+
* the union of per-key payloads, not the union of entries.
|
|
68
|
+
*/
|
|
69
|
+
export type PayloadOf<K extends keyof IntentRegistry> = K extends keyof IntentRegistry
|
|
70
|
+
? IntentRegistry[K] extends { payload: infer P }
|
|
71
|
+
? P
|
|
72
|
+
: IntentRegistry[K]
|
|
73
|
+
: never
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Reply type for intent `K` — the value a handler sends back through
|
|
77
|
+
* `ctx.reply` and a `requestIntent`/`useSendIntent` caller awaits. A
|
|
78
|
+
* `{ reply }` entry projects its `reply`; every other entry is `void`.
|
|
79
|
+
* Distributes over `K` (see {@link PayloadOf}).
|
|
80
|
+
*/
|
|
81
|
+
export type ReplyOf<K extends keyof IntentRegistry> = K extends keyof IntentRegistry
|
|
82
|
+
? IntentRegistry[K] extends { reply: infer R }
|
|
83
|
+
? R
|
|
84
|
+
: void
|
|
85
|
+
: never
|
|
86
|
+
|
|
87
|
+
export type IntentEnvelope<K extends keyof IntentRegistry = keyof IntentRegistry> = {
|
|
88
|
+
readonly name: K
|
|
89
|
+
readonly payload: PayloadOf<K>
|
|
90
|
+
readonly sender: { readonly windowId: string }
|
|
91
|
+
readonly correlationId?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type IntentMessage<K extends keyof IntentRegistry = keyof IntentRegistry> = {
|
|
95
|
+
readonly type: 'intent'
|
|
96
|
+
readonly version: 1
|
|
97
|
+
readonly envelope: IntentEnvelope<K>
|
|
98
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CtrlMessage } from './ctrl'
|
|
2
|
+
import type { ErrorMessage } from './error'
|
|
3
|
+
import type { IntentMessage } from './intent'
|
|
4
|
+
|
|
5
|
+
export type ShellMessage = IntentMessage | CtrlMessage | ErrorMessage
|
|
6
|
+
|
|
7
|
+
export type MessageHandler<M extends ShellMessage = ShellMessage> = (message: M) => void
|
|
8
|
+
|
|
9
|
+
export type MessageType = ShellMessage['type']
|
|
10
|
+
|
|
11
|
+
export type MessageOf<T extends MessageType> = Extract<ShellMessage, { type: T }>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NO_PERMS } from '@astrale-os/kernel-core/permissions'
|
|
2
|
+
|
|
3
|
+
import type { PayloadOf, ReplyOf } from '../message/intent'
|
|
4
|
+
import type { Shell } from './shell'
|
|
5
|
+
|
|
6
|
+
import { requestIntent } from '../intent/request'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* View SDK convenience: ask the user (via the root shell) to grant `perms` on
|
|
10
|
+
* `node` to `identity`. Resolves with the bitmask actually granted (0 if the
|
|
11
|
+
* user declined or there was no parent to ask). The view typically passes its
|
|
12
|
+
* own identity (from `whoami`) as `identity`.
|
|
13
|
+
*/
|
|
14
|
+
export function requestAccess(
|
|
15
|
+
shell: Shell,
|
|
16
|
+
opts: PayloadOf<'requestAccess'>,
|
|
17
|
+
): Promise<ReplyOf<'requestAccess'>> {
|
|
18
|
+
if (!shell.parent) return Promise.resolve({ granted: false, perms: NO_PERMS })
|
|
19
|
+
// A sandboxed shell's `parent` is a proxy that throws until `init()` resolves;
|
|
20
|
+
// honour the documented "no parent to ask" fallback instead of surfacing that
|
|
21
|
+
// opaque rejection to the caller.
|
|
22
|
+
return requestIntent(shell.parent, 'requestAccess', opts).catch(() => ({
|
|
23
|
+
granted: false,
|
|
24
|
+
perms: NO_PERMS,
|
|
25
|
+
}))
|
|
26
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { OsNamespaces } from '../application/namespaces'
|
|
2
|
+
import type { ChildrenChannelController } from '../channel/children'
|
|
3
|
+
import type { MessageObserver } from '../channel/observer'
|
|
4
|
+
import type { ParentChannel } from '../channel/parent'
|
|
5
|
+
import type { IntentPipeline } from '../intent/pipeline'
|
|
6
|
+
import type { LocalIntentHandler } from '../intent/router'
|
|
7
|
+
import type { KernelClient } from '../kernel/client'
|
|
8
|
+
import type { IntentRegistry } from '../message/intent'
|
|
9
|
+
import type { LifecycleController } from '../window/lifecycle/states'
|
|
10
|
+
import type { MountController } from '../window/mount/mount'
|
|
11
|
+
import type { Window } from '../window/window'
|
|
12
|
+
import type { Shell } from './shell'
|
|
13
|
+
|
|
14
|
+
import { createOsNamespaces } from '../application/namespaces'
|
|
15
|
+
|
|
16
|
+
export type FacadeDeps = {
|
|
17
|
+
parent: ParentChannel | null
|
|
18
|
+
childrenChannel: ChildrenChannelController
|
|
19
|
+
kernel: KernelClient
|
|
20
|
+
pipeline: IntentPipeline
|
|
21
|
+
observer: MessageObserver
|
|
22
|
+
lifecycle: LifecycleController
|
|
23
|
+
windows: Map<string, Window>
|
|
24
|
+
getMountController: () => MountController
|
|
25
|
+
onIntent: <K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>) => () => void
|
|
26
|
+
targetNodeId?: () => string | undefined
|
|
27
|
+
init?: () => Promise<void>
|
|
28
|
+
onDispose: () => Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Assemble the Shell object shared by both modes from their wired parts. */
|
|
32
|
+
export function buildShellFacade(deps: FacadeDeps): Shell {
|
|
33
|
+
let disposed = false
|
|
34
|
+
|
|
35
|
+
// The OS namespaces are lazy over the kernel proxy: constructing them touches
|
|
36
|
+
// no kernel, so the sandboxed shell's "init() first" guard fires only when a
|
|
37
|
+
// namespace method actually dispatches.
|
|
38
|
+
let os: OsNamespaces | null = null
|
|
39
|
+
const osNamespaces = (): OsNamespaces => (os ??= createOsNamespaces(deps.kernel))
|
|
40
|
+
|
|
41
|
+
const shell: Shell = {
|
|
42
|
+
get parent() {
|
|
43
|
+
return deps.parent
|
|
44
|
+
},
|
|
45
|
+
children: deps.childrenChannel,
|
|
46
|
+
kernel: deps.kernel,
|
|
47
|
+
windows: deps.windows,
|
|
48
|
+
get targetNodeId() {
|
|
49
|
+
return deps.targetNodeId?.()
|
|
50
|
+
},
|
|
51
|
+
get user() {
|
|
52
|
+
return osNamespaces().user
|
|
53
|
+
},
|
|
54
|
+
get spaces() {
|
|
55
|
+
return osNamespaces().spaces
|
|
56
|
+
},
|
|
57
|
+
get apps() {
|
|
58
|
+
return osNamespaces().apps
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
use(middleware) {
|
|
62
|
+
deps.pipeline.use(middleware)
|
|
63
|
+
return shell
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
onMessage(handler) {
|
|
67
|
+
return deps.observer.subscribe(handler)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
onIntent(name, handler) {
|
|
71
|
+
return deps.onIntent(name, handler)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
mount(params) {
|
|
75
|
+
return deps.getMountController().mount(params)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
close(windowId, opts) {
|
|
79
|
+
return deps.getMountController().close(windowId, opts)
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async init() {
|
|
83
|
+
if (deps.init) await deps.init()
|
|
84
|
+
return shell
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async dispose() {
|
|
88
|
+
if (disposed) return
|
|
89
|
+
disposed = true
|
|
90
|
+
await deps.onDispose()
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return shell
|
|
95
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FunctionSchema } from '@astrale-os/kernel-client'
|
|
2
|
+
|
|
3
|
+
import type { ShellAdapter } from '../window/embed/adapter'
|
|
4
|
+
|
|
5
|
+
export type StandaloneConfig = {
|
|
6
|
+
readonly mode: 'standalone'
|
|
7
|
+
readonly kernelUrl: string
|
|
8
|
+
readonly credential?: string
|
|
9
|
+
readonly getCredential?: () => string
|
|
10
|
+
readonly mintIdentity?: string
|
|
11
|
+
readonly schema?: FunctionSchema[]
|
|
12
|
+
readonly adapter?: ShellAdapter
|
|
13
|
+
readonly windowId?: string
|
|
14
|
+
/**
|
|
15
|
+
* Optional wire-layer `fetch` shim for the kernel transport. A cookie-fronted
|
|
16
|
+
* SPA passes a wrapper adding `credentials:'include'` + refresh-on-401 and an
|
|
17
|
+
* empty `credential`, so a router unseals the cookie into a Bearer (the SPA
|
|
18
|
+
* holds no token). Omit it (the default) for token-based callers — including
|
|
19
|
+
* every sandboxed child view, which authenticates with a delegation token.
|
|
20
|
+
*/
|
|
21
|
+
readonly fetch?: typeof fetch
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SandboxedConfig = {
|
|
25
|
+
readonly mode: 'sandboxed'
|
|
26
|
+
readonly schema?: FunctionSchema[]
|
|
27
|
+
readonly parentWindow?: WindowProxy
|
|
28
|
+
readonly initTimeoutMs?: number
|
|
29
|
+
readonly adapter?: ShellAdapter
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ShellConfig = StandaloneConfig | SandboxedConfig
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShellConfig } from './config'
|
|
2
|
+
import type { Shell } from './shell'
|
|
3
|
+
|
|
4
|
+
import { createSandboxedShell } from './sandboxed/sandboxed'
|
|
5
|
+
import { createStandaloneShell } from './standalone'
|
|
6
|
+
|
|
7
|
+
export function createShell(config: ShellConfig): Shell {
|
|
8
|
+
return config.mode === 'standalone' ? createStandaloneShell(config) : createSandboxedShell(config)
|
|
9
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { FunctionSchema } from '@astrale-os/kernel-client'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { createParentChannel } from '../channels/parent-channel'
|
|
3
|
+
import type { MessageObserver } from '../../channel/observer'
|
|
4
|
+
import type { ParentChannelController } from '../../channel/parent'
|
|
5
|
+
import type { DelegationToken } from '../../kernel/delegation/token'
|
|
6
|
+
import type { CtrlMessage } from '../../message/ctrl'
|
|
7
|
+
|
|
8
|
+
import { createMessagePortTransport } from '../../channel/message-port'
|
|
9
|
+
import { createParentChannel } from '../../channel/parent'
|
|
11
10
|
import {
|
|
12
11
|
createDelegationKernelFacade,
|
|
13
12
|
type DelegationKernelFacadeController,
|
|
14
|
-
} from '
|
|
15
|
-
import {
|
|
13
|
+
} from '../../kernel/delegated'
|
|
14
|
+
import { createKernelClientAdapter } from '../../kernel/session'
|
|
15
|
+
import { INIT_REQUEST_TYPE, isInitResponse } from '../../message/init'
|
|
16
16
|
|
|
17
17
|
export type ChildInitParams = {
|
|
18
18
|
readonly parent: WindowProxy
|
|
@@ -78,7 +78,6 @@ export function performChildInit(params: ChildInitParams): Promise<ChildInitResu
|
|
|
78
78
|
url: data.kernelUrl,
|
|
79
79
|
schema: params.schema,
|
|
80
80
|
getCredential: () => currentToken.credential,
|
|
81
|
-
mintIdentity: data.mintIdentity,
|
|
82
81
|
})
|
|
83
82
|
const kernel = createDelegationKernelFacade({
|
|
84
83
|
adapter,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ParentChannel, ParentChannelController } from '../../channel/parent'
|
|
2
|
+
import type { KernelClient } from '../../kernel/client'
|
|
3
|
+
|
|
4
|
+
/** Throw the canonical "call init() first" error while `get()` has no state yet. */
|
|
5
|
+
export function requireInit<T>(get: () => T | null): T {
|
|
6
|
+
const state = get()
|
|
7
|
+
if (!state) throw new Error('Sandboxed shell not initialized — call shell.init() first')
|
|
8
|
+
return state
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Forward the whole unified kernel surface (graph sugar + `.call/.stream/…` +
|
|
13
|
+
* the shell extras) to the post-init facade lazily, so the "init() first"
|
|
14
|
+
* guard applies uniformly to every member without re-declaring each one.
|
|
15
|
+
*/
|
|
16
|
+
export function guardedKernel(require: () => KernelClient): KernelClient {
|
|
17
|
+
return new Proxy({} as KernelClient, {
|
|
18
|
+
get(_target, prop) {
|
|
19
|
+
const kernel = require() as unknown as Record<PropertyKey, unknown>
|
|
20
|
+
const value = kernel[prop]
|
|
21
|
+
return typeof value === 'function' ? value.bind(kernel) : value
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Same guard for the parent channel — throws on use until `init()` resolves. */
|
|
27
|
+
export function guardedParent(require: () => ParentChannelController): ParentChannel {
|
|
28
|
+
return {
|
|
29
|
+
send: (message) => require().send(message),
|
|
30
|
+
on: (type, handler) => require().on(type, handler),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { IntentRouterController, LocalIntentHandler } from '../../intent/router'
|
|
2
|
+
import type { IntentRegistry } from '../../message/intent'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Buffer intent subscriptions made before `init()` resolves — the router is
|
|
6
|
+
* only built once the handshake completes, but consumers want to call
|
|
7
|
+
* `onIntent` at construction time.
|
|
8
|
+
*/
|
|
9
|
+
export interface PendingIntentSubs {
|
|
10
|
+
add<K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>): () => void
|
|
11
|
+
/** Register the buffered subscriptions on the real router, then drain. */
|
|
12
|
+
replay(router: IntentRouterController): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Entry = {
|
|
16
|
+
name: string
|
|
17
|
+
handler: LocalIntentHandler
|
|
18
|
+
unsubscribed: boolean
|
|
19
|
+
off?: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createPendingIntentSubs(): PendingIntentSubs {
|
|
23
|
+
const entries: Entry[] = []
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
add(name, handler) {
|
|
27
|
+
const entry: Entry = {
|
|
28
|
+
name: name as string,
|
|
29
|
+
handler: handler as LocalIntentHandler,
|
|
30
|
+
unsubscribed: false,
|
|
31
|
+
}
|
|
32
|
+
entries.push(entry)
|
|
33
|
+
return () => {
|
|
34
|
+
entry.unsubscribed = true
|
|
35
|
+
// After init, `off` is the router's real disposer (set during replay);
|
|
36
|
+
// before init, flipping `unsubscribed` is enough (replay skips it).
|
|
37
|
+
entry.off?.()
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
replay(router) {
|
|
42
|
+
// Capture each real disposer so a post-init unsubscribe (via the closure
|
|
43
|
+
// returned at add time) actually removes the handler from the router
|
|
44
|
+
// instead of no-op'ing.
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (entry.unsubscribed) continue
|
|
47
|
+
entry.off = router.onLocal(entry.name as keyof IntentRegistry, entry.handler)
|
|
48
|
+
}
|
|
49
|
+
entries.length = 0
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ParentChannelController } from '../../channel/parent'
|
|
2
|
+
import type { IntentRouterController, LocalIntentHandler } from '../../intent/router'
|
|
3
|
+
import type { KernelFacadeController } from '../../kernel/credential'
|
|
4
|
+
import type { IntentRegistry } from '../../message/intent'
|
|
5
|
+
import type { MountController } from '../../window/mount/mount'
|
|
6
|
+
import type { Window } from '../../window/window'
|
|
7
|
+
import type { SandboxedConfig } from '../config'
|
|
8
|
+
import type { Shell } from '../shell'
|
|
9
|
+
|
|
10
|
+
import { createChildrenChannel } from '../../channel/children'
|
|
11
|
+
import { createMessageObserver } from '../../channel/observer'
|
|
12
|
+
import { createIntentPipeline } from '../../intent/pipeline'
|
|
13
|
+
import { createIntentRouter } from '../../intent/router'
|
|
14
|
+
import { createLifecycleController } from '../../window/lifecycle/states'
|
|
15
|
+
import { createMountController } from '../../window/mount/mount'
|
|
16
|
+
import { buildShellFacade } from '../build'
|
|
17
|
+
import { performChildInit } from './boot'
|
|
18
|
+
import { guardedKernel, guardedParent, requireInit } from './guarded'
|
|
19
|
+
import { createPendingIntentSubs } from './pending'
|
|
20
|
+
|
|
21
|
+
/** Trusted sender id stamped on intents arriving from the parent channel. */
|
|
22
|
+
const PARENT_SENDER_ID = '<parent>'
|
|
23
|
+
|
|
24
|
+
type SandboxedState = {
|
|
25
|
+
windowId: string
|
|
26
|
+
kernelUrl: string
|
|
27
|
+
targetNodeId?: string
|
|
28
|
+
mintIdentity?: string
|
|
29
|
+
kernel: KernelFacadeController
|
|
30
|
+
parent: ParentChannelController
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createSandboxedShell(config: SandboxedConfig): Shell {
|
|
34
|
+
const observer = createMessageObserver()
|
|
35
|
+
const lifecycle = createLifecycleController()
|
|
36
|
+
const windows = new Map<string, Window>()
|
|
37
|
+
const childrenChannel = createChildrenChannel({
|
|
38
|
+
observer,
|
|
39
|
+
onDeliveryFailed: (toWindowId) => lifecycle.markCrashed(toWindowId),
|
|
40
|
+
})
|
|
41
|
+
const pipeline = createIntentPipeline()
|
|
42
|
+
|
|
43
|
+
let state: SandboxedState | null = null
|
|
44
|
+
let router: IntentRouterController | null = null
|
|
45
|
+
let mountController: MountController | null = null
|
|
46
|
+
|
|
47
|
+
const pending = createPendingIntentSubs()
|
|
48
|
+
|
|
49
|
+
function subscribeIntent<K extends keyof IntentRegistry>(
|
|
50
|
+
name: K,
|
|
51
|
+
handler: LocalIntentHandler<K>,
|
|
52
|
+
): () => void {
|
|
53
|
+
if (router) return router.onLocal(name, handler)
|
|
54
|
+
return pending.add(name, handler)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const requireState = () => requireInit(() => state)
|
|
58
|
+
|
|
59
|
+
const kernelProxy = guardedKernel(() => requireState().kernel)
|
|
60
|
+
const parentProxy = guardedParent(() => requireState().parent)
|
|
61
|
+
|
|
62
|
+
function getMountController(): MountController {
|
|
63
|
+
if (mountController) return mountController
|
|
64
|
+
if (!config.adapter) {
|
|
65
|
+
throw new Error('Shell.mount() requires a ShellAdapter in the config')
|
|
66
|
+
}
|
|
67
|
+
const current = requireState()
|
|
68
|
+
mountController = createMountController({
|
|
69
|
+
adapter: config.adapter,
|
|
70
|
+
kernel: kernelProxy,
|
|
71
|
+
kernelUrl: current.kernelUrl,
|
|
72
|
+
mintIdentity: current.mintIdentity,
|
|
73
|
+
childrenChannel,
|
|
74
|
+
windows,
|
|
75
|
+
lifecycle,
|
|
76
|
+
})
|
|
77
|
+
return mountController
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return buildShellFacade({
|
|
81
|
+
parent: parentProxy,
|
|
82
|
+
childrenChannel,
|
|
83
|
+
kernel: kernelProxy,
|
|
84
|
+
pipeline,
|
|
85
|
+
observer,
|
|
86
|
+
lifecycle,
|
|
87
|
+
windows,
|
|
88
|
+
getMountController,
|
|
89
|
+
targetNodeId: () => state?.targetNodeId,
|
|
90
|
+
onIntent: subscribeIntent,
|
|
91
|
+
init: async () => {
|
|
92
|
+
const result = await performChildInit({
|
|
93
|
+
parent: config.parentWindow ?? window.parent,
|
|
94
|
+
timeoutMs: config.initTimeoutMs,
|
|
95
|
+
schema: config.schema,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
state = {
|
|
99
|
+
windowId: result.windowId,
|
|
100
|
+
kernelUrl: result.kernelUrl,
|
|
101
|
+
targetNodeId: result.targetNodeId,
|
|
102
|
+
mintIdentity: result.mintIdentity,
|
|
103
|
+
kernel: result.kernel,
|
|
104
|
+
parent: result.parentChannel(observer),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
router = createIntentRouter({
|
|
108
|
+
selfWindowId: state.windowId,
|
|
109
|
+
pipeline,
|
|
110
|
+
parent: state.parent,
|
|
111
|
+
children: childrenChannel,
|
|
112
|
+
windows,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
childrenChannel.on('intent', (windowId, message) => {
|
|
116
|
+
void router!.handleInbound('child', message, windowId)
|
|
117
|
+
})
|
|
118
|
+
state.parent.on('intent', (message) => {
|
|
119
|
+
void router!.handleInbound('parent', message, PARENT_SENDER_ID)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
pending.replay(router)
|
|
123
|
+
},
|
|
124
|
+
onDispose: async () => {
|
|
125
|
+
if (mountController) await mountController.dispose()
|
|
126
|
+
childrenChannel.dispose()
|
|
127
|
+
state?.parent.dispose()
|
|
128
|
+
state?.kernel.dispose()
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
}
|