@altertable/data-app 0.64.0 → 0.65.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 (47) hide show
  1. package/AGENTS.md +3 -0
  2. package/README.md +1 -1
  3. package/dist/chunks/{contract-xtxza1fy.js → contract-a061yj6r.js} +2 -2
  4. package/dist/chunks/contract-awa2d5b1.js +272 -0
  5. package/dist/chunks/contract-awa2d5b1.js.map +12 -0
  6. package/dist/chunks/{contract-zr3jd7mr.js → contract-g6hky7x6.js} +93 -72
  7. package/dist/chunks/contract-g6hky7x6.js.map +12 -0
  8. package/dist/chunks/{contract-mwe7gnmh.js → contract-sm7j7k95.js} +109 -73
  9. package/dist/chunks/contract-sm7j7k95.js.map +14 -0
  10. package/dist/client/index.js +2 -2
  11. package/dist/embed/index.js +19 -9
  12. package/dist/embed/index.js.map +3 -3
  13. package/dist/react/embed/index.js +5 -2
  14. package/dist/react/embed/index.js.map +3 -3
  15. package/dist/react/index.js +715 -443
  16. package/dist/react/index.js.map +13 -10
  17. package/dist/types/client/iframe.d.ts +2 -0
  18. package/dist/types/client/index.d.ts +1 -0
  19. package/dist/types/client/logger.d.ts +6 -0
  20. package/dist/types/core/bridge-endpoint.d.ts +21 -0
  21. package/dist/types/core/bridge.d.ts +137 -16
  22. package/dist/types/core/logger.d.ts +12 -0
  23. package/dist/types/embed/host.d.ts +6 -2
  24. package/dist/types/embed/index.d.ts +1 -0
  25. package/dist/types/embed/source.d.ts +1 -1
  26. package/dist/types/react/embed/bridge.d.ts +1 -1
  27. package/dist/types/react/index.d.ts +1 -0
  28. package/dist/types/react/ui/AppToolbar.d.ts +3 -1
  29. package/dist/types/react/ui/DataApp.d.ts +6 -1
  30. package/dist/types/react/ui/ExportControl.d.ts +4 -0
  31. package/dist/types/react/ui/Toast.d.ts +8 -0
  32. package/dist/types/react/ui/csv-export.d.ts +19 -0
  33. package/dist/types/react/ui/icons.d.ts +1 -0
  34. package/dist/worker.js +381 -79
  35. package/docs/app-authoring.md +28 -2
  36. package/docs/embed.md +1 -1
  37. package/docs/hosted-apps.md +2 -2
  38. package/docs/layout.md +31 -0
  39. package/docs/local-data-apps.md +1 -1
  40. package/docs/react.md +64 -2
  41. package/examples/starter-data-app/index.tsx +50 -32
  42. package/package.json +2 -1
  43. package/dist/chunks/contract-cxr9t12b.js +0 -18
  44. package/dist/chunks/contract-cxr9t12b.js.map +0 -10
  45. package/dist/chunks/contract-mwe7gnmh.js.map +0 -13
  46. package/dist/chunks/contract-zr3jd7mr.js.map +0 -12
  47. /package/dist/chunks/{contract-xtxza1fy.js.map → contract-a061yj6r.js.map} +0 -0
package/AGENTS.md CHANGED
@@ -3,5 +3,8 @@
3
3
  Build Altertable data apps with `@altertable/data-app`. They render in an iframe
4
4
  in Altertable, a chat app, or locally in a browser.
5
5
 
6
+ Every analytical app includes CSV export and a story in the built-in toolbar,
7
+ both derived from the displayed result. Setup and static screens may omit them.
8
+
6
9
  Follow [app authoring](docs/app-authoring.md) for the shared workflow, the data app
7
10
  and local data app paths, and task-specific documentation.
package/README.md CHANGED
@@ -21,7 +21,7 @@ bun add @altertable/data-app
21
21
  ## Build an app
22
22
 
23
23
  Apps render in an iframe in Altertable, a chat app, or locally in a browser.
24
- Follow [app authoring](docs/app-authoring.md) to explore data and present findings.
24
+ Follow [app authoring](docs/app-authoring.md) to explore data, export displayed results as CSV, and present findings.
25
25
 
26
26
  | App | Start here |
27
27
  | ---------------------------------- | ---------------------------------------- |
