@soulcraft/sdk 2.0.1 → 2.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 (92) hide show
  1. package/dist/client/index.d.ts +5 -38
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +5 -47
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/client/namespace-proxy.d.ts +3 -4
  6. package/dist/client/namespace-proxy.d.ts.map +1 -1
  7. package/dist/client/namespace-proxy.js +3 -4
  8. package/dist/client/namespace-proxy.js.map +1 -1
  9. package/dist/index.d.ts +6 -6
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/modules/hall/admin.d.ts +39 -0
  14. package/dist/modules/hall/admin.d.ts.map +1 -0
  15. package/dist/modules/hall/admin.js +115 -0
  16. package/dist/modules/hall/admin.js.map +1 -0
  17. package/dist/modules/hall/browser.d.ts +83 -27
  18. package/dist/modules/hall/browser.d.ts.map +1 -1
  19. package/dist/modules/hall/browser.js +238 -49
  20. package/dist/modules/hall/browser.js.map +1 -1
  21. package/dist/modules/hall/media.d.ts +164 -0
  22. package/dist/modules/hall/media.d.ts.map +1 -0
  23. package/dist/modules/hall/media.js +182 -0
  24. package/dist/modules/hall/media.js.map +1 -0
  25. package/dist/modules/hall/server.d.ts +119 -6
  26. package/dist/modules/hall/server.d.ts.map +1 -1
  27. package/dist/modules/hall/server.js +299 -9
  28. package/dist/modules/hall/server.js.map +1 -1
  29. package/dist/modules/hall/types.d.ts +705 -25
  30. package/dist/modules/hall/types.d.ts.map +1 -1
  31. package/dist/modules/hall/types.js +12 -7
  32. package/dist/modules/hall/types.js.map +1 -1
  33. package/dist/server/hall-handlers.d.ts +60 -14
  34. package/dist/server/hall-handlers.d.ts.map +1 -1
  35. package/dist/server/hall-handlers.js +61 -12
  36. package/dist/server/hall-handlers.js.map +1 -1
  37. package/dist/server/hono-router.d.ts +2 -9
  38. package/dist/server/hono-router.d.ts.map +1 -1
  39. package/dist/server/hono-router.js +2 -46
  40. package/dist/server/hono-router.js.map +1 -1
  41. package/dist/server/index.d.ts +4 -19
  42. package/dist/server/index.d.ts.map +1 -1
  43. package/dist/server/index.js +10 -29
  44. package/dist/server/index.js.map +1 -1
  45. package/dist/types.d.ts +2 -41
  46. package/dist/types.d.ts.map +1 -1
  47. package/docs/ADR-005-hall-integration.md +449 -0
  48. package/package.json +1 -1
  49. package/dist/client/create-client-sdk.d.ts +0 -113
  50. package/dist/client/create-client-sdk.d.ts.map +0 -1
  51. package/dist/client/create-client-sdk.js +0 -169
  52. package/dist/client/create-client-sdk.js.map +0 -1
  53. package/dist/modules/app-context/index.d.ts +0 -214
  54. package/dist/modules/app-context/index.d.ts.map +0 -1
  55. package/dist/modules/app-context/index.js +0 -569
  56. package/dist/modules/app-context/index.js.map +0 -1
  57. package/dist/modules/billing/firestore-provider.d.ts +0 -60
  58. package/dist/modules/billing/firestore-provider.d.ts.map +0 -1
  59. package/dist/modules/billing/firestore-provider.js +0 -315
  60. package/dist/modules/billing/firestore-provider.js.map +0 -1
  61. package/dist/modules/brainy/proxy.d.ts +0 -48
  62. package/dist/modules/brainy/proxy.d.ts.map +0 -1
  63. package/dist/modules/brainy/proxy.js +0 -95
  64. package/dist/modules/brainy/proxy.js.map +0 -1
  65. package/dist/server/create-sdk.d.ts +0 -74
  66. package/dist/server/create-sdk.d.ts.map +0 -1
  67. package/dist/server/create-sdk.js +0 -104
  68. package/dist/server/create-sdk.js.map +0 -1
  69. package/dist/server/from-license.d.ts +0 -252
  70. package/dist/server/from-license.d.ts.map +0 -1
  71. package/dist/server/from-license.js +0 -349
  72. package/dist/server/from-license.js.map +0 -1
  73. package/dist/server/handlers.d.ts +0 -312
  74. package/dist/server/handlers.d.ts.map +0 -1
  75. package/dist/server/handlers.js +0 -376
  76. package/dist/server/handlers.js.map +0 -1
  77. package/dist/server/postmessage-handler.d.ts +0 -152
  78. package/dist/server/postmessage-handler.d.ts.map +0 -1
  79. package/dist/server/postmessage-handler.js +0 -138
  80. package/dist/server/postmessage-handler.js.map +0 -1
  81. package/dist/transports/http.d.ts +0 -86
  82. package/dist/transports/http.d.ts.map +0 -1
  83. package/dist/transports/http.js +0 -137
  84. package/dist/transports/http.js.map +0 -1
  85. package/dist/transports/postmessage.d.ts +0 -159
  86. package/dist/transports/postmessage.d.ts.map +0 -1
  87. package/dist/transports/postmessage.js +0 -207
  88. package/dist/transports/postmessage.js.map +0 -1
  89. package/dist/transports/workshop.d.ts +0 -173
  90. package/dist/transports/workshop.d.ts.map +0 -1
  91. package/dist/transports/workshop.js +0 -307
  92. package/dist/transports/workshop.js.map +0 -1
