@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,56 @@
|
|
|
1
|
+
import type { IntentRegistry } from '../../domain/message/registries';
|
|
2
|
+
import type { IntentMessage } from '../../domain/message/types';
|
|
3
|
+
import type { Window } from '../../domain/window/window';
|
|
4
|
+
import type { ChildrenChannelController } from '../channels/children-channel';
|
|
5
|
+
import type { ParentChannel } from '../channels/parent-channel';
|
|
6
|
+
import type { IntentPipeline } from './pipeline-runner';
|
|
7
|
+
/**
|
|
8
|
+
* Decision produced by a routing policy: where an inbound intent should go.
|
|
9
|
+
*
|
|
10
|
+
* - `local`: the shell (entry point or sandboxed parent) handles it itself.
|
|
11
|
+
* - `parent`: forward upstream (only valid for sandboxed children).
|
|
12
|
+
* - `child`: forward to a specific child window.
|
|
13
|
+
* - `drop`: discard silently (e.g., handled fully by a middleware).
|
|
14
|
+
*/
|
|
15
|
+
export type IntentDecision<K extends keyof IntentRegistry = keyof IntentRegistry> = {
|
|
16
|
+
kind: 'local';
|
|
17
|
+
message: IntentMessage<K>;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'parent';
|
|
20
|
+
message: IntentMessage<K>;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'child';
|
|
23
|
+
windowId: string;
|
|
24
|
+
message: IntentMessage<K>;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'drop';
|
|
27
|
+
};
|
|
28
|
+
export type IntentOrigin = 'parent' | 'child' | 'self';
|
|
29
|
+
export type IntentPolicy = (message: IntentMessage, ctx: IntentContext) => IntentDecision | Promise<IntentDecision>;
|
|
30
|
+
export interface IntentContext {
|
|
31
|
+
readonly selfWindowId: string;
|
|
32
|
+
readonly origin: IntentOrigin;
|
|
33
|
+
readonly windows: ReadonlyMap<string, Window>;
|
|
34
|
+
readonly hasParent: boolean;
|
|
35
|
+
}
|
|
36
|
+
export type LocalIntentHandler<K extends keyof IntentRegistry = keyof IntentRegistry> = (message: IntentMessage<K>) => void | Promise<void>;
|
|
37
|
+
export interface IntentRouterController {
|
|
38
|
+
/**
|
|
39
|
+
* Route an inbound intent. `senderWindowId`, when provided, is the trusted
|
|
40
|
+
* windowId the channel authenticated; it overrides the (forgeable)
|
|
41
|
+
* `envelope.sender.windowId` before the pipeline runs.
|
|
42
|
+
*/
|
|
43
|
+
handleInbound(origin: IntentOrigin, message: IntentMessage, senderWindowId?: string): Promise<void>;
|
|
44
|
+
dispatchLocal(message: IntentMessage): Promise<void>;
|
|
45
|
+
setPolicy(policy: IntentPolicy): void;
|
|
46
|
+
onLocal<K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>): () => void;
|
|
47
|
+
}
|
|
48
|
+
export type IntentRouterConfig = {
|
|
49
|
+
selfWindowId: string;
|
|
50
|
+
pipeline: IntentPipeline;
|
|
51
|
+
parent: ParentChannel | null;
|
|
52
|
+
children: ChildrenChannelController;
|
|
53
|
+
windows: ReadonlyMap<string, Window>;
|
|
54
|
+
};
|
|
55
|
+
export declare function createIntentRouter(config: IntentRouterConfig): IntentRouterController;
|
|
56
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/application/intent/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,cAAc,GAAG,MAAM,cAAc,IAC5E;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpB,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,aAAa,KACf,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,MAAM,cAAc,GAAG,MAAM,cAAc,IAAI,CACtF,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,KACtB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAEzB,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,aAAa,CACX,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IACrC,OAAO,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAA;CAC7F;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,yBAAyB,CAAA;IACnC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC,CAAA;AAuBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,sBAAsB,CA4ErF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default policy:
|
|
3
|
+
* - intent arrives from a child → handle locally
|
|
4
|
+
* - intent arrives from the parent → handle locally
|
|
5
|
+
* - intent initiated locally (self) → send up to parent if we have one,
|
|
6
|
+
* otherwise keep it local
|
|
7
|
+
*/
|
|
8
|
+
function defaultPolicy() {
|
|
9
|
+
return (message, ctx) => {
|
|
10
|
+
if (ctx.origin === 'self') {
|
|
11
|
+
return ctx.hasParent ? { kind: 'parent', message } : { kind: 'local', message };
|
|
12
|
+
}
|
|
13
|
+
return { kind: 'local', message };
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Return a copy of `message` with a trusted `sender.windowId`. */
|
|
17
|
+
function withSender(message, windowId) {
|
|
18
|
+
return { ...message, envelope: { ...message.envelope, sender: { windowId } } };
|
|
19
|
+
}
|
|
20
|
+
export function createIntentRouter(config) {
|
|
21
|
+
const { pipeline, parent, children, windows, selfWindowId } = config;
|
|
22
|
+
let policy = defaultPolicy();
|
|
23
|
+
const localHandlers = new Map();
|
|
24
|
+
async function forward(decision) {
|
|
25
|
+
switch (decision.kind) {
|
|
26
|
+
case 'drop':
|
|
27
|
+
return;
|
|
28
|
+
case 'parent':
|
|
29
|
+
if (!parent)
|
|
30
|
+
return;
|
|
31
|
+
parent.send(decision.message);
|
|
32
|
+
return;
|
|
33
|
+
case 'child':
|
|
34
|
+
if (!children.has(decision.windowId))
|
|
35
|
+
return;
|
|
36
|
+
children.send(decision.windowId, decision.message);
|
|
37
|
+
return;
|
|
38
|
+
case 'local': {
|
|
39
|
+
const bucket = localHandlers.get(decision.message.envelope.name);
|
|
40
|
+
if (!bucket)
|
|
41
|
+
return;
|
|
42
|
+
for (const handler of bucket) {
|
|
43
|
+
try {
|
|
44
|
+
await handler(decision.message);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Handler errors must not interrupt sibling handlers.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const ctxBase = {
|
|
55
|
+
selfWindowId,
|
|
56
|
+
windows,
|
|
57
|
+
hasParent: parent !== null,
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
async handleInbound(origin, message, senderWindowId) {
|
|
61
|
+
// `envelope.sender.windowId` is attacker-controlled wire data: a sandboxed
|
|
62
|
+
// child can claim any id. Overwrite it with the windowId the channel
|
|
63
|
+
// authenticated so capability/rate-limit middleware can't be bypassed by a
|
|
64
|
+
// forged sender. The original message object is left untouched for raw
|
|
65
|
+
// channel listeners; only this routed copy carries the trusted id.
|
|
66
|
+
const inbound = senderWindowId === undefined ? message : withSender(message, senderWindowId);
|
|
67
|
+
await pipeline.run(inbound, async () => {
|
|
68
|
+
const decision = await policy(inbound, { ...ctxBase, origin });
|
|
69
|
+
await forward(decision);
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
async dispatchLocal(message) {
|
|
73
|
+
const stamped = withSender(message, selfWindowId);
|
|
74
|
+
await pipeline.run(stamped, async () => {
|
|
75
|
+
const decision = await policy(stamped, { ...ctxBase, origin: 'self' });
|
|
76
|
+
await forward(decision);
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
setPolicy(next) {
|
|
80
|
+
policy = next;
|
|
81
|
+
},
|
|
82
|
+
onLocal(name, handler) {
|
|
83
|
+
let bucket = localHandlers.get(name);
|
|
84
|
+
if (!bucket) {
|
|
85
|
+
bucket = new Set();
|
|
86
|
+
localHandlers.set(name, bucket);
|
|
87
|
+
}
|
|
88
|
+
bucket.add(handler);
|
|
89
|
+
return () => {
|
|
90
|
+
bucket.delete(handler);
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { KernelClientAdapter } from '../adapters/kernel-client.adapter';
|
|
2
|
+
import type { DelegationToken } from '../domain/identity/delegation';
|
|
3
|
+
import type { KernelClient } from '../ports/kernel-client';
|
|
4
|
+
export type KernelFacadeConfig = {
|
|
5
|
+
adapter: KernelClientAdapter;
|
|
6
|
+
credential?: string;
|
|
7
|
+
getCredential?: () => string;
|
|
8
|
+
/** Kernel URL — exposed to consumers via `KernelClient.url`. */
|
|
9
|
+
url: string;
|
|
10
|
+
/**
|
|
11
|
+
* Explicit identity node to use as the mint subject. When omitted, the mint
|
|
12
|
+
* resolves the AUTHENTICATED caller via `whoami` (so a cookie-fronted GUI mints
|
|
13
|
+
* as the logged-in user, not the kernel root). Set this only to pin a subject.
|
|
14
|
+
*/
|
|
15
|
+
mintIdentity?: string;
|
|
16
|
+
};
|
|
17
|
+
export interface KernelFacadeController extends KernelClient {
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function createKernelFacade(config: KernelFacadeConfig): KernelFacadeController;
|
|
21
|
+
export type DelegationKernelFacadeConfig = {
|
|
22
|
+
adapter: KernelClientAdapter;
|
|
23
|
+
initialToken: DelegationToken;
|
|
24
|
+
url: string;
|
|
25
|
+
onTokenChange?: (token: DelegationToken) => void;
|
|
26
|
+
mintIdentity?: string;
|
|
27
|
+
};
|
|
28
|
+
export interface DelegationKernelFacadeController extends KernelFacadeController {
|
|
29
|
+
token(): DelegationToken;
|
|
30
|
+
setToken(next: DelegationToken): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function createDelegationKernelFacade(config: DelegationKernelFacadeConfig): DelegationKernelFacadeController;
|
|
33
|
+
//# sourceMappingURL=kernel-facade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-facade.d.ts","sourceRoot":"","sources":["../../src/application/kernel-facade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAO1D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,mBAAmB,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,MAAM,CAAA;IAC5B,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,WAAW,sBAAuB,SAAQ,YAAY;IAC1D,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,sBAAsB,CAerF;AAID,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,mBAAmB,CAAA;IAC5B,YAAY,EAAE,eAAe,CAAA;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IAChD,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,WAAW,gCAAiC,SAAQ,sBAAsB;IAC9E,KAAK,IAAI,eAAe,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAA;CACtC;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,4BAA4B,GACnC,gCAAgC,CA0BlC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { identityMethod } from '../domain/identity/path';
|
|
2
|
+
import { createSelfResolver, WHOAMI_PATH } from '../domain/identity/resolve-self';
|
|
3
|
+
export function createKernelFacade(config) {
|
|
4
|
+
const getCredential = config.getCredential ??
|
|
5
|
+
(() => {
|
|
6
|
+
if (config.credential === undefined) {
|
|
7
|
+
throw new Error('KernelFacade requires credential or getCredential');
|
|
8
|
+
}
|
|
9
|
+
return config.credential;
|
|
10
|
+
});
|
|
11
|
+
return buildCredentialFacade({
|
|
12
|
+
adapter: config.adapter,
|
|
13
|
+
getCredential,
|
|
14
|
+
url: config.url,
|
|
15
|
+
mintIdentity: config.mintIdentity,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function createDelegationKernelFacade(config) {
|
|
19
|
+
let token = config.initialToken;
|
|
20
|
+
const base = buildCredentialFacade({
|
|
21
|
+
adapter: config.adapter,
|
|
22
|
+
getCredential: () => token.credential,
|
|
23
|
+
url: config.url,
|
|
24
|
+
mintIdentity: config.mintIdentity,
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
token() {
|
|
29
|
+
return token;
|
|
30
|
+
},
|
|
31
|
+
setToken(next) {
|
|
32
|
+
if (next.credential === token.credential && next.expiresAt === token.expiresAt)
|
|
33
|
+
return;
|
|
34
|
+
token = next;
|
|
35
|
+
config.onTokenChange?.(next);
|
|
36
|
+
},
|
|
37
|
+
// The delegation facade owns its adapter exclusively (created per child in
|
|
38
|
+
// `performChildInit`), so disposing it must tear the adapter down — unlike
|
|
39
|
+
// the standalone facade, where the caller owns the adapter's lifetime.
|
|
40
|
+
dispose() {
|
|
41
|
+
config.adapter.disconnect();
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* v1 note: we mint a fresh identity credential (grant-bearing, directly
|
|
47
|
+
* usable). Proper delegation composition (outer envelope via `Cred(...)`)
|
|
48
|
+
* requires SDK v2 — tracked in `.dev/todo/shell-sdk-v2-delegation.md`.
|
|
49
|
+
*/
|
|
50
|
+
function buildCredentialFacade(params) {
|
|
51
|
+
const { adapter, getCredential, mintIdentity, url } = params;
|
|
52
|
+
const resolveSelf = createSelfResolver();
|
|
53
|
+
return {
|
|
54
|
+
url,
|
|
55
|
+
call(path, callParams) {
|
|
56
|
+
return adapter.call(path, callParams, getCredential());
|
|
57
|
+
},
|
|
58
|
+
async mintDelegation() {
|
|
59
|
+
const subject = mintIdentity ?? (await resolveSelf(() => adapter.call(WHOAMI_PATH, {}, getCredential())));
|
|
60
|
+
const result = await adapter.call(identityMethod(subject, 'mintIdentityCredential'), {}, getCredential());
|
|
61
|
+
if (typeof result === 'string')
|
|
62
|
+
return result;
|
|
63
|
+
if (result !== null &&
|
|
64
|
+
typeof result === 'object' &&
|
|
65
|
+
typeof result.credential === 'string') {
|
|
66
|
+
return result.credential;
|
|
67
|
+
}
|
|
68
|
+
throw new Error('mintIdentityCredential returned an unexpected shape');
|
|
69
|
+
},
|
|
70
|
+
authToken() {
|
|
71
|
+
return getCredential();
|
|
72
|
+
},
|
|
73
|
+
dispose() {
|
|
74
|
+
// adapter lifetime is owned by the caller
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { FunctionSchema } from '@astrale-os/kernel-client';
|
|
2
|
+
import type { IntentMiddleware } from '../domain/intent/pipeline';
|
|
3
|
+
import type { IntentRegistry } from '../domain/message/registries';
|
|
4
|
+
import type { WindowCapabilities, SandboxProfile } from '../domain/window/config';
|
|
5
|
+
import type { Window } from '../domain/window/window';
|
|
6
|
+
import type { KernelClient } from '../ports/kernel-client';
|
|
7
|
+
import type { ShellAdapter } from '../ports/shell-adapter';
|
|
8
|
+
import type { ChildrenChannel } from './channels/children-channel';
|
|
9
|
+
import type { ObserverHandler } from './channels/observer';
|
|
10
|
+
import type { ParentChannel } from './channels/parent-channel';
|
|
11
|
+
import type { LocalIntentHandler } from './intent/router';
|
|
12
|
+
export type { MessageDirection, ObserverHandler } from './channels/observer';
|
|
13
|
+
export type { ParentChannel } from './channels/parent-channel';
|
|
14
|
+
export type { ChildrenChannel } from './channels/children-channel';
|
|
15
|
+
export type { MessageHandler } from './channels/types';
|
|
16
|
+
export type MountParams = {
|
|
17
|
+
readonly host: HTMLElement;
|
|
18
|
+
readonly url: string;
|
|
19
|
+
readonly functionId: string;
|
|
20
|
+
readonly capabilities: WindowCapabilities;
|
|
21
|
+
readonly targetNodeId?: string;
|
|
22
|
+
/** See `WindowConfig.sandbox` — `null` opts the iframe out of the attribute. */
|
|
23
|
+
readonly sandbox?: SandboxProfile | null;
|
|
24
|
+
readonly handshakeTimeoutMs?: number;
|
|
25
|
+
readonly delegationTtlSeconds?: number;
|
|
26
|
+
/**
|
|
27
|
+
* `'shell'` (default) runs the parent-side handshake, opening a MessagePort
|
|
28
|
+
* transport with delegation tokens and an intent channel. `'none'` mounts a
|
|
29
|
+
* plain iframe with no transport — used for hosted views that don't speak
|
|
30
|
+
* the shell protocol (`BlaxelComputer.deployFunction(kind:'view')`), so they
|
|
31
|
+
* render immediately instead of paying the 5 s handshake timeout. In `'none'`
|
|
32
|
+
* mode `capabilities` / `targetNodeId` / `delegationTtlSeconds` are inert
|
|
33
|
+
* (no channel to deliver them).
|
|
34
|
+
*/
|
|
35
|
+
readonly handshake?: 'shell' | 'none';
|
|
36
|
+
};
|
|
37
|
+
export type CloseResult = {
|
|
38
|
+
kind: 'closed';
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'refused';
|
|
41
|
+
reason?: string;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'noop';
|
|
44
|
+
};
|
|
45
|
+
export type MountedWindow = {
|
|
46
|
+
readonly windowId: string;
|
|
47
|
+
readonly window: Window;
|
|
48
|
+
readonly handle: {
|
|
49
|
+
element: HTMLIFrameElement | HTMLElement;
|
|
50
|
+
};
|
|
51
|
+
close(opts?: {
|
|
52
|
+
force?: boolean;
|
|
53
|
+
timeoutMs?: number;
|
|
54
|
+
}): Promise<CloseResult>;
|
|
55
|
+
};
|
|
56
|
+
export interface Shell {
|
|
57
|
+
readonly parent: ParentChannel | null;
|
|
58
|
+
readonly children: ChildrenChannel;
|
|
59
|
+
readonly kernel: KernelClient;
|
|
60
|
+
readonly windows: ReadonlyMap<string, Window>;
|
|
61
|
+
readonly targetNodeId?: string;
|
|
62
|
+
use(middleware: IntentMiddleware): Shell;
|
|
63
|
+
onMessage(handler: ObserverHandler): () => void;
|
|
64
|
+
onIntent<K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>): () => void;
|
|
65
|
+
mount(params: MountParams): Promise<MountedWindow>;
|
|
66
|
+
close(windowId: string, opts?: {
|
|
67
|
+
force?: boolean;
|
|
68
|
+
timeoutMs?: number;
|
|
69
|
+
}): Promise<CloseResult>;
|
|
70
|
+
init(): Promise<Shell>;
|
|
71
|
+
dispose(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export type StandaloneConfig = {
|
|
74
|
+
readonly mode: 'standalone';
|
|
75
|
+
readonly kernelUrl: string;
|
|
76
|
+
readonly credential?: string;
|
|
77
|
+
readonly getCredential?: () => string;
|
|
78
|
+
readonly mintIdentity?: string;
|
|
79
|
+
readonly schema?: FunctionSchema[];
|
|
80
|
+
readonly adapter?: ShellAdapter;
|
|
81
|
+
readonly windowId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Optional wire-layer `fetch` shim for the kernel transport. A cookie-fronted
|
|
84
|
+
* SPA passes a wrapper adding `credentials:'include'` + refresh-on-401 and an
|
|
85
|
+
* empty `credential`, so a router unseals the cookie into a Bearer (the SPA
|
|
86
|
+
* holds no token). Omit it (the default) for token-based callers — including
|
|
87
|
+
* every sandboxed child view, which authenticates with a delegation token.
|
|
88
|
+
*/
|
|
89
|
+
readonly fetch?: typeof fetch;
|
|
90
|
+
};
|
|
91
|
+
export type SandboxedConfig = {
|
|
92
|
+
readonly mode: 'sandboxed';
|
|
93
|
+
readonly schema?: FunctionSchema[];
|
|
94
|
+
readonly parentWindow?: WindowProxy;
|
|
95
|
+
readonly initTimeoutMs?: number;
|
|
96
|
+
readonly adapter?: ShellAdapter;
|
|
97
|
+
};
|
|
98
|
+
export type ShellConfig = StandaloneConfig | SandboxedConfig;
|
|
99
|
+
export declare function createShell(config: ShellConfig): Shell;
|
|
100
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/application/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,KAAK,EAAE,eAAe,EAA6B,MAAM,6BAA6B,CAAA;AAC7F,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAA2B,MAAM,2BAA2B,CAAA;AAEvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAiBzD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC5E,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,YAAY,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAClE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAItD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAA;IACzC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IACxC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,iBAAiB,GAAG,WAAW,CAAA;KAAE,CAAA;IAC7D,KAAK,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAC5E,CAAA;AAID,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAE9B,GAAG,CAAC,UAAU,EAAE,gBAAgB,GAAG,KAAK,CAAA;IACxC,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,IAAI,CAAA;IAC/C,QAAQ,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAA;IAE7F,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAClD,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAE7F,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IACtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAID,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAA;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAA;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,eAAe,CAAA;AAQ5D,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAEtD"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { createKernelClientAdapter } from '../adapters/kernel-client.adapter';
|
|
2
|
+
import { createChildrenChannel } from './channels/children-channel';
|
|
3
|
+
import { createMessageObserver } from './channels/observer';
|
|
4
|
+
import { createIntentPipeline } from './intent/pipeline-runner';
|
|
5
|
+
import { createIntentRouter } from './intent/router';
|
|
6
|
+
import { createKernelFacade } from './kernel-facade';
|
|
7
|
+
import { performChildInit } from './windowing/child-init';
|
|
8
|
+
import { createLifecycleController } from './windowing/lifecycle-controller';
|
|
9
|
+
import { createMountController } from './windowing/mount-controller';
|
|
10
|
+
const DEFAULT_ROOT_WINDOW_ID = 'root';
|
|
11
|
+
/** Trusted sender id stamped on intents arriving from the parent channel. */
|
|
12
|
+
const PARENT_SENDER_ID = '<parent>';
|
|
13
|
+
// ─── Factory ────────────────────────────────────────────────────────────────
|
|
14
|
+
export function createShell(config) {
|
|
15
|
+
return config.mode === 'standalone' ? createStandaloneShell(config) : createSandboxedShell(config);
|
|
16
|
+
}
|
|
17
|
+
// ─── Standalone ─────────────────────────────────────────────────────────────
|
|
18
|
+
function createStandaloneShell(config) {
|
|
19
|
+
const windowId = config.windowId ?? DEFAULT_ROOT_WINDOW_ID;
|
|
20
|
+
const observer = createMessageObserver();
|
|
21
|
+
const getCredential = config.getCredential ??
|
|
22
|
+
(() => {
|
|
23
|
+
if (config.credential === undefined) {
|
|
24
|
+
throw new Error('Standalone shell requires credential or getCredential');
|
|
25
|
+
}
|
|
26
|
+
return config.credential;
|
|
27
|
+
});
|
|
28
|
+
const adapter = createKernelClientAdapter({
|
|
29
|
+
url: config.kernelUrl,
|
|
30
|
+
schema: config.schema,
|
|
31
|
+
getCredential,
|
|
32
|
+
mintIdentity: config.mintIdentity,
|
|
33
|
+
fetch: config.fetch,
|
|
34
|
+
});
|
|
35
|
+
const kernel = createKernelFacade({
|
|
36
|
+
adapter,
|
|
37
|
+
getCredential,
|
|
38
|
+
url: config.kernelUrl,
|
|
39
|
+
mintIdentity: config.mintIdentity,
|
|
40
|
+
});
|
|
41
|
+
const lifecycle = createLifecycleController();
|
|
42
|
+
const windows = new Map();
|
|
43
|
+
const childrenChannel = createChildrenChannel({
|
|
44
|
+
observer,
|
|
45
|
+
onDeliveryFailed: (toWindowId) => lifecycle.markCrashed(toWindowId),
|
|
46
|
+
});
|
|
47
|
+
const pipeline = createIntentPipeline();
|
|
48
|
+
const router = createIntentRouter({
|
|
49
|
+
selfWindowId: windowId,
|
|
50
|
+
pipeline,
|
|
51
|
+
parent: null,
|
|
52
|
+
children: childrenChannel,
|
|
53
|
+
windows,
|
|
54
|
+
});
|
|
55
|
+
childrenChannel.on('intent', (windowId, message) => {
|
|
56
|
+
void router.handleInbound('child', message, windowId);
|
|
57
|
+
});
|
|
58
|
+
let mountController = null;
|
|
59
|
+
function getMountController() {
|
|
60
|
+
if (mountController)
|
|
61
|
+
return mountController;
|
|
62
|
+
if (!config.adapter) {
|
|
63
|
+
throw new Error('Shell.mount() requires a ShellAdapter in the config');
|
|
64
|
+
}
|
|
65
|
+
mountController = createMountController({
|
|
66
|
+
adapter: config.adapter,
|
|
67
|
+
kernel,
|
|
68
|
+
kernelUrl: config.kernelUrl,
|
|
69
|
+
mintIdentity: config.mintIdentity,
|
|
70
|
+
childrenChannel,
|
|
71
|
+
windows,
|
|
72
|
+
lifecycle,
|
|
73
|
+
});
|
|
74
|
+
return mountController;
|
|
75
|
+
}
|
|
76
|
+
return buildShellFacade({
|
|
77
|
+
parent: null,
|
|
78
|
+
childrenChannel,
|
|
79
|
+
kernel,
|
|
80
|
+
pipeline,
|
|
81
|
+
observer,
|
|
82
|
+
lifecycle,
|
|
83
|
+
windows,
|
|
84
|
+
getMountController,
|
|
85
|
+
onIntent: (name, handler) => router.onLocal(name, handler),
|
|
86
|
+
onDispose: async () => {
|
|
87
|
+
if (mountController)
|
|
88
|
+
await mountController.dispose();
|
|
89
|
+
childrenChannel.dispose();
|
|
90
|
+
adapter.disconnect();
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function createSandboxedShell(config) {
|
|
95
|
+
const observer = createMessageObserver();
|
|
96
|
+
const lifecycle = createLifecycleController();
|
|
97
|
+
const windows = new Map();
|
|
98
|
+
const childrenChannel = createChildrenChannel({
|
|
99
|
+
observer,
|
|
100
|
+
onDeliveryFailed: (toWindowId) => lifecycle.markCrashed(toWindowId),
|
|
101
|
+
});
|
|
102
|
+
const pipeline = createIntentPipeline();
|
|
103
|
+
let state = null;
|
|
104
|
+
let router = null;
|
|
105
|
+
let mountController = null;
|
|
106
|
+
const pendingIntentSubs = [];
|
|
107
|
+
function subscribeIntent(name, handler) {
|
|
108
|
+
if (router)
|
|
109
|
+
return router.onLocal(name, handler);
|
|
110
|
+
const entry = {
|
|
111
|
+
name: name,
|
|
112
|
+
handler: handler,
|
|
113
|
+
unsubscribed: false,
|
|
114
|
+
};
|
|
115
|
+
pendingIntentSubs.push(entry);
|
|
116
|
+
return () => {
|
|
117
|
+
entry.unsubscribed = true;
|
|
118
|
+
// After init, `off` is the router's real disposer (set during replay);
|
|
119
|
+
// before init, flipping `unsubscribed` is enough (replay skips it).
|
|
120
|
+
entry.off?.();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const requireState = () => {
|
|
124
|
+
if (!state)
|
|
125
|
+
throw new Error('Sandboxed shell not initialized — call shell.init() first');
|
|
126
|
+
return state;
|
|
127
|
+
};
|
|
128
|
+
const kernelProxy = {
|
|
129
|
+
call: (path, params) => requireState().kernel.call(path, params),
|
|
130
|
+
mintDelegation: () => requireState().kernel.mintDelegation(),
|
|
131
|
+
authToken: () => requireState().kernel.authToken(),
|
|
132
|
+
get url() {
|
|
133
|
+
return requireState().kernel.url;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
const parentProxy = {
|
|
137
|
+
send: (message) => requireState().parent.send(message),
|
|
138
|
+
on: (type, handler) => requireState().parent.on(type, handler),
|
|
139
|
+
};
|
|
140
|
+
function getMountController() {
|
|
141
|
+
if (mountController)
|
|
142
|
+
return mountController;
|
|
143
|
+
if (!config.adapter) {
|
|
144
|
+
throw new Error('Shell.mount() requires a ShellAdapter in the config');
|
|
145
|
+
}
|
|
146
|
+
const current = requireState();
|
|
147
|
+
mountController = createMountController({
|
|
148
|
+
adapter: config.adapter,
|
|
149
|
+
kernel: kernelProxy,
|
|
150
|
+
kernelUrl: current.kernelUrl,
|
|
151
|
+
mintIdentity: current.mintIdentity,
|
|
152
|
+
childrenChannel,
|
|
153
|
+
windows,
|
|
154
|
+
lifecycle,
|
|
155
|
+
});
|
|
156
|
+
return mountController;
|
|
157
|
+
}
|
|
158
|
+
return buildShellFacade({
|
|
159
|
+
parent: parentProxy,
|
|
160
|
+
childrenChannel,
|
|
161
|
+
kernel: kernelProxy,
|
|
162
|
+
pipeline,
|
|
163
|
+
observer,
|
|
164
|
+
lifecycle,
|
|
165
|
+
windows,
|
|
166
|
+
getMountController,
|
|
167
|
+
targetNodeId: () => state?.targetNodeId,
|
|
168
|
+
onIntent: subscribeIntent,
|
|
169
|
+
init: async () => {
|
|
170
|
+
const result = await performChildInit({
|
|
171
|
+
parent: config.parentWindow ?? window.parent,
|
|
172
|
+
timeoutMs: config.initTimeoutMs,
|
|
173
|
+
schema: config.schema,
|
|
174
|
+
});
|
|
175
|
+
state = {
|
|
176
|
+
windowId: result.windowId,
|
|
177
|
+
kernelUrl: result.kernelUrl,
|
|
178
|
+
targetNodeId: result.targetNodeId,
|
|
179
|
+
mintIdentity: result.mintIdentity,
|
|
180
|
+
kernel: result.kernel,
|
|
181
|
+
parent: result.parentChannel(observer),
|
|
182
|
+
};
|
|
183
|
+
router = createIntentRouter({
|
|
184
|
+
selfWindowId: state.windowId,
|
|
185
|
+
pipeline,
|
|
186
|
+
parent: state.parent,
|
|
187
|
+
children: childrenChannel,
|
|
188
|
+
windows,
|
|
189
|
+
});
|
|
190
|
+
childrenChannel.on('intent', (windowId, message) => {
|
|
191
|
+
void router.handleInbound('child', message, windowId);
|
|
192
|
+
});
|
|
193
|
+
state.parent.on('intent', (message) => {
|
|
194
|
+
void router.handleInbound('parent', message, PARENT_SENDER_ID);
|
|
195
|
+
});
|
|
196
|
+
// Replay subscriptions registered before init. Capture each real disposer
|
|
197
|
+
// so a post-init unsubscribe (via the closure returned at subscribe time)
|
|
198
|
+
// actually removes the handler from the router instead of no-op'ing.
|
|
199
|
+
for (const entry of pendingIntentSubs) {
|
|
200
|
+
if (entry.unsubscribed)
|
|
201
|
+
continue;
|
|
202
|
+
entry.off = router.onLocal(entry.name, entry.handler);
|
|
203
|
+
}
|
|
204
|
+
pendingIntentSubs.length = 0;
|
|
205
|
+
},
|
|
206
|
+
onDispose: async () => {
|
|
207
|
+
if (mountController)
|
|
208
|
+
await mountController.dispose();
|
|
209
|
+
childrenChannel.dispose();
|
|
210
|
+
state?.parent.dispose();
|
|
211
|
+
state?.kernel.dispose();
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function buildShellFacade(deps) {
|
|
216
|
+
let disposed = false;
|
|
217
|
+
const shell = {
|
|
218
|
+
get parent() {
|
|
219
|
+
return deps.parent;
|
|
220
|
+
},
|
|
221
|
+
children: deps.childrenChannel,
|
|
222
|
+
kernel: deps.kernel,
|
|
223
|
+
windows: deps.windows,
|
|
224
|
+
get targetNodeId() {
|
|
225
|
+
return deps.targetNodeId?.();
|
|
226
|
+
},
|
|
227
|
+
use(middleware) {
|
|
228
|
+
deps.pipeline.use(middleware);
|
|
229
|
+
return shell;
|
|
230
|
+
},
|
|
231
|
+
onMessage(handler) {
|
|
232
|
+
return deps.observer.subscribe(handler);
|
|
233
|
+
},
|
|
234
|
+
onIntent(name, handler) {
|
|
235
|
+
return deps.onIntent(name, handler);
|
|
236
|
+
},
|
|
237
|
+
mount(params) {
|
|
238
|
+
return deps.getMountController().mount(params);
|
|
239
|
+
},
|
|
240
|
+
close(windowId, opts) {
|
|
241
|
+
return deps.getMountController().close(windowId, opts);
|
|
242
|
+
},
|
|
243
|
+
async init() {
|
|
244
|
+
if (deps.init)
|
|
245
|
+
await deps.init();
|
|
246
|
+
return shell;
|
|
247
|
+
},
|
|
248
|
+
async dispose() {
|
|
249
|
+
if (disposed)
|
|
250
|
+
return;
|
|
251
|
+
disposed = true;
|
|
252
|
+
await deps.onDispose();
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
return shell;
|
|
256
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FunctionSchema } from '@astrale-os/kernel-client';
|
|
2
|
+
import type { DelegationToken } from '../../domain/identity/delegation';
|
|
3
|
+
import type { MessageObserver } from '../channels/observer';
|
|
4
|
+
import type { ParentChannelController } from '../channels/parent-channel';
|
|
5
|
+
import { type DelegationKernelFacadeController } from '../kernel-facade';
|
|
6
|
+
export type ChildInitParams = {
|
|
7
|
+
readonly parent: WindowProxy;
|
|
8
|
+
readonly timeoutMs?: number;
|
|
9
|
+
readonly schema?: FunctionSchema[];
|
|
10
|
+
};
|
|
11
|
+
export type ChildInitResult = {
|
|
12
|
+
readonly windowId: string;
|
|
13
|
+
readonly parentChannel: (observer: MessageObserver) => ParentChannelController;
|
|
14
|
+
readonly kernel: DelegationKernelFacadeController;
|
|
15
|
+
readonly initialToken: DelegationToken;
|
|
16
|
+
readonly functionId: string;
|
|
17
|
+
readonly kernelUrl: string;
|
|
18
|
+
readonly targetNodeId?: string;
|
|
19
|
+
readonly mintIdentity?: string;
|
|
20
|
+
};
|
|
21
|
+
/** Child-side init: post INIT_REQUEST, receive MessagePort + ctrl:handshake, ack. */
|
|
22
|
+
export declare function performChildInit(params: ChildInitParams): Promise<ChildInitResult>;
|
|
23
|
+
//# sourceMappingURL=child-init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-init.d.ts","sourceRoot":"","sources":["../../../src/application/windowing/child-init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AAKzE,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,kBAAkB,CAAA;AAGzB,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,uBAAuB,CAAA;IAC9E,QAAQ,CAAC,MAAM,EAAE,gCAAgC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAA;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B,CAAA;AAID,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAoGlF"}
|