@omniaura/solid-pulse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/dist/bridge.d.ts +202 -0
- package/dist/bridge.js +10 -0
- package/dist/bridge.js.map +1 -0
- package/dist/chunk-4QA2G6S3.js +15 -0
- package/dist/chunk-4QA2G6S3.js.map +1 -0
- package/dist/chunk-5FYH2KEZ.js +66 -0
- package/dist/chunk-5FYH2KEZ.js.map +1 -0
- package/dist/chunk-C72EYM65.js +462 -0
- package/dist/chunk-C72EYM65.js.map +1 -0
- package/dist/chunk-IXNWEUNF.js +114 -0
- package/dist/chunk-IXNWEUNF.js.map +1 -0
- package/dist/chunk-TVSI7G5S.js +414 -0
- package/dist/chunk-TVSI7G5S.js.map +1 -0
- package/dist/chunk-WIMCBTHZ.js +21 -0
- package/dist/chunk-WIMCBTHZ.js.map +1 -0
- package/dist/cli.js +229 -0
- package/dist/cli.js.map +1 -0
- package/dist/controller-3akN6Qi0.d.ts +210 -0
- package/dist/core.d.ts +76 -0
- package/dist/core.js +41 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/dist/panel.d.ts +34 -0
- package/dist/panel.js +408 -0
- package/dist/panel.js.map +1 -0
- package/dist/query.d.ts +93 -0
- package/dist/query.js +164 -0
- package/dist/query.js.map +1 -0
- package/dist/vite.d.ts +41 -0
- package/dist/vite.js +76 -0
- package/dist/vite.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_PATH,
|
|
3
|
+
PROTOCOL_VERSION,
|
|
4
|
+
isPageFrame
|
|
5
|
+
} from "./chunk-4QA2G6S3.js";
|
|
6
|
+
|
|
7
|
+
// src/bridge/server.ts
|
|
8
|
+
import { createServer } from "http";
|
|
9
|
+
import { WebSocketServer } from "ws";
|
|
10
|
+
|
|
11
|
+
// src/core/ring-buffer.ts
|
|
12
|
+
var RingBuffer = class {
|
|
13
|
+
constructor(capacity) {
|
|
14
|
+
this.capacity = capacity;
|
|
15
|
+
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
16
|
+
throw new RangeError(`RingBuffer capacity must be a positive integer, got ${capacity}`);
|
|
17
|
+
}
|
|
18
|
+
this.items = new Array(capacity);
|
|
19
|
+
}
|
|
20
|
+
capacity;
|
|
21
|
+
items;
|
|
22
|
+
head = 0;
|
|
23
|
+
count = 0;
|
|
24
|
+
/** Total number of pushes since creation (dropped + retained). */
|
|
25
|
+
pushed = 0;
|
|
26
|
+
get size() {
|
|
27
|
+
return this.count;
|
|
28
|
+
}
|
|
29
|
+
get dropped() {
|
|
30
|
+
return this.pushed - this.count;
|
|
31
|
+
}
|
|
32
|
+
push(item) {
|
|
33
|
+
const idx = (this.head + this.count) % this.capacity;
|
|
34
|
+
if (this.count === this.capacity) {
|
|
35
|
+
this.items[this.head] = item;
|
|
36
|
+
this.head = (this.head + 1) % this.capacity;
|
|
37
|
+
} else {
|
|
38
|
+
this.items[idx] = item;
|
|
39
|
+
this.count++;
|
|
40
|
+
}
|
|
41
|
+
this.pushed++;
|
|
42
|
+
}
|
|
43
|
+
/** Oldest → newest. */
|
|
44
|
+
toArray() {
|
|
45
|
+
const out = new Array(this.count);
|
|
46
|
+
for (let i = 0; i < this.count; i++) {
|
|
47
|
+
out[i] = this.items[(this.head + i) % this.capacity];
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
this.items = new Array(this.capacity);
|
|
53
|
+
this.head = 0;
|
|
54
|
+
this.count = 0;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/core/events.ts
|
|
59
|
+
var KIND_GROUPS = {
|
|
60
|
+
solid: [
|
|
61
|
+
"solid.flush",
|
|
62
|
+
"solid.computation",
|
|
63
|
+
"solid.component.mount",
|
|
64
|
+
"solid.component.dispose",
|
|
65
|
+
"solid.component.remount",
|
|
66
|
+
"solid.root"
|
|
67
|
+
],
|
|
68
|
+
dom: ["dom.mutation", "dom.detach", "dom.reattach", "focus.lost"],
|
|
69
|
+
net: [
|
|
70
|
+
"net.fetch.start",
|
|
71
|
+
"net.fetch.end",
|
|
72
|
+
"net.fetch.error",
|
|
73
|
+
"net.ws.open",
|
|
74
|
+
"net.ws.message",
|
|
75
|
+
"net.ws.close",
|
|
76
|
+
"net.ws.error",
|
|
77
|
+
"net.sse.open",
|
|
78
|
+
"net.sse.message",
|
|
79
|
+
"net.sse.error",
|
|
80
|
+
"net.sse.close"
|
|
81
|
+
],
|
|
82
|
+
query: [
|
|
83
|
+
"query.added",
|
|
84
|
+
"query.removed",
|
|
85
|
+
"query.observe",
|
|
86
|
+
"query.unobserve",
|
|
87
|
+
"query.fetch.start",
|
|
88
|
+
"query.fetch.success",
|
|
89
|
+
"query.fetch.error",
|
|
90
|
+
"query.invalidate",
|
|
91
|
+
"query.update",
|
|
92
|
+
"mutation.start",
|
|
93
|
+
"mutation.success",
|
|
94
|
+
"mutation.error"
|
|
95
|
+
],
|
|
96
|
+
pulse: ["pulse.note"]
|
|
97
|
+
};
|
|
98
|
+
var ALL_KINDS = Object.values(KIND_GROUPS).flat();
|
|
99
|
+
function expandKinds(filters) {
|
|
100
|
+
const out = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const raw of filters) {
|
|
102
|
+
const f = raw.trim();
|
|
103
|
+
if (!f) continue;
|
|
104
|
+
if (f === "*" || f === "all") {
|
|
105
|
+
for (const k of ALL_KINDS) out.add(k);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const group = KIND_GROUPS[f];
|
|
109
|
+
if (group) {
|
|
110
|
+
for (const k of group) out.add(k);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (f.endsWith("*")) {
|
|
114
|
+
const prefix = f.slice(0, -1);
|
|
115
|
+
for (const k of ALL_KINDS) if (k.startsWith(prefix)) out.add(k);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ALL_KINDS.includes(f)) out.add(f);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/bridge/server.ts
|
|
124
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1", "localhost"]);
|
|
125
|
+
function hostIsLocal(host) {
|
|
126
|
+
if (!host) return false;
|
|
127
|
+
const h = host.replace(/^\[/, "").replace(/\]?(:\d+)?$/, "");
|
|
128
|
+
return LOOPBACK.has(h) || h.endsWith(".localhost");
|
|
129
|
+
}
|
|
130
|
+
function readBody(req) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
let data = "";
|
|
133
|
+
req.on("data", (c) => {
|
|
134
|
+
data += c.toString();
|
|
135
|
+
if (data.length > 1e6) reject(new Error("body too large"));
|
|
136
|
+
});
|
|
137
|
+
req.on("end", () => resolve(data));
|
|
138
|
+
req.on("error", reject);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function json(res, status, body) {
|
|
142
|
+
res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" });
|
|
143
|
+
res.end(JSON.stringify(body));
|
|
144
|
+
}
|
|
145
|
+
var BridgeServer = class {
|
|
146
|
+
path;
|
|
147
|
+
clients = /* @__PURE__ */ new Map();
|
|
148
|
+
wss;
|
|
149
|
+
listeners = /* @__PURE__ */ new Set();
|
|
150
|
+
nextCommand = 1;
|
|
151
|
+
startedWall = Date.now();
|
|
152
|
+
opts;
|
|
153
|
+
constructor(options = {}) {
|
|
154
|
+
this.path = (options.path ?? DEFAULT_PATH).replace(/\/$/, "");
|
|
155
|
+
this.opts = {
|
|
156
|
+
path: this.path,
|
|
157
|
+
allowRemote: options.allowRemote ?? false,
|
|
158
|
+
bufferSize: options.bufferSize ?? 5e3,
|
|
159
|
+
commandTimeoutMs: options.commandTimeoutMs ?? 1e4,
|
|
160
|
+
log: options.log ?? (() => {
|
|
161
|
+
})
|
|
162
|
+
};
|
|
163
|
+
this.wss = new WebSocketServer({ noServer: true });
|
|
164
|
+
this.wss.on("connection", (ws) => this.onConnection(ws));
|
|
165
|
+
}
|
|
166
|
+
/** Attach the WebSocket upgrade handler to an existing http server (e.g. Vite's). */
|
|
167
|
+
attach(server) {
|
|
168
|
+
server.on("upgrade", (req, socket, head) => {
|
|
169
|
+
const url = req.url ?? "";
|
|
170
|
+
if (url.split("?")[0] !== `${this.path}/ws`) return;
|
|
171
|
+
if (!this.isAllowed(req)) {
|
|
172
|
+
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
173
|
+
socket.destroy();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.wss.handleUpgrade(req, socket, head, (ws) => this.wss.emit("connection", ws, req));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
isAllowed(req) {
|
|
180
|
+
if (this.opts.allowRemote) return true;
|
|
181
|
+
const remote = req.socket.remoteAddress ?? "";
|
|
182
|
+
return LOOPBACK.has(remote) && hostIsLocal(req.headers.host);
|
|
183
|
+
}
|
|
184
|
+
/** Connect middleware: handles `<path>/api/*`; returns false when the URL is not ours. */
|
|
185
|
+
handleHttp(req, res) {
|
|
186
|
+
const raw = req.url ?? "";
|
|
187
|
+
if (!raw.startsWith(`${this.path}/api`)) return false;
|
|
188
|
+
void this.route(req, res).catch((err) => json(res, 500, { error: String(err instanceof Error ? err.message : err) }));
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
async route(req, res) {
|
|
192
|
+
if (!this.isAllowed(req)) return json(res, 403, { error: "solid-pulse bridge is loopback-only (set allowRemote to override)" });
|
|
193
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
194
|
+
const route = url.pathname.slice(`${this.path}/api`.length).replace(/\/$/, "") || "/";
|
|
195
|
+
const method = req.method ?? "GET";
|
|
196
|
+
const clientParam = url.searchParams.get("client") ?? void 0;
|
|
197
|
+
if (route === "/status" && method === "GET") return json(res, 200, this.status());
|
|
198
|
+
if (route === "/clients" && method === "GET") return json(res, 200, this.listClients());
|
|
199
|
+
if (route === "/commands" && method === "GET") {
|
|
200
|
+
const c = this.pick(clientParam);
|
|
201
|
+
return c ? json(res, 200, { client: c.summary.clientId, commands: c.commands }) : json(res, 404, { error: this.noClientMessage(clientParam) });
|
|
202
|
+
}
|
|
203
|
+
if (route === "/command" && (method === "POST" || method === "GET")) {
|
|
204
|
+
let name;
|
|
205
|
+
let args = {};
|
|
206
|
+
let client = clientParam;
|
|
207
|
+
if (method === "POST") {
|
|
208
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
209
|
+
name = body.name;
|
|
210
|
+
args = body.args ?? {};
|
|
211
|
+
client = body.client ?? client;
|
|
212
|
+
} else {
|
|
213
|
+
name = url.searchParams.get("name") ?? void 0;
|
|
214
|
+
const a = url.searchParams.get("args");
|
|
215
|
+
args = a ? JSON.parse(a) : {};
|
|
216
|
+
}
|
|
217
|
+
if (!name) return json(res, 400, { error: "name is required" });
|
|
218
|
+
const c = this.pick(client);
|
|
219
|
+
if (!c) return json(res, 404, { error: this.noClientMessage(client) });
|
|
220
|
+
const result = await this.command(c.summary.clientId, name, args);
|
|
221
|
+
return json(res, result.ok ? 200 : 400, { client: c.summary.clientId, name, ...result });
|
|
222
|
+
}
|
|
223
|
+
if (route === "/events" && method === "GET") {
|
|
224
|
+
const c = this.pick(clientParam);
|
|
225
|
+
if (!c) return json(res, 404, { error: this.noClientMessage(clientParam) });
|
|
226
|
+
const events = this.events(c.summary.clientId, {
|
|
227
|
+
since: Number(url.searchParams.get("since") ?? 0),
|
|
228
|
+
kinds: url.searchParams.get("kinds")?.split(",").filter(Boolean),
|
|
229
|
+
limit: Number(url.searchParams.get("limit") ?? 200)
|
|
230
|
+
});
|
|
231
|
+
return json(res, 200, { client: c.summary.clientId, count: events.length, last: events.at(-1)?.seq ?? 0, events });
|
|
232
|
+
}
|
|
233
|
+
if (route === "/events/stream" && method === "GET") {
|
|
234
|
+
const c = this.pick(clientParam);
|
|
235
|
+
const kinds = url.searchParams.get("kinds")?.split(",").filter(Boolean);
|
|
236
|
+
const kindSet = kinds && kinds.length ? expandKinds(kinds) : null;
|
|
237
|
+
const target = c?.summary.clientId ?? clientParam;
|
|
238
|
+
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-store", connection: "keep-alive" });
|
|
239
|
+
res.write(`: solid-pulse stream client=${target ?? "*"}
|
|
240
|
+
|
|
241
|
+
`);
|
|
242
|
+
const listener = (clientId, events) => {
|
|
243
|
+
if (target && clientId !== target) return;
|
|
244
|
+
for (const e of events) {
|
|
245
|
+
if (kindSet && !kindSet.has(e.kind)) continue;
|
|
246
|
+
res.write(`event: pulse
|
|
247
|
+
data: ${JSON.stringify({ client: clientId, ...e })}
|
|
248
|
+
|
|
249
|
+
`);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
this.listeners.add(listener);
|
|
253
|
+
const ka = setInterval(() => res.write(":keepalive\n\n"), 15e3);
|
|
254
|
+
req.on("close", () => {
|
|
255
|
+
clearInterval(ka);
|
|
256
|
+
this.listeners.delete(listener);
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
json(res, 404, { error: `unknown route ${method} ${route}` });
|
|
261
|
+
}
|
|
262
|
+
noClientMessage(requested) {
|
|
263
|
+
return requested ? `no connected page with client id "${requested}" (see /api/clients)` : "no page connected to the bridge \u2014 open the app in a browser with solid-pulse enabled";
|
|
264
|
+
}
|
|
265
|
+
onConnection(ws) {
|
|
266
|
+
let state = null;
|
|
267
|
+
ws.on("message", (raw) => {
|
|
268
|
+
let frame;
|
|
269
|
+
try {
|
|
270
|
+
frame = JSON.parse(raw.toString());
|
|
271
|
+
} catch {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (!isPageFrame(frame)) return;
|
|
275
|
+
if (frame.type === "hello") {
|
|
276
|
+
const existing = this.clients.get(frame.clientId);
|
|
277
|
+
if (existing && existing.ws !== ws) existing.ws.close(4e3, "replaced by a newer connection");
|
|
278
|
+
state = {
|
|
279
|
+
ws,
|
|
280
|
+
summary: { clientId: frame.clientId, url: frame.url, title: frame.title, userAgent: frame.userAgent, connectedWall: Date.now(), lastSeenWall: Date.now(), events: 0, commands: frame.commands.length },
|
|
281
|
+
commands: frame.commands,
|
|
282
|
+
buffer: existing?.buffer ?? new RingBuffer(this.opts.bufferSize),
|
|
283
|
+
pending: /* @__PURE__ */ new Map()
|
|
284
|
+
};
|
|
285
|
+
this.clients.set(frame.clientId, state);
|
|
286
|
+
ws.send(JSON.stringify({ type: "welcome", clientId: frame.clientId, protocol: PROTOCOL_VERSION }));
|
|
287
|
+
this.opts.log(`client connected ${frame.clientId} ${frame.url}`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (!state) return;
|
|
291
|
+
state.summary.lastSeenWall = Date.now();
|
|
292
|
+
if (frame.type === "events") {
|
|
293
|
+
const fresh = [];
|
|
294
|
+
for (const e of frame.events) {
|
|
295
|
+
const last = state.buffer.size ? state.buffer.toArray().at(-1).seq : 0;
|
|
296
|
+
if (e.seq <= last) continue;
|
|
297
|
+
state.buffer.push(e);
|
|
298
|
+
fresh.push(e);
|
|
299
|
+
}
|
|
300
|
+
state.summary.events += fresh.length;
|
|
301
|
+
if (fresh.length) for (const l of this.listeners) l(state.summary.clientId, fresh);
|
|
302
|
+
} else if (frame.type === "result") {
|
|
303
|
+
const p = state.pending.get(frame.id);
|
|
304
|
+
if (p) {
|
|
305
|
+
clearTimeout(p.timer);
|
|
306
|
+
state.pending.delete(frame.id);
|
|
307
|
+
p.resolve(frame.result);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
ws.on("close", () => {
|
|
312
|
+
if (state && this.clients.get(state.summary.clientId)?.ws === ws) {
|
|
313
|
+
this.clients.delete(state.summary.clientId);
|
|
314
|
+
for (const p of state.pending.values()) {
|
|
315
|
+
clearTimeout(p.timer);
|
|
316
|
+
p.resolve({ ok: false, error: "page disconnected" });
|
|
317
|
+
}
|
|
318
|
+
this.opts.log(`client disconnected ${state.summary.clientId}`);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/** Pick a client: explicit id, else the most recently seen. */
|
|
323
|
+
pick(clientId) {
|
|
324
|
+
if (clientId) return this.clients.get(clientId) ?? null;
|
|
325
|
+
let best = null;
|
|
326
|
+
for (const c of this.clients.values()) if (!best || c.summary.lastSeenWall > best.summary.lastSeenWall) best = c;
|
|
327
|
+
return best;
|
|
328
|
+
}
|
|
329
|
+
listClients() {
|
|
330
|
+
return [...this.clients.values()].map((c) => c.summary);
|
|
331
|
+
}
|
|
332
|
+
status() {
|
|
333
|
+
return { tool: "@omniaura/solid-pulse", protocol: PROTOCOL_VERSION, path: this.path, uptimeMs: Date.now() - this.startedWall, allowRemote: this.opts.allowRemote, clients: this.listClients() };
|
|
334
|
+
}
|
|
335
|
+
events(clientId, opts = {}) {
|
|
336
|
+
const c = this.clients.get(clientId);
|
|
337
|
+
if (!c) return [];
|
|
338
|
+
const kinds = opts.kinds && opts.kinds.length ? expandKinds(opts.kinds) : null;
|
|
339
|
+
const out = c.buffer.toArray().filter((e) => e.seq > (opts.since ?? 0) && (!kinds || kinds.has(e.kind)));
|
|
340
|
+
const limit = opts.limit ?? 200;
|
|
341
|
+
return out.length > limit ? out.slice(out.length - limit) : out;
|
|
342
|
+
}
|
|
343
|
+
command(clientId, name, args = {}) {
|
|
344
|
+
const c = this.clients.get(clientId);
|
|
345
|
+
if (!c) return Promise.resolve({ ok: false, error: `client not connected: ${clientId}` });
|
|
346
|
+
const id = `c${this.nextCommand++}`;
|
|
347
|
+
const frame = { type: "command", id, name, args };
|
|
348
|
+
return new Promise((resolve) => {
|
|
349
|
+
const timer = setTimeout(() => {
|
|
350
|
+
c.pending.delete(id);
|
|
351
|
+
resolve({ ok: false, error: `command timed out after ${this.opts.commandTimeoutMs}ms` });
|
|
352
|
+
}, this.opts.commandTimeoutMs);
|
|
353
|
+
c.pending.set(id, {
|
|
354
|
+
resolve: (result) => {
|
|
355
|
+
if (name === "events.clear" && result.ok) c.buffer.clear();
|
|
356
|
+
resolve(result);
|
|
357
|
+
},
|
|
358
|
+
timer
|
|
359
|
+
});
|
|
360
|
+
c.ws.send(JSON.stringify(frame));
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
onEvents(listener) {
|
|
364
|
+
this.listeners.add(listener);
|
|
365
|
+
return () => this.listeners.delete(listener);
|
|
366
|
+
}
|
|
367
|
+
close() {
|
|
368
|
+
for (const c of this.clients.values()) c.ws.terminate();
|
|
369
|
+
this.clients.clear();
|
|
370
|
+
this.wss.close();
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
function startBridgeServer(options = {}) {
|
|
374
|
+
const bridge = new BridgeServer(options);
|
|
375
|
+
const server = createServer((req, res) => {
|
|
376
|
+
if (bridge.handleHttp(req, res)) return;
|
|
377
|
+
json(res, 404, { error: "not found", hint: `${bridge.path}/api/status` });
|
|
378
|
+
});
|
|
379
|
+
bridge.attach(server);
|
|
380
|
+
const host = options.host ?? "127.0.0.1";
|
|
381
|
+
const port = options.port ?? 4567;
|
|
382
|
+
const ready = new Promise((resolve, reject) => {
|
|
383
|
+
server.once("error", reject);
|
|
384
|
+
server.listen(port, host, () => {
|
|
385
|
+
const addr = server.address();
|
|
386
|
+
const actual = typeof addr === "object" && addr ? addr.port : port;
|
|
387
|
+
resolve({ url: `http://${host}:${actual}${bridge.path}`, port: actual });
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
return {
|
|
391
|
+
bridge,
|
|
392
|
+
server,
|
|
393
|
+
ready,
|
|
394
|
+
url: `http://${host}:${port}${bridge.path}`,
|
|
395
|
+
close: () => new Promise((resolve) => {
|
|
396
|
+
bridge.close();
|
|
397
|
+
let done = false;
|
|
398
|
+
const finish = () => {
|
|
399
|
+
if (done) return;
|
|
400
|
+
done = true;
|
|
401
|
+
resolve();
|
|
402
|
+
};
|
|
403
|
+
server.close(finish);
|
|
404
|
+
server.closeAllConnections?.();
|
|
405
|
+
setTimeout(finish, 500).unref?.();
|
|
406
|
+
})
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export {
|
|
411
|
+
BridgeServer,
|
|
412
|
+
startBridgeServer
|
|
413
|
+
};
|
|
414
|
+
//# sourceMappingURL=chunk-TVSI7G5S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge/server.ts","../src/core/ring-buffer.ts","../src/core/events.ts"],"sourcesContent":["/**\n * Bridge server (Node/Bun). Accepts page runtimes over WebSocket at\n * `<path>/ws`, mirrors their events into a per-client ring buffer, and exposes\n * a small HTTP API for CLIs and agents at `<path>/api/*`:\n *\n * GET /api/status server + connected clients\n * GET /api/clients\n * GET /api/commands?client= the page's command contract\n * POST /api/command {client?,name,args} run a command in the page\n * GET /api/command?name=&args=<json>&client= (curl-friendly)\n * GET /api/events?client=&since=&kinds=&limit=\n * GET /api/events/stream?client=&kinds= SSE, one `pulse` event per PulseEvent\n *\n * Access: loopback only unless `allowRemote` is set — remote address and Host\n * header are both checked, so a LAN dev server does not leak instrumentation.\n */\n\nimport type { IncomingMessage, Server as HttpServer, ServerResponse } from \"node:http\";\nimport { createServer } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport { WebSocketServer, type WebSocket } from \"ws\";\nimport { RingBuffer } from \"../core/ring-buffer.js\";\nimport { expandKinds, type PulseEvent } from \"../core/events.js\";\nimport type { CommandResult, CommandSpec } from \"../core/controller.js\";\nimport { DEFAULT_PATH, PROTOCOL_VERSION, isPageFrame, type ClientSummary, type CommandFrame } from \"../core/protocol.js\";\n\nexport interface BridgeServerOptions {\n /** URL prefix (default `/__pulse`). */\n path?: string;\n /** Accept non-loopback peers and hosts (default false). */\n allowRemote?: boolean;\n /** Per-client mirror buffer (default 5000 events). */\n bufferSize?: number;\n /** Command timeout in ms (default 10000). */\n commandTimeoutMs?: number;\n log?: (message: string) => void;\n}\n\ninterface ClientState {\n ws: WebSocket;\n summary: ClientSummary;\n commands: CommandSpec[];\n buffer: RingBuffer<PulseEvent>;\n pending: Map<string, { resolve: (r: CommandResult) => void; timer: ReturnType<typeof setTimeout> }>;\n}\n\ntype EventListener = (clientId: string, events: PulseEvent[]) => void;\n\nconst LOOPBACK = new Set([\"127.0.0.1\", \"::1\", \"::ffff:127.0.0.1\", \"localhost\"]);\n\nfunction hostIsLocal(host: string | undefined): boolean {\n if (!host) return false;\n const h = host.replace(/^\\[/, \"\").replace(/\\]?(:\\d+)?$/, \"\");\n return LOOPBACK.has(h) || h.endsWith(\".localhost\");\n}\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n req.on(\"data\", (c: Buffer) => {\n data += c.toString();\n if (data.length > 1_000_000) reject(new Error(\"body too large\"));\n });\n req.on(\"end\", () => resolve(data));\n req.on(\"error\", reject);\n });\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown) {\n res.writeHead(status, { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" });\n res.end(JSON.stringify(body));\n}\n\nexport class BridgeServer {\n readonly path: string;\n private clients = new Map<string, ClientState>();\n private wss: WebSocketServer;\n private listeners = new Set<EventListener>();\n private nextCommand = 1;\n private startedWall = Date.now();\n private opts: Required<Omit<BridgeServerOptions, \"log\">> & { log: (m: string) => void };\n\n constructor(options: BridgeServerOptions = {}) {\n this.path = (options.path ?? DEFAULT_PATH).replace(/\\/$/, \"\");\n this.opts = {\n path: this.path,\n allowRemote: options.allowRemote ?? false,\n bufferSize: options.bufferSize ?? 5000,\n commandTimeoutMs: options.commandTimeoutMs ?? 10_000,\n log: options.log ?? (() => {}),\n };\n this.wss = new WebSocketServer({ noServer: true });\n this.wss.on(\"connection\", (ws) => this.onConnection(ws));\n }\n\n /** Attach the WebSocket upgrade handler to an existing http server (e.g. Vite's). */\n attach(server: HttpServer) {\n server.on(\"upgrade\", (req: IncomingMessage, socket: Duplex, head: Buffer) => {\n const url = req.url ?? \"\";\n if (url.split(\"?\")[0] !== `${this.path}/ws`) return; // not ours (Vite HMR etc.)\n if (!this.isAllowed(req)) {\n socket.write(\"HTTP/1.1 403 Forbidden\\r\\n\\r\\n\");\n socket.destroy();\n return;\n }\n this.wss.handleUpgrade(req, socket, head, (ws) => this.wss.emit(\"connection\", ws, req));\n });\n }\n\n isAllowed(req: IncomingMessage): boolean {\n if (this.opts.allowRemote) return true;\n const remote = req.socket.remoteAddress ?? \"\";\n return LOOPBACK.has(remote) && hostIsLocal(req.headers.host);\n }\n\n /** Connect middleware: handles `<path>/api/*`; returns false when the URL is not ours. */\n handleHttp(req: IncomingMessage, res: ServerResponse): boolean {\n const raw = req.url ?? \"\";\n if (!raw.startsWith(`${this.path}/api`)) return false;\n void this.route(req, res).catch((err) => json(res, 500, { error: String(err instanceof Error ? err.message : err) }));\n return true;\n }\n\n private async route(req: IncomingMessage, res: ServerResponse) {\n if (!this.isAllowed(req)) return json(res, 403, { error: \"solid-pulse bridge is loopback-only (set allowRemote to override)\" });\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const route = url.pathname.slice(`${this.path}/api`.length).replace(/\\/$/, \"\") || \"/\";\n const method = req.method ?? \"GET\";\n const clientParam = url.searchParams.get(\"client\") ?? undefined;\n\n if (route === \"/status\" && method === \"GET\") return json(res, 200, this.status());\n if (route === \"/clients\" && method === \"GET\") return json(res, 200, this.listClients());\n if (route === \"/commands\" && method === \"GET\") {\n const c = this.pick(clientParam);\n return c ? json(res, 200, { client: c.summary.clientId, commands: c.commands }) : json(res, 404, { error: this.noClientMessage(clientParam) });\n }\n if (route === \"/command\" && (method === \"POST\" || method === \"GET\")) {\n let name: string | undefined;\n let args: Record<string, unknown> = {};\n let client = clientParam;\n if (method === \"POST\") {\n const body = JSON.parse((await readBody(req)) || \"{}\") as { client?: string; name?: string; args?: Record<string, unknown> };\n name = body.name;\n args = body.args ?? {};\n client = body.client ?? client;\n } else {\n name = url.searchParams.get(\"name\") ?? undefined;\n const a = url.searchParams.get(\"args\");\n args = a ? (JSON.parse(a) as Record<string, unknown>) : {};\n }\n if (!name) return json(res, 400, { error: \"name is required\" });\n const c = this.pick(client);\n if (!c) return json(res, 404, { error: this.noClientMessage(client) });\n const result = await this.command(c.summary.clientId, name, args);\n return json(res, result.ok ? 200 : 400, { client: c.summary.clientId, name, ...result });\n }\n if (route === \"/events\" && method === \"GET\") {\n const c = this.pick(clientParam);\n if (!c) return json(res, 404, { error: this.noClientMessage(clientParam) });\n const events = this.events(c.summary.clientId, {\n since: Number(url.searchParams.get(\"since\") ?? 0),\n kinds: url.searchParams.get(\"kinds\")?.split(\",\").filter(Boolean),\n limit: Number(url.searchParams.get(\"limit\") ?? 200),\n });\n return json(res, 200, { client: c.summary.clientId, count: events.length, last: events.at(-1)?.seq ?? 0, events });\n }\n if (route === \"/events/stream\" && method === \"GET\") {\n const c = this.pick(clientParam);\n const kinds = url.searchParams.get(\"kinds\")?.split(\",\").filter(Boolean);\n const kindSet = kinds && kinds.length ? expandKinds(kinds) : null;\n const target = c?.summary.clientId ?? clientParam;\n res.writeHead(200, { \"content-type\": \"text/event-stream\", \"cache-control\": \"no-store\", connection: \"keep-alive\" });\n res.write(`: solid-pulse stream client=${target ?? \"*\"}\\n\\n`);\n const listener: EventListener = (clientId, events) => {\n if (target && clientId !== target) return;\n for (const e of events) {\n if (kindSet && !kindSet.has(e.kind)) continue;\n res.write(`event: pulse\\ndata: ${JSON.stringify({ client: clientId, ...e })}\\n\\n`);\n }\n };\n this.listeners.add(listener);\n const ka = setInterval(() => res.write(\":keepalive\\n\\n\"), 15_000);\n req.on(\"close\", () => {\n clearInterval(ka);\n this.listeners.delete(listener);\n });\n return;\n }\n json(res, 404, { error: `unknown route ${method} ${route}` });\n }\n\n private noClientMessage(requested?: string) {\n return requested\n ? `no connected page with client id \"${requested}\" (see /api/clients)`\n : \"no page connected to the bridge — open the app in a browser with solid-pulse enabled\";\n }\n\n private onConnection(ws: WebSocket) {\n let state: ClientState | null = null;\n ws.on(\"message\", (raw) => {\n let frame: unknown;\n try {\n frame = JSON.parse(raw.toString());\n } catch {\n return;\n }\n if (!isPageFrame(frame)) return;\n if (frame.type === \"hello\") {\n const existing = this.clients.get(frame.clientId);\n if (existing && existing.ws !== ws) existing.ws.close(4000, \"replaced by a newer connection\");\n state = {\n ws,\n summary: { clientId: frame.clientId, url: frame.url, title: frame.title, userAgent: frame.userAgent, connectedWall: Date.now(), lastSeenWall: Date.now(), events: 0, commands: frame.commands.length },\n commands: frame.commands,\n buffer: existing?.buffer ?? new RingBuffer<PulseEvent>(this.opts.bufferSize),\n pending: new Map(),\n };\n this.clients.set(frame.clientId, state);\n ws.send(JSON.stringify({ type: \"welcome\", clientId: frame.clientId, protocol: PROTOCOL_VERSION }));\n this.opts.log(`client connected ${frame.clientId} ${frame.url}`);\n return;\n }\n if (!state) return;\n state.summary.lastSeenWall = Date.now();\n if (frame.type === \"events\") {\n const fresh: PulseEvent[] = [];\n for (const e of frame.events) {\n // Replayed history after a reconnect may repeat; keep the buffer monotonic.\n const last = state.buffer.size ? state.buffer.toArray().at(-1)!.seq : 0;\n if (e.seq <= last) continue;\n state.buffer.push(e);\n fresh.push(e);\n }\n state.summary.events += fresh.length;\n if (fresh.length) for (const l of this.listeners) l(state.summary.clientId, fresh);\n } else if (frame.type === \"result\") {\n const p = state.pending.get(frame.id);\n if (p) {\n clearTimeout(p.timer);\n state.pending.delete(frame.id);\n p.resolve(frame.result);\n }\n }\n });\n ws.on(\"close\", () => {\n if (state && this.clients.get(state.summary.clientId)?.ws === ws) {\n this.clients.delete(state.summary.clientId);\n for (const p of state.pending.values()) {\n clearTimeout(p.timer);\n p.resolve({ ok: false, error: \"page disconnected\" });\n }\n this.opts.log(`client disconnected ${state.summary.clientId}`);\n }\n });\n }\n\n /** Pick a client: explicit id, else the most recently seen. */\n pick(clientId?: string): ClientState | null {\n if (clientId) return this.clients.get(clientId) ?? null;\n let best: ClientState | null = null;\n for (const c of this.clients.values()) if (!best || c.summary.lastSeenWall > best.summary.lastSeenWall) best = c;\n return best;\n }\n\n listClients(): ClientSummary[] {\n return [...this.clients.values()].map((c) => c.summary);\n }\n\n status() {\n return { tool: \"@omniaura/solid-pulse\", protocol: PROTOCOL_VERSION, path: this.path, uptimeMs: Date.now() - this.startedWall, allowRemote: this.opts.allowRemote, clients: this.listClients() };\n }\n\n events(clientId: string, opts: { since?: number; kinds?: string[]; limit?: number } = {}): PulseEvent[] {\n const c = this.clients.get(clientId);\n if (!c) return [];\n const kinds = opts.kinds && opts.kinds.length ? expandKinds(opts.kinds) : null;\n const out = c.buffer.toArray().filter((e) => e.seq > (opts.since ?? 0) && (!kinds || kinds.has(e.kind)));\n const limit = opts.limit ?? 200;\n return out.length > limit ? out.slice(out.length - limit) : out;\n }\n\n command(clientId: string, name: string, args: Record<string, unknown> = {}): Promise<CommandResult> {\n const c = this.clients.get(clientId);\n if (!c) return Promise.resolve({ ok: false, error: `client not connected: ${clientId}` });\n const id = `c${this.nextCommand++}`;\n const frame: CommandFrame = { type: \"command\", id, name, args };\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n c.pending.delete(id);\n resolve({ ok: false, error: `command timed out after ${this.opts.commandTimeoutMs}ms` });\n }, this.opts.commandTimeoutMs);\n c.pending.set(id, {\n resolve: (result) => {\n // Keep the mirror in step with the page: a cleared page buffer must\n // not keep serving stale history to `events`.\n if (name === \"events.clear\" && result.ok) c.buffer.clear();\n resolve(result);\n },\n timer,\n });\n c.ws.send(JSON.stringify(frame));\n });\n }\n\n onEvents(listener: EventListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n close() {\n for (const c of this.clients.values()) c.ws.terminate();\n this.clients.clear();\n this.wss.close();\n }\n}\n\nexport interface StandaloneOptions extends BridgeServerOptions {\n port?: number;\n host?: string;\n}\n\n/** Run the bridge as its own process (for non-Vite setups): `solid-pulse bridge --port 4567`. */\nexport function startBridgeServer(options: StandaloneOptions = {}) {\n const bridge = new BridgeServer(options);\n const server = createServer((req, res) => {\n if (bridge.handleHttp(req, res)) return;\n json(res, 404, { error: \"not found\", hint: `${bridge.path}/api/status` });\n });\n bridge.attach(server);\n const host = options.host ?? \"127.0.0.1\";\n const port = options.port ?? 4567;\n const ready = new Promise<{ url: string; port: number }>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, host, () => {\n const addr = server.address();\n const actual = typeof addr === \"object\" && addr ? addr.port : port;\n resolve({ url: `http://${host}:${actual}${bridge.path}`, port: actual });\n });\n });\n return {\n bridge,\n server,\n ready,\n url: `http://${host}:${port}${bridge.path}`,\n close: () =>\n new Promise<void>((resolve) => {\n bridge.close();\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n resolve();\n };\n server.close(finish);\n // Keep-alive HTTP and open SSE connections would otherwise hold `close` open.\n (server as unknown as { closeAllConnections?: () => void }).closeAllConnections?.();\n setTimeout(finish, 500).unref?.();\n }),\n };\n}\n","/** Fixed-capacity FIFO. Overwrites the oldest entry; never grows. */\nexport class RingBuffer<T> {\n private items: (T | undefined)[];\n private head = 0;\n private count = 0;\n /** Total number of pushes since creation (dropped + retained). */\n pushed = 0;\n\n constructor(public readonly capacity: number) {\n if (!Number.isInteger(capacity) || capacity <= 0) {\n throw new RangeError(`RingBuffer capacity must be a positive integer, got ${capacity}`);\n }\n this.items = new Array(capacity);\n }\n\n get size() {\n return this.count;\n }\n\n get dropped() {\n return this.pushed - this.count;\n }\n\n push(item: T) {\n const idx = (this.head + this.count) % this.capacity;\n if (this.count === this.capacity) {\n this.items[this.head] = item;\n this.head = (this.head + 1) % this.capacity;\n } else {\n this.items[idx] = item;\n this.count++;\n }\n this.pushed++;\n }\n\n /** Oldest → newest. */\n toArray(): T[] {\n const out: T[] = new Array(this.count);\n for (let i = 0; i < this.count; i++) {\n out[i] = this.items[(this.head + i) % this.capacity] as T;\n }\n return out;\n }\n\n clear() {\n this.items = new Array(this.capacity);\n this.head = 0;\n this.count = 0;\n }\n}\n","/**\n * Event model. Every observation the runtime makes is one PulseEvent with a\n * precise `kind`. Kinds are deliberately specific: Solid has no \"rerender\", so\n * we never report one. What actually happens is one of:\n *\n * solid.flush a reactive flush completed (n computations re-ran)\n * solid.computation one memo/effect/render-effect re-ran (verbose mode)\n * solid.component.mount a component function ran (fresh mount, or hydrate)\n * solid.component.dispose\n * solid.component.remount same-named component disposed + mounted in one flush\n * solid.root a reactive root was created (multiple roots are fine)\n * dom.mutation the DOM actually changed (childList/attr/text)\n * dom.detach a subtree left the document (with scroll/focus state)\n * dom.reattach the same node instance came back (Suspense flip etc.)\n * focus.lost the focused element vanished from the document\n * net.fetch.* fetch lifecycle (start/end/error), SSE-aware\n * net.ws.* WebSocket lifecycle (open/message/close/error)\n * net.sse.* EventSource lifecycle\n * query.* Solid Query cache/observer events (adapter)\n * mutation.* Solid Query mutation cache events (adapter)\n * pulse.* runtime lifecycle / control-plane notes\n */\n\nexport type PulseEventKind =\n | \"solid.flush\"\n | \"solid.computation\"\n | \"solid.component.mount\"\n | \"solid.component.dispose\"\n | \"solid.component.remount\"\n | \"solid.root\"\n | \"dom.mutation\"\n | \"dom.detach\"\n | \"dom.reattach\"\n | \"focus.lost\"\n | \"net.fetch.start\"\n | \"net.fetch.end\"\n | \"net.fetch.error\"\n | \"net.ws.open\"\n | \"net.ws.message\"\n | \"net.ws.close\"\n | \"net.ws.error\"\n | \"net.sse.open\"\n | \"net.sse.message\"\n | \"net.sse.error\"\n | \"net.sse.close\"\n | \"query.added\"\n | \"query.removed\"\n | \"query.observe\"\n | \"query.unobserve\"\n | \"query.fetch.start\"\n | \"query.fetch.success\"\n | \"query.fetch.error\"\n | \"query.invalidate\"\n | \"query.update\"\n | \"mutation.start\"\n | \"mutation.success\"\n | \"mutation.error\"\n | \"pulse.note\";\n\nexport interface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface ComponentRef {\n id: number;\n name: string;\n /** Component ancestry, innermost first (names only). */\n chain?: string[];\n /** Source location if solid-grab's data-solid-source attribute was found. */\n source?: string | null;\n}\n\nexport interface ElementRef {\n tag: string;\n id?: string;\n testId?: string;\n classes?: string;\n /** Nearest `data-solid-component` ancestor name, if any. */\n component?: string | null;\n /** Nearest `data-solid-source` value, if any. */\n source?: string | null;\n rect?: Rect;\n}\n\nexport interface PulseEventBase {\n /** Monotonic per-runtime sequence. */\n seq: number;\n /** performance.now() at capture. */\n t: number;\n /** Date.now() at capture (for cross-process correlation). */\n wall: number;\n kind: PulseEventKind;\n /** Flush group the event belongs to, when attributable. */\n flush?: number;\n /** Component attribution, when known. */\n component?: ComponentRef | null;\n /** Free-form structured payload; shape depends on `kind`. */\n data: Record<string, unknown>;\n}\n\nexport type PulseEvent = PulseEventBase;\n\nexport const KIND_GROUPS: Record<string, PulseEventKind[]> = {\n solid: [\n \"solid.flush\",\n \"solid.computation\",\n \"solid.component.mount\",\n \"solid.component.dispose\",\n \"solid.component.remount\",\n \"solid.root\",\n ],\n dom: [\"dom.mutation\", \"dom.detach\", \"dom.reattach\", \"focus.lost\"],\n net: [\n \"net.fetch.start\",\n \"net.fetch.end\",\n \"net.fetch.error\",\n \"net.ws.open\",\n \"net.ws.message\",\n \"net.ws.close\",\n \"net.ws.error\",\n \"net.sse.open\",\n \"net.sse.message\",\n \"net.sse.error\",\n \"net.sse.close\",\n ],\n query: [\n \"query.added\",\n \"query.removed\",\n \"query.observe\",\n \"query.unobserve\",\n \"query.fetch.start\",\n \"query.fetch.success\",\n \"query.fetch.error\",\n \"query.invalidate\",\n \"query.update\",\n \"mutation.start\",\n \"mutation.success\",\n \"mutation.error\",\n ],\n pulse: [\"pulse.note\"],\n};\n\nexport const ALL_KINDS: PulseEventKind[] = Object.values(KIND_GROUPS).flat();\n\n/** Expand a kind filter: exact kinds, `group` names, or `prefix.*` globs. */\nexport function expandKinds(filters: readonly string[]): Set<PulseEventKind> {\n const out = new Set<PulseEventKind>();\n for (const raw of filters) {\n const f = raw.trim();\n if (!f) continue;\n if (f === \"*\" || f === \"all\") {\n for (const k of ALL_KINDS) out.add(k);\n continue;\n }\n const group = KIND_GROUPS[f];\n if (group) {\n for (const k of group) out.add(k);\n continue;\n }\n if (f.endsWith(\"*\")) {\n const prefix = f.slice(0, -1);\n for (const k of ALL_KINDS) if (k.startsWith(prefix)) out.add(k);\n continue;\n }\n if ((ALL_KINDS as string[]).includes(f)) out.add(f as PulseEventKind);\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/core/protocol.ts
|
|
2
|
+
var PROTOCOL_VERSION = 1;
|
|
3
|
+
var DEFAULT_PATH = "/__pulse";
|
|
4
|
+
function isPageFrame(value) {
|
|
5
|
+
if (!value || typeof value !== "object") return false;
|
|
6
|
+
const t = value.type;
|
|
7
|
+
return t === "hello" || t === "events" || t === "result";
|
|
8
|
+
}
|
|
9
|
+
function isServerFrame(value) {
|
|
10
|
+
if (!value || typeof value !== "object") return false;
|
|
11
|
+
const t = value.type;
|
|
12
|
+
return t === "command" || t === "welcome";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
PROTOCOL_VERSION,
|
|
17
|
+
DEFAULT_PATH,
|
|
18
|
+
isPageFrame,
|
|
19
|
+
isServerFrame
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=chunk-WIMCBTHZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/protocol.ts"],"sourcesContent":["/**\n * Bridge protocol (JSON over WebSocket) between a page runtime and the bridge\n * server, and the HTTP shape the server exposes to CLIs and agents.\n *\n * Page → server\n * hello first frame; identifies the tab\n * events batched PulseEvents (≤ 50 ms coalescing)\n * result reply to a `command`\n * Server → page\n * command run a controller command; page answers with `result`\n * welcome ack of hello with the server-assigned client id\n */\n\nimport type { PulseEvent } from \"./events.js\";\nimport type { CommandResult, CommandSpec } from \"./controller.js\";\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_PATH = \"/__pulse\";\n\nexport interface HelloFrame {\n type: \"hello\";\n protocol: number;\n clientId: string;\n url: string;\n title: string;\n userAgent: string;\n commands: CommandSpec[];\n startedWall: number;\n}\n\nexport interface EventsFrame {\n type: \"events\";\n events: PulseEvent[];\n}\n\nexport interface ResultFrame {\n type: \"result\";\n id: string;\n result: CommandResult;\n}\n\nexport interface CommandFrame {\n type: \"command\";\n id: string;\n name: string;\n args: Record<string, unknown>;\n}\n\nexport interface WelcomeFrame {\n type: \"welcome\";\n clientId: string;\n protocol: number;\n}\n\nexport type PageFrame = HelloFrame | EventsFrame | ResultFrame;\nexport type ServerFrame = CommandFrame | WelcomeFrame;\n\nexport interface ClientSummary {\n clientId: string;\n url: string;\n title: string;\n userAgent: string;\n connectedWall: number;\n lastSeenWall: number;\n events: number;\n commands: number;\n}\n\nexport function isPageFrame(value: unknown): value is PageFrame {\n if (!value || typeof value !== \"object\") return false;\n const t = (value as { type?: unknown }).type;\n return t === \"hello\" || t === \"events\" || t === \"result\";\n}\n\nexport function isServerFrame(value: unknown): value is ServerFrame {\n if (!value || typeof value !== \"object\") return false;\n const t = (value as { type?: unknown }).type;\n return t === \"command\" || t === \"welcome\";\n}\n"],"mappings":";AAgBO,IAAM,mBAAmB;AACzB,IAAM,eAAe;AAmDrB,SAAS,YAAY,OAAoC;AAC9D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAK,MAA6B;AACxC,SAAO,MAAM,WAAW,MAAM,YAAY,MAAM;AAClD;AAEO,SAAS,cAAc,OAAsC;AAClE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAK,MAA6B;AACxC,SAAO,MAAM,aAAa,MAAM;AAClC;","names":[]}
|