@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.
Files changed (145) hide show
  1. package/dist/adapters/iframe.shell-adapter.d.ts +10 -0
  2. package/dist/adapters/iframe.shell-adapter.d.ts.map +1 -0
  3. package/dist/adapters/iframe.shell-adapter.js +55 -0
  4. package/dist/adapters/kernel-client.adapter.d.ts +53 -0
  5. package/dist/adapters/kernel-client.adapter.d.ts.map +1 -0
  6. package/dist/adapters/kernel-client.adapter.js +69 -0
  7. package/dist/adapters/message-port.transport.d.ts +7 -0
  8. package/dist/adapters/message-port.transport.d.ts.map +1 -0
  9. package/dist/adapters/message-port.transport.js +42 -0
  10. package/dist/application/channels/children-channel.d.ts +21 -0
  11. package/dist/application/channels/children-channel.d.ts.map +1 -0
  12. package/dist/application/channels/children-channel.js +82 -0
  13. package/dist/application/channels/observer.d.ts +9 -0
  14. package/dist/application/channels/observer.d.ts.map +1 -0
  15. package/dist/application/channels/observer.js +23 -0
  16. package/dist/application/channels/parent-channel.d.ts +17 -0
  17. package/dist/application/channels/parent-channel.d.ts.map +1 -0
  18. package/dist/application/channels/parent-channel.js +21 -0
  19. package/dist/application/channels/types.d.ts +12 -0
  20. package/dist/application/channels/types.d.ts.map +1 -0
  21. package/dist/application/channels/types.js +29 -0
  22. package/dist/application/intent/pipeline-runner.d.ts +12 -0
  23. package/dist/application/intent/pipeline-runner.d.ts.map +1 -0
  24. package/dist/application/intent/pipeline-runner.js +27 -0
  25. package/dist/application/intent/request.d.ts +34 -0
  26. package/dist/application/intent/request.d.ts.map +1 -0
  27. package/dist/application/intent/request.js +70 -0
  28. package/dist/application/intent/router.d.ts +56 -0
  29. package/dist/application/intent/router.d.ts.map +1 -0
  30. package/dist/application/intent/router.js +94 -0
  31. package/dist/application/kernel-facade.d.ts +33 -0
  32. package/dist/application/kernel-facade.d.ts.map +1 -0
  33. package/dist/application/kernel-facade.js +77 -0
  34. package/dist/application/shell.d.ts +100 -0
  35. package/dist/application/shell.d.ts.map +1 -0
  36. package/dist/application/shell.js +256 -0
  37. package/dist/application/windowing/child-init.d.ts +23 -0
  38. package/dist/application/windowing/child-init.d.ts.map +1 -0
  39. package/dist/application/windowing/child-init.js +101 -0
  40. package/dist/application/windowing/handshake.d.ts +30 -0
  41. package/dist/application/windowing/handshake.d.ts.map +1 -0
  42. package/dist/application/windowing/handshake.js +63 -0
  43. package/dist/application/windowing/lifecycle-controller.d.ts +10 -0
  44. package/dist/application/windowing/lifecycle-controller.d.ts.map +1 -0
  45. package/dist/application/windowing/lifecycle-controller.js +30 -0
  46. package/dist/application/windowing/mount-controller.d.ts +25 -0
  47. package/dist/application/windowing/mount-controller.d.ts.map +1 -0
  48. package/dist/application/windowing/mount-controller.js +277 -0
  49. package/dist/application/windowing/protocol.d.ts +21 -0
  50. package/dist/application/windowing/protocol.d.ts.map +1 -0
  51. package/dist/application/windowing/protocol.js +19 -0
  52. package/dist/application/windowing/token-refresh.d.ts +19 -0
  53. package/dist/application/windowing/token-refresh.d.ts.map +1 -0
  54. package/dist/application/windowing/token-refresh.js +60 -0
  55. package/dist/domain/identity/delegation.d.ts +15 -0
  56. package/dist/domain/identity/delegation.d.ts.map +1 -0
  57. package/dist/domain/identity/delegation.js +12 -0
  58. package/dist/domain/identity/path.d.ts +11 -0
  59. package/dist/domain/identity/path.d.ts.map +1 -0
  60. package/dist/domain/identity/path.js +13 -0
  61. package/dist/domain/identity/resolve-self.d.ts +21 -0
  62. package/dist/domain/identity/resolve-self.d.ts.map +1 -0
  63. package/dist/domain/identity/resolve-self.js +36 -0
  64. package/dist/domain/intent/middleware/capabilities.d.ts +28 -0
  65. package/dist/domain/intent/middleware/capabilities.d.ts.map +1 -0
  66. package/dist/domain/intent/middleware/capabilities.js +18 -0
  67. package/dist/domain/intent/middleware/logging.d.ts +14 -0
  68. package/dist/domain/intent/middleware/logging.d.ts.map +1 -0
  69. package/dist/domain/intent/middleware/logging.js +21 -0
  70. package/dist/domain/intent/middleware/rate-limit.d.ts +20 -0
  71. package/dist/domain/intent/middleware/rate-limit.d.ts.map +1 -0
  72. package/dist/domain/intent/middleware/rate-limit.js +33 -0
  73. package/dist/domain/intent/pipeline.d.ts +3 -0
  74. package/dist/domain/intent/pipeline.d.ts.map +1 -0
  75. package/dist/domain/intent/pipeline.js +1 -0
  76. package/dist/domain/intent/router.d.ts +6 -0
  77. package/dist/domain/intent/router.d.ts.map +1 -0
  78. package/dist/domain/intent/router.js +1 -0
  79. package/dist/domain/message/errors.d.ts +2 -0
  80. package/dist/domain/message/errors.d.ts.map +1 -0
  81. package/dist/domain/message/errors.js +1 -0
  82. package/dist/domain/message/registries.d.ts +145 -0
  83. package/dist/domain/message/registries.d.ts.map +1 -0
  84. package/dist/domain/message/registries.js +1 -0
  85. package/dist/domain/message/types.d.ts +30 -0
  86. package/dist/domain/message/types.d.ts.map +1 -0
  87. package/dist/domain/message/types.js +1 -0
  88. package/dist/domain/window/config.d.ts +26 -0
  89. package/dist/domain/window/config.d.ts.map +1 -0
  90. package/dist/domain/window/config.js +1 -0
  91. package/dist/domain/window/lifecycle.d.ts +27 -0
  92. package/dist/domain/window/lifecycle.d.ts.map +1 -0
  93. package/dist/domain/window/lifecycle.js +36 -0
  94. package/dist/domain/window/window.d.ts +12 -0
  95. package/dist/domain/window/window.d.ts.map +1 -0
  96. package/dist/domain/window/window.js +1 -0
  97. package/dist/index.d.ts +42 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +20 -0
  100. package/dist/ports/kernel-client.d.ts +33 -0
  101. package/dist/ports/kernel-client.d.ts.map +1 -0
  102. package/dist/ports/kernel-client.js +1 -0
  103. package/dist/ports/shell-adapter.d.ts +13 -0
  104. package/dist/ports/shell-adapter.d.ts.map +1 -0
  105. package/dist/ports/shell-adapter.js +1 -0
  106. package/dist/ports/transport.d.ts +7 -0
  107. package/dist/ports/transport.d.ts.map +1 -0
  108. package/dist/ports/transport.js +1 -0
  109. package/package.json +41 -0
  110. package/src/adapters/iframe.shell-adapter.ts +68 -0
  111. package/src/adapters/kernel-client.adapter.ts +138 -0
  112. package/src/adapters/message-port.transport.ts +50 -0
  113. package/src/application/channels/children-channel.ts +112 -0
  114. package/src/application/channels/observer.ts +33 -0
  115. package/src/application/channels/parent-channel.ts +43 -0
  116. package/src/application/channels/types.ts +41 -0
  117. package/src/application/intent/pipeline-runner.ts +36 -0
  118. package/src/application/intent/request.ts +87 -0
  119. package/src/application/intent/router.ts +161 -0
  120. package/src/application/kernel-facade.ts +143 -0
  121. package/src/application/shell.ts +433 -0
  122. package/src/application/windowing/child-init.ts +137 -0
  123. package/src/application/windowing/handshake.ts +98 -0
  124. package/src/application/windowing/lifecycle-controller.ts +40 -0
  125. package/src/application/windowing/mount-controller.ts +335 -0
  126. package/src/application/windowing/protocol.ts +37 -0
  127. package/src/application/windowing/token-refresh.ts +78 -0
  128. package/src/domain/identity/delegation.ts +25 -0
  129. package/src/domain/identity/path.ts +14 -0
  130. package/src/domain/identity/resolve-self.ts +38 -0
  131. package/src/domain/intent/middleware/capabilities.ts +53 -0
  132. package/src/domain/intent/middleware/logging.ts +45 -0
  133. package/src/domain/intent/middleware/rate-limit.ts +54 -0
  134. package/src/domain/intent/pipeline.ts +3 -0
  135. package/src/domain/intent/router.ts +6 -0
  136. package/src/domain/message/errors.ts +9 -0
  137. package/src/domain/message/registries.ts +76 -0
  138. package/src/domain/message/types.ts +32 -0
  139. package/src/domain/window/config.ts +28 -0
  140. package/src/domain/window/lifecycle.ts +39 -0
  141. package/src/domain/window/window.ts +13 -0
  142. package/src/index.ts +103 -0
  143. package/src/ports/kernel-client.ts +32 -0
  144. package/src/ports/shell-adapter.ts +11 -0
  145. package/src/ports/transport.ts +7 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pure state-machine transition. Used by the lifecycle controller —