@@ -6,10 +6,10 @@ import {
6
6
  getDataAppTransport2,
7
7
  executeDataOperation,
8
8
  toDataOperationFailure
9
- } from "./contract-mwe7gnmh.js";
9
+ } from "./contract-sm7j7k95.js";
10
10
  import {
11
11
  PARENT_PARAM
12
- } from "./contract-cxr9t12b.js";
12
+ } from "./contract-awa2d5b1.js";
13
13
  import {
14
14
  validLocation,
15
15
  MessageRoutingError2,
@@ -0,0 +1,272 @@
1
+ // src/core/logger.ts
2
+ function isLogEntry(value) {
3
+ if (!value || typeof value !== "object")
4
+ return false;
5
+ const entry = value;
6
+ return (entry.method === "log" || entry.method === "info" || entry.method === "warn" || entry.method === "error") && Array.isArray(entry.args) && entry.args.length <= 128;
7
+ }
8
+
9
+ // src/core/bridge.ts
10
+ var BRIDGE = "altertable:data-app";
11
+ var PARENT_PARAM = "__altertable_parent";
12
+ var MAX_PENDING = 128;
13
+ var REQUEST_TIMEOUT_MS = 60000;
14
+ function validId(value) {
15
+ return typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
16
+ }
17
+ function event({
18
+ wire,
19
+ from,
20
+ session,
21
+ parse
22
+ }) {
23
+ return { wire, from, session, parse };
24
+ }
25
+ function id(fields) {
26
+ if (!validId(fields.id))
27
+ throw new Error("Invalid request ID.");
28
+ return fields.id;
29
+ }
30
+ function string(fields, key) {
31
+ const value = fields[key];
32
+ if (typeof value !== "string")
33
+ throw new Error(`Invalid ${key}.`);
34
+ return value;
35
+ }
36
+ function empty() {
37
+ return {};
38
+ }
39
+ var bridgeProtocol = {
40
+ channel: BRIDGE,
41
+ version: 1,
42
+ events: {
43
+ connect: event({
44
+ wire: "bridge:connect",
45
+ from: "host",
46
+ session: "none",
47
+ parse: empty
48
+ }),
49
+ ready: event({
50
+ wire: "bridge:ready",
51
+ from: "app",
52
+ session: "none",
53
+ parse: empty
54
+ }),
55
+ initialize: event({
56
+ wire: "bridge:initialize",
57
+ from: "host",
58
+ session: "establish",
59
+ parse: (fields) => ({ state: fields.state })
60
+ }),
61
+ request: event({
62
+ wire: "bridge:request",
63
+ from: "app",
64
+ session: "current",
65
+ parse: (fields) => {
66
+ const route = string(fields, "route");
67
+ if (!route || route.length > 256)
68
+ throw new Error("Invalid message route.");
69
+ return { id: id(fields), route, payload: fields.payload };
70
+ }
71
+ }),
72
+ result: event({
73
+ wire: "bridge:result",
74
+ from: "host",
75
+ session: "current",
76
+ parse: (fields) => {
77
+ if (!Object.hasOwn(fields, "response"))
78
+ throw new Error("Missing response.");
79
+ return { id: id(fields), response: fields.response };
80
+ }
81
+ }),
82
+ error: event({
83
+ wire: "bridge:error",
84
+ from: "host",
85
+ session: "current",
86
+ parse: (fields) => {
87
+ if (fields.requestId !== undefined && typeof fields.requestId !== "string")
88
+ throw new Error("Invalid requestId.");
89
+ return {
90
+ id: id(fields),
91
+ code: string(fields, "code"),
92
+ message: string(fields, "message"),
93
+ ...fields.requestId === undefined ? {} : { requestId: fields.requestId }
94
+ };
95
+ }
96
+ }),
97
+ cancel: event({
98
+ wire: "bridge:cancel",
99
+ from: "app",
100
+ session: "current",
101
+ parse: (fields) => ({ id: id(fields) })
102
+ }),
103
+ disconnect: event({
104
+ wire: "bridge:disconnect",
105
+ from: "app",
106
+ session: "current",
107
+ parse: empty
108
+ }),
109
+ scriptLoad: event({
110
+ wire: "script:load",
111
+ from: "host",
112
+ session: "current",
113
+ parse: (fields) => ({ javascript: string(fields, "javascript") })
114
+ }),
115
+ stateUpdate: event({
116
+ wire: "state:update",
117
+ from: "host",
118
+ session: "current",
119
+ parse: (fields) => ({ state: fields.state })
120
+ }),
121
+ runtimeLog: event({
122
+ wire: "runtime:log",
123
+ from: "app",
124
+ session: "current",
125
+ parse: (fields) => {
126
+ if (!isLogEntry(fields.payload))
127
+ throw new Error("Invalid log entry.");
128
+ return { payload: fields.payload };
129
+ }
130
+ }),
131
+ runtimeReady: event({
132
+ wire: "runtime:ready",
133
+ from: "app",
134
+ session: "current",
135
+ parse: empty
136
+ }),
137
+ runtimeError: event({
138
+ wire: "runtime:error",
139
+ from: "app",
140
+ session: "current",
141
+ parse: empty
142
+ })
143
+ }
144
+ };
145
+ var byWire = new Map;
146
+ for (const name of Object.keys(bridgeProtocol.events)) {
147
+ const wire = bridgeProtocol.events[name].wire;
148
+ if (byWire.has(wire))
149
+ throw new Error(`Duplicate bridge event: ${wire}`);
150
+ byWire.set(wire, name);
151
+ }
152
+ function bridgeEventName(wire) {
153
+ return byWire.get(wire);
154
+ }
155
+ function parseBridgeEnvelope(value) {
156
+ if (!value || typeof value !== "object")
157
+ return;
158
+ const fields = value;
159
+ if (fields.channel !== BRIDGE || fields.version !== bridgeProtocol.version || typeof fields.type !== "string" || typeof fields.documentId !== "string" || !fields.documentId || fields.documentId.length > 128 || fields.sessionId !== undefined && !validId(fields.sessionId) || fields.token !== undefined && typeof fields.token !== "string")
160
+ return;
161
+ return fields;
162
+ }
163
+ function parseBridgePayload(envelope, definition) {
164
+ try {
165
+ return {
166
+ channel: BRIDGE,
167
+ version: bridgeProtocol.version,
168
+ documentId: envelope.documentId,
169
+ ...envelope.sessionId === undefined ? {} : { sessionId: envelope.sessionId },
170
+ ...envelope.token === undefined ? {} : { token: envelope.token },
171
+ type: definition.wire,
172
+ ...definition.parse(envelope)
173
+ };
174
+ } catch {
175
+ return;
176
+ }
177
+ }
178
+ function parseBridgeMessage(value) {
179
+ const envelope = parseBridgeEnvelope(value);
180
+ if (!envelope)
181
+ return;
182
+ const name = bridgeEventName(envelope.type);
183
+ if (!name)
184
+ return;
185
+ const definition = bridgeProtocol.events[name];
186
+ if (definition.session !== "none" && envelope.sessionId === undefined)
187
+ return;
188
+ return parseBridgePayload(envelope, definition);
189
+ }
190
+
191
+ // src/core/bridge-endpoint.ts
192
+ function createBridgeEndpoint({
193
+ role,
194
+ origin,
195
+ source,
196
+ opaque,
197
+ context,
198
+ post,
199
+ diagnostic,
200
+ invalidRequest,
201
+ handlers
202
+ }) {
203
+ let disposed = false;
204
+ function send(name, payload) {
205
+ if (disposed)
206
+ throw new Error("The bridge endpoint has closed.");
207
+ const definition = bridgeProtocol.events[name];
208
+ const current = context();
209
+ const message = parseBridgeMessage({
210
+ ...payload,
211
+ channel: bridgeProtocol.channel,
212
+ version: bridgeProtocol.version,
213
+ type: definition.wire,
214
+ documentId: name === "connect" ? "host" : current.documentId,
215
+ sessionId: current.sessionId,
216
+ token: opaque ? current.token : undefined
217
+ });
218
+ if (!message || opaque && !validId(current.token))
219
+ throw new Error("The bridge is not ready to send this event.");
220
+ diagnostic?.("send", definition.wire);
221
+ if (disposed)
222
+ return;
223
+ post(message);
224
+ }
225
+ function receive(event) {
226
+ const peer = source();
227
+ if (disposed || !peer || event.origin !== origin || event.source !== peer)
228
+ return;
229
+ const envelope = parseBridgeEnvelope(event.data);
230
+ if (!envelope)
231
+ return;
232
+ const current = context();
233
+ const connecting = role === "app" && envelope.type === bridgeProtocol.events.connect.wire;
234
+ if (opaque && (connecting ? !validId(envelope.token) : !current.token || envelope.token !== current.token))
235
+ return;
236
+ const name = bridgeEventName(envelope.type);
237
+ if (!name)
238
+ return;
239
+ const definition = bridgeProtocol.events[name];
240
+ if (definition.from === role)
241
+ return;
242
+ if (definition.session === "establish") {
243
+ if (envelope.documentId !== current.documentId || envelope.sessionId === undefined)
244
+ return;
245
+ } else if (definition.session === "current") {
246
+ if (!current.sessionId || envelope.sessionId !== current.sessionId || envelope.documentId !== current.documentId)
247
+ return;
248
+ }
249
+ const message = parseBridgePayload(envelope, definition);
250
+ if (!message) {
251
+ if (role === "host" && name === "request" && validId(envelope.id))
252
+ invalidRequest?.(envelope.id);
253
+ return;
254
+ }
255
+ diagnostic?.("receive", definition.wire);
256
+ if (disposed)
257
+ return;
258
+ const handler = handlers[name];
259
+ handler(message);
260
+ }
261
+ return {
262
+ send,
263
+ receive,
264
+ dispose() {
265
+ disposed = true;
266
+ }
267
+ };
268
+ }
269
+
270
+ export { PARENT_PARAM, MAX_PENDING, REQUEST_TIMEOUT_MS, validId, createBridgeEndpoint };
271
+
272
+ //# debugId=856B2CD0C55C1CE964756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/logger.ts", "../src/core/bridge.ts", "../src/core/bridge-endpoint.ts"],
4
+ "sourcesContent": [
5
+ "/** Host-owned log sink; compatible with console and frontend createLogger(). */\nexport type DataAppLogger = {\n log: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n};\n\nexport type LogEntry = {\n method: keyof DataAppLogger;\n args: unknown[];\n};\n\nexport function isLogEntry(value: unknown): value is LogEntry {\n if (!value || typeof value !== 'object') return false;\n const entry = value as Record<string, unknown>;\n return (\n (entry.method === 'log' ||\n entry.method === 'info' ||\n entry.method === 'warn' ||\n entry.method === 'error') &&\n Array.isArray(entry.args) &&\n entry.args.length <= 128\n );\n}\n",
6
+ "import { isLogEntry } from '@/src/core/logger';\n\n/** Data app delivery protocol. IDs identify a document, a shell session, and an individual call. */\nexport const BRIDGE = 'altertable:data-app';\nexport const PARENT_PARAM = '__altertable_parent';\n/** Bound pending requests in the client and host independently.\n * The host enforces its own limit because frames can send messages directly. */\nexport const MAX_PENDING = 128;\nexport const REQUEST_TIMEOUT_MS = 60_000;\nexport type { TransportResponse } from '@/src/core/operation-types';\n\nexport function validId(value: unknown): value is string {\n return typeof value === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(value);\n}\n\ntype Fields = Record<string, unknown>;\nexport type BridgeRole = 'host' | 'app';\ntype SessionRule = 'none' | 'establish' | 'current';\n\nfunction event<\n const Wire extends string,\n const Role extends BridgeRole,\n const Session extends SessionRule,\n Payload,\n>({\n wire,\n from,\n session,\n parse,\n}: {\n wire: Wire;\n from: Role;\n session: Session;\n parse: (fields: Fields) => Payload;\n}) {\n return { wire, from, session, parse };\n}\n\nfunction id(fields: Fields) {\n if (!validId(fields.id)) throw new Error('Invalid request ID.');\n return fields.id;\n}\n\nfunction string(fields: Fields, key: string) {\n const value = fields[key];\n if (typeof value !== 'string') throw new Error(`Invalid ${key}.`);\n return value;\n}\n\nfunction empty() {\n return {};\n}\n\n/** Parsers project known fields; opaque values are validated by their owning route or adapter. */\nexport const bridgeProtocol = {\n channel: BRIDGE,\n version: 1,\n events: {\n connect: event({\n wire: 'bridge:connect',\n from: 'host',\n session: 'none',\n parse: empty,\n }),\n ready: event({\n wire: 'bridge:ready',\n from: 'app',\n session: 'none',\n parse: empty,\n }),\n initialize: event({\n wire: 'bridge:initialize',\n from: 'host',\n session: 'establish',\n parse: fields => ({ state: fields.state }),\n }),\n request: event({\n wire: 'bridge:request',\n from: 'app',\n session: 'current',\n parse: fields => {\n const route = string(fields, 'route');\n if (!route || route.length > 256)\n throw new Error('Invalid message route.');\n return { id: id(fields), route, payload: fields.payload };\n },\n }),\n result: event({\n wire: 'bridge:result',\n from: 'host',\n session: 'current',\n parse: fields => {\n if (!Object.hasOwn(fields, 'response'))\n throw new Error('Missing response.');\n return { id: id(fields), response: fields.response };\n },\n }),\n error: event({\n wire: 'bridge:error',\n from: 'host',\n session: 'current',\n parse: fields => {\n if (\n fields.requestId !== undefined &&\n typeof fields.requestId !== 'string'\n )\n throw new Error('Invalid requestId.');\n return {\n id: id(fields),\n code: string(fields, 'code'),\n message: string(fields, 'message'),\n ...(fields.requestId === undefined\n ? {}\n : { requestId: fields.requestId }),\n };\n },\n }),\n cancel: event({\n wire: 'bridge:cancel',\n from: 'app',\n session: 'current',\n parse: fields => ({ id: id(fields) }),\n }),\n disconnect: event({\n wire: 'bridge:disconnect',\n from: 'app',\n session: 'current',\n parse: empty,\n }),\n scriptLoad: event({\n wire: 'script:load',\n from: 'host',\n session: 'current',\n parse: fields => ({ javascript: string(fields, 'javascript') }),\n }),\n stateUpdate: event({\n wire: 'state:update',\n from: 'host',\n session: 'current',\n parse: fields => ({ state: fields.state }),\n }),\n runtimeLog: event({\n wire: 'runtime:log',\n from: 'app',\n session: 'current',\n parse: fields => {\n if (!isLogEntry(fields.payload)) throw new Error('Invalid log entry.');\n return { payload: fields.payload };\n },\n }),\n runtimeReady: event({\n wire: 'runtime:ready',\n from: 'app',\n session: 'current',\n parse: empty,\n }),\n runtimeError: event({\n wire: 'runtime:error',\n from: 'app',\n session: 'current',\n parse: empty,\n }),\n },\n} as const;\n\ntype Events = typeof bridgeProtocol.events;\nexport type BridgeEventName = keyof Events;\nexport type BridgePayload<Name extends BridgeEventName> = ReturnType<\n Events[Name]['parse']\n>;\nexport type BridgeEnvelope = {\n channel: typeof BRIDGE;\n version: typeof bridgeProtocol.version;\n documentId: string;\n sessionId?: string;\n token?: string;\n};\nexport type BridgeEventMessage<Name extends BridgeEventName> =\n Name extends BridgeEventName\n ? BridgeEnvelope & {\n type: Events[Name]['wire'];\n } & (Events[Name]['session'] extends 'none'\n ? {}\n : { sessionId: string }) &\n BridgePayload<Name>\n : never;\nexport type BridgeMessage = {\n [Name in BridgeEventName]: BridgeEventMessage<Name>;\n}[BridgeEventName];\nexport type BridgeEventsFrom<Role extends BridgeRole> = {\n [Name in BridgeEventName]: Events[Name]['from'] extends Role ? Name : never;\n}[BridgeEventName];\nexport type BridgeHandlers<Role extends BridgeRole> = {\n [Name in BridgeEventsFrom<Role extends 'host' ? 'app' : 'host'>]: (\n message: BridgeEventMessage<Name>\n ) => void;\n};\n\nconst byWire = new Map<string, BridgeEventName>();\nfor (const name of Object.keys(bridgeProtocol.events) as BridgeEventName[]) {\n const wire = bridgeProtocol.events[name].wire;\n if (byWire.has(wire)) throw new Error(`Duplicate bridge event: ${wire}`);\n byWire.set(wire, name);\n}\n\nexport function bridgeEventName(wire: string) {\n return byWire.get(wire);\n}\n\n/** Envelope parsing precedes authentication; event-specific fields remain untrusted. */\nexport function parseBridgeEnvelope(\n value: unknown\n): (BridgeEnvelope & Fields & { type: string }) | undefined {\n if (!value || typeof value !== 'object') return;\n const fields = value as Fields;\n if (\n fields.channel !== BRIDGE ||\n fields.version !== bridgeProtocol.version ||\n typeof fields.type !== 'string' ||\n typeof fields.documentId !== 'string' ||\n !fields.documentId ||\n fields.documentId.length > 128 ||\n (fields.sessionId !== undefined && !validId(fields.sessionId)) ||\n (fields.token !== undefined && typeof fields.token !== 'string')\n )\n return;\n return fields as BridgeEnvelope & Fields & { type: string };\n}\n\n/** Project event fields after the caller has checked envelope eligibility. */\nexport function parseBridgePayload(\n envelope: NonNullable<ReturnType<typeof parseBridgeEnvelope>>,\n definition: Events[BridgeEventName]\n): BridgeMessage | undefined {\n try {\n return {\n channel: BRIDGE,\n version: bridgeProtocol.version,\n documentId: envelope.documentId,\n ...(envelope.sessionId === undefined\n ? {}\n : { sessionId: envelope.sessionId }),\n ...(envelope.token === undefined ? {} : { token: envelope.token }),\n type: definition.wire,\n ...definition.parse(envelope),\n } as BridgeMessage;\n } catch {\n return;\n }\n}\n\nexport function parseBridgeMessage(value: unknown): BridgeMessage | undefined {\n const envelope = parseBridgeEnvelope(value);\n if (!envelope) return;\n const name = bridgeEventName(envelope.type);\n if (!name) return;\n const definition = bridgeProtocol.events[name];\n if (definition.session !== 'none' && envelope.sessionId === undefined) return;\n return parseBridgePayload(envelope, definition);\n}\n",
7
+ "import {\n bridgeProtocol,\n bridgeEventName,\n parseBridgeEnvelope,\n parseBridgeMessage,\n parseBridgePayload,\n validId,\n type BridgeMessage,\n type BridgeRole,\n type BridgeEventsFrom,\n type BridgePayload,\n type BridgeEnvelope,\n type BridgeHandlers,\n} from '@/src/core/bridge';\n\ntype ConnectionContext = Pick<BridgeEnvelope, 'sessionId' | 'token'> & {\n documentId?: string;\n};\n\n/** The connection owns IDs and token rotation; callers can only supply event payloads. */\nexport function createBridgeEndpoint<const Role extends BridgeRole>({\n role,\n origin,\n source,\n opaque,\n context,\n post,\n diagnostic,\n invalidRequest,\n handlers,\n}: {\n role: Role;\n handlers: BridgeHandlers<NoInfer<Role>>;\n origin: string;\n source: () => MessageEventSource | null;\n opaque: boolean;\n context: () => ConnectionContext;\n post: (message: BridgeMessage) => void;\n diagnostic?: (\n direction: 'send' | 'receive',\n type: BridgeMessage['type']\n ) => void;\n invalidRequest?: (id: string) => void;\n}) {\n let disposed = false;\n\n function send<\n Name extends BridgeEventsFrom<Role>,\n const Payload extends BridgePayload<NoInfer<Name>>,\n >(\n name: Name,\n payload: Payload &\n Record<Exclude<keyof Payload, keyof BridgePayload<NoInfer<Name>>>, never>\n ) {\n if (disposed) throw new Error('The bridge endpoint has closed.');\n const definition = bridgeProtocol.events[name];\n const current = context();\n const message = parseBridgeMessage({\n ...payload,\n channel: bridgeProtocol.channel,\n version: bridgeProtocol.version,\n type: definition.wire,\n documentId: name === 'connect' ? 'host' : current.documentId,\n sessionId: current.sessionId,\n token: opaque ? current.token : undefined,\n });\n if (!message || (opaque && !validId(current.token)))\n throw new Error('The bridge is not ready to send this event.');\n diagnostic?.('send', definition.wire);\n if (disposed) return;\n post(message);\n }\n\n function receive(event: MessageEvent) {\n const peer = source();\n if (disposed || !peer || event.origin !== origin || event.source !== peer)\n return;\n const envelope = parseBridgeEnvelope(event.data);\n if (!envelope) return;\n const current = context();\n // The app learns its opaque token from a trusted host connect message.\n const connecting =\n role === 'app' && envelope.type === bridgeProtocol.events.connect.wire;\n if (\n opaque &&\n (connecting\n ? !validId(envelope.token)\n : !current.token || envelope.token !== current.token)\n )\n return;\n const name = bridgeEventName(envelope.type);\n if (!name) return;\n const definition = bridgeProtocol.events[name];\n if (definition.from === role) return;\n if (definition.session === 'establish') {\n if (\n envelope.documentId !== current.documentId ||\n envelope.sessionId === undefined\n )\n return;\n } else if (definition.session === 'current') {\n if (\n !current.sessionId ||\n envelope.sessionId !== current.sessionId ||\n envelope.documentId !== current.documentId\n )\n return;\n }\n const message = parseBridgePayload(envelope, definition);\n if (!message) {\n if (role === 'host' && name === 'request' && validId(envelope.id))\n invalidRequest?.(envelope.id);\n return;\n }\n diagnostic?.('receive', definition.wire);\n if (disposed) return;\n // The catalog lookup correlates the key and parsed discriminated message.\n const handler = (handlers as Record<string, (message: unknown) => void>)[\n name\n ]!;\n handler(message);\n }\n\n return {\n send,\n receive,\n dispose() {\n disposed = true;\n },\n };\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "856B2CD0C55C1CE964756E2164756E21",
11
+ "names": []
12
+ }
@@ -1,11 +1,10 @@
1
1
  import {
2
- BRIDGE,
3
2
  PARENT_PARAM,
4
3
  MAX_PENDING,
5
4
  REQUEST_TIMEOUT_MS,
6
- isBridgeMessage,
7
- validId
8
- } from "./contract-cxr9t12b.js";
5
+ validId,
6
+ createBridgeEndpoint
7
+ } from "./contract-awa2d5b1.js";
9
8
  import {
10
9
  MessageRoutingError2
11
10
  } from "./contract-tf8c3qpv.js";
