@astrale-os/shell 0.1.1 → 0.2.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 (284) hide show
  1. package/LICENSE +202 -0
  2. package/dist/application/apps.d.ts +18 -0
  3. package/dist/application/apps.d.ts.map +1 -0
  4. package/dist/application/apps.js +35 -0
  5. package/dist/application/namespaces.d.ts +21 -0
  6. package/dist/application/namespaces.d.ts.map +1 -0
  7. package/dist/application/namespaces.js +12 -0
  8. package/dist/application/session/open.d.ts +19 -0
  9. package/dist/application/session/open.d.ts.map +1 -0
  10. package/dist/application/session/open.js +46 -0
  11. package/dist/application/session/session.d.ts +41 -0
  12. package/dist/application/session/session.d.ts.map +1 -0
  13. package/dist/application/session/session.js +9 -0
  14. package/dist/application/session/token.d.ts +24 -0
  15. package/dist/application/session/token.d.ts.map +1 -0
  16. package/dist/application/session/token.js +51 -0
  17. package/dist/application/spaces.d.ts +17 -0
  18. package/dist/application/spaces.d.ts.map +1 -0
  19. package/dist/application/spaces.js +30 -0
  20. package/dist/application/user.d.ts +20 -0
  21. package/dist/application/user.d.ts.map +1 -0
  22. package/dist/application/user.js +26 -0
  23. package/dist/application/view.d.ts +20 -0
  24. package/dist/application/view.d.ts.map +1 -0
  25. package/dist/application/view.js +18 -0
  26. package/dist/{application/channels/children-channel.d.ts → channel/children.d.ts} +4 -4
  27. package/dist/channel/children.d.ts.map +1 -0
  28. package/dist/channel/listeners.d.ts +8 -0
  29. package/dist/channel/listeners.d.ts.map +1 -0
  30. package/dist/{adapters/message-port.transport.d.ts → channel/message-port.d.ts} +2 -2
  31. package/dist/channel/message-port.d.ts.map +1 -0
  32. package/dist/{application/channels → channel}/observer.d.ts +2 -1
  33. package/dist/channel/observer.d.ts.map +1 -0
  34. package/dist/{application/channels/parent-channel.d.ts → channel/parent.d.ts} +4 -4
  35. package/dist/channel/parent.d.ts.map +1 -0
  36. package/dist/{application/channels/parent-channel.js → channel/parent.js} +1 -1
  37. package/dist/channel/transport.d.ts +8 -0
  38. package/dist/channel/transport.d.ts.map +1 -0
  39. package/dist/index.d.ts +56 -41
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +23 -20
  42. package/dist/{domain/intent → intent}/middleware/capabilities.d.ts +2 -2
  43. package/dist/intent/middleware/capabilities.d.ts.map +1 -0
  44. package/dist/intent/middleware/logging.d.ts.map +1 -0
  45. package/dist/{domain/intent → intent}/middleware/rate-limit.d.ts +1 -1
  46. package/dist/intent/middleware/rate-limit.d.ts.map +1 -0
  47. package/dist/{application/intent/pipeline-runner.d.ts → intent/pipeline.d.ts} +3 -3
  48. package/dist/intent/pipeline.d.ts.map +1 -0
  49. package/dist/intent/policy.d.ts +40 -0
  50. package/dist/intent/policy.d.ts.map +1 -0
  51. package/dist/intent/policy.js +15 -0
  52. package/dist/{application/intent → intent}/request.d.ts +5 -14
  53. package/dist/intent/request.d.ts.map +1 -0
  54. package/dist/{application/intent → intent}/request.js +6 -20
  55. package/dist/intent/router.d.ts +31 -0
  56. package/dist/intent/router.d.ts.map +1 -0
  57. package/dist/{application/intent → intent}/router.js +1 -15
  58. package/dist/kernel/client.d.ts +33 -0
  59. package/dist/kernel/client.d.ts.map +1 -0
  60. package/dist/kernel/credential.d.ts +35 -0
  61. package/dist/kernel/credential.d.ts.map +1 -0
  62. package/dist/kernel/credential.js +43 -0
  63. package/dist/kernel/delegated.d.ts +17 -0
  64. package/dist/kernel/delegated.d.ts.map +1 -0
  65. package/dist/kernel/delegated.js +29 -0
  66. package/dist/{application/windowing/token-refresh.d.ts → kernel/delegation/refresh.d.ts} +2 -2
  67. package/dist/kernel/delegation/refresh.d.ts.map +1 -0
  68. package/dist/{application/windowing/token-refresh.js → kernel/delegation/refresh.js} +1 -1
  69. package/dist/{domain/identity/delegation.d.ts → kernel/delegation/token.d.ts} +1 -1
  70. package/dist/kernel/delegation/token.d.ts.map +1 -0
  71. package/dist/{adapters/kernel-client.adapter.d.ts → kernel/session.d.ts} +15 -12
  72. package/dist/kernel/session.d.ts.map +1 -0
  73. package/dist/{adapters/kernel-client.adapter.js → kernel/session.js} +10 -15
  74. package/dist/message/ctrl.d.ts +34 -0
  75. package/dist/message/ctrl.d.ts.map +1 -0
  76. package/dist/message/error.d.ts +9 -0
  77. package/dist/message/error.d.ts.map +1 -0
  78. package/dist/{application/windowing/protocol.d.ts → message/init.d.ts} +1 -1
  79. package/dist/message/init.d.ts.map +1 -0
  80. package/dist/{domain/message/registries.d.ts → message/intent.d.ts} +45 -37
  81. package/dist/message/intent.d.ts.map +1 -0
  82. package/dist/message/message.d.ts +10 -0
  83. package/dist/message/message.d.ts.map +1 -0
  84. package/dist/shell/access.d.ts +10 -0
  85. package/dist/shell/access.d.ts.map +1 -0
  86. package/dist/shell/access.js +19 -0
  87. package/dist/shell/build.d.ts +28 -0
  88. package/dist/shell/build.d.ts.map +1 -0
  89. package/dist/shell/build.js +58 -0
  90. package/dist/shell/config.d.ts +29 -0
  91. package/dist/shell/config.d.ts.map +1 -0
  92. package/dist/shell/create.d.ts +4 -0
  93. package/dist/shell/create.d.ts.map +1 -0
  94. package/dist/shell/create.js +5 -0
  95. package/dist/{application/windowing/child-init.d.ts → shell/sandboxed/boot.d.ts} +5 -5
  96. package/dist/shell/sandboxed/boot.d.ts.map +1 -0
  97. package/dist/{application/windowing/child-init.js → shell/sandboxed/boot.js} +5 -6
  98. package/dist/shell/sandboxed/guarded.d.ts +13 -0
  99. package/dist/shell/sandboxed/guarded.d.ts.map +1 -0
  100. package/dist/shell/sandboxed/guarded.js +28 -0
  101. package/dist/shell/sandboxed/pending.d.ts +14 -0
  102. package/dist/shell/sandboxed/pending.d.ts.map +1 -0
  103. package/dist/shell/sandboxed/pending.js +30 -0
  104. package/dist/shell/sandboxed/sandboxed.d.ts +4 -0
  105. package/dist/shell/sandboxed/sandboxed.d.ts.map +1 -0
  106. package/dist/shell/sandboxed/sandboxed.js +100 -0
  107. package/dist/shell/shell.d.ts +36 -0
  108. package/dist/shell/shell.d.ts.map +1 -0
  109. package/dist/shell/standalone.d.ts +4 -0
  110. package/dist/shell/standalone.d.ts.map +1 -0
  111. package/dist/shell/standalone.js +85 -0
  112. package/dist/{domain/window/config.d.ts → window/capabilities.d.ts} +3 -3
  113. package/dist/window/capabilities.d.ts.map +1 -0
  114. package/dist/{ports/shell-adapter.d.ts → window/embed/adapter.d.ts} +3 -2
  115. package/dist/window/embed/adapter.d.ts.map +1 -0
  116. package/dist/{adapters/iframe.shell-adapter.d.ts → window/embed/iframe.d.ts} +2 -2
  117. package/dist/window/embed/iframe.d.ts.map +1 -0
  118. package/dist/{application/windowing/lifecycle-controller.d.ts → window/lifecycle/states.d.ts} +4 -3
  119. package/dist/window/lifecycle/states.d.ts.map +1 -0
  120. package/dist/{application/windowing/lifecycle-controller.js → window/lifecycle/states.js} +1 -1
  121. package/dist/{domain/window/lifecycle.d.ts → window/lifecycle/transition.d.ts} +3 -3
  122. package/dist/window/lifecycle/transition.d.ts.map +1 -0
  123. package/dist/{domain/window/lifecycle.js → window/lifecycle/transition.js} +1 -1
  124. package/dist/window/mount/close.d.ts +27 -0
  125. package/dist/window/mount/close.d.ts.map +1 -0
  126. package/dist/window/mount/close.js +74 -0
  127. package/dist/window/mount/credential.d.ts +41 -0
  128. package/dist/window/mount/credential.d.ts.map +1 -0
  129. package/dist/window/mount/credential.js +74 -0
  130. package/dist/window/mount/handshake.d.ts.map +1 -0
  131. package/dist/{application/windowing → window/mount}/handshake.js +1 -1
  132. package/dist/{application/windowing/mount-controller.d.ts → window/mount/mount.d.ts} +7 -7
  133. package/dist/window/mount/mount.d.ts.map +1 -0
  134. package/dist/window/mount/mount.js +143 -0
  135. package/dist/window/mount/params.d.ts +73 -0
  136. package/dist/window/mount/params.d.ts.map +1 -0
  137. package/dist/window/mount/params.js +1 -0
  138. package/dist/window/mount/plain.d.ts +20 -0
  139. package/dist/window/mount/plain.d.ts.map +1 -0
  140. package/dist/window/mount/plain.js +46 -0
  141. package/dist/{domain/window → window}/window.d.ts +1 -1
  142. package/dist/window/window.d.ts.map +1 -0
  143. package/dist/window/window.js +1 -0
  144. package/package.json +11 -6
  145. package/src/application/apps.ts +60 -0
  146. package/src/application/namespaces.ts +38 -0
  147. package/src/application/session/open.ts +73 -0
  148. package/src/application/session/session.ts +46 -0
  149. package/src/application/session/token.ts +87 -0
  150. package/src/application/spaces.ts +52 -0
  151. package/src/application/user.ts +54 -0
  152. package/src/application/view.ts +42 -0
  153. package/src/{application/channels/children-channel.ts → channel/children.ts} +3 -3
  154. package/src/{application/channels/types.ts → channel/listeners.ts} +2 -7
  155. package/src/{adapters/message-port.transport.ts → channel/message-port.ts} +2 -2
  156. package/src/{application/channels → channel}/observer.ts +2 -1
  157. package/src/{application/channels/parent-channel.ts → channel/parent.ts} +4 -4
  158. package/src/{ports → channel}/transport.ts +2 -1
  159. package/src/index.ts +80 -88
  160. package/src/{domain/intent → intent}/middleware/capabilities.ts +2 -3
  161. package/src/{domain/intent → intent}/middleware/logging.ts +1 -1
  162. package/src/{domain/intent → intent}/middleware/rate-limit.ts +1 -1
  163. package/src/{application/intent/pipeline-runner.ts → intent/pipeline.ts} +3 -2
  164. package/src/intent/policy.ts +46 -0
  165. package/src/{application/intent → intent}/request.ts +14 -34
  166. package/src/{application/intent → intent}/router.ts +12 -49
  167. package/src/kernel/client.ts +33 -0
  168. package/src/kernel/credential.ts +78 -0
  169. package/src/kernel/delegated.ts +49 -0
  170. package/src/{application/windowing/token-refresh.ts → kernel/delegation/refresh.ts} +2 -2
  171. package/src/{adapters/kernel-client.adapter.ts → kernel/session.ts} +26 -33
  172. package/src/message/ctrl.ts +25 -0
  173. package/src/{domain/message/errors.ts → message/error.ts} +8 -0
  174. package/src/message/intent.ts +98 -0
  175. package/src/message/message.ts +11 -0
  176. package/src/shell/access.ts +26 -0
  177. package/src/shell/build.ts +95 -0
  178. package/src/shell/config.ts +32 -0
  179. package/src/shell/create.ts +9 -0
  180. package/src/{application/windowing/child-init.ts → shell/sandboxed/boot.ts} +10 -11
  181. package/src/shell/sandboxed/guarded.ts +32 -0
  182. package/src/shell/sandboxed/pending.ts +52 -0
  183. package/src/shell/sandboxed/sandboxed.ts +131 -0
  184. package/src/shell/shell.ts +37 -0
  185. package/src/shell/standalone.ts +97 -0
  186. package/src/{domain/window/config.ts → window/capabilities.ts} +2 -2
  187. package/src/{ports/shell-adapter.ts → window/embed/adapter.ts} +2 -1
  188. package/src/{adapters/iframe.shell-adapter.ts → window/embed/iframe.ts} +2 -2
  189. package/src/{application/windowing/lifecycle-controller.ts → window/lifecycle/states.ts} +3 -2
  190. package/src/{domain/window/lifecycle.ts → window/lifecycle/transition.ts} +2 -2
  191. package/src/window/mount/close.ts +111 -0
  192. package/src/window/mount/credential.ts +101 -0
  193. package/src/{application/windowing → window/mount}/handshake.ts +3 -2
  194. package/src/window/mount/mount.ts +185 -0
  195. package/src/window/mount/params.ts +64 -0
  196. package/src/window/mount/plain.ts +68 -0
  197. package/src/{domain/window → window}/window.ts +1 -1
  198. package/dist/adapters/iframe.shell-adapter.d.ts.map +0 -1
  199. package/dist/adapters/kernel-client.adapter.d.ts.map +0 -1
  200. package/dist/adapters/message-port.transport.d.ts.map +0 -1
  201. package/dist/application/channels/children-channel.d.ts.map +0 -1
  202. package/dist/application/channels/observer.d.ts.map +0 -1
  203. package/dist/application/channels/parent-channel.d.ts.map +0 -1
  204. package/dist/application/channels/types.d.ts +0 -12
  205. package/dist/application/channels/types.d.ts.map +0 -1
  206. package/dist/application/intent/pipeline-runner.d.ts.map +0 -1
  207. package/dist/application/intent/request.d.ts.map +0 -1
  208. package/dist/application/intent/router.d.ts +0 -56
  209. package/dist/application/intent/router.d.ts.map +0 -1
  210. package/dist/application/kernel-facade.d.ts +0 -33
  211. package/dist/application/kernel-facade.d.ts.map +0 -1
  212. package/dist/application/kernel-facade.js +0 -77
  213. package/dist/application/shell.d.ts +0 -100
  214. package/dist/application/shell.d.ts.map +0 -1
  215. package/dist/application/shell.js +0 -256
  216. package/dist/application/windowing/child-init.d.ts.map +0 -1
  217. package/dist/application/windowing/handshake.d.ts.map +0 -1
  218. package/dist/application/windowing/lifecycle-controller.d.ts.map +0 -1
  219. package/dist/application/windowing/mount-controller.d.ts.map +0 -1
  220. package/dist/application/windowing/mount-controller.js +0 -277
  221. package/dist/application/windowing/protocol.d.ts.map +0 -1
  222. package/dist/application/windowing/token-refresh.d.ts.map +0 -1
  223. package/dist/domain/identity/delegation.d.ts.map +0 -1
  224. package/dist/domain/identity/path.d.ts +0 -11
  225. package/dist/domain/identity/path.d.ts.map +0 -1
  226. package/dist/domain/identity/path.js +0 -13
  227. package/dist/domain/identity/resolve-self.d.ts +0 -21
  228. package/dist/domain/identity/resolve-self.d.ts.map +0 -1
  229. package/dist/domain/identity/resolve-self.js +0 -36
  230. package/dist/domain/intent/middleware/capabilities.d.ts.map +0 -1
  231. package/dist/domain/intent/middleware/logging.d.ts.map +0 -1
  232. package/dist/domain/intent/middleware/rate-limit.d.ts.map +0 -1
  233. package/dist/domain/intent/pipeline.d.ts +0 -3
  234. package/dist/domain/intent/pipeline.d.ts.map +0 -1
  235. package/dist/domain/intent/router.d.ts +0 -6
  236. package/dist/domain/intent/router.d.ts.map +0 -1
  237. package/dist/domain/message/errors.d.ts +0 -2
  238. package/dist/domain/message/errors.d.ts.map +0 -1
  239. package/dist/domain/message/registries.d.ts.map +0 -1
  240. package/dist/domain/message/types.d.ts +0 -30
  241. package/dist/domain/message/types.d.ts.map +0 -1
  242. package/dist/domain/window/config.d.ts.map +0 -1
  243. package/dist/domain/window/lifecycle.d.ts.map +0 -1
  244. package/dist/domain/window/window.d.ts.map +0 -1
  245. package/dist/ports/kernel-client.d.ts +0 -33
  246. package/dist/ports/kernel-client.d.ts.map +0 -1
  247. package/dist/ports/shell-adapter.d.ts.map +0 -1
  248. package/dist/ports/transport.d.ts +0 -7
  249. package/dist/ports/transport.d.ts.map +0 -1
  250. package/src/application/kernel-facade.ts +0 -143
  251. package/src/application/shell.ts +0 -433
  252. package/src/application/windowing/mount-controller.ts +0 -335
  253. package/src/domain/identity/path.ts +0 -14
  254. package/src/domain/identity/resolve-self.ts +0 -38
  255. package/src/domain/intent/pipeline.ts +0 -3
  256. package/src/domain/intent/router.ts +0 -6
  257. package/src/domain/message/registries.ts +0 -76
  258. package/src/domain/message/types.ts +0 -32
  259. package/src/ports/kernel-client.ts +0 -32
  260. /package/dist/{application/channels/children-channel.js → channel/children.js} +0 -0
  261. /package/dist/{application/channels/types.js → channel/listeners.js} +0 -0
  262. /package/dist/{adapters/message-port.transport.js → channel/message-port.js} +0 -0
  263. /package/dist/{application/channels → channel}/observer.js +0 -0
  264. /package/dist/{ports → channel}/transport.js +0 -0
  265. /package/dist/{domain/intent → intent}/middleware/capabilities.js +0 -0
  266. /package/dist/{domain/intent → intent}/middleware/logging.d.ts +0 -0
  267. /package/dist/{domain/intent → intent}/middleware/logging.js +0 -0
  268. /package/dist/{domain/intent → intent}/middleware/rate-limit.js +0 -0
  269. /package/dist/{application/intent/pipeline-runner.js → intent/pipeline.js} +0 -0
  270. /package/dist/{domain/intent/pipeline.js → kernel/client.js} +0 -0
  271. /package/dist/{domain/identity/delegation.js → kernel/delegation/token.js} +0 -0
  272. /package/dist/{domain/intent/router.js → message/ctrl.js} +0 -0
  273. /package/dist/{domain/message/errors.js → message/error.js} +0 -0
  274. /package/dist/{application/windowing/protocol.js → message/init.js} +0 -0
  275. /package/dist/{domain/message/registries.js → message/intent.js} +0 -0
  276. /package/dist/{domain/message/types.js → message/message.js} +0 -0
  277. /package/dist/{domain/window → shell}/config.js +0 -0
  278. /package/dist/{domain/window/window.js → shell/shell.js} +0 -0
  279. /package/dist/{ports/kernel-client.js → window/capabilities.js} +0 -0
  280. /package/dist/{ports/shell-adapter.js → window/embed/adapter.js} +0 -0
  281. /package/dist/{adapters/iframe.shell-adapter.js → window/embed/iframe.js} +0 -0
  282. /package/dist/{application/windowing → window/mount}/handshake.d.ts +0 -0
  283. /package/src/{domain/identity/delegation.ts → kernel/delegation/token.ts} +0 -0
  284. /package/src/{application/windowing/protocol.ts → message/init.ts} +0 -0