3
+ * no timers, no side effects here.
4
+ */
5
+ export function transition(state, event) {
6
+ switch (state) {
7
+ case 'active':
8
+ if (event.kind === 'hide')
9
+ return { ok: true, next: 'hidden' };
10
+ if (event.kind === 'requestClose')
11
+ return { ok: true, next: 'closing' };
12
+ if (event.kind === 'forceClose')
13
+ return { ok: true, next: 'closed' };
14
+ break;
15
+ case 'hidden':
16
+ if (event.kind === 'restore')
17
+ return { ok: true, next: 'active' };
18
+ if (event.kind === 'requestClose')
19
+ return { ok: true, next: 'closing' };
20
+ if (event.kind === 'forceClose')
21
+ return { ok: true, next: 'closed' };
22
+ break;
23
+ case 'closing':
24
+ if (event.kind === 'closeAck')
25
+ return { ok: true, next: 'closed' };
26
+ if (event.kind === 'closeRefuse')
27
+ return { ok: true, next: 'active' };
28
+ if (event.kind === 'forceClose')
29
+ return { ok: true, next: 'closed' };
30
+ break;
31
+ case 'closed':
32
+ // Terminal — no transitions out.
33
+ break;
34
+ }
35
+ return { ok: false, reason: `invalid transition ${state}.${event.kind}` };
36
+ }
@@ -0,0 +1,12 @@
1
+ import type { WindowCapabilities } from './config';
2
+ export type WindowState = 'active' | 'hidden' | 'closing' | 'closed';
3
+ export interface Window {
4
+ readonly windowId: string;
5
+ readonly functionId: string;
6
+ readonly targetNodeId?: string;
7
+ readonly isolation: 'sandboxed' | 'shared';
8
+ readonly state: WindowState;
9
+ readonly delegationToken: string | null;
10
+ readonly capabilities: WindowCapabilities;
11
+ }
12
+ //# sourceMappingURL=window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../../../src/domain/window/window.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAElD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEpE,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,QAAQ,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAA;CAC1C"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ export type { ErrorCode } from './domain/message/errors';
2
+ export type { IntentRegistry, CtrlRegistry, ViewRef } from './domain/message/registries';
3
+ export type { ShellMessage, IntentMessage, IntentEnvelope, CtrlMessage, ErrorMessage, } from './domain/message/types';
4
+ export type { WindowConfig, WindowCapabilities, SandboxProfile } from './domain/window/config';
5
+ export type { Window, WindowState } from './domain/window/window';
6
+ export { transition as lifecycleTransition } from './domain/window/lifecycle';
7
+ export type { LifecycleEvent, TransitionResult } from './domain/window/lifecycle';
8
+ export type { IntentMiddleware } from './domain/intent/pipeline';
9
+ export type { IntentRouter } from './domain/intent/router';
10
+ export { loggingMiddleware } from './domain/intent/middleware/logging';
11
+ export type { LoggingConfig, LoggingEntry, LoggingSink } from './domain/intent/middleware/logging';
12
+ export { capabilitiesMiddleware } from './domain/intent/middleware/capabilities';
13
+ export type { CapabilitiesConfig, CapabilityLookup, CapabilityViolation, } from './domain/intent/middleware/capabilities';
14
+ export { rateLimitMiddleware } from './domain/intent/middleware/rate-limit';
15
+ export type { RateLimitConfig, RateLimitViolation } from './domain/intent/middleware/rate-limit';
16
+ export type { DelegationScope, DelegationToken, TokenState } from './domain/identity/delegation';
17
+ export { stateOf, isExpired } from './domain/identity/delegation';
18
+ export type { Transport } from './ports/transport';
19
+ export type { ShellAdapter, WindowHandle } from './ports/shell-adapter';
20
+ export type { KernelClient } from './ports/kernel-client';
21
+ export { createKernelClientAdapter } from './adapters/kernel-client.adapter';
22
+ export type { KernelClientAdapter, CreateKernelClientAdapterOpts, } from './adapters/kernel-client.adapter';
23
+ export { createMessagePortTransport } from './adapters/message-port.transport';
24
+ export type { MessagePortTransportConfig } from './adapters/message-port.transport';
25
+ export { createIframeShellAdapter } from './adapters/iframe.shell-adapter';
26
+ export type { IframeShellAdapterConfig } from './adapters/iframe.shell-adapter';
27
+ export type { Shell, ShellConfig, StandaloneConfig, SandboxedConfig, MountParams, MountedWindow, CloseResult, ParentChannel, ChildrenChannel, MessageDirection, MessageHandler, ObserverHandler, } from './application/shell';
28
+ export { createShell } from './application/shell';
29
+ export { runParentHandshake } from './application/windowing/handshake';
30
+ export type { HandshakeParams, HandshakeError, HandshakeResult, } from './application/windowing/handshake';
31
+ export { performChildInit } from './application/windowing/child-init';
32
+ export type { ChildInitParams, ChildInitResult } from './application/windowing/child-init';
33
+ export { createTokenRefresher } from './application/windowing/token-refresh';
34
+ export type { TokenRefreshConfig, TokenRefresher } from './application/windowing/token-refresh';
35
+ export { createIntentPipeline } from './application/intent/pipeline-runner';
36
+ export type { IntentPipeline } from './application/intent/pipeline-runner';
37
+ export { createIntentRouter } from './application/intent/router';
38
+ export type { IntentRouterController, IntentRouterConfig, IntentDecision, IntentOrigin, IntentPolicy, IntentContext, LocalIntentHandler, } from './application/intent/router';
39
+ export { requestIntent, replyToIntent, requestAccess } from './application/intent/request';
40
+ export { INIT_REQUEST_TYPE, INIT_RESPONSE_TYPE, isInitRequest, isInitResponse, } from './application/windowing/protocol';
41
+ export type { InitRequestMessage, InitResponseMessage } from './application/windowing/protocol';
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ export { transition as lifecycleTransition } from './domain/window/lifecycle';
2
+ export { loggingMiddleware } from './domain/intent/middleware/logging';
3
+ export { capabilitiesMiddleware } from './domain/intent/middleware/capabilities';
4
+ export { rateLimitMiddleware } from './domain/intent/middleware/rate-limit';
5
+ export { stateOf, isExpired } from './domain/identity/delegation';
6
+ // ─── adapters ───────────────────────────────────────────────────────────────
7
+ export { createKernelClientAdapter } from './adapters/kernel-client.adapter';
8
+ export { createMessagePortTransport } from './adapters/message-port.transport';
9
+ export { createIframeShellAdapter } from './adapters/iframe.shell-adapter';
10
+ export { createShell } from './application/shell';
11
+ // ─── application/windowing (advanced / testing) ─────────────────────────────
12
+ export { runParentHandshake } from './application/windowing/handshake';
13
+ export { performChildInit } from './application/windowing/child-init';
14
+ export { createTokenRefresher } from './application/windowing/token-refresh';
15
+ // ─── application/intent (advanced / testing) ────────────────────────────────
16
+ export { createIntentPipeline } from './application/intent/pipeline-runner';
17
+ export { createIntentRouter } from './application/intent/router';
18
+ export { requestIntent, replyToIntent, requestAccess } from './application/intent/request';
19
+ // ─── Init-request protocol (shared with host pages) ─────────────────────────
20
+ export { INIT_REQUEST_TYPE, INIT_RESPONSE_TYPE, isInitRequest, isInitResponse, } from './application/windowing/protocol';
@@ -0,0 +1,33 @@
1
+ export interface KernelClient {
2
+ call(path: string, params: unknown): Promise<unknown>;
3
+ /**
4
+ * Produce a credential usable by a child shell.
5
+ *
6
+ * v1: returns a fresh identity credential scoped to the current principal.
7
+ * Future (SDK v2): will accept `{ functionId }` to scope the delegation
8
+ * to a specific Function — tracked in `.dev/todo/shell-sdk-v2-delegation.md`.
9
+ */
10
+ mintDelegation(): Promise<string>;
11
+ /**
12
+ * Return the JWT this shell currently uses to authenticate `call`. Iframes
13
+ * use it to make their own authenticated HTTP requests to the kernel —
14
+ * e.g. when a third-party SDK like Vercel AI's `useChat` issues a raw
15
+ * `fetch()` that can't run through `call()`. Callers form the Authorization
16
+ * header themselves: `Authorization: \`Bearer ${shell.kernel.authToken()}\``.
17
+ *
18
+ * The value is the *current* token; the parent shell auto-refreshes it
19
+ * before expiry, so always read fresh on each request rather than caching.
20
+ */
21
+ authToken(): string;
22
+ /**
23
+ * URL of the kernel this shell talks to. Pair with `authToken()` when an
24
+ * iframe needs to make a raw `fetch()` from a third-party SDK (e.g.
25
+ * Vercel AI's `useChat`): the kernel-issued JWT's `aud` matches the
26
+ * kernel's own issuer, so requests must target the kernel directly. If
27
+ * a sandboxed iframe POSTs to its own `window.location.origin` (the
28
+ * worker hosting the iframe), the worker's audience check rejects the
29
+ * cred with "Credential audience mismatch".
30
+ */
31
+ readonly url: string;
32
+ }
33
+ //# sourceMappingURL=kernel-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel-client.d.ts","sourceRoot":"","sources":["../../src/ports/kernel-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrD;;;;;;OAMG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACjC;;;;;;;;;OASG;IACH,SAAS,IAAI,MAAM,CAAA;IACnB;;;;;;;;OAQG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { WindowConfig } from '../domain/window/config';
2
+ export interface WindowHandle {
3
+ readonly element: HTMLIFrameElement | HTMLElement;
4
+ readonly port: MessagePort | null;
5
+ readonly origin: string;
6
+ }
7
+ export interface ShellAdapter {
8
+ mount(config: WindowConfig): {
9
+ handle: WindowHandle;
10
+ dispose: () => void;
11
+ };
12
+ }
13
+ //# sourceMappingURL=shell-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-adapter.d.ts","sourceRoot":"","sources":["../../src/ports/shell-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,CAAA;IACjD,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG;QAAE,MAAM,EAAE,YAAY,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CAC3E"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { ShellMessage } from '../domain/message/types';
2
+ export interface Transport {
3
+ send(message: ShellMessage): void;
4
+ onMessage(handler: (message: ShellMessage) => void): void;
5
+ close(): void;
6
+ }
7
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/ports/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IACjC,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAA;IACzD,KAAK,IAAI,IAAI,CAAA;CACd"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@astrale-os/shell",
3
+ "version": "0.1.0",
4
+ "description": "Astrale Shell SDK — parent/child/kernel communication",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/astrale-os/shell.git",
8
+ "directory": "packages/shell"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "src"
13
+ ],
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "publishConfig": {
24
+ "access": "restricted",
25
+ "registry": "https://npm.pkg.github.com"
26
+ },
27
+ "dependencies": {
28
+ "@astrale-os/kernel-client": ">=0.1.0 <1.0.0",
29
+ "@astrale-os/kernel-core": ">=0.4.1 <1.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@typescript/native-preview": "latest",
33
+ "typescript": "~6.0.1-rc"
34
+ },
35
+ "scripts": {
36
+ "build": "tsgo",
37
+ "clean": "rm -rf dist *.tsbuildinfo",
38
+ "typecheck": "tsgo --noEmit",
39
+ "preinstall": "node .check-workspace.cjs"
40
+ }
41
+ }
@@ -0,0 +1,68 @@
1
+ import type { SandboxProfile } from '../domain/window/config'
2
+ import type { ShellAdapter, WindowHandle } from '../ports/shell-adapter'
3
+
4
+ export type IframeShellAdapterConfig = {
5
+ /**
6
+ * Optional class list applied to every iframe created by this adapter.
7
+ * Useful for consistent styling without leaking styling into the SDK.
8
+ */
9
+ readonly className?: string
10
+ }
11
+
12
+ const DEFAULT_SANDBOX: SandboxProfile = {
13
+ allowScripts: true,
14
+ allowForms: false,
15
+ allowPopups: false,
16
+ allowSameOrigin: false,
17
+ allowModals: false,
18
+ }
19
+
20
+ export function createIframeShellAdapter(config: IframeShellAdapterConfig = {}): ShellAdapter {
21
+ return {
22
+ mount(windowConfig) {
23
+ if (windowConfig.isolation !== 'sandboxed') {
24
+ throw new Error(
25
+ `iframe adapter only supports sandboxed windows, got '${windowConfig.isolation}'`,
26
+ )
27
+ }
28
+
29
+ const element = document.createElement('iframe')
30
+ element.dataset.astraleFunctionId = windowConfig.functionId
31
+ if (config.className) element.className = config.className
32
+
33
+ // `sandbox: null` is an explicit opt-out — leave the iframe without a
34
+ // `sandbox` attribute (only cross-origin isolation applies). Used by
35
+ // the GUI for first-party trusted views (see trusted-view-origins).
36
+ if (windowConfig.sandbox !== null) {
37
+ const profile = windowConfig.sandbox ?? DEFAULT_SANDBOX
38
+ element.setAttribute('sandbox', sandboxAttrFor(profile))
39
+ }
40
+ element.setAttribute('allow', 'clipboard-read; clipboard-write')
41
+ element.setAttribute('loading', 'eager')
42
+ element.setAttribute('referrerpolicy', 'no-referrer')
43
+
44
+ const handle: WindowHandle = {
45
+ element,
46
+ port: null,
47
+ origin: '',
48
+ }
49
+
50
+ return {
51
+ handle,
52
+ dispose: () => {
53
+ element.remove()
54
+ },
55
+ }
56
+ },
57
+ }
58
+ }
59
+
60
+ function sandboxAttrFor(profile: SandboxProfile): string {
61
+ const tokens: string[] = []
62
+ if (profile.allowScripts) tokens.push('allow-scripts')
63
+ if (profile.allowForms) tokens.push('allow-forms')
64
+ if (profile.allowPopups) tokens.push('allow-popups')
65
+ if (profile.allowSameOrigin) tokens.push('allow-same-origin')
66
+ if (profile.allowModals) tokens.push('allow-modals')
67
+ return tokens.join(' ')
68
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ type CallOptions,
3
+ type CredentialInput,
4
+ type FnMap,
5
+ type FunctionSchema,
6
+ } from '@astrale-os/kernel-client'
7
+ import { ClientSession, type ClientSessionConfig } from '@astrale-os/kernel-client/session'
8
+
9
+ import { identityMethod } from '../domain/identity/path'
10
+ import { createSelfResolver, WHOAMI_PATH } from '../domain/identity/resolve-self'
11
+
12
+ // Untyped FnMap — the shell uses string paths, not typed method keys.
13
+ type AnyFnMap = FnMap
14
+
15
+ /**
16
+ * Low-level kernel adapter — wraps a `ClientSession` (the post-v2 orchestration
17
+ * layer above `KernelClient`).
18
+ *
19
+ * Methods take an explicit credential. The application-layer facade
20
+ * manages which credential to use.
21
+ *
22
+ * Remote-domain redirects are followed transparently: when the kernel
23
+ * responds with a `redirect` result (pointing at an external worker URL),
24
+ * the ClientSession mints a worker-scoped delegation credential — for the
25
+ * audience the kernel carries on the redirect (`CallRedirection.iss`, the
26
+ * target's own identity) — and re-dials the target URL with it. So
27
+ * `shell.kernel.call(...)` "just works" for methods whose Function has a
28
+ * `binding.remoteUrl`.
29
+ */
30
+ export type KernelClientAdapter = {
31
+ call(
32
+ method: string,
33
+ params: unknown,
34
+ credential: CredentialInput,
35
+ opts?: CallOptions,
36
+ ): Promise<unknown>
37
+ load(schemas: FunctionSchema[]): void
38
+ disconnect(): void
39
+ readonly url: string
40
+ }
41
+
42
+ export type CreateKernelClientAdapterOpts = {
43
+ url: string
44
+ schema?: FunctionSchema[]
45
+ requestTimeout?: number
46
+ /**
47
+ * Returns the current base credential. When provided, the adapter wires a
48
+ * `delegation: { mint }` on the ClientSession so cross-origin redirects
49
+ * (kernel → worker) are followed with a fresh worker-scoped delegation
50
+ * minted from the active identity. Without it,
51
+ * ClientSession falls back to the base credential and workers reject
52
+ * with `Credential audience mismatch`.
53
+ */
54
+ 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
+ /**
64
+ * Wire-layer `fetch` shim, forwarded to the underlying `KernelClient`s.
65
+ * Lets a same-origin SPA inject `credentials:'include'` + refresh-on-401 so
66
+ * a router can unseal a cookie into a Bearer (the parent holds no token).
67
+ * Default: global fetch — sandboxed child views never set it.
68
+ */
69
+ fetch?: typeof fetch
70
+ }
71
+
72
+ export function createKernelClientAdapter(
73
+ opts: CreateKernelClientAdapterOpts,
74
+ ): KernelClientAdapter {
75
+ let session: ClientSession<AnyFnMap> | null = null
76
+ const config: ClientSessionConfig<AnyFnMap> = {
77
+ default: opts.url,
78
+ // Identity is supplied per-call via `opts.credential`; no picker bound.
79
+ // No `delegationPolicy` → ClientSession's default `allowAllDelegations`
80
+ // mints for the audience the kernel carries on the redirect
81
+ // (`CallRedirection.iss`), which is exactly what the target worker verifies.
82
+ identity: undefined,
83
+ }
84
+ if (opts.schema) config.schema = opts.schema
85
+ if (opts.fetch) config.fetch = opts.fetch
86
+ if (opts.getCredential) {
87
+ const getCred = opts.getCredential
88
+ const explicitMintIdentity = opts.mintIdentity
89
+ const resolveSelf = createSelfResolver()
90
+ config.delegation = {
91
+ mint: async (audience, delegation) => {
92
+ if (!session) throw new Error('ClientSession not initialised')
93
+ const s = session
94
+ const subject =
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
+ },
109
+ }
110
+ }
111
+ session = new ClientSession<AnyFnMap>(config)
112
+ const ready = session
113
+
114
+ return {
115
+ url: opts.url,
116
+
117
+ call(method, params, credential, callOpts) {
118
+ const sessionOpts: Parameters<typeof ready.call>[2] = { credential }
119
+ if (callOpts?.timeout !== undefined) sessionOpts.timeout = callOpts.timeout
120
+ if (callOpts?.signal !== undefined) sessionOpts.signal = callOpts.signal
121
+ if (callOpts?.class !== undefined) sessionOpts.class = callOpts.class
122
+ if (callOpts?.transport !== undefined) sessionOpts.transport = callOpts.transport
123
+ if (callOpts?.protocol !== undefined) sessionOpts.protocol = callOpts.protocol
124
+ if (callOpts?.correlationId !== undefined) sessionOpts.correlationId = callOpts.correlationId
125
+ if (callOpts?.self !== undefined) sessionOpts.self = callOpts.self
126
+ if (callOpts?.url !== undefined) sessionOpts.url = callOpts.url
127
+ return ready.call(method, params, sessionOpts)
128
+ },
129
+
130
+ load(schemas) {
131
+ ready.registry.load(schemas)
132
+ },
133
+
134
+ disconnect() {
135
+ ready.disconnect()
136
+ },
137
+ }
138
+ }
@@ -0,0 +1,50 @@
1
+ import type { ShellMessage } from '../domain/message/types'
2
+ import type { Transport } from '../ports/transport'
3
+
4
+ export type MessagePortTransportConfig = {
5
+ port: MessagePort
6
+ onClose?: () => void
7
+ }
8
+
9
+ export function createMessagePortTransport(config: MessagePortTransportConfig): Transport {
10
+ const { port, onClose } = config
11
+
12
+ let closed = false
13
+ const handlers = new Set<(message: ShellMessage) => void>()
14
+
15
+ port.onmessage = (event: MessageEvent) => {
16
+ if (closed) return
17
+ const message = event.data as ShellMessage
18
+ for (const handler of handlers) handler(message)
19
+ }
20
+
21
+ port.onmessageerror = () => {
22
+ if (closed) return
23
+ close()
24
+ }
25
+
26
+ port.start()
27
+
28
+ function close() {
29
+ if (closed) return
30
+ closed = true
31
+ handlers.clear()
32
+ try {
33
+ port.close()
34
+ } catch {
35
+ // port may already be closed
36
+ }
37
+ onClose?.()
38
+ }
39
+
40
+ return {
41
+ send(message: ShellMessage) {
42
+ if (closed) return
43
+ port.postMessage(message)
44
+ },
45
+ onMessage(handler) {
46
+ handlers.add(handler)
47
+ },
48
+ close,
49
+ }
50
+ }
@@ -0,0 +1,112 @@
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
+
6
+ export interface ChildrenChannel {
7
+ send(windowId: string, message: ShellMessage): void
8
+ on<T extends MessageType>(
9
+ type: T,
10
+ handler: (windowId: string, message: MessageOf<T>) => void,
11
+ ): () => void
12
+ has(windowId: string): boolean
13
+ onClose(handler: (windowId: string) => void): () => void
14
+ }
15
+
16
+ export interface ChildrenChannelController extends ChildrenChannel {
17
+ attach(windowId: string, transport: Transport): void
18
+ detach(windowId: string): void
19
+ dispose(): void
20
+ }
21
+
22
+ export type ChildrenChannelConfig = {
23
+ observer: MessageObserver
24
+ onDeliveryFailed?: (windowId: string, message: ShellMessage) => void
25
+ }
26
+
27
+ type ChildListener = (windowId: string, message: ShellMessage) => void
28
+
29
+ export function createChildrenChannel(config: ChildrenChannelConfig): ChildrenChannelController {
30
+ const { observer, onDeliveryFailed } = config
31
+ const entries = new Map<string, Transport>()
32
+ const byType = new Map<MessageType, Set<ChildListener>>()
33
+ const closeListeners = new Set<(windowId: string) => void>()
34
+
35
+ function notifyListeners(windowId: string, message: ShellMessage) {
36
+ const bucket = byType.get(message.type)
37
+ if (!bucket || bucket.size === 0) return
38
+ for (const listener of bucket) {
39
+ try {
40
+ listener(windowId, message)
41
+ } catch {
42
+ // one rogue listener must not break siblings
43
+ }
44
+ }
45
+ }
46
+
47
+ return {
48
+ attach(windowId, transport) {
49
+ const existing = entries.get(windowId)
50
+ if (existing) throw new Error(`Child '${windowId}' already attached`)
51
+ entries.set(windowId, transport)
52
+ transport.onMessage((message) => {
53
+ observer.notify('inbound', message)
54
+ notifyListeners(windowId, message)
55
+ })
56
+ },
57
+
58
+ detach(windowId) {
59
+ const transport = entries.get(windowId)
60
+ if (!transport) return
61
+ entries.delete(windowId)
62
+ transport.close()
63
+ // `entries.delete` ran first, so `has(windowId)` is already false here —
64
+ // a listener replying to the gone child is a best-effort no-op.
65
+ for (const listener of closeListeners) {
66
+ try {
67
+ listener(windowId)
68
+ } catch {
69
+ // one rogue listener must not break siblings
70
+ }
71
+ }
72
+ },
73
+
74
+ dispose() {
75
+ for (const transport of entries.values()) transport.close()
76
+ entries.clear()
77
+ },
78
+
79
+ send(windowId, message) {
80
+ const transport = entries.get(windowId)
81
+ if (!transport) {
82
+ onDeliveryFailed?.(windowId, message)
83
+ return
84
+ }
85
+ observer.notify('outbound', message)
86
+ transport.send(message)
87
+ },
88
+
89
+ on(type, handler) {
90
+ let bucket = byType.get(type)
91
+ if (!bucket) {
92
+ bucket = new Set()
93
+ byType.set(type, bucket)
94
+ }
95
+ bucket.add(handler as ChildListener)
96
+ return () => {
97
+ bucket.delete(handler as ChildListener)
98
+ }
99
+ },
100
+
101
+ has(windowId) {
102
+ return entries.has(windowId)
103
+ },
104
+
105
+ onClose(handler) {
106
+ closeListeners.add(handler)
107
+ return () => {
108
+ closeListeners.delete(handler)
109
+ }
110
+ },
111
+ }
112
+ }
@@ -0,0 +1,33 @@
1
+ import type { ShellMessage } from '../../domain/message/types'
2
+
3
+ export type MessageDirection = 'inbound' | 'outbound'
4
+
5
+ export type ObserverHandler = (message: ShellMessage, direction: MessageDirection) => void
6
+
7
+ export interface MessageObserver {
8
+ notify(direction: MessageDirection, message: ShellMessage): void
9
+ subscribe(handler: ObserverHandler): () => void
10
+ }
11
+
12
+ export function createMessageObserver(): MessageObserver {
13
+ const handlers = new Set<ObserverHandler>()
14
+
15
+ return {
16
+ notify(direction, message) {
17
+ if (handlers.size === 0) return
18
+ for (const handler of handlers) {
19
+ try {
20
+ handler(message, direction)
21
+ } catch {
22
+ // one rogue observer must not break siblings
23
+ }
24
+ }
25
+ },
26
+ subscribe(handler) {
27
+ handlers.add(handler)
28
+ return () => {
29
+ handlers.delete(handler)
30
+ }
31
+ },
32
+ }
33
+ }