@@ -16,6 +15,7 @@ function attachDataAppConnection({
16
15
  connection,
17
16
  javascript,
18
17
  presentation,
18
+ logger,
19
19
  onStatusChange,
20
20
  onDiagnostic,
21
21
  onMessage,
@@ -37,45 +37,100 @@ function attachDataAppConnection({
37
37
  return {
38
38
  search: host.location.search,
39
39
  hash: host.location.hash,
40
- ...currentPresentation ? { presentation: currentPresentation } : {}
40
+ ...currentPresentation ? { presentation: currentPresentation } : {},
41
+ logging: logger !== undefined
41
42
  };
42
43
  }
44
+ function setLogger(value) {
45
+ if (disposed || value === logger)
46
+ return;
47
+ const wasEnabled = logger !== undefined;
48
+ logger = value;
49
+ if (wasEnabled !== (logger !== undefined))
50
+ publishState();
51
+ }
43
52
  function setPresentation(value) {
44
53
  if (disposed)
45
54
  return;
46
55
  if (value?.surface === currentPresentation?.surface && value?.theme === currentPresentation?.theme)
47
56
  return;
48
57
  currentPresentation = value && { ...value };
49
- if (sessionId)
50
- publishState();
58
+ publishState();
51
59
  }
52
60
  function cancelAll() {
53
61
  for (const controller of pending.values())
54
62
  controller.abort();
55
63
  pending.clear();
56
64
  }
57
- function send(message) {
58
- onDiagnostic?.({ direction: "send", type: message.type ?? "unknown" });
59
- iframe.contentWindow?.postMessage({
60
- channel: BRIDGE,
61
- version: 1,
62
- documentId,
63
- sessionId,
64
- ...connection.type === "opaque" ? { token } : {},
65
- ...message
66
- }, targetOrigin);
67
- }
65
+ const endpoint = createBridgeEndpoint({
66
+ role: "host",
67
+ origin: frameOrigin,
68
+ source: () => iframe.contentWindow,
69
+ opaque: connection.type === "opaque",
70
+ context: () => ({ documentId, sessionId, token }),
71
+ post: (message) => iframe.contentWindow?.postMessage(message, targetOrigin),
72
+ diagnostic: (direction, type) => onDiagnostic?.({ direction, type }),
73
+ invalidRequest: (id) => {
74
+ if (pending.has(id))
75
+ return;
76
+ if (pending.size >= MAX_PENDING)
77
+ error(id, "bridge_busy", "Too many pending data requests.");
78
+ else
79
+ error(id, "invalid_message", "Invalid message route.");
80
+ },
81
+ handlers: {
82
+ ready(message) {
83
+ if (message.documentId !== documentId) {
84
+ cancelAll();
85
+ documentId = message.documentId;
86
+ sessionId = crypto.randomUUID();
87
+ }
88
+ onStatusChange?.("connected");
89
+ if (disposed)
90
+ return;
91
+ send("initialize", { state: hostState() });
92
+ if (disposed)
93
+ return;
94
+ if (javascript !== undefined)
95
+ send("scriptLoad", { javascript });
96
+ },
97
+ runtimeLog({ payload }) {
98
+ try {
99
+ logger?.[payload.method](...payload.args);
100
+ } catch {}
101
+ },
102
+ runtimeReady() {
103
+ onStatusChange?.("ready");
104
+ },
105
+ runtimeError() {
106
+ onStatusChange?.("failed");
107
+ },
108
+ request(message) {
109
+ request(message);
110
+ },
111
+ cancel(message) {
112
+ pending.get(message.id)?.abort();
113
+ pending.delete(message.id);
114
+ },
115
+ disconnect() {
116
+ cancelAll();
117
+ documentId = undefined;
118
+ sessionId = undefined;
119
+ onStatusChange?.("disconnected");
120
+ }
121
+ }
122
+ });
123
+ const send = endpoint.send;
124
+ const receive = endpoint.receive;
68
125
  function error(id, code, message, requestId) {
69
- send({ type: "bridge:error", id, code, message, requestId });
126
+ send("error", { id, code, message, requestId });
70
127
  }
71
128
  async function request(message) {
72
129
  const id = message.id;
73
- if (!validId(id) || pending.has(id))
130
+ if (pending.has(id))
74
131
  return;
75
132
  if (pending.size >= MAX_PENDING)
76
133
  return error(id, "bridge_busy", "Too many pending data requests.");
77
- if (typeof message.route !== "string" || !message.route || message.route.length > 256)
78
- return error(id, "invalid_message", "Invalid message route.");
79
134
  try {
80
135
  const body = JSON.stringify(message.payload);
81
136
  if (message.payload !== undefined && (typeof body !== "string" || body.length > 16384))
@@ -98,7 +153,7 @@ function attachDataAppConnection({
98
153
  try {
99
154
  const response = await onMessage({ route: message.route, payload: message.payload }, { signal: controller.signal });
100
155
  if (pending.get(id) === controller)
101
- send({ type: "bridge:result", id, response });
156
+ send("result", { id, response });
102
157
  } catch (failure) {
103
158
  if (pending.get(id) === controller) {
104
159
  if (failure instanceof MessageRoutingError2)
@@ -112,51 +167,10 @@ function attachDataAppConnection({
112
167
  pending.delete(id);
113
168
  }
114
169
  }
115
- function receive(event) {
116
- if (event.origin !== frameOrigin || event.source !== iframe.contentWindow || !isBridgeMessage(event.data))
117
- return;
118
- const message = event.data;
119
- if (connection.type === "opaque" && message.token !== token)
120
- return;
121
- onDiagnostic?.({ direction: "receive", type: message.type });
122
- if (message.type === "bridge:ready") {
123
- if (message.documentId !== documentId) {
124
- cancelAll();
125
- documentId = message.documentId;
126
- sessionId = crypto.randomUUID();
127
- }
128
- onStatusChange?.("connected");
129
- send({
130
- type: "bridge:initialize",
131
- state: hostState()
132
- });
133
- if (javascript !== undefined)
134
- send({ type: "script:load", javascript });
135
- return;
136
- }
137
- if (!sessionId || message.sessionId !== sessionId || message.documentId !== documentId)
138
- return;
139
- if (message.type === "runtime:ready")
140
- onStatusChange?.("ready");
141
- else if (message.type === "runtime:error")
142
- onStatusChange?.("failed");
143
- else if (message.type === "bridge:request")
144
- request(message);
145
- else if (message.type === "bridge:cancel" && validId(message.id)) {
146
- pending.get(message.id)?.abort();
147
- pending.delete(message.id);
148
- } else if (message.type === "bridge:disconnect") {
149
- cancelAll();
150
- documentId = undefined;
151
- sessionId = undefined;
152
- onStatusChange?.("disconnected");
153
- }
154
- }
155
170
  function publishState() {
156
- send({
157
- type: "state:update",
158
- state: hostState()
159
- });
171
+ if (disposed || !sessionId)
172
+ return;
173
+ send("stateUpdate", { state: hostState() });
160
174
  }
161
175
  function load() {
162
176
  if (connection.type === "opaque")
@@ -165,10 +179,11 @@ function attachDataAppConnection({
165
179
  documentId = undefined;
166
180
  sessionId = undefined;
167
181
  onStatusChange?.("connecting");
168
- send({ type: "bridge:connect", documentId: "host" });
182
+ send("connect", {});
169
183
  }
170
184
  function dispose() {
171
185
  disposed = true;
186
+ endpoint.dispose();
172
187
  cancelAll();
173
188
  iframe.removeEventListener("load", load);
174
189
  host.removeEventListener("message", receive);
@@ -181,8 +196,8 @@ function attachDataAppConnection({
181
196
  host.addEventListener("popstate", publishState);
182
197
  host.addEventListener("pagehide", cancelAll);
183
198
  onStatusChange?.("connecting");
184
- send({ type: "bridge:connect", documentId: "host" });
185
- return { dispose, setPresentation };
199
+ send("connect", {});
200
+ return { dispose, setPresentation, setLogger };
186
201
  }
187
202
 
188
203
  // src/embed/source.ts
@@ -190,6 +205,7 @@ function attachDataAppSource({
190
205
  iframe,
191
206
  source,
192
207
  presentation,
208
+ logger,
193
209
  onMessage,
194
210
  onStatusChange,
195
211
  onDiagnostic,
@@ -233,6 +249,7 @@ function attachDataAppSource({
233
249
  connection: opaque ? { type: "opaque", token: crypto.randomUUID() } : { type: "origin", origin: url.origin },
234
250
  javascript: opaque ? source.javascript : undefined,
235
251
  presentation,
252
+ logger,
236
253
  onMessage,
237
254
  onStatusChange: status,
238
255
  onDiagnostic,
@@ -248,7 +265,11 @@ function attachDataAppSource({
248
265
  iframe.removeEventListener("error", error);
249
266
  bridge.dispose();
250
267
  }
251
- return { dispose, setPresentation: bridge.setPresentation };
268
+ return {
269
+ dispose,
270
+ setPresentation: bridge.setPresentation,
271
+ setLogger: bridge.setLogger
272
+ };
252
273
  }
253
274
 
254
275
  // src/embed/bridge.ts
@@ -258,4 +279,4 @@ function attachDataAppBridge2(options) {
258
279
 
259
280
  export { attachDataAppBridge2 };
260
281
 
261
- //# debugId=F23BB728E5A0F78664756E2164756E21
282
+ //# debugId=5826F4790D0666BF64756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/embed/host.ts", "../src/embed/source.ts", "../src/embed/bridge.ts"],
4
+ "sourcesContent": [
5
+ "import { createBridgeEndpoint } from '@/src/core/bridge-endpoint';\nimport type { DataAppPresentation } from '@/src/core/presentation';\nimport type { DataAppLogger } from '@/src/core/logger';\nimport {\n MAX_PENDING,\n REQUEST_TIMEOUT_MS,\n validId,\n type BridgeEventMessage,\n type BridgeMessage,\n} from '@/src/core/bridge';\n\nimport {\n MessageRoutingError,\n type MessageDispatcher,\n} from '@/src/core/messages';\n\nexport type DataAppConnection =\n | { type: 'origin'; origin: string }\n | { type: 'opaque'; token: string };\nexport type DataAppStatus =\n | 'connecting'\n | 'connected'\n | 'ready'\n | 'failed'\n | 'disconnected';\nexport type DataAppDiagnostic = {\n direction: 'send' | 'receive';\n type: BridgeMessage['type'];\n};\n\nexport type DataAppHost = {\n dispose: () => void;\n setPresentation: (presentation?: DataAppPresentation) => void;\n setLogger: (logger?: DataAppLogger) => void;\n};\n\nexport type DataAppHostOptions = {\n iframe: HTMLIFrameElement;\n presentation?: DataAppPresentation;\n logger?: DataAppLogger;\n onStatusChange?: (status: DataAppStatus) => void;\n onDiagnostic?: (event: DataAppDiagnostic) => void;\n onMessage: MessageDispatcher;\n};\n\nexport type DataAppConnectionOptions = DataAppHostOptions & {\n connection: DataAppConnection;\n javascript?: string;\n window?: Window;\n};\n\n/** The bridge owns delivery and cancellation; the routed handler owns validation, authorization and execution. */\nexport function attachDataAppConnection({\n iframe,\n connection,\n javascript,\n presentation,\n logger,\n onStatusChange,\n onDiagnostic,\n onMessage,\n window: host = window,\n}: DataAppConnectionOptions): DataAppHost {\n const frameOrigin = connection.type === 'origin' ? connection.origin : 'null';\n if (\n connection.type === 'origin' &&\n new URL(frameOrigin).origin !== frameOrigin\n )\n throw new Error('Expected an exact frame origin.');\n if (connection.type === 'opaque' && !validId(connection.token))\n throw new Error('Invalid sandbox token.');\n let token = connection.type === 'opaque' ? connection.token : undefined;\n const targetOrigin = connection.type === 'opaque' ? '*' : frameOrigin;\n let currentPresentation: DataAppPresentation | undefined;\n let disposed = false;\n let documentId: string | undefined;\n let sessionId: string | undefined;\n const pending = new Map<string, AbortController>();\n\n function hostState() {\n return {\n search: host.location.search,\n hash: host.location.hash,\n ...(currentPresentation ? { presentation: currentPresentation } : {}),\n logging: logger !== undefined,\n };\n }\n\n function setLogger(value: DataAppLogger | undefined) {\n if (disposed || value === logger) return;\n const wasEnabled = logger !== undefined;\n logger = value;\n if (wasEnabled !== (logger !== undefined)) publishState();\n }\n\n function setPresentation(value: DataAppPresentation | undefined) {\n if (disposed) return;\n if (\n value?.surface === currentPresentation?.surface &&\n value?.theme === currentPresentation?.theme\n )\n return;\n currentPresentation = value && { ...value };\n publishState();\n }\n\n function cancelAll() {\n for (const controller of pending.values()) controller.abort();\n pending.clear();\n }\n\n const endpoint = createBridgeEndpoint({\n role: 'host',\n origin: frameOrigin,\n source: () => iframe.contentWindow,\n opaque: connection.type === 'opaque',\n context: () => ({ documentId, sessionId, token }),\n post: message => iframe.contentWindow?.postMessage(message, targetOrigin),\n diagnostic: (direction, type) => onDiagnostic?.({ direction, type }),\n invalidRequest: id => {\n if (pending.has(id)) return;\n if (pending.size >= MAX_PENDING)\n error(id, 'bridge_busy', 'Too many pending data requests.');\n else error(id, 'invalid_message', 'Invalid message route.');\n },\n handlers: {\n ready(message) {\n if (message.documentId !== documentId) {\n cancelAll();\n documentId = message.documentId;\n sessionId = crypto.randomUUID();\n }\n onStatusChange?.('connected');\n if (disposed) return;\n send('initialize', { state: hostState() });\n if (disposed) return;\n if (javascript !== undefined) send('scriptLoad', { javascript });\n },\n runtimeLog({ payload }) {\n try {\n logger?.[payload.method](...payload.args);\n } catch {\n // A host logger failure must not break bridge delivery.\n }\n },\n runtimeReady() {\n onStatusChange?.('ready');\n },\n runtimeError() {\n onStatusChange?.('failed');\n },\n request(message) {\n void request(message);\n },\n cancel(message) {\n pending.get(message.id)?.abort();\n pending.delete(message.id);\n },\n disconnect() {\n cancelAll();\n documentId = undefined;\n sessionId = undefined;\n onStatusChange?.('disconnected');\n },\n },\n });\n const send = endpoint.send;\n const receive = endpoint.receive;\n\n function error(\n id: string,\n code: string,\n message: string,\n requestId?: string\n ) {\n send('error', { id, code, message, requestId });\n }\n\n async function request(message: BridgeEventMessage<'request'>) {\n const id = message.id;\n if (pending.has(id)) return;\n if (pending.size >= MAX_PENDING)\n return error(id, 'bridge_busy', 'Too many pending data requests.');\n try {\n const body = JSON.stringify(message.payload);\n if (\n message.payload !== undefined &&\n (typeof body !== 'string' || body.length > 16_384)\n )\n throw new Error('Invalid payload');\n } catch {\n return error(\n id,\n 'invalid_payload',\n 'The message payload is invalid or too large.'\n );\n }\n const controller = new AbortController();\n pending.set(id, controller);\n const timer = setTimeout(() => {\n if (pending.get(id) !== controller) return;\n pending.delete(id);\n controller.abort();\n error(id, 'timeout', 'The data request timed out.');\n }, REQUEST_TIMEOUT_MS);\n controller.signal.addEventListener('abort', () => clearTimeout(timer), {\n once: true,\n });\n try {\n const response = await onMessage(\n { route: message.route, payload: message.payload },\n { signal: controller.signal }\n );\n if (pending.get(id) === controller) send('result', { id, response });\n } catch (failure) {\n if (pending.get(id) === controller) {\n if (failure instanceof MessageRoutingError)\n error(id, failure.code, failure.message, failure.requestId);\n else\n error(\n id,\n 'request_failed',\n 'The message request failed. Retry the request.'\n );\n }\n } finally {\n clearTimeout(timer);\n if (pending.get(id) === controller) pending.delete(id);\n }\n }\n\n function publishState() {\n if (disposed || !sessionId) return;\n send('stateUpdate', { state: hostState() });\n }\n\n function load() {\n if (connection.type === 'opaque') token = crypto.randomUUID();\n cancelAll();\n documentId = undefined;\n sessionId = undefined;\n onStatusChange?.('connecting');\n send('connect', {});\n }\n\n function dispose() {\n disposed = true;\n endpoint.dispose();\n cancelAll();\n iframe.removeEventListener('load', load);\n host.removeEventListener('message', receive);\n host.removeEventListener('popstate', publishState);\n host.removeEventListener('pagehide', cancelAll);\n }\n\n setPresentation(presentation);\n iframe.addEventListener('load', load);\n host.addEventListener('message', receive);\n host.addEventListener('popstate', publishState);\n host.addEventListener('pagehide', cancelAll);\n // Reconnect an already-loaded iframe when its host bridge mounts again.\n onStatusChange?.('connecting');\n send('connect', {});\n\n return { dispose, setPresentation, setLogger };\n}\n",
6
+ "import { PARENT_PARAM } from '@/src/core/bridge';\nimport {\n attachDataAppConnection,\n type DataAppStatus,\n type DataAppHost,\n type DataAppHostOptions,\n} from '@/src/embed/host';\n\nexport type DataAppSource =\n | { type: 'url'; url: string }\n | {\n type: 'bundle';\n bootstrapUrl: string;\n javascript: string;\n };\nexport type DataAppSourceOptions = DataAppHostOptions & {\n source: DataAppSource;\n startupTimeoutMs?: number;\n};\n\n/** Owns loading and sandbox policy. Dispose before replacing the source or retrying. */\nexport function attachDataAppSource({\n iframe,\n source,\n presentation,\n logger,\n onMessage,\n onStatusChange,\n onDiagnostic,\n startupTimeoutMs = 30_000,\n}: DataAppSourceOptions): DataAppHost {\n const host = iframe.ownerDocument.defaultView;\n if (!host) throw new Error('The iframe requires a host window.');\n const url = new URL(\n source.type === 'url' ? source.url : source.bootstrapUrl,\n host.location.href\n );\n if (!/^https?:$/.test(url.protocol))\n throw new Error('Data apps require an HTTP(S) URL.');\n const opaque = source.type === 'bundle';\n if (!opaque && url.origin === host.location.origin)\n throw new Error('URL data apps require a separate origin from the host.');\n iframe.setAttribute(\n 'sandbox',\n opaque\n ? 'allow-scripts'\n : 'allow-scripts allow-same-origin allow-downloads allow-popups allow-popups-to-escape-sandbox'\n );\n iframe.referrerPolicy = 'no-referrer';\n if (!opaque) url.searchParams.set(PARENT_PARAM, host.location.origin);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let failed = false;\n\n function status(value: DataAppStatus) {\n if (value === 'connecting') failed = false;\n if (failed && value !== 'connecting') return;\n clearTimeout(timer);\n if (value === 'connecting' || value === 'connected')\n timer = setTimeout(() => {\n failed = true;\n bridge.dispose();\n onStatusChange?.('failed');\n }, startupTimeoutMs);\n if (value === 'failed') {\n failed = true;\n bridge.dispose();\n }\n onStatusChange?.(value);\n }\n\n const bridge = attachDataAppConnection({\n iframe,\n connection: opaque\n ? { type: 'opaque', token: crypto.randomUUID() }\n : { type: 'origin', origin: url.origin },\n javascript: opaque ? source.javascript : undefined,\n presentation,\n logger,\n onMessage,\n onStatusChange: status,\n onDiagnostic,\n window: host,\n });\n\n function error() {\n return status('failed');\n }\n\n iframe.addEventListener('error', error);\n iframe.src = url.href;\n\n function dispose() {\n clearTimeout(timer);\n iframe.removeEventListener('error', error);\n bridge.dispose();\n }\n\n return {\n dispose,\n setPresentation: bridge.setPresentation,\n setLogger: bridge.setLogger,\n };\n}\n",
7
+ "import {\n attachDataAppConnection,\n type DataAppConnectionOptions,\n} from '@/src/embed/host';\nimport {\n attachDataAppSource,\n type DataAppSourceOptions,\n} from '@/src/embed/source';\n\nexport type DataAppBridgeOptions =\n | (DataAppSourceOptions & {\n connection?: never;\n window?: never;\n javascript?: never;\n })\n | (DataAppConnectionOptions & { source?: never; startupTimeoutMs?: never });\n\n/** Source mode configures and loads the iframe; connection mode attaches to a host-owned document. */\nexport function attachDataAppBridge(options: DataAppBridgeOptions) {\n return options.source\n ? attachDataAppSource(options)\n : attachDataAppConnection(options);\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "5826F4790D0666BF64756E2164756E21",
11
+ "names": []
12
+ }