@@ -1,433 +0,0 @@
1
- import type { FunctionSchema } from '@astrale-os/kernel-client'
2
-
3
- import type { IntentMiddleware } from '../domain/intent/pipeline'
4
- import type { IntentRegistry } from '../domain/message/registries'
5
- import type { WindowCapabilities, SandboxProfile } from '../domain/window/config'
6
- import type { Window } from '../domain/window/window'
7
- import type { KernelClient } from '../ports/kernel-client'
8
- import type { ShellAdapter } from '../ports/shell-adapter'
9
- import type { ChildrenChannel, ChildrenChannelController } from './channels/children-channel'
10
- import type { MessageObserver, ObserverHandler } from './channels/observer'
11
- import type { ParentChannel, ParentChannelController } from './channels/parent-channel'
12
- import type { IntentPipeline } from './intent/pipeline-runner'
13
- import type { LocalIntentHandler } from './intent/router'
14
- import type { KernelFacadeController } from './kernel-facade'
15
- import type { LifecycleController } from './windowing/lifecycle-controller'
16
- import type { MountController } from './windowing/mount-controller'
17
-
18
- import { createKernelClientAdapter } from '../adapters/kernel-client.adapter'
19
- import { createChildrenChannel } from './channels/children-channel'
20
- import { createMessageObserver } from './channels/observer'
21
- import { createIntentPipeline } from './intent/pipeline-runner'
22
- import { createIntentRouter } from './intent/router'
23
- import { createKernelFacade } from './kernel-facade'
24
- import { performChildInit } from './windowing/child-init'
25
- import { createLifecycleController } from './windowing/lifecycle-controller'
26
- import { createMountController } from './windowing/mount-controller'
27
-
28
- // ─── Re-exports ─────────────────────────────────────────────────────────────
29
-
30
- export type { MessageDirection, ObserverHandler } from './channels/observer'
31
- export type { ParentChannel } from './channels/parent-channel'
32
- export type { ChildrenChannel } from './channels/children-channel'
33
- export type { MessageHandler } from './channels/types'
34
-
35
- // ─── Mount API ──────────────────────────────────────────────────────────────
36
-
37
- export type MountParams = {
38
- readonly host: HTMLElement
39
- readonly url: string
40
- readonly functionId: string
41
- readonly capabilities: WindowCapabilities
42
- readonly targetNodeId?: string
43
- /** See `WindowConfig.sandbox` — `null` opts the iframe out of the attribute. */
44
- readonly sandbox?: SandboxProfile | null
45
- readonly handshakeTimeoutMs?: number
46
- readonly delegationTtlSeconds?: number
47
- /**
48
- * `'shell'` (default) runs the parent-side handshake, opening a MessagePort
49
- * transport with delegation tokens and an intent channel. `'none'` mounts a
50
- * plain iframe with no transport — used for hosted views that don't speak
51
- * the shell protocol (`BlaxelComputer.deployFunction(kind:'view')`), so they
52
- * render immediately instead of paying the 5 s handshake timeout. In `'none'`
53
- * mode `capabilities` / `targetNodeId` / `delegationTtlSeconds` are inert
54
- * (no channel to deliver them).
55
- */
56
- readonly handshake?: 'shell' | 'none'
57
- }
58
-
59
- export type CloseResult =
60
- | { kind: 'closed' }
61
- | { kind: 'refused'; reason?: string }
62
- | { kind: 'noop' }
63
-
64
- export type MountedWindow = {
65
- readonly windowId: string
66
- readonly window: Window
67
- readonly handle: { element: HTMLIFrameElement | HTMLElement }
68
- close(opts?: { force?: boolean; timeoutMs?: number }): Promise<CloseResult>
69
- }
70
-
71
- // ─── Shell ──────────────────────────────────────────────────────────────────
72
-
73
- export interface Shell {
74
- readonly parent: ParentChannel | null
75
- readonly children: ChildrenChannel
76
- readonly kernel: KernelClient
77
- readonly windows: ReadonlyMap<string, Window>
78
- readonly targetNodeId?: string
79
-
80
- use(middleware: IntentMiddleware): Shell
81
- onMessage(handler: ObserverHandler): () => void
82
- onIntent<K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>): () => void
83
-
84
- mount(params: MountParams): Promise<MountedWindow>
85
- close(windowId: string, opts?: { force?: boolean; timeoutMs?: number }): Promise<CloseResult>
86
-
87
- init(): Promise<Shell>
88
- dispose(): Promise<void>
89
- }
90
-
91
- // ─── Config ─────────────────────────────────────────────────────────────────
92
-
93
- export type StandaloneConfig = {
94
- readonly mode: 'standalone'
95
- readonly kernelUrl: string
96
- readonly credential?: string
97
- readonly getCredential?: () => string
98
- readonly mintIdentity?: string
99
- readonly schema?: FunctionSchema[]
100
- readonly adapter?: ShellAdapter
101
- readonly windowId?: string
102
- /**
103
- * Optional wire-layer `fetch` shim for the kernel transport. A cookie-fronted
104
- * SPA passes a wrapper adding `credentials:'include'` + refresh-on-401 and an
105
- * empty `credential`, so a router unseals the cookie into a Bearer (the SPA
106
- * holds no token). Omit it (the default) for token-based callers — including
107
- * every sandboxed child view, which authenticates with a delegation token.
108
- */
109
- readonly fetch?: typeof fetch
110
- }
111
-
112
- export type SandboxedConfig = {
113
- readonly mode: 'sandboxed'
114
- readonly schema?: FunctionSchema[]
115
- readonly parentWindow?: WindowProxy
116
- readonly initTimeoutMs?: number
117
- readonly adapter?: ShellAdapter
118
- }
119
-
120
- export type ShellConfig = StandaloneConfig | SandboxedConfig
121
-
122
- const DEFAULT_ROOT_WINDOW_ID = 'root'
123
- /** Trusted sender id stamped on intents arriving from the parent channel. */
124
- const PARENT_SENDER_ID = '<parent>'
125
-
126
- // ─── Factory ────────────────────────────────────────────────────────────────
127
-
128
- export function createShell(config: ShellConfig): Shell {
129
- return config.mode === 'standalone' ? createStandaloneShell(config) : createSandboxedShell(config)
130
- }
131
-
132
- // ─── Standalone ─────────────────────────────────────────────────────────────
133
-
134
- function createStandaloneShell(config: StandaloneConfig): Shell {
135
- const windowId = config.windowId ?? DEFAULT_ROOT_WINDOW_ID
136
- const observer = createMessageObserver()
137
- const getCredential =
138
- config.getCredential ??
139
- (() => {
140
- if (config.credential === undefined) {
141
- throw new Error('Standalone shell requires credential or getCredential')
142
- }
143
- return config.credential
144
- })
145
- const adapter = createKernelClientAdapter({
146
- url: config.kernelUrl,
147
- schema: config.schema,
148
- getCredential,
149
- mintIdentity: config.mintIdentity,
150
- fetch: config.fetch,
151
- })
152
- const kernel = createKernelFacade({
153
- adapter,
154
- getCredential,
155
- url: config.kernelUrl,
156
- mintIdentity: config.mintIdentity,
157
- })
158
-
159
- const lifecycle = createLifecycleController()
160
- const windows = new Map<string, Window>()
161
-
162
- const childrenChannel = createChildrenChannel({
163
- observer,
164
- onDeliveryFailed: (toWindowId) => lifecycle.markCrashed(toWindowId),
165
- })
166
-
167
- const pipeline = createIntentPipeline()
168
- const router = createIntentRouter({
169
- selfWindowId: windowId,
170
- pipeline,
171
- parent: null,
172
- children: childrenChannel,
173
- windows,
174
- })
175
-
176
- childrenChannel.on('intent', (windowId, message) => {
177
- void router.handleInbound('child', message, windowId)
178
- })
179
-
180
- let mountController: MountController | null = null
181
- function getMountController(): MountController {
182
- if (mountController) return mountController
183
- if (!config.adapter) {
184
- throw new Error('Shell.mount() requires a ShellAdapter in the config')
185
- }
186
- mountController = createMountController({
187
- adapter: config.adapter,
188
- kernel,
189
- kernelUrl: config.kernelUrl,
190
- mintIdentity: config.mintIdentity,
191
- childrenChannel,
192
- windows,
193
- lifecycle,
194
- })
195
- return mountController
196
- }
197
-
198
- return buildShellFacade({
199
- parent: null,
200
- childrenChannel,
201
- kernel,
202
- pipeline,
203
- observer,
204
- lifecycle,
205
- windows,
206
- getMountController,
207
- onIntent: (name, handler) => router.onLocal(name, handler),
208
- onDispose: async () => {
209
- if (mountController) await mountController.dispose()
210
- childrenChannel.dispose()
211
- adapter.disconnect()
212
- },
213
- })
214
- }
215
-
216
- // ─── Sandboxed child ────────────────────────────────────────────────────────
217
-
218
- type SandboxedState = {
219
- windowId: string
220
- kernelUrl: string
221
- targetNodeId?: string
222
- mintIdentity?: string
223
- kernel: KernelFacadeController
224
- parent: ParentChannelController
225
- }
226
-
227
- function createSandboxedShell(config: SandboxedConfig): Shell {
228
- const observer = createMessageObserver()
229
- const lifecycle = createLifecycleController()
230
- const windows = new Map<string, Window>()
231
- const childrenChannel = createChildrenChannel({
232
- observer,
233
- onDeliveryFailed: (toWindowId) => lifecycle.markCrashed(toWindowId),
234
- })
235
- const pipeline = createIntentPipeline()
236
-
237
- let state: SandboxedState | null = null
238
- let router: ReturnType<typeof createIntentRouter> | null = null
239
- let mountController: MountController | null = null
240
-
241
- // Buffer intent subscriptions made before `init()` resolves — the router
242
- // is only built once the handshake completes, but consumers want to call
243
- // `onIntent` at construction time.
244
- type PendingIntentSub = {
245
- name: string
246
- handler: LocalIntentHandler
247
- unsubscribed: boolean
248
- off?: () => void
249
- }
250
- const pendingIntentSubs: PendingIntentSub[] = []
251
-
252
- function subscribeIntent<K extends keyof IntentRegistry>(
253
- name: K,
254
- handler: LocalIntentHandler<K>,
255
- ): () => void {
256
- if (router) return router.onLocal(name, handler)
257
- const entry: PendingIntentSub = {
258
- name: name as string,
259
- handler: handler as LocalIntentHandler,
260
- unsubscribed: false,
261
- }
262
- pendingIntentSubs.push(entry)
263
- return () => {
264
- entry.unsubscribed = true
265
- // After init, `off` is the router's real disposer (set during replay);
266
- // before init, flipping `unsubscribed` is enough (replay skips it).
267
- entry.off?.()
268
- }
269
- }
270
-
271
- const requireState = (): SandboxedState => {
272
- if (!state) throw new Error('Sandboxed shell not initialized — call shell.init() first')
273
- return state
274
- }
275
-
276
- const kernelProxy: KernelClient = {
277
- call: (path, params) => requireState().kernel.call(path, params),
278
- mintDelegation: () => requireState().kernel.mintDelegation(),
279
- authToken: () => requireState().kernel.authToken(),
280
- get url() {
281
- return requireState().kernel.url
282
- },
283
- }
284
-
285
- const parentProxy: ParentChannel = {
286
- send: (message) => requireState().parent.send(message),
287
- on: (type, handler) => requireState().parent.on(type, handler),
288
- }
289
-
290
- function getMountController(): MountController {
291
- if (mountController) return mountController
292
- if (!config.adapter) {
293
- throw new Error('Shell.mount() requires a ShellAdapter in the config')
294
- }
295
- const current = requireState()
296
- mountController = createMountController({
297
- adapter: config.adapter,
298
- kernel: kernelProxy,
299
- kernelUrl: current.kernelUrl,
300
- mintIdentity: current.mintIdentity,
301
- childrenChannel,
302
- windows,
303
- lifecycle,
304
- })
305
- return mountController
306
- }
307
-
308
- return buildShellFacade({
309
- parent: parentProxy,
310
- childrenChannel,
311
- kernel: kernelProxy,
312
- pipeline,
313
- observer,
314
- lifecycle,
315
- windows,
316
- getMountController,
317
- targetNodeId: () => state?.targetNodeId,
318
- onIntent: subscribeIntent,
319
- init: async () => {
320
- const result = await performChildInit({
321
- parent: config.parentWindow ?? window.parent,
322
- timeoutMs: config.initTimeoutMs,
323
- schema: config.schema,
324
- })
325
-
326
- state = {
327
- windowId: result.windowId,
328
- kernelUrl: result.kernelUrl,
329
- targetNodeId: result.targetNodeId,
330
- mintIdentity: result.mintIdentity,
331
- kernel: result.kernel,
332
- parent: result.parentChannel(observer),
333
- }
334
-
335
- router = createIntentRouter({
336
- selfWindowId: state.windowId,
337
- pipeline,
338
- parent: state.parent,
339
- children: childrenChannel,
340
- windows,
341
- })
342
-
343
- childrenChannel.on('intent', (windowId, message) => {
344
- void router!.handleInbound('child', message, windowId)
345
- })
346
- state.parent.on('intent', (message) => {
347
- void router!.handleInbound('parent', message, PARENT_SENDER_ID)
348
- })
349
-
350
- // Replay subscriptions registered before init. Capture each real disposer
351
- // so a post-init unsubscribe (via the closure returned at subscribe time)
352
- // actually removes the handler from the router instead of no-op'ing.
353
- for (const entry of pendingIntentSubs) {
354
- if (entry.unsubscribed) continue
355
- entry.off = router.onLocal(entry.name as keyof IntentRegistry, entry.handler)
356
- }
357
- pendingIntentSubs.length = 0
358
- },
359
- onDispose: async () => {
360
- if (mountController) await mountController.dispose()
361
- childrenChannel.dispose()
362
- state?.parent.dispose()
363
- state?.kernel.dispose()
364
- },
365
- })
366
- }
367
-
368
- // ─── Shared facade builder ──────────────────────────────────────────────────
369
-
370
- type FacadeDeps = {
371
- parent: ParentChannel | null
372
- childrenChannel: ChildrenChannelController
373
- kernel: KernelClient
374
- pipeline: IntentPipeline
375
- observer: MessageObserver
376
- lifecycle: LifecycleController
377
- windows: Map<string, Window>
378
- getMountController: () => MountController
379
- onIntent: <K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>) => () => void
380
- targetNodeId?: () => string | undefined
381
- init?: () => Promise<void>
382
- onDispose: () => Promise<void>
383
- }
384
-
385
- function buildShellFacade(deps: FacadeDeps): Shell {
386
- let disposed = false
387
-
388
- const shell: Shell = {
389
- get parent() {
390
- return deps.parent
391
- },
392
- children: deps.childrenChannel,
393
- kernel: deps.kernel,
394
- windows: deps.windows,
395
- get targetNodeId() {
396
- return deps.targetNodeId?.()
397
- },
398
-
399
- use(middleware) {
400
- deps.pipeline.use(middleware)
401
- return shell
402
- },
403
-
404
- onMessage(handler) {
405
- return deps.observer.subscribe(handler)
406
- },
407
-
408
- onIntent(name, handler) {
409
- return deps.onIntent(name, handler)
410
- },
411
-
412
- mount(params) {
413
- return deps.getMountController().mount(params)
414
- },
415
-
416
- close(windowId, opts) {
417
- return deps.getMountController().close(windowId, opts)
418
- },
419
-
420
- async init() {
421
- if (deps.init) await deps.init()
422
- return shell
423
- },
424
-
425
- async dispose() {
426
- if (disposed) return
427
- disposed = true
428
- await deps.onDispose()
429
- },
430
- }
431
-
432
- return shell
433
- }