@@ -1,376 +0,0 @@
1
- /**
2
- * @module server/handlers
3
- * @description HTTP RPC and WebSocket handler factories for mounting Brainy server endpoints.
4
- *
5
- * Products mount exactly two routes:
6
- * - `POST /api/brainy/rpc` — handled by {@link createBrainyHandler}
7
- * - `GET /api/brainy/ws` — WebSocket upgrade handled by {@link createBrainyWsHandler}
8
- *
9
- * Both factories are framework-agnostic — they accept and return `Request` / `Response`
10
- * objects (Fetch API), making them compatible with SvelteKit, Hono, and raw Bun.serve.
11
- *
12
- * ## HTTP RPC handler
13
- *
14
- * Accepts `{ method: string, args: unknown[] }` as a JSON body and returns
15
- * `{ result: unknown }` or `{ error: { code, message } }`. All authentication and
16
- * authorization is delegated to the caller-supplied callbacks.
17
- *
18
- * ## WebSocket handler
19
- *
20
- * Accepts binary MessagePack frames. See wire protocol in `transports/ws.ts`.
21
- * The `broadcastChange()` method on the returned handler pushes `BrainyChangeEvent`
22
- * objects to connected clients.
23
- *
24
- * @example SvelteKit route mounting the HTTP handler
25
- * ```typescript
26
- * // src/routes/api/brainy/rpc/+server.ts
27
- * import { createBrainyHandler } from '@soulcraft/sdk/server'
28
- * import { pool } from '$lib/server/sdk'
29
- * import { verifySession } from '$lib/server/auth'
30
- *
31
- * const handler = createBrainyHandler({
32
- * resolveBrain: async (req) => {
33
- * const userId = req.headers.get('x-user-id') ?? ''
34
- * const workspaceId = req.headers.get('x-workspace-id') ?? ''
35
- * return pool.forUser(userId, workspaceId)
36
- * },
37
- * authenticate: async (req) => verifySession(req.headers.get('cookie') ?? ''),
38
- * })
39
- *
40
- * export const POST: RequestHandler = ({ request }) => handler(request)
41
- * ```
42
- *
43
- * @example Bun.serve WebSocket upgrade
44
- * ```typescript
45
- * import { createBrainyWsHandler } from '@soulcraft/sdk/server'
46
- * import { pool } from './sdk'
47
- * import { verifyCapabilityToken } from '@soulcraft/sdk'
48
- *
49
- * const wsHandler = createBrainyWsHandler({
50
- * resolveBrain: (scope) => pool.forTenant(scope),
51
- * authenticate: (token, scope) => verifyCapabilityToken(token, process.env.VENUE_RPC_SECRET!),
52
- * })
53
- *
54
- * Bun.serve({
55
- * async fetch(req, server) {
56
- * if (req.url.endsWith('/api/brainy/ws')) {
57
- * const session = await wsHandler.handleUpgrade(req)
58
- * if (!session) return new Response('Unauthorized', { status: 401 })
59
- * server.upgrade(req, { data: session })
60
- * return undefined
61
- * }
62
- * return new Response('Not found', { status: 404 })
63
- * },
64
- * websocket: {
65
- * async message(ws, data) {
66
- * await wsHandler.handleMessage(ws.data, data as ArrayBuffer, (msg) => ws.send(msg))
67
- * },
68
- * },
69
- * })
70
- * ```
71
- */
72
- import { encode, decode } from '@msgpack/msgpack';
73
- import { LocalTransport } from '../transports/local.js';
74
- import { YDocManager } from '../modules/brainy/ydoc-manager.js';
75
- /**
76
- * Creates a Fetch-API-compatible HTTP handler for Brainy RPC calls.
77
- *
78
- * Mount at `POST /api/brainy/rpc` in your product's router.
79
- *
80
- * @param config - Handler configuration.
81
- * @returns `(request: Request) => Promise<Response>`
82
- */
83
- export function createBrainyHandler(config) {
84
- const { resolveBrain, authenticate, authorize, blockedMethods = [] } = config;
85
- return async function brainyRpcHandler(request) {
86
- // ── Auth ──────────────────────────────────────────────────────────────
87
- const auth = await authenticate(request);
88
- if (!auth)
89
- return _jsonError(401, 'UNAUTHORIZED', 'Authentication required');
90
- // ── Parse ─────────────────────────────────────────────────────────────
91
- let body;
92
- try {
93
- body = (await request.json());
94
- }
95
- catch {
96
- return _jsonError(400, 'BAD_REQUEST', 'Request body must be valid JSON');
97
- }
98
- if (typeof body.method !== 'string' || !Array.isArray(body.args)) {
99
- return _jsonError(400, 'BAD_REQUEST', '{ method: string, args: unknown[] } required');
100
- }
101
- const method = body.method;
102
- const args = body.args;
103
- // ── Blocklist ─────────────────────────────────────────────────────────
104
- if (blockedMethods.includes(method)) {
105
- return _jsonError(403, 'FORBIDDEN', `Method '${method}' is not available via RPC`);
106
- }
107
- // ── Authorize ─────────────────────────────────────────────────────────
108
- if (authorize) {
109
- const allowed = await authorize(method, args, auth);
110
- if (!allowed) {
111
- return _jsonError(403, 'FORBIDDEN', `Not authorized to call '${method}'`);
112
- }
113
- }
114
- // ── Resolve brain + dispatch ──────────────────────────────────────────
115
- let brain;
116
- try {
117
- brain = await resolveBrain(request);
118
- }
119
- catch (err) {
120
- console.error('[SDK/handler] resolveBrain threw:', err);
121
- return _jsonError(503, 'BRAIN_UNAVAILABLE', 'Brainy instance not available');
122
- }
123
- const transport = new LocalTransport(brain);
124
- try {
125
- const result = await transport.call(method, args);
126
- return new Response(JSON.stringify({ result }), {
127
- status: 200,
128
- headers: { 'Content-Type': 'application/json' },
129
- });
130
- }
131
- catch (err) {
132
- const message = err instanceof Error ? err.message : String(err);
133
- return _jsonError(500, 'RPC_ERROR', message);
134
- }
135
- };
136
- }
137
- // ─────────────────────────────────────────────────────────────────────────────
138
- // Factory
139
- // ─────────────────────────────────────────────────────────────────────────────
140
- /**
141
- * Creates a server-side WebSocket handler for bidirectional Brainy RPC, real-time
142
- * change push, and optional Y.js collaborative document editing.
143
- *
144
- * ## Y.js collaborative editing
145
- *
146
- * Pass a `ydocManager` in the config to enable co-editing. The handler will
147
- * automatically route `y-sync`, `y-update`, and `y-awareness` messages through the
148
- * manager. The product server must:
149
- *
150
- * 1. Maintain a `Map<scope, Map<peerId, sendFn>>` of connected peers.
151
- * 2. Set `session.broadcast` after `handleUpgrade()` to fan out Y.js frames.
152
- * 3. Call `handleClose(session)` when a peer disconnects.
153
- *
154
- * @example Workshop server wiring with Y.js
155
- * ```typescript
156
- * import { createBrainyWsHandler, YDocManager } from '@soulcraft/sdk/server'
157
- *
158
- * const ydocManager = new YDocManager()
159
- * const wsHandler = createBrainyWsHandler({ resolveBrain, authenticate, ydocManager })
160
- *
161
- * // Map<scope, Map<peerId, send>>
162
- * const peers = new Map<string, Map<string, (msg: Uint8Array) => void>>()
163
- *
164
- * Bun.serve({
165
- * async fetch(req, server) {
166
- * if (new URL(req.url).pathname === '/api/brainy/ws') {
167
- * const session = await wsHandler.handleUpgrade(req)
168
- * if (!session) return new Response('Unauthorized', { status: 401 })
169
- * // Register peer in broadcast map
170
- * if (!peers.has(session.scope)) peers.set(session.scope, new Map())
171
- * server.upgrade(req, { data: session })
172
- * return undefined
173
- * }
174
- * },
175
- * websocket: {
176
- * open(ws) {
177
- * const session = ws.data as WsSession
178
- * peers.get(session.scope)!.set(session.peerId, (msg) => ws.send(msg))
179
- * session.broadcast = (msg, excludeId) => {
180
- * for (const [id, send] of peers.get(session.scope) ?? []) {
181
- * if (id !== excludeId) send(msg)
182
- * }
183
- * }
184
- * ws.send(encode({ type: 'ready', scope: session.scope }))
185
- * },
186
- * async message(ws, data) {
187
- * await wsHandler.handleMessage(ws.data, data as ArrayBuffer, (msg) => ws.send(msg))
188
- * },
189
- * async close(ws) {
190
- * const session = ws.data as WsSession
191
- * peers.get(session.scope)?.delete(session.peerId)
192
- * await wsHandler.handleClose(session)
193
- * },
194
- * },
195
- * })
196
- * ```
197
- *
198
- * @param config - Handler configuration.
199
- * @returns A {@link BrainyWsHandler} instance.
200
- */
201
- export function createBrainyWsHandler(config) {
202
- const { resolveBrain, authenticate, authorize, blockedMethods = [], ydocManager } = config;
203
- return {
204
- async handleUpgrade(request) {
205
- const url = new URL(request.url);
206
- const scope = url.searchParams.get('scope') ?? '';
207
- // Browser sends token as ?token= query param; Bun/Node uses Authorization header.
208
- const authHeader = request.headers.get('authorization') ?? '';
209
- const token = authHeader.startsWith('Bearer ')
210
- ? authHeader.slice(7)
211
- : (url.searchParams.get('token') ?? '');
212
- const auth = await authenticate(token, scope);
213
- if (!auth)
214
- return null;
215
- const brain = await resolveBrain(scope);
216
- const transport = new LocalTransport(brain);
217
- const peerId = _generatePeerId();
218
- // broadcast is a no-op until the product server's websocket.open() sets it.
219
- const session = {
220
- scope,
221
- auth,
222
- brain,
223
- transport,
224
- peerId,
225
- broadcast: () => { },
226
- };
227
- return session;
228
- },
229
- async handleMessage(session, data, send) {
230
- let decoded;
231
- try {
232
- decoded = decode(data instanceof ArrayBuffer ? new Uint8Array(data) : data);
233
- }
234
- catch {
235
- return; // Malformed frame — silently ignore
236
- }
237
- if (!decoded || typeof decoded !== 'object')
238
- return;
239
- const msg = decoded;
240
- // ── Y.js messages ────────────────────────────────────────────────────
241
- if (typeof msg['type'] === 'string' && msg['type'].startsWith('y-')) {
242
- await _handleYjsMessage(msg, session, send, ydocManager);
243
- return;
244
- }
245
- // ── Brainy RPC ───────────────────────────────────────────────────────
246
- const req = msg;
247
- if (typeof req.id !== 'string' || typeof req.method !== 'string' || !Array.isArray(req.args)) {
248
- return;
249
- }
250
- const { id, method, args } = req;
251
- if (blockedMethods.includes(method)) {
252
- send(encode({ id, error: { code: 'FORBIDDEN', message: `Method '${method}' blocked` } }));
253
- return;
254
- }
255
- if (authorize) {
256
- const allowed = await authorize(method, args, session.auth);
257
- if (!allowed) {
258
- send(encode({ id, error: { code: 'FORBIDDEN', message: `Not authorized: '${method}'` } }));
259
- return;
260
- }
261
- }
262
- try {
263
- const result = await session.transport.call(method, args);
264
- send(encode({ id, result }));
265
- }
266
- catch (err) {
267
- const message = err instanceof Error ? err.message : String(err);
268
- send(encode({ id, error: { code: 'RPC_ERROR', message } }));
269
- }
270
- },
271
- async handleClose(session) {
272
- if (!ydocManager)
273
- return;
274
- // Notify YDocManager for every file this peer had open.
275
- // The manager tracks peers per-file; onPeerDisconnect flushes + discards
276
- // when the last peer leaves a document.
277
- //
278
- // We don't track which files each peer opened at the session level —
279
- // YDocManager.onPeerDisconnect is idempotent for peers that never joined a doc.
280
- // The product server can also call this selectively if it tracks open files.
281
- //
282
- // For simplicity, the manager handles the "peer not known for this file" case
283
- // gracefully (no-op).
284
- await ydocManager.onPeerDisconnectAll(session.scope, session.peerId);
285
- },
286
- broadcastChange(event, send) {
287
- send(encode(event));
288
- },
289
- };
290
- }
291
- // ─────────────────────────────────────────────────────────────────────────────
292
- // Internal helpers
293
- // ─────────────────────────────────────────────────────────────────────────────
294
- /**
295
- * Builds a JSON error response with `{ error: { code, message } }` body.
296
- *
297
- * @param status - HTTP status code.
298
- * @param code - Machine-readable error code.
299
- * @param message - Human-readable description.
300
- * @returns A `Response` with the error payload.
301
- */
302
- function _jsonError(status, code, message) {
303
- return new Response(JSON.stringify({ error: { code, message } }), {
304
- status,
305
- headers: { 'Content-Type': 'application/json' },
306
- });
307
- }
308
- /**
309
- * Routes a Y.js protocol message to the appropriate `YDocManager` method and
310
- * broadcasts updates to other peers via `session.broadcast`.
311
- *
312
- * ## Sync protocol (Y.js standard two-phase handshake):
313
- * 1. Client connects → sends `y-sync` with its state vector.
314
- * 2. Server replies with `y-sync` diff (all updates client is missing).
315
- * 3. Client applies diff → sends `y-update` for each pending local op.
316
- * 4. From this point: all ops are `y-update` frames, broadcast to all other peers.
317
- *
318
- * ## Awareness:
319
- * - `y-awareness` frames carry cursor positions, user name/color, selection ranges.
320
- * - NOT persisted — forwarded immediately to all other peers then discarded.
321
- *
322
- * @param msg - Decoded Y.js message frame.
323
- * @param session - The WebSocket session for the sending peer.
324
- * @param send - Sends a response to THIS peer only.
325
- * @param ydocManager - The server-side Y.Doc pool (may be undefined if collab disabled).
326
- */
327
- async function _handleYjsMessage(msg, session, send, ydocManager) {
328
- if (!ydocManager)
329
- return;
330
- switch (msg.type) {
331
- case 'y-sync': {
332
- // Phase 1 of the Y.js sync handshake.
333
- // Client sends its state vector; server replies with the diff.
334
- const clientSv = msg.stateVector instanceof Uint8Array
335
- ? msg.stateVector
336
- : new Uint8Array(Object.values(msg.stateVector));
337
- const diff = await ydocManager.getDiff(session.scope, msg.filePath, session.brain, clientSv);
338
- // Reply only to this peer with the diff so it can catch up.
339
- send(encode({ type: 'y-sync', filePath: msg.filePath, diff }));
340
- break;
341
- }
342
- case 'y-update': {
343
- // A CRDT update from this peer — apply it and fan out to all others.
344
- const update = msg.update instanceof Uint8Array
345
- ? msg.update
346
- : new Uint8Array(Object.values(msg.update));
347
- // Ensure this peer is registered as active for this document.
348
- await ydocManager.getOrCreate(session.scope, msg.filePath, session.brain, session.peerId);
349
- await ydocManager.applyUpdate(session.scope, msg.filePath, update);
350
- // Broadcast to all OTHER peers in this scope.
351
- const frame = encode({ type: 'y-update', filePath: msg.filePath, update });
352
- session.broadcast(frame, session.peerId);
353
- break;
354
- }
355
- case 'y-awareness': {
356
- // Cursor / presence update — forward to all other peers, do not persist.
357
- const update = msg.update instanceof Uint8Array
358
- ? msg.update
359
- : new Uint8Array(Object.values(msg.update));
360
- const frame = encode({ type: 'y-awareness', update });
361
- session.broadcast(frame, session.peerId);
362
- break;
363
- }
364
- }
365
- }
366
- /**
367
- * Generates a unique peer ID for a new WebSocket connection.
368
- *
369
- * Uses `crypto.randomUUID()` which is available in Bun, Node 19+, and modern browsers.
370
- *
371
- * @returns A UUID string.
372
- */
373
- function _generatePeerId() {
374
- return crypto.randomUUID();
375
- }
376
- //# sourceMappingURL=handlers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"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"}
@@ -1,152 +0,0 @@
1
- /**
2
- * @module server/postmessage-handler
3
- * @description Parent-frame PostMessage handler for Workshop WebContainer kit iframes.
4
- *
5
- * Workshop kit apps run as WebContainer iframes inside the Workshop editor. They use
6
- * {@link PostMessageTransport} from `@soulcraft/sdk/client` to post Brainy RPC calls to the
7
- * parent Workshop frame. This module provides the parent-side handler that receives those
8
- * calls, dispatches them to the live Brainy instance, and posts the response back.
9
- *
10
- * ## Wire protocol
11
- *
12
- * **Inbound** (from kit iframe):
13
- * ```json
14
- * { "type": "brainy:request", "id": "<uuid>", "method": "find", "args": [{...}] }
15
- * ```
16
- *
17
- * **Outbound** (to kit iframe):
18
- * ```json
19
- * { "type": "brainy:response", "id": "<uuid>", "result": [...] }
20
- * { "type": "brainy:response", "id": "<uuid>", "error": { "code": "...", "message": "..." } }
21
- * ```
22
- *
23
- * ## Usage (Workshop parent frame)
24
- *
25
- * ```typescript
26
- * import { createBrainyPostMessageHandler } from '@soulcraft/sdk/server'
27
- * import { createSDK, BrainyInstancePool } from '@soulcraft/sdk/server'
28
- *
29
- * const pool = new BrainyInstancePool({ ... })
30
- *
31
- * // Mount once per kit iframe window:
32
- * const handler = createBrainyPostMessageHandler({
33
- * resolveBrain: async (method) => {
34
- * const brain = await pool.forUser(currentUser.emailHash, currentWorkspaceId)
35
- * return brain
36
- * },
37
- * allowedOrigin: 'null', // WebContainer iframes have origin 'null'
38
- * targetWindow: kitIframeRef.contentWindow!,
39
- * })
40
- *
41
- * // When the kit iframe is torn down:
42
- * handler.dispose()
43
- * ```
44
- *
45
- * ## Security
46
- *
47
- * The handler validates the `event.origin` against `allowedOrigin` before dispatching.
48
- * WebContainer iframes have `origin: 'null'` — pass `'null'` (string) for those.
49
- * For externally hosted kit apps, pass the expected origin string.
50
- *
51
- * Optionally, set `allowedSource` to the `contentWindow` of the specific iframe to
52
- * reject messages from other frames on the same origin.
53
- */
54
- import type { Brainy } from '@soulcraft/brainy';
55
- import type { BrainyChangeEvent } from '../modules/brainy/events.js';
56
- /**
57
- * Configuration for {@link createBrainyPostMessageHandler}.
58
- */
59
- export interface BrainyPostMessageHandlerConfig {
60
- /**
61
- * Resolve the Brainy instance to use for a given RPC call.
62
- *
63
- * Called once per request. Workshop typically resolves from a `BrainyInstancePool`
64
- * keyed to the current user and workspace.
65
- *
66
- * @param method - The Brainy method being called (e.g. `'find'`, `'vfs.readFile'`).
67
- * @returns A live Brainy instance.
68
- */
69
- resolveBrain: (method: string) => Promise<Brainy> | Brainy;
70
- /**
71
- * The expected `event.origin` of inbound messages.
72
- *
73
- * Messages from any other origin are silently ignored.
74
- * Pass `'null'` (string) for WebContainer iframes, which always report origin `'null'`.
75
- * Pass `'*'` to accept any origin (not recommended in production).
76
- */
77
- allowedOrigin: string;
78
- /**
79
- * The window to which responses are posted.
80
- *
81
- * Defaults to the `source` of the inbound message event. Pass the kit iframe's
82
- * `contentWindow` explicitly to further restrict which window receives responses.
83
- */
84
- targetWindow?: Window;
85
- /**
86
- * The window on which to listen for inbound `message` events.
87
- *
88
- * Defaults to the global `window`. Override in tests or if the handler should
89
- * listen on a specific frame's window.
90
- */
91
- listenerWindow?: Window & typeof globalThis;
92
- /**
93
- * Optional hook to subscribe to Brainy change events and forward them to the kit iframe.
94
- *
95
- * Called once when the handler is mounted. The provided `emit` callback posts a
96
- * `brainy:event` message to the kit iframe whenever a Brainy change fires. Return an
97
- * unsubscribe function; it will be called on `dispose()`.
98
- *
99
- * @param emit - Call this with each {@link BrainyChangeEvent} to forward it to the iframe.
100
- * @returns An unsubscribe function called on `dispose()`.
101
- *
102
- * @example
103
- * ```typescript
104
- * createBrainyPostMessageHandler({
105
- * resolveBrain: () => pool.forUser(user.emailHash, workspaceId),
106
- * allowedOrigin: 'null',
107
- * targetWindow: iframe.contentWindow!,
108
- * subscribeToEvents: (emit) => {
109
- * const off = brainyChangeEmitter.on('change', emit)
110
- * return off
111
- * },
112
- * })
113
- * ```
114
- */
115
- subscribeToEvents?: (emit: (event: BrainyChangeEvent) => void) => () => void;
116
- }
117
- /**
118
- * A mounted PostMessage handler. Call `dispose()` when the kit iframe is torn down
119
- * to remove the message listener and prevent memory leaks.
120
- */
121
- export interface BrainyPostMessageHandler {
122
- /**
123
- * Remove the `message` event listener from the listener window.
124
- *
125
- * Call this when the associated kit iframe is unmounted or navigated away.
126
- */
127
- dispose(): void;
128
- }
129
- /**
130
- * Mount a PostMessage handler in the parent Workshop frame.
131
- *
132
- * Listens for `brainy:request` messages from kit iframes, dispatches each call to
133
- * the resolved Brainy instance via a {@link LocalTransport}, and posts the result
134
- * back as a `brainy:response` message.
135
- *
136
- * @param config - Handler configuration including brain resolver and allowed origin.
137
- * @returns A {@link BrainyPostMessageHandler} with a `dispose()` method.
138
- *
139
- * @example
140
- * ```typescript
141
- * const handler = createBrainyPostMessageHandler({
142
- * resolveBrain: () => pool.forUser(user.emailHash, workspaceId),
143
- * allowedOrigin: 'null', // WebContainer iframes report origin 'null'
144
- * targetWindow: iframe.contentWindow!,
145
- * })
146
- *
147
- * // On iframe unmount:
148
- * handler.dispose()
149
- * ```
150
- */
151
- export declare function createBrainyPostMessageHandler(config: BrainyPostMessageHandlerConfig): BrainyPostMessageHandler;
152
- //# sourceMappingURL=postmessage-handler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"postmessage-handler.d.ts","sourceRoot":"","sources":["../../src/server/postmessage-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAG/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAMpE;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;IAE1D;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAA;IAErB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,UAAU,CAAA;IAE3C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CAC7E;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,8BAA8B,GACrC,wBAAwB,CA2D1B"}