@astrale-os/shell 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/dist/adapters/iframe.shell-adapter.d.ts +10 -0
- package/dist/adapters/iframe.shell-adapter.d.ts.map +1 -0
- package/dist/adapters/iframe.shell-adapter.js +55 -0
- package/dist/adapters/kernel-client.adapter.d.ts +53 -0
- package/dist/adapters/kernel-client.adapter.d.ts.map +1 -0
- package/dist/adapters/kernel-client.adapter.js +69 -0
- package/dist/adapters/message-port.transport.d.ts +7 -0
- package/dist/adapters/message-port.transport.d.ts.map +1 -0
- package/dist/adapters/message-port.transport.js +42 -0
- package/dist/application/channels/children-channel.d.ts +21 -0
- package/dist/application/channels/children-channel.d.ts.map +1 -0
- package/dist/application/channels/children-channel.js +82 -0
- package/dist/application/channels/observer.d.ts +9 -0
- package/dist/application/channels/observer.d.ts.map +1 -0
- package/dist/application/channels/observer.js +23 -0
- package/dist/application/channels/parent-channel.d.ts +17 -0
- package/dist/application/channels/parent-channel.d.ts.map +1 -0
- package/dist/application/channels/parent-channel.js +21 -0
- package/dist/application/channels/types.d.ts +12 -0
- package/dist/application/channels/types.d.ts.map +1 -0
- package/dist/application/channels/types.js +29 -0
- package/dist/application/intent/pipeline-runner.d.ts +12 -0
- package/dist/application/intent/pipeline-runner.d.ts.map +1 -0
- package/dist/application/intent/pipeline-runner.js +27 -0
- package/dist/application/intent/request.d.ts +34 -0
- package/dist/application/intent/request.d.ts.map +1 -0
- package/dist/application/intent/request.js +70 -0
- package/dist/application/intent/router.d.ts +56 -0
- package/dist/application/intent/router.d.ts.map +1 -0
- package/dist/application/intent/router.js +94 -0
- package/dist/application/kernel-facade.d.ts +33 -0
- package/dist/application/kernel-facade.d.ts.map +1 -0
- package/dist/application/kernel-facade.js +77 -0
- package/dist/application/shell.d.ts +100 -0
- package/dist/application/shell.d.ts.map +1 -0
- package/dist/application/shell.js +256 -0
- package/dist/application/windowing/child-init.d.ts +23 -0
- package/dist/application/windowing/child-init.d.ts.map +1 -0
- package/dist/application/windowing/child-init.js +101 -0
- package/dist/application/windowing/handshake.d.ts +30 -0
- package/dist/application/windowing/handshake.d.ts.map +1 -0
- package/dist/application/windowing/handshake.js +63 -0
- package/dist/application/windowing/lifecycle-controller.d.ts +10 -0
- package/dist/application/windowing/lifecycle-controller.d.ts.map +1 -0
- package/dist/application/windowing/lifecycle-controller.js +30 -0
- package/dist/application/windowing/mount-controller.d.ts +25 -0
- package/dist/application/windowing/mount-controller.d.ts.map +1 -0
- package/dist/application/windowing/mount-controller.js +277 -0
- package/dist/application/windowing/protocol.d.ts +21 -0
- package/dist/application/windowing/protocol.d.ts.map +1 -0
- package/dist/application/windowing/protocol.js +19 -0
- package/dist/application/windowing/token-refresh.d.ts +19 -0
- package/dist/application/windowing/token-refresh.d.ts.map +1 -0
- package/dist/application/windowing/token-refresh.js +60 -0
- package/dist/domain/identity/delegation.d.ts +15 -0
- package/dist/domain/identity/delegation.d.ts.map +1 -0
- package/dist/domain/identity/delegation.js +12 -0
- package/dist/domain/identity/path.d.ts +11 -0
- package/dist/domain/identity/path.d.ts.map +1 -0
- package/dist/domain/identity/path.js +13 -0
- package/dist/domain/identity/resolve-self.d.ts +21 -0
- package/dist/domain/identity/resolve-self.d.ts.map +1 -0
- package/dist/domain/identity/resolve-self.js +36 -0
- package/dist/domain/intent/middleware/capabilities.d.ts +28 -0
- package/dist/domain/intent/middleware/capabilities.d.ts.map +1 -0
- package/dist/domain/intent/middleware/capabilities.js +18 -0
- package/dist/domain/intent/middleware/logging.d.ts +14 -0
- package/dist/domain/intent/middleware/logging.d.ts.map +1 -0
- package/dist/domain/intent/middleware/logging.js +21 -0
- package/dist/domain/intent/middleware/rate-limit.d.ts +20 -0
- package/dist/domain/intent/middleware/rate-limit.d.ts.map +1 -0
- package/dist/domain/intent/middleware/rate-limit.js +33 -0
- package/dist/domain/intent/pipeline.d.ts +3 -0
- package/dist/domain/intent/pipeline.d.ts.map +1 -0
- package/dist/domain/intent/pipeline.js +1 -0
- package/dist/domain/intent/router.d.ts +6 -0
- package/dist/domain/intent/router.d.ts.map +1 -0
- package/dist/domain/intent/router.js +1 -0
- package/dist/domain/message/errors.d.ts +2 -0
- package/dist/domain/message/errors.d.ts.map +1 -0
- package/dist/domain/message/errors.js +1 -0
- package/dist/domain/message/registries.d.ts +145 -0
- package/dist/domain/message/registries.d.ts.map +1 -0
- package/dist/domain/message/registries.js +1 -0
- package/dist/domain/message/types.d.ts +30 -0
- package/dist/domain/message/types.d.ts.map +1 -0
- package/dist/domain/message/types.js +1 -0
- package/dist/domain/window/config.d.ts +26 -0
- package/dist/domain/window/config.d.ts.map +1 -0
- package/dist/domain/window/config.js +1 -0
- package/dist/domain/window/lifecycle.d.ts +27 -0
- package/dist/domain/window/lifecycle.d.ts.map +1 -0
- package/dist/domain/window/lifecycle.js +36 -0
- package/dist/domain/window/window.d.ts +12 -0
- package/dist/domain/window/window.d.ts.map +1 -0
- package/dist/domain/window/window.js +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/ports/kernel-client.d.ts +33 -0
- package/dist/ports/kernel-client.d.ts.map +1 -0
- package/dist/ports/kernel-client.js +1 -0
- package/dist/ports/shell-adapter.d.ts +13 -0
- package/dist/ports/shell-adapter.d.ts.map +1 -0
- package/dist/ports/shell-adapter.js +1 -0
- package/dist/ports/transport.d.ts +7 -0
- package/dist/ports/transport.d.ts.map +1 -0
- package/dist/ports/transport.js +1 -0
- package/package.json +41 -0
- package/src/adapters/iframe.shell-adapter.ts +68 -0
- package/src/adapters/kernel-client.adapter.ts +138 -0
- package/src/adapters/message-port.transport.ts +50 -0
- package/src/application/channels/children-channel.ts +112 -0
- package/src/application/channels/observer.ts +33 -0
- package/src/application/channels/parent-channel.ts +43 -0
- package/src/application/channels/types.ts +41 -0
- package/src/application/intent/pipeline-runner.ts +36 -0
- package/src/application/intent/request.ts +87 -0
- package/src/application/intent/router.ts +161 -0
- package/src/application/kernel-facade.ts +143 -0
- package/src/application/shell.ts +433 -0
- package/src/application/windowing/child-init.ts +137 -0
- package/src/application/windowing/handshake.ts +98 -0
- package/src/application/windowing/lifecycle-controller.ts +40 -0
- package/src/application/windowing/mount-controller.ts +335 -0
- package/src/application/windowing/protocol.ts +37 -0
- package/src/application/windowing/token-refresh.ts +78 -0
- package/src/domain/identity/delegation.ts +25 -0
- package/src/domain/identity/path.ts +14 -0
- package/src/domain/identity/resolve-self.ts +38 -0
- package/src/domain/intent/middleware/capabilities.ts +53 -0
- package/src/domain/intent/middleware/logging.ts +45 -0
- package/src/domain/intent/middleware/rate-limit.ts +54 -0
- package/src/domain/intent/pipeline.ts +3 -0
- package/src/domain/intent/router.ts +6 -0
- package/src/domain/message/errors.ts +9 -0
- package/src/domain/message/registries.ts +76 -0
- package/src/domain/message/types.ts +32 -0
- package/src/domain/window/config.ts +28 -0
- package/src/domain/window/lifecycle.ts +39 -0
- package/src/domain/window/window.ts +13 -0
- package/src/index.ts +103 -0
- package/src/ports/kernel-client.ts +32 -0
- package/src/ports/shell-adapter.ts +11 -0
- package/src/ports/transport.ts +7 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShellAdapter } from '../ports/shell-adapter';
|
|
2
|
+
export type IframeShellAdapterConfig = {
|
|
3
|
+
/**
|
|
4
|
+
* Optional class list applied to every iframe created by this adapter.
|
|
5
|
+
* Useful for consistent styling without leaking styling into the SDK.
|
|
6
|
+
*/
|
|
7
|
+
readonly className?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function createIframeShellAdapter(config?: IframeShellAdapterConfig): ShellAdapter;
|
|
10
|
+
//# sourceMappingURL=iframe.shell-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframe.shell-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/iframe.shell-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,wBAAwB,CAAA;AAExE,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAUD,wBAAgB,wBAAwB,CAAC,MAAM,GAAE,wBAA6B,GAAG,YAAY,CAsC5F"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const DEFAULT_SANDBOX = {
|
|
2
|
+
allowScripts: true,
|
|
3
|
+
allowForms: false,
|
|
4
|
+
allowPopups: false,
|
|
5
|
+
allowSameOrigin: false,
|
|
6
|
+
allowModals: false,
|
|
7
|
+
};
|
|
8
|
+
export function createIframeShellAdapter(config = {}) {
|
|
9
|
+
return {
|
|
10
|
+
mount(windowConfig) {
|
|
11
|
+
if (windowConfig.isolation !== 'sandboxed') {
|
|
12
|
+
throw new Error(`iframe adapter only supports sandboxed windows, got '${windowConfig.isolation}'`);
|
|
13
|
+
}
|
|
14
|
+
const element = document.createElement('iframe');
|
|
15
|
+
element.dataset.astraleFunctionId = windowConfig.functionId;
|
|
16
|
+
if (config.className)
|
|
17
|
+
element.className = config.className;
|
|
18
|
+
// `sandbox: null` is an explicit opt-out — leave the iframe without a
|
|
19
|
+
// `sandbox` attribute (only cross-origin isolation applies). Used by
|
|
20
|
+
// the GUI for first-party trusted views (see trusted-view-origins).
|
|
21
|
+
if (windowConfig.sandbox !== null) {
|
|
22
|
+
const profile = windowConfig.sandbox ?? DEFAULT_SANDBOX;
|
|
23
|
+
element.setAttribute('sandbox', sandboxAttrFor(profile));
|
|
24
|
+
}
|
|
25
|
+
element.setAttribute('allow', 'clipboard-read; clipboard-write');
|
|
26
|
+
element.setAttribute('loading', 'eager');
|
|
27
|
+
element.setAttribute('referrerpolicy', 'no-referrer');
|
|
28
|
+
const handle = {
|
|
29
|
+
element,
|
|
30
|
+
port: null,
|
|
31
|
+
origin: '',
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
handle,
|
|
35
|
+
dispose: () => {
|
|
36
|
+
element.remove();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function sandboxAttrFor(profile) {
|
|
43
|
+
const tokens = [];
|
|
44
|
+
if (profile.allowScripts)
|
|
45
|
+
tokens.push('allow-scripts');
|
|
46
|
+
if (profile.allowForms)
|
|
47
|
+
tokens.push('allow-forms');
|
|
48
|
+
if (profile.allowPopups)
|
|
49
|
+
tokens.push('allow-popups');
|
|
50
|
+
if (profile.allowSameOrigin)
|
|
51
|
+
tokens.push('allow-same-origin');
|
|
52
|
+
if (profile.allowModals)
|
|
53
|
+
tokens.push('allow-modals');
|
|
54
|
+
return tokens.join(' ');
|
|
55
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CallOptions, type CredentialInput, type FunctionSchema } from '@astrale-os/kernel-client';
|
|
2
|
+
/**
|
|
3
|
+
* Low-level kernel adapter — wraps a `ClientSession` (the post-v2 orchestration
|
|
4
|
+
* layer above `KernelClient`).
|
|
5
|
+
*
|
|
6
|
+
* Methods take an explicit credential. The application-layer facade
|
|
7
|
+
* manages which credential to use.
|
|
8
|
+
*
|
|
9
|
+
* Remote-domain redirects are followed transparently: when the kernel
|
|
10
|
+
* responds with a `redirect` result (pointing at an external worker URL),
|
|
11
|
+
* the ClientSession mints a worker-scoped delegation credential — for the
|
|
12
|
+
* audience the kernel carries on the redirect (`CallRedirection.iss`, the
|
|
13
|
+
* target's own identity) — and re-dials the target URL with it. So
|
|
14
|
+
* `shell.kernel.call(...)` "just works" for methods whose Function has a
|
|
15
|
+
* `binding.remoteUrl`.
|
|
16
|
+
*/
|
|
17
|
+
export type KernelClientAdapter = {
|
|
18
|
+
call(method: string, params: unknown, credential: CredentialInput, opts?: CallOptions): Promise<unknown>;
|
|
19
|
+
load(schemas: FunctionSchema[]): void;
|
|
20
|
+
disconnect(): void;
|
|
21
|
+
readonly url: string;
|
|
22
|
+
};
|
|
23
|
+
export type CreateKernelClientAdapterOpts = {
|
|
24
|
+
url: string;
|
|
25
|
+
schema?: FunctionSchema[];
|
|
26
|
+
requestTimeout?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Returns the current base credential. When provided, the adapter wires a
|
|
29
|
+
* `delegation: { mint }` on the ClientSession so cross-origin redirects
|
|
30
|
+
* (kernel → worker) are followed with a fresh worker-scoped delegation
|
|
31
|
+
* minted from the active identity. Without it,
|
|
32
|
+
* ClientSession falls back to the base credential and workers reject
|
|
33
|
+
* with `Credential audience mismatch`.
|
|
34
|
+
*/
|
|
35
|
+
getCredential?: () => CredentialInput;
|
|
36
|
+
/**
|
|
37
|
+
* Explicit identity node/path to use as the delegation mint subject. When
|
|
38
|
+
* omitted, the authenticated caller is resolved via `whoami` (so a cookie-fronted
|
|
39
|
+
* SPA mints as the logged-in user). Set this only to pin a subject; if set it
|
|
40
|
+
* MUST match the principal `getCredential()` authenticates as, or
|
|
41
|
+
* `mintDelegationCredential`'s self-invariant rejects the mint.
|
|
42
|
+
*/
|
|
43
|
+
mintIdentity?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Wire-layer `fetch` shim, forwarded to the underlying `KernelClient`s.
|
|
46
|
+
* Lets a same-origin SPA inject `credentials:'include'` + refresh-on-401 so
|
|
47
|
+
* a router can unseal a cookie into a Bearer (the parent holds no token).
|
|
48
|
+
* Default: global fetch — sandboxed child views never set it.
|
|
49
|
+
*/
|
|
50
|
+
fetch?: typeof fetch;
|
|
51
|
+
};
|
|
52
|
+
export declare function createKernelClientAdapter(opts: CreateKernelClientAdapterOpts): KernelClientAdapter;
|
|
53
|
+
//# sourceMappingURL=kernel-client.adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-client.adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/kernel-client.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAA;AASlC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CACF,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,eAAe,EAC3B,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,CAAA;IACrC,UAAU,IAAI,IAAI,CAAA;IAClB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,eAAe,CAAA;IACrC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB,CAAA;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,6BAA6B,GAClC,mBAAmB,CAgErB"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {} from '@astrale-os/kernel-client';
|
|
2
|
+
import { ClientSession } from '@astrale-os/kernel-client/session';
|
|
3
|
+
import { identityMethod } from '../domain/identity/path';
|
|
4
|
+
import { createSelfResolver, WHOAMI_PATH } from '../domain/identity/resolve-self';
|
|
5
|
+
export function createKernelClientAdapter(opts) {
|
|
6
|
+
let session = null;
|
|
7
|
+
const config = {
|
|
8
|
+
default: opts.url,
|
|
9
|
+
// Identity is supplied per-call via `opts.credential`; no picker bound.
|
|
10
|
+
// No `delegationPolicy` → ClientSession's default `allowAllDelegations`
|
|
11
|
+
// mints for the audience the kernel carries on the redirect
|
|
12
|
+
// (`CallRedirection.iss`), which is exactly what the target worker verifies.
|
|
13
|
+
identity: undefined,
|
|
14
|
+
};
|
|
15
|
+
if (opts.schema)
|
|
16
|
+
config.schema = opts.schema;
|
|
17
|
+
if (opts.fetch)
|
|
18
|
+
config.fetch = opts.fetch;
|
|
19
|
+
if (opts.getCredential) {
|
|
20
|
+
const getCred = opts.getCredential;
|
|
21
|
+
const explicitMintIdentity = opts.mintIdentity;
|
|
22
|
+
const resolveSelf = createSelfResolver();
|
|
23
|
+
config.delegation = {
|
|
24
|
+
mint: async (audience, delegation) => {
|
|
25
|
+
if (!session)
|
|
26
|
+
throw new Error('ClientSession not initialised');
|
|
27
|
+
const s = session;
|
|
28
|
+
const subject = explicitMintIdentity ??
|
|
29
|
+
(await resolveSelf(() => s.call(WHOAMI_PATH, {}, { credential: getCred(), skipDelegation: true })));
|
|
30
|
+
const result = await s.call(identityMethod(subject, 'mintDelegationCredential'), { audience, delegation, ttl: 3600 }, { credential: getCred(), skipDelegation: true });
|
|
31
|
+
if (typeof result !== 'string') {
|
|
32
|
+
throw new Error(`mintDelegationCredential returned non-string: ${typeof result}`);
|
|
33
|
+
}
|
|
34
|
+
return { credential: result, ttl: 3600 };
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
session = new ClientSession(config);
|
|
39
|
+
const ready = session;
|
|
40
|
+
return {
|
|
41
|
+
url: opts.url,
|
|
42
|
+
call(method, params, credential, callOpts) {
|
|
43
|
+
const sessionOpts = { credential };
|
|
44
|
+
if (callOpts?.timeout !== undefined)
|
|
45
|
+
sessionOpts.timeout = callOpts.timeout;
|
|
46
|
+
if (callOpts?.signal !== undefined)
|
|
47
|
+
sessionOpts.signal = callOpts.signal;
|
|
48
|
+
if (callOpts?.class !== undefined)
|
|
49
|
+
sessionOpts.class = callOpts.class;
|
|
50
|
+
if (callOpts?.transport !== undefined)
|
|
51
|
+
sessionOpts.transport = callOpts.transport;
|
|
52
|
+
if (callOpts?.protocol !== undefined)
|
|
53
|
+
sessionOpts.protocol = callOpts.protocol;
|
|
54
|
+
if (callOpts?.correlationId !== undefined)
|
|
55
|
+
sessionOpts.correlationId = callOpts.correlationId;
|
|
56
|
+
if (callOpts?.self !== undefined)
|
|
57
|
+
sessionOpts.self = callOpts.self;
|
|
58
|
+
if (callOpts?.url !== undefined)
|
|
59
|
+
sessionOpts.url = callOpts.url;
|
|
60
|
+
return ready.call(method, params, sessionOpts);
|
|
61
|
+
},
|
|
62
|
+
load(schemas) {
|
|
63
|
+
ready.registry.load(schemas);
|
|
64
|
+
},
|
|
65
|
+
disconnect() {
|
|
66
|
+
ready.disconnect();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Transport } from '../ports/transport';
|
|
2
|
+
export type MessagePortTransportConfig = {
|
|
3
|
+
port: MessagePort;
|
|
4
|
+
onClose?: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function createMessagePortTransport(config: MessagePortTransportConfig): Transport;
|
|
7
|
+
//# sourceMappingURL=message-port.transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-port.transport.d.ts","sourceRoot":"","sources":["../../src/adapters/message-port.transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,SAAS,CAyCxF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function createMessagePortTransport(config) {
|
|
2
|
+
const { port, onClose } = config;
|
|
3
|
+
let closed = false;
|
|
4
|
+
const handlers = new Set();
|
|
5
|
+
port.onmessage = (event) => {
|
|
6
|
+
if (closed)
|
|
7
|
+
return;
|
|
8
|
+
const message = event.data;
|
|
9
|
+
for (const handler of handlers)
|
|
10
|
+
handler(message);
|
|
11
|
+
};
|
|
12
|
+
port.onmessageerror = () => {
|
|
13
|
+
if (closed)
|
|
14
|
+
return;
|
|
15
|
+
close();
|
|
16
|
+
};
|
|
17
|
+
port.start();
|
|
18
|
+
function close() {
|
|
19
|
+
if (closed)
|
|
20
|
+
return;
|
|
21
|
+
closed = true;
|
|
22
|
+
handlers.clear();
|
|
23
|
+
try {
|
|
24
|
+
port.close();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// port may already be closed
|
|
28
|
+
}
|
|
29
|
+
onClose?.();
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
send(message) {
|
|
33
|
+
if (closed)
|
|
34
|
+
return;
|
|
35
|
+
port.postMessage(message);
|
|
36
|
+
},
|
|
37
|
+
onMessage(handler) {
|
|
38
|
+
handlers.add(handler);
|
|
39
|
+
},
|
|
40
|
+
close,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ShellMessage } from '../../domain/message/types';
|
|
2
|
+
import type { Transport } from '../../ports/transport';
|
|
3
|
+
import type { MessageObserver } from './observer';
|
|
4
|
+
import type { MessageOf, MessageType } from './types';
|
|
5
|
+
export interface ChildrenChannel {
|
|
6
|
+
send(windowId: string, message: ShellMessage): void;
|
|
7
|
+
on<T extends MessageType>(type: T, handler: (windowId: string, message: MessageOf<T>) => void): () => void;
|
|
8
|
+
has(windowId: string): boolean;
|
|
9
|
+
onClose(handler: (windowId: string) => void): () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface ChildrenChannelController extends ChildrenChannel {
|
|
12
|
+
attach(windowId: string, transport: Transport): void;
|
|
13
|
+
detach(windowId: string): void;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
export type ChildrenChannelConfig = {
|
|
17
|
+
observer: MessageObserver;
|
|
18
|
+
onDeliveryFailed?: (windowId: string, message: ShellMessage) => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function createChildrenChannel(config: ChildrenChannelConfig): ChildrenChannelController;
|
|
21
|
+
//# sourceMappingURL=children-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"children-channel.d.ts","sourceRoot":"","sources":["../../../src/application/channels/children-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAErD,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IACnD,EAAE,CAAC,CAAC,SAAS,WAAW,EACtB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,GACzD,MAAM,IAAI,CAAA;IACb,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;CACzD;AAED,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAChE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;IACpD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,eAAe,CAAA;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;CACrE,CAAA;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,yBAAyB,CAmF9F"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function createChildrenChannel(config) {
|
|
2
|
+
const { observer, onDeliveryFailed } = config;
|
|
3
|
+
const entries = new Map();
|
|
4
|
+
const byType = new Map();
|
|
5
|
+
const closeListeners = new Set();
|
|
6
|
+
function notifyListeners(windowId, message) {
|
|
7
|
+
const bucket = byType.get(message.type);
|
|
8
|
+
if (!bucket || bucket.size === 0)
|
|
9
|
+
return;
|
|
10
|
+
for (const listener of bucket) {
|
|
11
|
+
try {
|
|
12
|
+
listener(windowId, message);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// one rogue listener must not break siblings
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
attach(windowId, transport) {
|
|
21
|
+
const existing = entries.get(windowId);
|
|
22
|
+
if (existing)
|
|
23
|
+
throw new Error(`Child '${windowId}' already attached`);
|
|
24
|
+
entries.set(windowId, transport);
|
|
25
|
+
transport.onMessage((message) => {
|
|
26
|
+
observer.notify('inbound', message);
|
|
27
|
+
notifyListeners(windowId, message);
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
detach(windowId) {
|
|
31
|
+
const transport = entries.get(windowId);
|
|
32
|
+
if (!transport)
|
|
33
|
+
return;
|
|
34
|
+
entries.delete(windowId);
|
|
35
|
+
transport.close();
|
|
36
|
+
// `entries.delete` ran first, so `has(windowId)` is already false here —
|
|
37
|
+
// a listener replying to the gone child is a best-effort no-op.
|
|
38
|
+
for (const listener of closeListeners) {
|
|
39
|
+
try {
|
|
40
|
+
listener(windowId);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// one rogue listener must not break siblings
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
dispose() {
|
|
48
|
+
for (const transport of entries.values())
|
|
49
|
+
transport.close();
|
|
50
|
+
entries.clear();
|
|
51
|
+
},
|
|
52
|
+
send(windowId, message) {
|
|
53
|
+
const transport = entries.get(windowId);
|
|
54
|
+
if (!transport) {
|
|
55
|
+
onDeliveryFailed?.(windowId, message);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
observer.notify('outbound', message);
|
|
59
|
+
transport.send(message);
|
|
60
|
+
},
|
|
61
|
+
on(type, handler) {
|
|
62
|
+
let bucket = byType.get(type);
|
|
63
|
+
if (!bucket) {
|
|
64
|
+
bucket = new Set();
|
|
65
|
+
byType.set(type, bucket);
|
|
66
|
+
}
|
|
67
|
+
bucket.add(handler);
|
|
68
|
+
return () => {
|
|
69
|
+
bucket.delete(handler);
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
has(windowId) {
|
|
73
|
+
return entries.has(windowId);
|
|
74
|
+
},
|
|
75
|
+
onClose(handler) {
|
|
76
|
+
closeListeners.add(handler);
|
|
77
|
+
return () => {
|
|
78
|
+
closeListeners.delete(handler);
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShellMessage } from '../../domain/message/types';
|
|
2
|
+
export type MessageDirection = 'inbound' | 'outbound';
|
|
3
|
+
export type ObserverHandler = (message: ShellMessage, direction: MessageDirection) => void;
|
|
4
|
+
export interface MessageObserver {
|
|
5
|
+
notify(direction: MessageDirection, message: ShellMessage): void;
|
|
6
|
+
subscribe(handler: ObserverHandler): () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createMessageObserver(): MessageObserver;
|
|
9
|
+
//# sourceMappingURL=observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.d.ts","sourceRoot":"","sources":["../../../src/application/channels/observer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAA;AAE1F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAChE,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,IAAI,CAAA;CAChD;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAqBvD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createMessageObserver() {
|
|
2
|
+
const handlers = new Set();
|
|
3
|
+
return {
|
|
4
|
+
notify(direction, message) {
|
|
5
|
+
if (handlers.size === 0)
|
|
6
|
+
return;
|
|
7
|
+
for (const handler of handlers) {
|
|
8
|
+
try {
|
|
9
|
+
handler(message, direction);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// one rogue observer must not break siblings
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
subscribe(handler) {
|
|
17
|
+
handlers.add(handler);
|
|
18
|
+
return () => {
|
|
19
|
+
handlers.delete(handler);
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ShellMessage } from '../../domain/message/types';
|
|
2
|
+
import type { Transport } from '../../ports/transport';
|
|
3
|
+
import type { MessageObserver } from './observer';
|
|
4
|
+
import type { MessageHandler, MessageOf, MessageType } from './types';
|
|
5
|
+
export interface ParentChannel {
|
|
6
|
+
send(message: ShellMessage): void;
|
|
7
|
+
on<T extends MessageType>(type: T, handler: MessageHandler<MessageOf<T>>): () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface ParentChannelController extends ParentChannel {
|
|
10
|
+
dispose(): void;
|
|
11
|
+
}
|
|
12
|
+
export type ParentChannelConfig = {
|
|
13
|
+
transport: Transport;
|
|
14
|
+
observer: MessageObserver;
|
|
15
|
+
};
|
|
16
|
+
export declare function createParentChannel(config: ParentChannelConfig): ParentChannelController;
|
|
17
|
+
//# sourceMappingURL=parent-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parent-channel.d.ts","sourceRoot":"","sources":["../../../src/application/channels/parent-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAIrE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IACjC,EAAE,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAA;CACtF;AAED,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,eAAe,CAAA;CAC1B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,uBAAuB,CAqBxF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createMessageListeners } from './types';
|
|
2
|
+
export function createParentChannel(config) {
|
|
3
|
+
const { transport, observer } = config;
|
|
4
|
+
const listeners = createMessageListeners();
|
|
5
|
+
transport.onMessage((message) => {
|
|
6
|
+
observer.notify('inbound', message);
|
|
7
|
+
listeners.dispatch(message);
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
send(message) {
|
|
11
|
+
observer.notify('outbound', message);
|
|
12
|
+
transport.send(message);
|
|
13
|
+
},
|
|
14
|
+
on(type, handler) {
|
|
15
|
+
return listeners.on(type, handler);
|
|
16
|
+
},
|
|
17
|
+
dispose() {
|
|
18
|
+
transport.close();
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ShellMessage } from '../../domain/message/types';
|
|
2
|
+
export type MessageHandler<M extends ShellMessage = ShellMessage> = (message: M) => void;
|
|
3
|
+
export type MessageType = ShellMessage['type'];
|
|
4
|
+
export type MessageOf<T extends MessageType> = Extract<ShellMessage, {
|
|
5
|
+
type: T;
|
|
6
|
+
}>;
|
|
7
|
+
export interface MessageListeners {
|
|
8
|
+
on<T extends MessageType>(type: T, handler: MessageHandler<MessageOf<T>>): () => void;
|
|
9
|
+
dispatch(message: ShellMessage): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function createMessageListeners(): MessageListeners;
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/application/channels/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAA;AAExF,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;AAE9C,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAA;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAA;IACrF,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CACtC;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,CA2BzD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function createMessageListeners() {
|
|
2
|
+
const byType = new Map();
|
|
3
|
+
return {
|
|
4
|
+
on(type, handler) {
|
|
5
|
+
let bucket = byType.get(type);
|
|
6
|
+
if (!bucket) {
|
|
7
|
+
bucket = new Set();
|
|
8
|
+
byType.set(type, bucket);
|
|
9
|
+
}
|
|
10
|
+
bucket.add(handler);
|
|
11
|
+
return () => {
|
|
12
|
+
bucket.delete(handler);
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
dispatch(message) {
|
|
16
|
+
const bucket = byType.get(message.type);
|
|
17
|
+
if (!bucket || bucket.size === 0)
|
|
18
|
+
return;
|
|
19
|
+
for (const handler of bucket) {
|
|
20
|
+
try {
|
|
21
|
+
handler(message);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// one rogue handler must not break siblings
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IntentMiddleware } from '../../domain/intent/pipeline';
|
|
2
|
+
import type { IntentMessage } from '../../domain/message/types';
|
|
3
|
+
export interface IntentPipeline {
|
|
4
|
+
use(middleware: IntentMiddleware): void;
|
|
5
|
+
run(message: IntentMessage, terminate: () => Promise<void>): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Composable middleware runner. Each middleware may transform, short-circuit,
|
|
9
|
+
* or wrap the call to `next`. `terminate` is the innermost handler (router).
|
|
10
|
+
*/
|
|
11
|
+
export declare function createIntentPipeline(): IntentPipeline;
|
|
12
|
+
//# sourceMappingURL=pipeline-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-runner.d.ts","sourceRoot":"","sources":["../../../src/application/intent/pipeline-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE/D,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvC,GAAG,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAuBrD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composable middleware runner. Each middleware may transform, short-circuit,
|
|
3
|
+
* or wrap the call to `next`. `terminate` is the innermost handler (router).
|
|
4
|
+
*/
|
|
5
|
+
export function createIntentPipeline() {
|
|
6
|
+
const middlewares = [];
|
|
7
|
+
return {
|
|
8
|
+
use(middleware) {
|
|
9
|
+
middlewares.push(middleware);
|
|
10
|
+
},
|
|
11
|
+
async run(message, terminate) {
|
|
12
|
+
let i = -1;
|
|
13
|
+
const dispatch = async (idx) => {
|
|
14
|
+
if (idx <= i)
|
|
15
|
+
throw new Error('next() called multiple times');
|
|
16
|
+
i = idx;
|
|
17
|
+
if (idx === middlewares.length) {
|
|
18
|
+
await terminate();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const mw = middlewares[idx];
|
|
22
|
+
await mw(message, () => dispatch(idx + 1));
|
|
23
|
+
};
|
|
24
|
+
await dispatch(0);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IntentRegistry } from '../../domain/message/registries';
|
|
2
|
+
import type { IntentMessage } from '../../domain/message/types';
|
|
3
|
+
import type { ChildrenChannel } from '../channels/children-channel';
|
|
4
|
+
import type { ParentChannel } from '../channels/parent-channel';
|
|
5
|
+
import type { Shell } from '../shell';
|
|
6
|
+
/**
|
|
7
|
+
* Request/reply over the otherwise fire-and-forget intent transport.
|
|
8
|
+
*
|
|
9
|
+
* The child raises `name` with a generated `correlationId` and awaits the
|
|
10
|
+
* `intentReply` whose payload echoes that id. The reply also re-enters the
|
|
11
|
+
* child router as an inbound parent intent, but no handler is registered for
|
|
12
|
+
* `intentReply`, so it is harmlessly dropped there — only this resolver,
|
|
13
|
+
* matching by `correlationId`, consumes it.
|
|
14
|
+
*
|
|
15
|
+
* No timeout by design: callers (e.g. `requestAccess`) wait on a human, and
|
|
16
|
+
* the parent always replies on every path. A dead parent means the view is
|
|
17
|
+
* gone too, so a never-resolving promise cannot strand a live view.
|
|
18
|
+
*/
|
|
19
|
+
export declare function requestIntent<K extends keyof IntentRegistry>(parent: ParentChannel, name: K, payload: IntentRegistry[K]['payload']): Promise<IntentRegistry[K]['response']>;
|
|
20
|
+
/**
|
|
21
|
+
* Parent-side counterpart to `requestIntent`: deliver `result` back to the
|
|
22
|
+
* originating child window, keyed by the request's `correlationId`. No-op for
|
|
23
|
+
* a request that carried no `correlationId` (a fire-and-forget intent), or if
|
|
24
|
+
* the child window has since detached (best-effort, drops silently).
|
|
25
|
+
*/
|
|
26
|
+
export declare function replyToIntent<K extends keyof IntentRegistry>(children: ChildrenChannel, windowId: string, request: IntentMessage<K>, result: IntentRegistry[K]['response']): void;
|
|
27
|
+
/**
|
|
28
|
+
* View SDK convenience: ask the user (via the root shell) to grant `perms` on
|
|
29
|
+
* `node` to `identity`. Resolves with the bitmask actually granted (0 if the
|
|
30
|
+
* user declined or there was no parent to ask). The view typically passes its
|
|
31
|
+
* own identity (from `whoami`) as `identity`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function requestAccess(shell: Shell, opts: IntentRegistry['requestAccess']['payload']): Promise<IntentRegistry['requestAccess']['response']>;
|
|
34
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../src/application/intent/request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAErC;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,cAAc,EAC1D,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GACpC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAgBxC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,cAAc,EAC1D,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GACpC,IAAI,CAYN;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,GAC/C,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC,CAStD"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NO_PERMS } from '@astrale-os/kernel-core/permissions';
|
|
2
|
+
/**
|
|
3
|
+
* Request/reply over the otherwise fire-and-forget intent transport.
|
|
4
|
+
*
|
|
5
|
+
* The child raises `name` with a generated `correlationId` and awaits the
|
|
6
|
+
* `intentReply` whose payload echoes that id. The reply also re-enters the
|
|
7
|
+
* child router as an inbound parent intent, but no handler is registered for
|
|
8
|
+
* `intentReply`, so it is harmlessly dropped there — only this resolver,
|
|
9
|
+
* matching by `correlationId`, consumes it.
|
|
10
|
+
*
|
|
11
|
+
* No timeout by design: callers (e.g. `requestAccess`) wait on a human, and
|
|
12
|
+
* the parent always replies on every path. A dead parent means the view is
|
|
13
|
+
* gone too, so a never-resolving promise cannot strand a live view.
|
|
14
|
+
*/
|
|
15
|
+
export function requestIntent(parent, name, payload) {
|
|
16
|
+
const correlationId = crypto.randomUUID();
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const off = parent.on('intent', (message) => {
|
|
19
|
+
if (message.envelope.name !== 'intentReply')
|
|
20
|
+
return;
|
|
21
|
+
const reply = message.envelope.payload;
|
|
22
|
+
if (reply.correlationId !== correlationId)
|
|
23
|
+
return;
|
|
24
|
+
off();
|
|
25
|
+
resolve(reply.result);
|
|
26
|
+
});
|
|
27
|
+
parent.send({
|
|
28
|
+
type: 'intent',
|
|
29
|
+
version: 1,
|
|
30
|
+
envelope: { name, payload, sender: { windowId: 'self' }, correlationId },
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parent-side counterpart to `requestIntent`: deliver `result` back to the
|
|
36
|
+
* originating child window, keyed by the request's `correlationId`. No-op for
|
|
37
|
+
* a request that carried no `correlationId` (a fire-and-forget intent), or if
|
|
38
|
+
* the child window has since detached (best-effort, drops silently).
|
|
39
|
+
*/
|
|
40
|
+
export function replyToIntent(children, windowId, request, result) {
|
|
41
|
+
const correlationId = request.envelope.correlationId;
|
|
42
|
+
if (!correlationId)
|
|
43
|
+
return;
|
|
44
|
+
children.send(windowId, {
|
|
45
|
+
type: 'intent',
|
|
46
|
+
version: 1,
|
|
47
|
+
envelope: {
|
|
48
|
+
name: 'intentReply',
|
|
49
|
+
payload: { correlationId, result },
|
|
50
|
+
sender: { windowId: 'root' },
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* View SDK convenience: ask the user (via the root shell) to grant `perms` on
|
|
56
|
+
* `node` to `identity`. Resolves with the bitmask actually granted (0 if the
|
|
57
|
+
* user declined or there was no parent to ask). The view typically passes its
|
|
58
|
+
* own identity (from `whoami`) as `identity`.
|
|
59
|
+
*/
|
|
60
|
+
export function requestAccess(shell, opts) {
|
|
61
|
+
if (!shell.parent)
|
|
62
|
+
return Promise.resolve({ granted: false, perms: NO_PERMS });
|
|
63
|
+
// A sandboxed shell's `parent` is a proxy that throws until `init()` resolves;
|
|
64
|
+
// honour the documented "no parent to ask" fallback instead of surfacing that
|
|
65
|
+
// opaque rejection to the caller.
|
|
66
|
+
return requestIntent(shell.parent, 'requestAccess', opts).catch(() => ({
|
|
67
|
+
granted: false,
|
|
68
|
+
perms: NO_PERMS,
|
|
69
|
+
}));
|
|
70
|
+
}
|