@shadow-js/devtools 0.0.0-stage → 1.0.1
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +576 -2
- package/dist/dev/agent.d.ts +239 -0
- package/dist/dev/agent.js +626 -0
- package/dist/dev/agent.js.map +1 -0
- package/dist/dev/backend.d.ts +53 -0
- package/dist/dev/backend.js +53 -0
- package/dist/dev/backend.js.map +1 -0
- package/dist/dev/hook.d.ts +83 -0
- package/dist/dev/hook.js +136 -0
- package/dist/dev/hook.js.map +1 -0
- package/dist/dev/index.d.ts +17 -0
- package/dist/dev/index.js +17 -0
- package/dist/dev/inspection.d.ts +226 -0
- package/dist/dev/inspection.js +263 -0
- package/dist/dev/inspection.js.map +1 -0
- package/dist/dev/inspector.d.ts +331 -0
- package/dist/dev/inspector.js +429 -0
- package/dist/dev/inspector.js.map +1 -0
- package/dist/dev/model.d.ts +418 -0
- package/dist/dev/model.js +559 -0
- package/dist/dev/model.js.map +1 -0
- package/dist/dev/mount.d.ts +207 -0
- package/dist/dev/mount.js +256 -0
- package/dist/dev/mount.js.map +1 -0
- package/dist/dev/overlay.d.ts +102 -0
- package/dist/dev/overlay.js +176 -0
- package/dist/dev/overlay.js.map +1 -0
- package/dist/dev/preview.d.ts +74 -0
- package/dist/dev/preview.js +122 -0
- package/dist/dev/preview.js.map +1 -0
- package/dist/dev/protocol.d.ts +288 -0
- package/dist/dev/protocol.js +109 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/ring.d.ts +50 -0
- package/dist/dev/ring.js +94 -0
- package/dist/dev/ring.js.map +1 -0
- package/dist/dev/transport.d.ts +347 -0
- package/dist/dev/transport.js +394 -0
- package/dist/dev/transport.js.map +1 -0
- package/dist/dev/ui/details.js +294 -0
- package/dist/dev/ui/details.js.map +1 -0
- package/dist/dev/ui/inspection-state.d.ts +43 -0
- package/dist/dev/ui/inspection-state.js +62 -0
- package/dist/dev/ui/inspection-state.js.map +1 -0
- package/dist/dev/ui/issues.js +85 -0
- package/dist/dev/ui/issues.js.map +1 -0
- package/dist/dev/ui/panel.d.ts +55 -0
- package/dist/dev/ui/panel.js +160 -0
- package/dist/dev/ui/panel.js.map +1 -0
- package/dist/dev/ui/rows.d.ts +44 -0
- package/dist/dev/ui/rows.js +132 -0
- package/dist/dev/ui/rows.js.map +1 -0
- package/dist/dev/ui/state-tab.js +88 -0
- package/dist/dev/ui/state-tab.js.map +1 -0
- package/dist/dev/ui/state.d.ts +192 -0
- package/dist/dev/ui/state.js +147 -0
- package/dist/dev/ui/state.js.map +1 -0
- package/dist/dev/ui/styles.d.ts +31 -0
- package/dist/dev/ui/styles.js +113 -0
- package/dist/dev/ui/styles.js.map +1 -0
- package/dist/dev/ui/timeline.js +135 -0
- package/dist/dev/ui/timeline.js.map +1 -0
- package/dist/dev/ui/tree.js +186 -0
- package/dist/dev/ui/tree.js.map +1 -0
- package/dist/prod/agent.js +2 -0
- package/dist/prod/agent.js.map +1 -0
- package/dist/prod/backend.js +2 -0
- package/dist/prod/backend.js.map +1 -0
- package/dist/prod/hook.js +2 -0
- package/dist/prod/hook.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/inspection.js +2 -0
- package/dist/prod/inspection.js.map +1 -0
- package/dist/prod/inspector.js +2 -0
- package/dist/prod/inspector.js.map +1 -0
- package/dist/prod/model.js +2 -0
- package/dist/prod/model.js.map +1 -0
- package/dist/prod/mount.js +2 -0
- package/dist/prod/mount.js.map +1 -0
- package/dist/prod/overlay.js +11 -0
- package/dist/prod/overlay.js.map +1 -0
- package/dist/prod/preview.js +2 -0
- package/dist/prod/preview.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/ring.js +2 -0
- package/dist/prod/ring.js.map +1 -0
- package/dist/prod/transport.js +2 -0
- package/dist/prod/transport.js.map +1 -0
- package/dist/prod/ui/details.js +2 -0
- package/dist/prod/ui/details.js.map +1 -0
- package/dist/prod/ui/inspection-state.js +2 -0
- package/dist/prod/ui/inspection-state.js.map +1 -0
- package/dist/prod/ui/issues.js +2 -0
- package/dist/prod/ui/issues.js.map +1 -0
- package/dist/prod/ui/panel.js +2 -0
- package/dist/prod/ui/panel.js.map +1 -0
- package/dist/prod/ui/rows.js +2 -0
- package/dist/prod/ui/rows.js.map +1 -0
- package/dist/prod/ui/state-tab.js +2 -0
- package/dist/prod/ui/state-tab.js.map +1 -0
- package/dist/prod/ui/state.js +2 -0
- package/dist/prod/ui/state.js.map +1 -0
- package/dist/prod/ui/styles.js +82 -0
- package/dist/prod/ui/styles.js.map +1 -0
- package/dist/prod/ui/timeline.js +2 -0
- package/dist/prod/ui/timeline.js.map +1 -0
- package/dist/prod/ui/tree.js +2 -0
- package/dist/prod/ui/tree.js.map +1 -0
- package/package.json +69 -4
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import "./protocol.js";
|
|
2
|
+
import { InspectorModel } from "./model.js";
|
|
3
|
+
//#region src/transport.ts
|
|
4
|
+
/**
|
|
5
|
+
* The `channel` field every devtools message carries. It lets the messages share a `postMessage`
|
|
6
|
+
* or `BroadcastChannel` with other traffic: anything without this value is ignored.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { CHANNEL } from "@shadow-js/devtools";
|
|
10
|
+
* window.addEventListener("message", (event: MessageEvent) => {
|
|
11
|
+
* if (event.data?.channel === CHANNEL) console.log("devtools message", event.data.type);
|
|
12
|
+
* });
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
const CHANNEL = "shadowjs-devtools";
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null;
|
|
18
|
+
}
|
|
19
|
+
function isEnvelope(value, from) {
|
|
20
|
+
return isRecord(value) && value["channel"] === "shadowjs-devtools" && value["from"] === from && typeof value["v"] === "number" && typeof value["type"] === "string";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* True for a well-formed message sent by an agent. Anything else on the channel is ignored. The check
|
|
24
|
+
* covers the envelope (`channel`, `from`, `v`, `type`) and the fields each message type needs. It does
|
|
25
|
+
* not validate every event inside an `events` message.
|
|
26
|
+
*
|
|
27
|
+
* @param value A received message.
|
|
28
|
+
* @returns True when `value` is an {@link AgentMessage}.
|
|
29
|
+
* @example
|
|
30
|
+
* import { CHANNEL, isAgentMessage } from "@shadow-js/devtools";
|
|
31
|
+
* isAgentMessage({ channel: CHANNEL, v: 1, from: "agent", type: "hello" }); // true
|
|
32
|
+
* isAgentMessage({ channel: CHANNEL, v: 1, from: "panel", type: "hello" }); // false
|
|
33
|
+
* @since 1.0.0
|
|
34
|
+
*/
|
|
35
|
+
function isAgentMessage(value) {
|
|
36
|
+
if (!isEnvelope(value, "agent")) return false;
|
|
37
|
+
switch (value["type"]) {
|
|
38
|
+
case "hello": return true;
|
|
39
|
+
case "events": return Array.isArray(value["events"]);
|
|
40
|
+
case "response": return typeof value["id"] === "number" && isRecord(value["response"]);
|
|
41
|
+
default: return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* True for a well-formed message sent by a panel. Anything else on the channel is ignored. The check
|
|
46
|
+
* covers the envelope and the fields each message type needs, including the request's `type`.
|
|
47
|
+
*
|
|
48
|
+
* @param value A received message.
|
|
49
|
+
* @returns True when `value` is a {@link PanelMessage}.
|
|
50
|
+
* @example
|
|
51
|
+
* import { CHANNEL, isPanelMessage } from "@shadow-js/devtools";
|
|
52
|
+
* isPanelMessage({ channel: CHANNEL, v: 1, from: "panel", type: "hello" }); // true
|
|
53
|
+
* isPanelMessage({ channel: CHANNEL, v: 1, from: "agent", type: "hello" }); // false
|
|
54
|
+
* @since 1.0.0
|
|
55
|
+
*/
|
|
56
|
+
function isPanelMessage(value) {
|
|
57
|
+
if (!isEnvelope(value, "panel")) return false;
|
|
58
|
+
switch (value["type"]) {
|
|
59
|
+
case "hello": return true;
|
|
60
|
+
case "request": return typeof value["id"] === "number" && isRecord(value["request"]) && typeof value["request"]["type"] === "string";
|
|
61
|
+
default: return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Two connected in-memory transports, for tests and for connecting an agent and a panel inside one
|
|
66
|
+
* page. Messages are structured-cloned, so anything that is not plain serializable data fails here
|
|
67
|
+
* exactly as it would over `postMessage`. Closing either end closes both.
|
|
68
|
+
*
|
|
69
|
+
* @returns `[agentSide, panelSide]`.
|
|
70
|
+
* @example
|
|
71
|
+
* import { connectRemote, createAgent, createMemoryTransportPair, serveAgent } from "@shadow-js/devtools";
|
|
72
|
+
* const [agentSide, panelSide] = createMemoryTransportPair();
|
|
73
|
+
* const stop = serveAgent(createAgent(), agentSide);
|
|
74
|
+
* const backend = connectRemote(panelSide);
|
|
75
|
+
* await backend.ready;
|
|
76
|
+
* stop();
|
|
77
|
+
* @since 1.0.0
|
|
78
|
+
*/
|
|
79
|
+
function createMemoryTransportPair() {
|
|
80
|
+
const handlers = [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()];
|
|
81
|
+
let closed = false;
|
|
82
|
+
const side = (index) => ({
|
|
83
|
+
send(message) {
|
|
84
|
+
if (closed) return;
|
|
85
|
+
const copy = structuredClone(message);
|
|
86
|
+
for (const handler of [...handlers[index === 0 ? 1 : 0]]) handler(copy);
|
|
87
|
+
},
|
|
88
|
+
listen(handler) {
|
|
89
|
+
handlers[index].add(handler);
|
|
90
|
+
return () => {
|
|
91
|
+
handlers[index].delete(handler);
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
close() {
|
|
95
|
+
closed = true;
|
|
96
|
+
handlers[0].clear();
|
|
97
|
+
handlers[1].clear();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return [side(0), side(1)];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A transport over a `MessagePort`: a `MessageChannel` end, a worker port or an extension port.
|
|
104
|
+
* Incoming messages reach the listeners registered with `listen`. `close` closes the port when it
|
|
105
|
+
* supports closing.
|
|
106
|
+
*
|
|
107
|
+
* @param port The port.
|
|
108
|
+
* @returns The transport.
|
|
109
|
+
* @example
|
|
110
|
+
* import { connectRemote, createAgent, createPortTransport, serveAgent } from "@shadow-js/devtools";
|
|
111
|
+
* const channel = new MessageChannel();
|
|
112
|
+
* serveAgent(createAgent(), createPortTransport(channel.port1));
|
|
113
|
+
* const backend = connectRemote(createPortTransport(channel.port2));
|
|
114
|
+
* await backend.ready;
|
|
115
|
+
* @since 1.0.0
|
|
116
|
+
*/
|
|
117
|
+
function createPortTransport(port) {
|
|
118
|
+
return {
|
|
119
|
+
send(message) {
|
|
120
|
+
port.postMessage(message);
|
|
121
|
+
},
|
|
122
|
+
listen(handler) {
|
|
123
|
+
const listener = (event) => handler(event.data);
|
|
124
|
+
port.addEventListener("message", listener);
|
|
125
|
+
port.start?.();
|
|
126
|
+
return () => port.removeEventListener("message", listener);
|
|
127
|
+
},
|
|
128
|
+
close() {
|
|
129
|
+
port.close?.();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A transport over `window.postMessage` between a page and a panel in another window or frame (a
|
|
135
|
+
* DevTools extension page, a popup). Both sides call it, each naming the other window and its origin.
|
|
136
|
+
* Messages are sent only to `target`, with `origin` as the target origin. Incoming messages are
|
|
137
|
+
* delivered only when their origin equals `origin` and their source is `target`; all others are
|
|
138
|
+
* ignored.
|
|
139
|
+
*
|
|
140
|
+
* @param options Target window, its origin and the receiving window.
|
|
141
|
+
* @returns The transport.
|
|
142
|
+
* @throws {TypeError} When `origin` is `"*"`: the messages would reach any origin.
|
|
143
|
+
* @example
|
|
144
|
+
* import { createAgent, createWindowTransport, serveAgent } from "@shadow-js/devtools";
|
|
145
|
+
* const frame = document.querySelector("iframe");
|
|
146
|
+
* if (frame?.contentWindow) {
|
|
147
|
+
* const transport = createWindowTransport({ target: frame.contentWindow, origin: "https://panel.example" });
|
|
148
|
+
* serveAgent(createAgent(), transport);
|
|
149
|
+
* }
|
|
150
|
+
* @since 1.0.0
|
|
151
|
+
*/
|
|
152
|
+
function createWindowTransport(options) {
|
|
153
|
+
if (options.origin === "*") throw new TypeError("createWindowTransport: pass the real origin, not \"*\".");
|
|
154
|
+
const receiver = options.receiver ?? globalThis;
|
|
155
|
+
return {
|
|
156
|
+
send(message) {
|
|
157
|
+
options.target.postMessage(message, options.origin);
|
|
158
|
+
},
|
|
159
|
+
listen(handler) {
|
|
160
|
+
const listener = (event) => {
|
|
161
|
+
if (event.origin !== options.origin) return;
|
|
162
|
+
if (event.source !== options.target) return;
|
|
163
|
+
handler(event.data);
|
|
164
|
+
};
|
|
165
|
+
receiver.addEventListener("message", listener);
|
|
166
|
+
return () => receiver.removeEventListener("message", listener);
|
|
167
|
+
},
|
|
168
|
+
close() {}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A transport over a `BroadcastChannel`, for a panel in another tab or window of the same origin.
|
|
173
|
+
* Several panels can listen to one agent; each message goes to all of them.
|
|
174
|
+
*
|
|
175
|
+
* @param name Channel name. Both sides use the same.
|
|
176
|
+
* @param factory Creates the channel (default `new BroadcastChannel(name)`).
|
|
177
|
+
* @returns The transport.
|
|
178
|
+
* @example
|
|
179
|
+
* import { connectRemote, createBroadcastChannelTransport } from "@shadow-js/devtools";
|
|
180
|
+
* const backend = connectRemote(createBroadcastChannelTransport("shadowjs-devtools"));
|
|
181
|
+
* await backend.ready;
|
|
182
|
+
* @since 1.0.0
|
|
183
|
+
*/
|
|
184
|
+
function createBroadcastChannelTransport(name, factory = (n) => new BroadcastChannel(n)) {
|
|
185
|
+
const channel = factory(name);
|
|
186
|
+
return {
|
|
187
|
+
send(message) {
|
|
188
|
+
channel.postMessage(message);
|
|
189
|
+
},
|
|
190
|
+
listen(handler) {
|
|
191
|
+
const listener = (event) => handler(event.data);
|
|
192
|
+
channel.addEventListener("message", listener);
|
|
193
|
+
return () => channel.removeEventListener("message", listener);
|
|
194
|
+
},
|
|
195
|
+
close() {
|
|
196
|
+
channel.close();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Exposes an agent over a transport: forwards its event stream to the panel and answers the panel's
|
|
202
|
+
* requests. The agent keeps recording while no panel listens, and the stream is sent regardless. What
|
|
203
|
+
* happens to events nobody receives depends on the transport. A `highlight` request is passed to
|
|
204
|
+
* `options.highlight` and answered without reaching the agent. Stop serving with the returned function.
|
|
205
|
+
*
|
|
206
|
+
* @param agent The agent in the inspected page.
|
|
207
|
+
* @param transport The channel to the panel.
|
|
208
|
+
* @param options Page-side behavior (highlight overlay) and batch size.
|
|
209
|
+
* @returns A function that stops serving.
|
|
210
|
+
* @example
|
|
211
|
+
* import { createAgent, createMemoryTransportPair, serveAgent } from "@shadow-js/devtools";
|
|
212
|
+
* const [pageSide] = createMemoryTransportPair();
|
|
213
|
+
* const stop = serveAgent(createAgent(), pageSide, { highlight: (id) => console.log("highlight", id) });
|
|
214
|
+
* stop();
|
|
215
|
+
* @since 1.0.0
|
|
216
|
+
*/
|
|
217
|
+
function serveAgent(agent, transport, options = {}) {
|
|
218
|
+
const limit = options.batch ?? 2e3;
|
|
219
|
+
const send = (message) => transport.send(message);
|
|
220
|
+
const stopListening = transport.listen((raw) => {
|
|
221
|
+
if (!isPanelMessage(raw)) return;
|
|
222
|
+
if (raw.type === "hello") {
|
|
223
|
+
send({
|
|
224
|
+
channel: CHANNEL,
|
|
225
|
+
v: 1,
|
|
226
|
+
from: "agent",
|
|
227
|
+
type: "hello"
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const { request } = raw;
|
|
232
|
+
let response;
|
|
233
|
+
if (request.type === "highlight") {
|
|
234
|
+
options.highlight?.(request.id);
|
|
235
|
+
response = {
|
|
236
|
+
ok: true,
|
|
237
|
+
result: null
|
|
238
|
+
};
|
|
239
|
+
} else response = agent.handle(request);
|
|
240
|
+
send({
|
|
241
|
+
channel: CHANNEL,
|
|
242
|
+
v: 1,
|
|
243
|
+
from: "agent",
|
|
244
|
+
type: "response",
|
|
245
|
+
id: raw.id,
|
|
246
|
+
response
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
const stopStreaming = agent.subscribe((events) => {
|
|
250
|
+
for (let i = 0; i < events.length; i += limit) send({
|
|
251
|
+
channel: CHANNEL,
|
|
252
|
+
v: 1,
|
|
253
|
+
from: "agent",
|
|
254
|
+
type: "events",
|
|
255
|
+
events: events.slice(i, i + limit)
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
return () => {
|
|
259
|
+
stopListening();
|
|
260
|
+
stopStreaming();
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Connects a panel to a remote agent: asks for the complete state, then applies the event stream
|
|
265
|
+
* to a local model. Events that arrive before the state are buffered, and the ones the state already
|
|
266
|
+
* contains are skipped (sequence numbers), so no event is lost or applied twice. Disposing the backend
|
|
267
|
+
* closes the transport.
|
|
268
|
+
*
|
|
269
|
+
* @param transport The channel to the agent.
|
|
270
|
+
* @param options Model limits.
|
|
271
|
+
* @returns A {@link Backend}.
|
|
272
|
+
* @example
|
|
273
|
+
* import { connectRemote, createBroadcastChannelTransport } from "@shadow-js/devtools";
|
|
274
|
+
* const backend = connectRemote(createBroadcastChannelTransport("shadowjs-devtools"));
|
|
275
|
+
* await backend.ready;
|
|
276
|
+
* @since 1.0.0
|
|
277
|
+
*/
|
|
278
|
+
function connectRemote(transport, options = {}) {
|
|
279
|
+
const model = new InspectorModel(options.limits);
|
|
280
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
281
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
282
|
+
let buffered = [];
|
|
283
|
+
let synced = false;
|
|
284
|
+
let nextRequest = 1;
|
|
285
|
+
let disposed = false;
|
|
286
|
+
let notifyScheduled = false;
|
|
287
|
+
let resolveReady = () => void 0;
|
|
288
|
+
const ready = new Promise((resolve) => {
|
|
289
|
+
resolveReady = resolve;
|
|
290
|
+
});
|
|
291
|
+
const notify = () => {
|
|
292
|
+
if (notifyScheduled) return;
|
|
293
|
+
notifyScheduled = true;
|
|
294
|
+
queueMicrotask(() => {
|
|
295
|
+
notifyScheduled = false;
|
|
296
|
+
if (disposed) return;
|
|
297
|
+
for (const listener of listeners) listener();
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
const send = (message) => transport.send(message);
|
|
301
|
+
const applyState = (state) => {
|
|
302
|
+
model.importState(state);
|
|
303
|
+
synced = true;
|
|
304
|
+
const pending = buffered;
|
|
305
|
+
buffered = [];
|
|
306
|
+
for (const event of pending) model.apply(event);
|
|
307
|
+
resolveReady();
|
|
308
|
+
notify();
|
|
309
|
+
};
|
|
310
|
+
const request = (req) => {
|
|
311
|
+
if (disposed) return Promise.reject(/* @__PURE__ */ new Error("The devtools backend was disposed."));
|
|
312
|
+
const id = nextRequest;
|
|
313
|
+
nextRequest += 1;
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
waiting.set(id, {
|
|
316
|
+
resolve,
|
|
317
|
+
reject
|
|
318
|
+
});
|
|
319
|
+
send({
|
|
320
|
+
channel: CHANNEL,
|
|
321
|
+
v: 1,
|
|
322
|
+
from: "panel",
|
|
323
|
+
type: "request",
|
|
324
|
+
id,
|
|
325
|
+
request: req
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
const stop = transport.listen((raw) => {
|
|
330
|
+
if (!isAgentMessage(raw)) return;
|
|
331
|
+
if (raw.v !== 1) return;
|
|
332
|
+
if (raw.type === "events") {
|
|
333
|
+
if (!synced) {
|
|
334
|
+
buffered.push(...raw.events);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
for (const event of raw.events) model.apply(event);
|
|
338
|
+
notify();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (raw.type === "response") {
|
|
342
|
+
const entry = waiting.get(raw.id);
|
|
343
|
+
if (entry === void 0) return;
|
|
344
|
+
waiting.delete(raw.id);
|
|
345
|
+
entry.resolve(raw.response);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
const sync = () => {
|
|
349
|
+
request({ type: "snapshot" }).then((response) => {
|
|
350
|
+
if (response.ok) applyState(response.result);
|
|
351
|
+
}, () => {
|
|
352
|
+
synced = false;
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
send({
|
|
356
|
+
channel: CHANNEL,
|
|
357
|
+
v: 1,
|
|
358
|
+
from: "panel",
|
|
359
|
+
type: "hello"
|
|
360
|
+
});
|
|
361
|
+
sync();
|
|
362
|
+
return {
|
|
363
|
+
model,
|
|
364
|
+
ready,
|
|
365
|
+
subscribe(listener) {
|
|
366
|
+
listeners.add(listener);
|
|
367
|
+
return () => {
|
|
368
|
+
listeners.delete(listener);
|
|
369
|
+
};
|
|
370
|
+
},
|
|
371
|
+
request(req) {
|
|
372
|
+
if (req.type === "clear") return request(req).then((response) => {
|
|
373
|
+
if (response.ok) {
|
|
374
|
+
synced = false;
|
|
375
|
+
sync();
|
|
376
|
+
}
|
|
377
|
+
return response;
|
|
378
|
+
});
|
|
379
|
+
return request(req);
|
|
380
|
+
},
|
|
381
|
+
dispose() {
|
|
382
|
+
disposed = true;
|
|
383
|
+
stop();
|
|
384
|
+
listeners.clear();
|
|
385
|
+
for (const entry of waiting.values()) entry.reject(/* @__PURE__ */ new Error("The devtools backend was disposed."));
|
|
386
|
+
waiting.clear();
|
|
387
|
+
transport.close();
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
export { CHANNEL, connectRemote, createBroadcastChannelTransport, createMemoryTransportPair, createPortTransport, createWindowTransport, isAgentMessage, isPanelMessage, serveAgent };
|
|
393
|
+
|
|
394
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","names":[],"sources":["../../src/transport.ts"],"sourcesContent":["import type { Agent, AgentRequest, AgentResponse } from \"./agent\";\nimport type { Backend, PageHooks } from \"./backend\";\nimport { InspectorModel, type ModelLimits, type ModelState } from \"./model\";\nimport { PROTOCOL_VERSION, type WireEvent } from \"./protocol\";\n\n/**\n * The `channel` field every devtools message carries. It lets the messages share a `postMessage`\n * or `BroadcastChannel` with other traffic: anything without this value is ignored.\n *\n * @example\n * import { CHANNEL } from \"@shadow-js/devtools\";\n * window.addEventListener(\"message\", (event: MessageEvent) => {\n * if (event.data?.channel === CHANNEL) console.log(\"devtools message\", event.data.type);\n * });\n * @since 1.0.0\n */\nexport const CHANNEL = \"shadowjs-devtools\";\n\n/**\n * A message the agent side sends to the panel: the answer to a `hello`, a batch of events, or the\n * answer to a request. All fields are plain data.\n *\n * @since 1.0.0\n */\nexport type AgentMessage =\n | { channel: typeof CHANNEL; v: number; from: \"agent\"; type: \"hello\" }\n | { channel: typeof CHANNEL; v: number; from: \"agent\"; type: \"events\"; events: WireEvent[] }\n | {\n channel: typeof CHANNEL;\n v: number;\n from: \"agent\";\n type: \"response\";\n id: number;\n response: AgentResponse;\n };\n\n/**\n * A message the panel side sends to the agent: a `hello`, or a request. The agent answers a request\n * with a `response` that carries the same `id`. All fields are plain data.\n *\n * @since 1.0.0\n */\nexport type PanelMessage =\n | { channel: typeof CHANNEL; v: number; from: \"panel\"; type: \"hello\" }\n | {\n channel: typeof CHANNEL;\n v: number;\n from: \"panel\";\n type: \"request\";\n id: number;\n request: AgentRequest;\n };\n\n/**\n * A duplex channel of plain messages. Implementations must deliver messages in order; they need not\n * be synchronous. `postMessage`, `MessagePort` and `BroadcastChannel` adapters are provided.\n *\n * @since 1.0.0\n */\nexport interface Transport {\n /**\n * Sends a message to the other end. The message must be plain data that can be structured-cloned.\n */\n send(message: unknown): void;\n /**\n * Starts delivering incoming messages to `handler`, in order.\n *\n * @returns A function that stops this listener.\n */\n listen(handler: (message: unknown) => void): () => void;\n /**\n * Releases what the transport holds. Closing a port or a `BroadcastChannel` closes it; a window\n * transport holds nothing and does nothing.\n */\n close(): void;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isEnvelope(value: unknown, from: \"agent\" | \"panel\"): value is Record<string, unknown> {\n return (\n isRecord(value) &&\n value[\"channel\"] === CHANNEL &&\n value[\"from\"] === from &&\n typeof value[\"v\"] === \"number\" &&\n typeof value[\"type\"] === \"string\"\n );\n}\n\n/**\n * True for a well-formed message sent by an agent. Anything else on the channel is ignored. The check\n * covers the envelope (`channel`, `from`, `v`, `type`) and the fields each message type needs. It does\n * not validate every event inside an `events` message.\n *\n * @param value A received message.\n * @returns True when `value` is an {@link AgentMessage}.\n * @example\n * import { CHANNEL, isAgentMessage } from \"@shadow-js/devtools\";\n * isAgentMessage({ channel: CHANNEL, v: 1, from: \"agent\", type: \"hello\" }); // true\n * isAgentMessage({ channel: CHANNEL, v: 1, from: \"panel\", type: \"hello\" }); // false\n * @since 1.0.0\n */\nexport function isAgentMessage(value: unknown): value is AgentMessage {\n if (!isEnvelope(value, \"agent\")) return false;\n switch (value[\"type\"]) {\n case \"hello\":\n return true;\n case \"events\":\n return Array.isArray(value[\"events\"]);\n case \"response\":\n return typeof value[\"id\"] === \"number\" && isRecord(value[\"response\"]);\n default:\n return false;\n }\n}\n\n/**\n * True for a well-formed message sent by a panel. Anything else on the channel is ignored. The check\n * covers the envelope and the fields each message type needs, including the request's `type`.\n *\n * @param value A received message.\n * @returns True when `value` is a {@link PanelMessage}.\n * @example\n * import { CHANNEL, isPanelMessage } from \"@shadow-js/devtools\";\n * isPanelMessage({ channel: CHANNEL, v: 1, from: \"panel\", type: \"hello\" }); // true\n * isPanelMessage({ channel: CHANNEL, v: 1, from: \"agent\", type: \"hello\" }); // false\n * @since 1.0.0\n */\nexport function isPanelMessage(value: unknown): value is PanelMessage {\n if (!isEnvelope(value, \"panel\")) return false;\n switch (value[\"type\"]) {\n case \"hello\":\n return true;\n case \"request\":\n return (\n typeof value[\"id\"] === \"number\" &&\n isRecord(value[\"request\"]) &&\n typeof value[\"request\"][\"type\"] === \"string\"\n );\n default:\n return false;\n }\n}\n\n/**\n * Two connected in-memory transports, for tests and for connecting an agent and a panel inside one\n * page. Messages are structured-cloned, so anything that is not plain serializable data fails here\n * exactly as it would over `postMessage`. Closing either end closes both.\n *\n * @returns `[agentSide, panelSide]`.\n * @example\n * import { connectRemote, createAgent, createMemoryTransportPair, serveAgent } from \"@shadow-js/devtools\";\n * const [agentSide, panelSide] = createMemoryTransportPair();\n * const stop = serveAgent(createAgent(), agentSide);\n * const backend = connectRemote(panelSide);\n * await backend.ready;\n * stop();\n * @since 1.0.0\n */\nexport function createMemoryTransportPair(): [Transport, Transport] {\n const handlers: [Set<(message: unknown) => void>, Set<(message: unknown) => void>] = [\n new Set(),\n new Set(),\n ];\n let closed = false;\n const side = (index: 0 | 1): Transport => ({\n send(message): void {\n if (closed) return;\n const copy = structuredClone(message);\n for (const handler of [...handlers[index === 0 ? 1 : 0]]) handler(copy);\n },\n listen(handler): () => void {\n handlers[index].add(handler);\n return () => {\n handlers[index].delete(handler);\n };\n },\n close(): void {\n closed = true;\n handlers[0].clear();\n handlers[1].clear();\n },\n });\n return [side(0), side(1)];\n}\n\n/**\n * The part of a `MessagePort` the adapter uses. A `MessageChannel` end, a worker port and an\n * extension port all fit this shape.\n *\n * @since 1.0.0\n */\nexport interface MessagePortLike {\n /**\n * Posts a message to the other end of the port.\n */\n postMessage(message: unknown): void;\n /**\n * Adds a listener for incoming messages.\n */\n addEventListener(type: \"message\", listener: (event: { data: unknown }) => void): void;\n /**\n * Removes a listener added with `addEventListener`.\n */\n removeEventListener(type: \"message\", listener: (event: { data: unknown }) => void): void;\n /**\n * Starts the port. The adapter calls it after it adds a listener. Optional.\n */\n start?(): void;\n /**\n * Closes the port. Optional.\n */\n close?(): void;\n}\n\n/**\n * A transport over a `MessagePort`: a `MessageChannel` end, a worker port or an extension port.\n * Incoming messages reach the listeners registered with `listen`. `close` closes the port when it\n * supports closing.\n *\n * @param port The port.\n * @returns The transport.\n * @example\n * import { connectRemote, createAgent, createPortTransport, serveAgent } from \"@shadow-js/devtools\";\n * const channel = new MessageChannel();\n * serveAgent(createAgent(), createPortTransport(channel.port1));\n * const backend = connectRemote(createPortTransport(channel.port2));\n * await backend.ready;\n * @since 1.0.0\n */\nexport function createPortTransport(port: MessagePortLike): Transport {\n return {\n send(message): void {\n port.postMessage(message);\n },\n listen(handler): () => void {\n const listener = (event: { data: unknown }): void => handler(event.data);\n port.addEventListener(\"message\", listener);\n port.start?.();\n return () => port.removeEventListener(\"message\", listener);\n },\n close(): void {\n port.close?.();\n },\n };\n}\n\n/**\n * The part of a `Window` the adapter uses: its `postMessage`. A `WindowProxy` such as\n * `iframe.contentWindow` fits this shape.\n *\n * @since 1.0.0\n */\nexport interface WindowLike {\n /**\n * Posts a message to the window. The adapter always passes a real origin as `targetOrigin`.\n */\n postMessage(message: unknown, targetOrigin: string): void;\n}\n\n/**\n * The part of the receiving window the adapter uses: its `message` listeners. `globalThis` in a page\n * or a frame fits this shape.\n *\n * @since 1.0.0\n */\nexport interface WindowEvents {\n /**\n * Adds a listener for `message` events.\n */\n addEventListener(type: \"message\", listener: (event: MessageEvent) => void): void;\n /**\n * Removes a listener added with `addEventListener`.\n */\n removeEventListener(type: \"message\", listener: (event: MessageEvent) => void): void;\n}\n\n/**\n * Options of {@link createWindowTransport}.\n *\n * @since 1.0.0\n */\nexport interface WindowTransportOptions {\n /**\n * The window to talk to: the page being inspected, or the panel's frame.\n */\n target: WindowLike;\n /**\n * The origin of `target`. It is used as `targetOrigin` and to check where messages come from. Never\n * `\"*\"`: {@link createWindowTransport} refuses it.\n */\n origin: string;\n /**\n * Where messages arrive.\n *\n * @default globalThis\n */\n receiver?: WindowEvents;\n}\n\n/**\n * A transport over `window.postMessage` between a page and a panel in another window or frame (a\n * DevTools extension page, a popup). Both sides call it, each naming the other window and its origin.\n * Messages are sent only to `target`, with `origin` as the target origin. Incoming messages are\n * delivered only when their origin equals `origin` and their source is `target`; all others are\n * ignored.\n *\n * @param options Target window, its origin and the receiving window.\n * @returns The transport.\n * @throws {TypeError} When `origin` is `\"*\"`: the messages would reach any origin.\n * @example\n * import { createAgent, createWindowTransport, serveAgent } from \"@shadow-js/devtools\";\n * const frame = document.querySelector(\"iframe\");\n * if (frame?.contentWindow) {\n * const transport = createWindowTransport({ target: frame.contentWindow, origin: \"https://panel.example\" });\n * serveAgent(createAgent(), transport);\n * }\n * @since 1.0.0\n */\nexport function createWindowTransport(options: WindowTransportOptions): Transport {\n if (options.origin === \"*\")\n throw new TypeError('createWindowTransport: pass the real origin, not \"*\".');\n const receiver: WindowEvents = options.receiver ?? (globalThis as unknown as WindowEvents);\n return {\n send(message): void {\n options.target.postMessage(message, options.origin);\n },\n listen(handler): () => void {\n const listener = (event: MessageEvent): void => {\n if (event.origin !== options.origin) return;\n if ((event.source as unknown) !== options.target) return;\n handler(event.data);\n };\n receiver.addEventListener(\"message\", listener);\n return () => receiver.removeEventListener(\"message\", listener);\n },\n close(): void {\n // The window belongs to the page; nothing to release.\n },\n };\n}\n\n/**\n * The part of `BroadcastChannel` the adapter uses.\n *\n * @since 1.0.0\n */\nexport interface BroadcastChannelLike {\n /**\n * Posts a message to every other channel object with the same name.\n */\n postMessage(message: unknown): void;\n /**\n * Adds a listener for incoming messages.\n */\n addEventListener(type: \"message\", listener: (event: { data: unknown }) => void): void;\n /**\n * Removes a listener added with `addEventListener`.\n */\n removeEventListener(type: \"message\", listener: (event: { data: unknown }) => void): void;\n /**\n * Closes the channel object.\n */\n close(): void;\n}\n\n/**\n * A transport over a `BroadcastChannel`, for a panel in another tab or window of the same origin.\n * Several panels can listen to one agent; each message goes to all of them.\n *\n * @param name Channel name. Both sides use the same.\n * @param factory Creates the channel (default `new BroadcastChannel(name)`).\n * @returns The transport.\n * @example\n * import { connectRemote, createBroadcastChannelTransport } from \"@shadow-js/devtools\";\n * const backend = connectRemote(createBroadcastChannelTransport(\"shadowjs-devtools\"));\n * await backend.ready;\n * @since 1.0.0\n */\nexport function createBroadcastChannelTransport(\n name: string,\n factory: (name: string) => BroadcastChannelLike = (n) => new BroadcastChannel(n),\n): Transport {\n const channel = factory(name);\n return {\n send(message): void {\n channel.postMessage(message);\n },\n listen(handler): () => void {\n const listener = (event: { data: unknown }): void => handler(event.data);\n channel.addEventListener(\"message\", listener);\n return () => channel.removeEventListener(\"message\", listener);\n },\n close(): void {\n channel.close();\n },\n };\n}\n\n/**\n * Options of {@link serveAgent}: the page-side hooks and the batch size.\n *\n * @since 1.0.0\n */\nexport interface ServeOptions extends PageHooks {\n /**\n * Largest event batch sent in one message.\n *\n * @default 2000\n */\n batch?: number;\n}\n\n/**\n * Exposes an agent over a transport: forwards its event stream to the panel and answers the panel's\n * requests. The agent keeps recording while no panel listens, and the stream is sent regardless. What\n * happens to events nobody receives depends on the transport. A `highlight` request is passed to\n * `options.highlight` and answered without reaching the agent. Stop serving with the returned function.\n *\n * @param agent The agent in the inspected page.\n * @param transport The channel to the panel.\n * @param options Page-side behavior (highlight overlay) and batch size.\n * @returns A function that stops serving.\n * @example\n * import { createAgent, createMemoryTransportPair, serveAgent } from \"@shadow-js/devtools\";\n * const [pageSide] = createMemoryTransportPair();\n * const stop = serveAgent(createAgent(), pageSide, { highlight: (id) => console.log(\"highlight\", id) });\n * stop();\n * @since 1.0.0\n */\nexport function serveAgent(\n agent: Agent,\n transport: Transport,\n options: ServeOptions = {},\n): () => void {\n const limit = options.batch ?? 2000;\n const send = (message: AgentMessage): void => transport.send(message);\n const stopListening = transport.listen((raw) => {\n if (!isPanelMessage(raw)) return;\n if (raw.type === \"hello\") {\n send({ channel: CHANNEL, v: PROTOCOL_VERSION, from: \"agent\", type: \"hello\" });\n return;\n }\n const { request } = raw;\n let response: AgentResponse;\n if (request.type === \"highlight\") {\n options.highlight?.(request.id);\n response = { ok: true, result: null };\n } else {\n response = agent.handle(request);\n }\n send({\n channel: CHANNEL,\n v: PROTOCOL_VERSION,\n from: \"agent\",\n type: \"response\",\n id: raw.id,\n response,\n });\n });\n const stopStreaming = agent.subscribe((events) => {\n for (let i = 0; i < events.length; i += limit) {\n send({\n channel: CHANNEL,\n v: PROTOCOL_VERSION,\n from: \"agent\",\n type: \"events\",\n events: events.slice(i, i + limit) as WireEvent[],\n });\n }\n });\n return () => {\n stopListening();\n stopStreaming();\n };\n}\n\n/**\n * Options of {@link connectRemote}.\n *\n * @since 1.0.0\n */\nexport interface RemoteOptions {\n /**\n * Capacities of the panel's copy of the model (see {@link ModelLimits}). Fields that are not set keep\n * their defaults.\n */\n limits?: Partial<ModelLimits>;\n}\n\n/**\n * Connects a panel to a remote agent: asks for the complete state, then applies the event stream\n * to a local model. Events that arrive before the state are buffered, and the ones the state already\n * contains are skipped (sequence numbers), so no event is lost or applied twice. Disposing the backend\n * closes the transport.\n *\n * @param transport The channel to the agent.\n * @param options Model limits.\n * @returns A {@link Backend}.\n * @example\n * import { connectRemote, createBroadcastChannelTransport } from \"@shadow-js/devtools\";\n * const backend = connectRemote(createBroadcastChannelTransport(\"shadowjs-devtools\"));\n * await backend.ready;\n * @since 1.0.0\n */\nexport function connectRemote(transport: Transport, options: RemoteOptions = {}): Backend {\n const model = new InspectorModel(options.limits);\n const listeners = new Set<() => void>();\n const waiting = new Map<\n number,\n { resolve: (response: AgentResponse) => void; reject: (error: Error) => void }\n >();\n let buffered: WireEvent[] = [];\n let synced = false;\n let nextRequest = 1;\n let disposed = false;\n let notifyScheduled = false;\n let resolveReady: () => void = () => undefined;\n const ready = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n const notify = (): void => {\n if (notifyScheduled) return;\n notifyScheduled = true;\n queueMicrotask(() => {\n notifyScheduled = false;\n if (disposed) return;\n for (const listener of listeners) listener();\n });\n };\n\n const send = (message: PanelMessage): void => transport.send(message);\n\n const applyState = (state: ModelState): void => {\n model.importState(state);\n synced = true;\n const pending = buffered;\n buffered = [];\n for (const event of pending) model.apply(event);\n resolveReady();\n notify();\n };\n\n const request = (req: AgentRequest): Promise<AgentResponse> => {\n if (disposed) return Promise.reject(new Error(\"The devtools backend was disposed.\"));\n const id = nextRequest;\n nextRequest += 1;\n return new Promise<AgentResponse>((resolve, reject) => {\n waiting.set(id, { resolve, reject });\n send({\n channel: CHANNEL,\n v: PROTOCOL_VERSION,\n from: \"panel\",\n type: \"request\",\n id,\n request: req,\n });\n });\n };\n\n const stop = transport.listen((raw) => {\n if (!isAgentMessage(raw)) return;\n if (raw.v !== PROTOCOL_VERSION) return;\n if (raw.type === \"events\") {\n if (!synced) {\n buffered.push(...raw.events);\n return;\n }\n for (const event of raw.events) model.apply(event);\n notify();\n return;\n }\n if (raw.type === \"response\") {\n const entry = waiting.get(raw.id);\n if (entry === undefined) return;\n waiting.delete(raw.id);\n entry.resolve(raw.response);\n }\n });\n\n const sync = (): void => {\n void request({ type: \"snapshot\" }).then(\n (response) => {\n if (response.ok) applyState(response.result as ModelState);\n },\n () => {\n // The only rejection is dispose(): there is nothing left to synchronize.\n synced = false;\n },\n );\n };\n\n send({ channel: CHANNEL, v: PROTOCOL_VERSION, from: \"panel\", type: \"hello\" });\n sync();\n\n return {\n model,\n ready,\n subscribe(listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n request(req): Promise<AgentResponse> {\n if (req.type === \"clear\") {\n // A clear empties the agent; the panel's copy must follow.\n return request(req).then((response) => {\n if (response.ok) {\n synced = false;\n sync();\n }\n return response;\n });\n }\n return request(req);\n },\n dispose(): void {\n disposed = true;\n stop();\n listeners.clear();\n for (const entry of waiting.values())\n entry.reject(new Error(\"The devtools backend was disposed.\"));\n waiting.clear();\n transport.close();\n },\n };\n}\n"],"mappings":"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"}
|