@altertable/data-app 0.63.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.
- package/AGENTS.md +3 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +1 -1
- package/dist/chunks/{contract-tkc552ze.js → contract-a061yj6r.js} +2 -2
- package/dist/chunks/contract-awa2d5b1.js +272 -0
- package/dist/chunks/contract-awa2d5b1.js.map +12 -0
- package/dist/chunks/{contract-zr3jd7mr.js → contract-g6hky7x6.js} +93 -72
- package/dist/chunks/contract-g6hky7x6.js.map +12 -0
- package/dist/chunks/{contract-8wdybxj7.js → contract-sm7j7k95.js} +110 -74
- package/dist/chunks/contract-sm7j7k95.js.map +14 -0
- package/dist/chunks/{contract-farfe948.js → contract-txjy0en2.js} +31 -31
- package/dist/chunks/contract-txjy0en2.js.map +10 -0
- package/dist/chunks/contract-wz59z8pq.js.map +2 -2
- package/dist/chunks/{contract-wd8qe3mt.js → contract-yxbjea23.js} +8 -6
- package/dist/chunks/contract-yxbjea23.js.map +12 -0
- package/dist/client/index.js +3 -3
- package/dist/core/appearance.js +4 -2
- package/dist/core/appearance.js.map +1 -1
- package/dist/core/contract.js +1 -1
- package/dist/embed/index.js +20 -10
- package/dist/embed/index.js.map +3 -3
- package/dist/react/embed/index.js +5 -2
- package/dist/react/embed/index.js.map +3 -3
- package/dist/react/index.js +726 -454
- package/dist/react/index.js.map +17 -14
- package/dist/types/client/iframe.d.ts +2 -0
- package/dist/types/client/index.d.ts +1 -0
- package/dist/types/client/logger.d.ts +6 -0
- package/dist/types/core/appearance.d.ts +12 -1
- package/dist/types/core/bridge-endpoint.d.ts +21 -0
- package/dist/types/core/bridge.d.ts +137 -16
- package/dist/types/core/config.d.ts +2 -1
- package/dist/types/core/logger.d.ts +12 -0
- package/dist/types/embed/host.d.ts +6 -2
- package/dist/types/embed/index.d.ts +1 -0
- package/dist/types/embed/source.d.ts +1 -1
- package/dist/types/react/embed/bridge.d.ts +1 -1
- package/dist/types/react/index.d.ts +1 -0
- package/dist/types/react/ui/AppToolbar.d.ts +3 -1
- package/dist/types/react/ui/DataApp.d.ts +6 -1
- package/dist/types/react/ui/ExportControl.d.ts +4 -0
- package/dist/types/react/ui/Toast.d.ts +8 -0
- package/dist/types/react/ui/csv-export.d.ts +19 -0
- package/dist/types/react/ui/icons.d.ts +1 -0
- package/dist/types/react/ui/useAppAppearance.d.ts +2 -2
- package/dist/worker.js +382 -79
- package/docs/app-authoring.md +30 -3
- package/docs/embed.md +1 -1
- package/docs/hosted-apps.md +2 -2
- package/docs/layout.md +31 -0
- package/docs/local-data-apps.md +1 -1
- package/docs/react.md +64 -2
- package/examples/starter-data-app/index.tsx +50 -32
- package/package.json +2 -1
- package/dist/chunks/contract-8q35dcyh.js +0 -9
- package/dist/chunks/contract-8q35dcyh.js.map +0 -10
- package/dist/chunks/contract-8wdybxj7.js.map +0 -13
- package/dist/chunks/contract-cxr9t12b.js +0 -18
- package/dist/chunks/contract-cxr9t12b.js.map +0 -10
- package/dist/chunks/contract-farfe948.js.map +0 -10
- package/dist/chunks/contract-wd8qe3mt.js.map +0 -11
- package/dist/chunks/contract-zr3jd7mr.js.map +0 -12
- /package/dist/chunks/{contract-tkc552ze.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/CONTRIBUTING.md
CHANGED
|
@@ -30,6 +30,10 @@ Use `@/src/...` imports in repository source and `@altertable/data-app/<entry>`
|
|
|
30
30
|
in consumer tests and examples. Run `bun run lint:fix` and `bun run format` for
|
|
31
31
|
style fixes.
|
|
32
32
|
|
|
33
|
+
Compose the gallery from provided components, including layouts. Let primitives
|
|
34
|
+
own sizing, spacing, and responsive behavior. Report missing primitives and keep
|
|
35
|
+
fallbacks minimal.
|
|
36
|
+
|
|
33
37
|
## Verify changes
|
|
34
38
|
|
|
35
39
|
Tests should tell the package's public usage stories: define an operation,
|
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-
|
|
9
|
+
} from "./contract-sm7j7k95.js";
|
|
10
10
|
import {
|
|
11
11
|
PARENT_PARAM
|
|
12
|
-
} from "./contract-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
} from "./contract-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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(
|
|
126
|
+
send("error", { id, code, message, requestId });
|
|
70
127
|
}
|
|
71
128
|
async function request(message) {
|
|
72
129
|
const id = message.id;
|
|
73
|
-
if (
|
|
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(
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
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=
|
|
282
|
+
//# debugId=5826F4790D0666BF64756E2164756E21
|