@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_PATH,
|
|
3
|
+
PROTOCOL_VERSION,
|
|
4
|
+
isServerFrame
|
|
5
|
+
} from "./chunk-WIMCBTHZ.js";
|
|
6
|
+
import {
|
|
7
|
+
redactText,
|
|
8
|
+
redactUrl
|
|
9
|
+
} from "./chunk-5FYH2KEZ.js";
|
|
10
|
+
import {
|
|
11
|
+
FlashOverlay,
|
|
12
|
+
OWN_ATTR
|
|
13
|
+
} from "./chunk-IXNWEUNF.js";
|
|
14
|
+
import {
|
|
15
|
+
EventBus,
|
|
16
|
+
PulseController
|
|
17
|
+
} from "./chunk-C72EYM65.js";
|
|
18
|
+
|
|
19
|
+
// src/solid/instrument.ts
|
|
20
|
+
import { DEV, getOwner, sharedConfig } from "solid-js";
|
|
21
|
+
var MAX_RECENT_DISPOSED = 64;
|
|
22
|
+
var MAX_ELEMENTS_PER_COMPONENT = 8;
|
|
23
|
+
function installSolid(controller) {
|
|
24
|
+
const bus = controller.bus;
|
|
25
|
+
const hooks = DEV?.hooks;
|
|
26
|
+
const compByOwner = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
const live = /* @__PURE__ */ new Map();
|
|
28
|
+
const elements = /* @__PURE__ */ new Map();
|
|
29
|
+
const recentDisposed = /* @__PURE__ */ new Map();
|
|
30
|
+
let nextId = 1;
|
|
31
|
+
let flushId = 0;
|
|
32
|
+
let flushOpen = false;
|
|
33
|
+
let runs = 0;
|
|
34
|
+
let byKind = { memo: 0, computed: 0, effect: 0, render: 0 };
|
|
35
|
+
let byComponent = /* @__PURE__ */ new Map();
|
|
36
|
+
let componentsRan = /* @__PURE__ */ new Map();
|
|
37
|
+
let lastClosed = [];
|
|
38
|
+
let roots = 0;
|
|
39
|
+
const now = () => performance.now();
|
|
40
|
+
function toRef(info) {
|
|
41
|
+
if (!info) return null;
|
|
42
|
+
const chain = [];
|
|
43
|
+
let cur = info;
|
|
44
|
+
while (cur && chain.length < 8) {
|
|
45
|
+
chain.push(cur.name);
|
|
46
|
+
cur = cur.parent === null ? void 0 : live.get(cur.parent);
|
|
47
|
+
}
|
|
48
|
+
return { id: info.id, name: info.name, chain };
|
|
49
|
+
}
|
|
50
|
+
function componentInfoFor(owner) {
|
|
51
|
+
let cur = owner;
|
|
52
|
+
let hops = 0;
|
|
53
|
+
while (cur && hops++ < 200) {
|
|
54
|
+
const info = compByOwner.get(cur);
|
|
55
|
+
if (info) return info;
|
|
56
|
+
cur = cur.owner;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function ensureFlush() {
|
|
61
|
+
if (flushOpen) return;
|
|
62
|
+
flushOpen = true;
|
|
63
|
+
flushId++;
|
|
64
|
+
queueMicrotask(closeFlush);
|
|
65
|
+
}
|
|
66
|
+
function closeFlush() {
|
|
67
|
+
if (!flushOpen) return;
|
|
68
|
+
flushOpen = false;
|
|
69
|
+
lastClosed = [...componentsRan.values()];
|
|
70
|
+
if (runs > 0 || componentsRan.size > 0) {
|
|
71
|
+
const topComponents = {};
|
|
72
|
+
const sorted = [...byComponent.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
73
|
+
for (const [name, n] of sorted) topComponents[name] = n;
|
|
74
|
+
bus.emit(
|
|
75
|
+
"solid.flush",
|
|
76
|
+
{
|
|
77
|
+
computations: runs,
|
|
78
|
+
byKind: { ...byKind },
|
|
79
|
+
byComponent: topComponents,
|
|
80
|
+
components: lastClosed.map((c) => c.name)
|
|
81
|
+
},
|
|
82
|
+
{ flush: flushId, component: lastClosed.length === 1 ? lastClosed[0] : null }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
runs = 0;
|
|
86
|
+
byKind = { memo: 0, computed: 0, effect: 0, render: 0 };
|
|
87
|
+
byComponent = /* @__PURE__ */ new Map();
|
|
88
|
+
componentsRan = /* @__PURE__ */ new Map();
|
|
89
|
+
}
|
|
90
|
+
function kindOf(owner) {
|
|
91
|
+
if (owner.pure) return owner.comparator !== void 0 ? "memo" : "computed";
|
|
92
|
+
return owner.user ? "effect" : "render";
|
|
93
|
+
}
|
|
94
|
+
function wrapComputation(owner) {
|
|
95
|
+
const orig = owner.fn;
|
|
96
|
+
if (typeof orig !== "function") return;
|
|
97
|
+
let kind = "render";
|
|
98
|
+
let first = true;
|
|
99
|
+
let isComponent = false;
|
|
100
|
+
owner.fn = function pulseWrapped(...args) {
|
|
101
|
+
if (first) {
|
|
102
|
+
first = false;
|
|
103
|
+
if (owner.component) {
|
|
104
|
+
isComponent = true;
|
|
105
|
+
registerComponent(owner);
|
|
106
|
+
} else {
|
|
107
|
+
kind = kindOf(owner);
|
|
108
|
+
}
|
|
109
|
+
return orig.apply(this, args);
|
|
110
|
+
}
|
|
111
|
+
if (isComponent) {
|
|
112
|
+
if (controller.isOn("solid")) bus.emit("pulse.note", { note: `component body re-executed: ${owner.name ?? "?"}` });
|
|
113
|
+
return orig.apply(this, args);
|
|
114
|
+
}
|
|
115
|
+
if (controller.isOn("solid")) {
|
|
116
|
+
ensureFlush();
|
|
117
|
+
runs++;
|
|
118
|
+
byKind[kind]++;
|
|
119
|
+
const info = componentInfoFor(owner);
|
|
120
|
+
if (info) {
|
|
121
|
+
byComponent.set(info.name, (byComponent.get(info.name) ?? 0) + 1);
|
|
122
|
+
if (!componentsRan.has(info.id)) componentsRan.set(info.id, toRef(info));
|
|
123
|
+
}
|
|
124
|
+
if (controller.isOn("verboseComputations")) {
|
|
125
|
+
bus.emit("solid.computation", { kind, name: owner.name ?? null }, { flush: flushId, component: toRef(info) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return orig.apply(this, args);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function registerComponent(owner) {
|
|
132
|
+
const name = owner.name || owner.component?.name || "Anonymous";
|
|
133
|
+
const parent = componentInfoFor(owner.owner);
|
|
134
|
+
const t = now();
|
|
135
|
+
const info = {
|
|
136
|
+
id: nextId++,
|
|
137
|
+
name,
|
|
138
|
+
parent: parent?.id ?? null,
|
|
139
|
+
hydrated: Boolean(sharedConfig.context),
|
|
140
|
+
mountedAt: t,
|
|
141
|
+
mountedWall: Date.now(),
|
|
142
|
+
flush: flushId,
|
|
143
|
+
disposedAt: null
|
|
144
|
+
};
|
|
145
|
+
compByOwner.set(owner, info);
|
|
146
|
+
live.set(info.id, info);
|
|
147
|
+
const key = `${name}|${parent?.name ?? ""}`;
|
|
148
|
+
if (controller.isOn("solid")) ensureFlush();
|
|
149
|
+
const disposed = recentDisposed.get(key);
|
|
150
|
+
const remount = disposed !== void 0 && flushId - disposed.flush <= 1 && t - disposed.t < 250;
|
|
151
|
+
if (disposed) recentDisposed.delete(key);
|
|
152
|
+
if (controller.isOn("solid")) {
|
|
153
|
+
ensureFlush();
|
|
154
|
+
const ref = toRef(info);
|
|
155
|
+
componentsRan.set(info.id, ref);
|
|
156
|
+
bus.emit(
|
|
157
|
+
remount ? "solid.component.remount" : "solid.component.mount",
|
|
158
|
+
{
|
|
159
|
+
name,
|
|
160
|
+
parent: parent?.name ?? null,
|
|
161
|
+
hydrated: info.hydrated,
|
|
162
|
+
id: info.id,
|
|
163
|
+
...remount ? { gapMs: Math.round((t - disposed.t) * 100) / 100 } : {}
|
|
164
|
+
},
|
|
165
|
+
{ flush: flushId, component: ref }
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
(owner.cleanups ||= []).push(() => {
|
|
169
|
+
const d = now();
|
|
170
|
+
if (controller.isOn("solid")) ensureFlush();
|
|
171
|
+
info.disposedAt = d;
|
|
172
|
+
live.delete(info.id);
|
|
173
|
+
elements.delete(info.id);
|
|
174
|
+
recentDisposed.set(key, { t: d, flush: flushId });
|
|
175
|
+
while (recentDisposed.size > MAX_RECENT_DISPOSED) {
|
|
176
|
+
const oldest = recentDisposed.keys().next().value;
|
|
177
|
+
if (oldest === void 0) break;
|
|
178
|
+
recentDisposed.delete(oldest);
|
|
179
|
+
}
|
|
180
|
+
if (controller.isOn("solid")) {
|
|
181
|
+
ensureFlush();
|
|
182
|
+
bus.emit(
|
|
183
|
+
"solid.component.dispose",
|
|
184
|
+
{ name, parent: parent?.name ?? null, id: info.id, lifetimeMs: Math.round((d - info.mountedAt) * 100) / 100 },
|
|
185
|
+
{ flush: flushId, component: toRef(info) }
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
let prevAfterCreateOwner = null;
|
|
191
|
+
let prevAfterUpdate = null;
|
|
192
|
+
const available = Boolean(hooks);
|
|
193
|
+
if (hooks) {
|
|
194
|
+
prevAfterCreateOwner = hooks.afterCreateOwner ?? null;
|
|
195
|
+
prevAfterUpdate = hooks.afterUpdate ?? null;
|
|
196
|
+
hooks.afterCreateOwner = ((owner) => {
|
|
197
|
+
prevAfterCreateOwner?.(owner);
|
|
198
|
+
try {
|
|
199
|
+
if (typeof owner.fn === "function") wrapComputation(owner);
|
|
200
|
+
else if (!owner.owner) {
|
|
201
|
+
roots++;
|
|
202
|
+
if (controller.isOn("solid")) bus.emit("solid.root", { roots });
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.warn("[solid-pulse] instrumentation error", err);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
hooks.afterUpdate = () => {
|
|
209
|
+
prevAfterUpdate?.();
|
|
210
|
+
closeFlush();
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const api = {
|
|
214
|
+
available,
|
|
215
|
+
flushId: () => flushId,
|
|
216
|
+
lastFlushComponents: () => flushOpen ? [...componentsRan.values()] : lastClosed,
|
|
217
|
+
componentFor: (owner) => toRef(componentInfoFor(owner)),
|
|
218
|
+
currentComponent: () => toRef(componentInfoFor(getOwner())),
|
|
219
|
+
components: () => [...live.values()],
|
|
220
|
+
attachElement(componentId, el) {
|
|
221
|
+
const list = elements.get(componentId) ?? [];
|
|
222
|
+
if (list.some((r) => r.deref() === el)) return;
|
|
223
|
+
list.push(new WeakRef(el));
|
|
224
|
+
while (list.length > MAX_ELEMENTS_PER_COMPONENT) list.shift();
|
|
225
|
+
elements.set(componentId, list);
|
|
226
|
+
},
|
|
227
|
+
elementsFor(componentId) {
|
|
228
|
+
const out = [];
|
|
229
|
+
for (const ref of elements.get(componentId) ?? []) {
|
|
230
|
+
const el = ref.deref();
|
|
231
|
+
if (el && el.isConnected) out.push(el);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
},
|
|
235
|
+
rectFor(component) {
|
|
236
|
+
let els = api.elementsFor(component.id);
|
|
237
|
+
if (els.length === 0 && typeof document !== "undefined") {
|
|
238
|
+
const escaped = component.name.replace(/["\\]/g, "\\$&");
|
|
239
|
+
els = [...document.querySelectorAll(`[data-solid-component="${escaped}"]`)].slice(0, 4);
|
|
240
|
+
}
|
|
241
|
+
let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity;
|
|
242
|
+
for (const el of els) {
|
|
243
|
+
const r = el.getBoundingClientRect();
|
|
244
|
+
if (r.width === 0 && r.height === 0) continue;
|
|
245
|
+
x1 = Math.min(x1, r.left);
|
|
246
|
+
y1 = Math.min(y1, r.top);
|
|
247
|
+
x2 = Math.max(x2, r.right);
|
|
248
|
+
y2 = Math.max(y2, r.bottom);
|
|
249
|
+
}
|
|
250
|
+
if (!Number.isFinite(x1)) return null;
|
|
251
|
+
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
|
252
|
+
},
|
|
253
|
+
dispose() {
|
|
254
|
+
if (hooks) {
|
|
255
|
+
hooks.afterCreateOwner = prevAfterCreateOwner;
|
|
256
|
+
hooks.afterUpdate = prevAfterUpdate;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
controller.register(
|
|
261
|
+
{ name: "inspect.components", summary: "Live component instances (name, parent, hydrated, age).", args: { name: "substring filter" }, ui: "Pulse tab \u203A Components" },
|
|
262
|
+
(a) => {
|
|
263
|
+
const needle = a.name === void 0 ? "" : String(a.name).toLowerCase();
|
|
264
|
+
const t = now();
|
|
265
|
+
return api.components().filter((c) => !needle || c.name.toLowerCase().includes(needle)).map((c) => ({ id: c.id, name: c.name, parent: c.parent === null ? null : live.get(c.parent)?.name ?? null, hydrated: c.hydrated, ageMs: Math.round(t - c.mountedAt), elements: api.elementsFor(c.id).length }));
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
controller.register(
|
|
269
|
+
{ name: "inspect.solid", summary: "Solid dev-hook availability, root count, live component count, current flush id." },
|
|
270
|
+
() => ({ available, roots, liveComponents: live.size, flush: flushId })
|
|
271
|
+
);
|
|
272
|
+
if (!available) {
|
|
273
|
+
bus.emit("pulse.note", { note: "solid-js DEV hooks unavailable (production build?) \u2014 Solid instrumentation disabled" });
|
|
274
|
+
}
|
|
275
|
+
return api;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/solid/dom.ts
|
|
279
|
+
var MAX_TARGETS_PER_BATCH = 40;
|
|
280
|
+
var MAX_TARGETS_IN_EVENT = 25;
|
|
281
|
+
var MAX_TRACKED_SCROLLERS = 50;
|
|
282
|
+
var MAX_DETACHED = 200;
|
|
283
|
+
var DETACHED_TTL_MS = 3e3;
|
|
284
|
+
function describeElement(el, withRect = true) {
|
|
285
|
+
const out = { tag: el.tagName.toLowerCase() };
|
|
286
|
+
if (el.id) out.id = el.id;
|
|
287
|
+
const testId = el.getAttribute("data-testid");
|
|
288
|
+
if (testId) out.testId = testId;
|
|
289
|
+
const cls = typeof el.className === "string" ? el.className.trim() : "";
|
|
290
|
+
if (cls) out.classes = cls.length > 80 ? cls.slice(0, 77) + "..." : cls;
|
|
291
|
+
const comp = el.closest("[data-solid-component]");
|
|
292
|
+
out.component = comp ? comp.getAttribute("data-solid-component") : null;
|
|
293
|
+
const src = el.closest("[data-solid-source]");
|
|
294
|
+
out.source = src ? src.getAttribute("data-solid-source") : null;
|
|
295
|
+
if (withRect) {
|
|
296
|
+
const r = el.getBoundingClientRect();
|
|
297
|
+
out.rect = { x: r.left, y: r.top, w: r.width, h: r.height };
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
function toSelector(el) {
|
|
302
|
+
if (el.id) return `#${cssEscape(el.id)}`;
|
|
303
|
+
const testId = el.getAttribute("data-testid");
|
|
304
|
+
if (testId) return `[data-testid="${testId.replace(/"/g, '\\"')}"]`;
|
|
305
|
+
const parts = [];
|
|
306
|
+
let cur = el;
|
|
307
|
+
while (cur && parts.length < 5 && cur !== document.documentElement) {
|
|
308
|
+
const parent = cur.parentElement;
|
|
309
|
+
let part = cur.tagName.toLowerCase();
|
|
310
|
+
if (parent) {
|
|
311
|
+
const siblings = [...parent.children].filter((c) => c.tagName === cur.tagName);
|
|
312
|
+
if (siblings.length > 1) part += `:nth-of-type(${siblings.indexOf(cur) + 1})`;
|
|
313
|
+
}
|
|
314
|
+
parts.unshift(part);
|
|
315
|
+
if (cur.id) {
|
|
316
|
+
parts[0] = `#${cssEscape(cur.id)}`;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
cur = parent;
|
|
320
|
+
}
|
|
321
|
+
return parts.join(" > ");
|
|
322
|
+
}
|
|
323
|
+
function cssEscape(s) {
|
|
324
|
+
return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(s) : s.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
325
|
+
}
|
|
326
|
+
function nextFrame(fn) {
|
|
327
|
+
let done = false;
|
|
328
|
+
const run = () => {
|
|
329
|
+
if (done) return;
|
|
330
|
+
done = true;
|
|
331
|
+
fn();
|
|
332
|
+
};
|
|
333
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
|
|
334
|
+
setTimeout(run, 50);
|
|
335
|
+
}
|
|
336
|
+
function installDom(controller, solid, overlay) {
|
|
337
|
+
const bus = controller.bus;
|
|
338
|
+
const scrollTops = /* @__PURE__ */ new Map();
|
|
339
|
+
let lastFocused = null;
|
|
340
|
+
const detached = /* @__PURE__ */ new Map();
|
|
341
|
+
const now = () => performance.now();
|
|
342
|
+
const isOwn = (n) => {
|
|
343
|
+
const el = n instanceof Element ? n : n.parentElement;
|
|
344
|
+
return el ? el.closest(`[${OWN_ATTR}]`) !== null : false;
|
|
345
|
+
};
|
|
346
|
+
const onScroll = (e) => {
|
|
347
|
+
const t = e.target;
|
|
348
|
+
if (!(t instanceof Element)) return;
|
|
349
|
+
scrollTops.delete(t);
|
|
350
|
+
scrollTops.set(t, t.scrollTop);
|
|
351
|
+
if (scrollTops.size > MAX_TRACKED_SCROLLERS) {
|
|
352
|
+
const oldest = scrollTops.keys().next().value;
|
|
353
|
+
if (oldest) scrollTops.delete(oldest);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const onFocusIn = (e) => {
|
|
357
|
+
if (e.target instanceof Element && !isOwn(e.target)) lastFocused = e.target;
|
|
358
|
+
};
|
|
359
|
+
document.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
360
|
+
document.addEventListener("focusin", onFocusIn, true);
|
|
361
|
+
const focusPoll = setInterval(() => {
|
|
362
|
+
const active = document.activeElement;
|
|
363
|
+
if (active && active !== document.body && active !== document.documentElement && !isOwn(active)) lastFocused = active;
|
|
364
|
+
}, 200);
|
|
365
|
+
function attributionFor(target, flushComps) {
|
|
366
|
+
if (flushComps.length === 1) return flushComps[0];
|
|
367
|
+
const named = target.closest("[data-solid-component]")?.getAttribute("data-solid-component");
|
|
368
|
+
if (named) {
|
|
369
|
+
const match = flushComps.find((c) => c.name === named);
|
|
370
|
+
if (match) return match;
|
|
371
|
+
return { id: -1, name: named };
|
|
372
|
+
}
|
|
373
|
+
return flushComps.length > 1 ? null : null;
|
|
374
|
+
}
|
|
375
|
+
function pruneDetached(t) {
|
|
376
|
+
for (const [node, rec] of detached) {
|
|
377
|
+
if (t - rec.t > DETACHED_TTL_MS) detached.delete(node);
|
|
378
|
+
else break;
|
|
379
|
+
}
|
|
380
|
+
while (detached.size > MAX_DETACHED) {
|
|
381
|
+
const oldest = detached.keys().next().value;
|
|
382
|
+
if (oldest === void 0) break;
|
|
383
|
+
detached.delete(oldest);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const observer = new MutationObserver((records) => {
|
|
387
|
+
if (globalThis.__PULSE_DEBUG) console.error("DBG-MO", records.map((r) => `${r.type}:${r.target.tagName ?? "?"}:+${r.addedNodes.length}/-${r.removedNodes.length}`).join(" "), "dom on:", controller.isOn("dom"));
|
|
388
|
+
if (!controller.isOn("dom")) return;
|
|
389
|
+
const t = now();
|
|
390
|
+
const flush = solid?.flushId() ?? 0;
|
|
391
|
+
const flushComps = solid?.lastFlushComponents() ?? [];
|
|
392
|
+
const targets = /* @__PURE__ */ new Map();
|
|
393
|
+
const detachedNow = [];
|
|
394
|
+
const reattached = [];
|
|
395
|
+
for (const r of records) {
|
|
396
|
+
const target = r.target instanceof Element ? r.target : r.target.parentElement;
|
|
397
|
+
if (!target || isOwn(target)) continue;
|
|
398
|
+
let info = targets.get(target);
|
|
399
|
+
if (!info) {
|
|
400
|
+
if (targets.size >= MAX_TARGETS_PER_BATCH) continue;
|
|
401
|
+
info = { types: /* @__PURE__ */ new Set(), attrs: /* @__PURE__ */ new Set(), added: 0, removed: 0 };
|
|
402
|
+
targets.set(target, info);
|
|
403
|
+
}
|
|
404
|
+
info.types.add(r.type);
|
|
405
|
+
if (r.type === "attributes" && r.attributeName) info.attrs.add(r.attributeName);
|
|
406
|
+
info.added += r.addedNodes.length;
|
|
407
|
+
info.removed += r.removedNodes.length;
|
|
408
|
+
for (const n of r.removedNodes) {
|
|
409
|
+
if (!(n instanceof Element) || isOwn(n)) continue;
|
|
410
|
+
const scrollers = [];
|
|
411
|
+
for (const [el, top] of scrollTops) {
|
|
412
|
+
if (top > 0 && (n === el || n.contains(el))) scrollers.push({ el, desc: describeElement(el, false), scrollTop: top });
|
|
413
|
+
}
|
|
414
|
+
const hadFocus = lastFocused !== null && (n === lastFocused || n.contains(lastFocused));
|
|
415
|
+
const rec = {
|
|
416
|
+
t,
|
|
417
|
+
desc: describeElement(n, false),
|
|
418
|
+
scrollers,
|
|
419
|
+
hadFocus,
|
|
420
|
+
focused: hadFocus && lastFocused ? describeElement(lastFocused, false) : null,
|
|
421
|
+
component: attributionFor(n, flushComps)
|
|
422
|
+
};
|
|
423
|
+
detached.set(n, rec);
|
|
424
|
+
if (scrollers.length || hadFocus) detachedNow.push(rec);
|
|
425
|
+
}
|
|
426
|
+
for (const n of r.addedNodes) {
|
|
427
|
+
if (!(n instanceof Element)) continue;
|
|
428
|
+
const rec = detached.get(n);
|
|
429
|
+
if (rec) {
|
|
430
|
+
detached.delete(n);
|
|
431
|
+
reattached.push({ node: n, rec });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
pruneDetached(t);
|
|
436
|
+
if (targets.size > 0) {
|
|
437
|
+
const wantRects = Boolean(overlay && controller.isOn("flash"));
|
|
438
|
+
const rects = [];
|
|
439
|
+
const summary = [];
|
|
440
|
+
let attributed = null;
|
|
441
|
+
let i = 0;
|
|
442
|
+
for (const [el, info] of targets) {
|
|
443
|
+
if (wantRects || i < MAX_TARGETS_IN_EVENT) {
|
|
444
|
+
const desc = describeElement(el, wantRects);
|
|
445
|
+
if (wantRects && desc.rect && el.isConnected) rects.push(desc.rect);
|
|
446
|
+
if (i < MAX_TARGETS_IN_EVENT) {
|
|
447
|
+
summary.push({
|
|
448
|
+
...desc,
|
|
449
|
+
types: [...info.types],
|
|
450
|
+
...info.attrs.size ? { attrs: [...info.attrs] } : {},
|
|
451
|
+
...info.added ? { added: info.added } : {},
|
|
452
|
+
...info.removed ? { removed: info.removed } : {}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const comp = attributionFor(el, flushComps);
|
|
457
|
+
if (comp && !attributed) attributed = comp;
|
|
458
|
+
if (solid && comp && comp.id > 0 && el.isConnected) solid.attachElement(comp.id, el);
|
|
459
|
+
i++;
|
|
460
|
+
}
|
|
461
|
+
bus.emit(
|
|
462
|
+
"dom.mutation",
|
|
463
|
+
{
|
|
464
|
+
records: records.length,
|
|
465
|
+
targets: targets.size,
|
|
466
|
+
summary,
|
|
467
|
+
attributedTo: flushComps.length === 1 ? "single-component-flush" : flushComps.length > 1 ? "multi-component-flush" : "outside-solid-flush"
|
|
468
|
+
},
|
|
469
|
+
{ flush, component: attributed }
|
|
470
|
+
);
|
|
471
|
+
if (wantRects && rects.length) overlay.flash(rects, "dom", { label: attributed?.name });
|
|
472
|
+
}
|
|
473
|
+
for (const rec of detachedNow) {
|
|
474
|
+
bus.emit(
|
|
475
|
+
"dom.detach",
|
|
476
|
+
{
|
|
477
|
+
element: rec.desc,
|
|
478
|
+
scrollers: rec.scrollers.map((s) => ({ element: s.desc, scrollTop: s.scrollTop })),
|
|
479
|
+
hadFocus: rec.hadFocus,
|
|
480
|
+
focused: rec.focused
|
|
481
|
+
},
|
|
482
|
+
{ flush, component: rec.component }
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (reattached.length) {
|
|
486
|
+
nextFrame(() => {
|
|
487
|
+
const t2 = now();
|
|
488
|
+
for (const { node, rec } of reattached) {
|
|
489
|
+
const scrollReset = rec.scrollers.map((s) => ({
|
|
490
|
+
element: s.desc,
|
|
491
|
+
before: s.scrollTop,
|
|
492
|
+
after: s.el.scrollTop,
|
|
493
|
+
reset: s.scrollTop > 0 && s.el.scrollTop === 0
|
|
494
|
+
}));
|
|
495
|
+
const focusLost = rec.hadFocus && !node.contains(document.activeElement);
|
|
496
|
+
const chain = rec.component?.chain ?? [];
|
|
497
|
+
const data = {
|
|
498
|
+
element: rec.desc,
|
|
499
|
+
gapMs: Math.round((t2 - rec.t) * 100) / 100,
|
|
500
|
+
scrollReset,
|
|
501
|
+
focusLost,
|
|
502
|
+
suspenseInChain: chain.includes("Suspense"),
|
|
503
|
+
selector: toSelector(node)
|
|
504
|
+
};
|
|
505
|
+
bus.emit("dom.reattach", data, { flush, component: rec.component });
|
|
506
|
+
if (overlay && controller.isOn("flash") && node.isConnected) {
|
|
507
|
+
const r = node.getBoundingClientRect();
|
|
508
|
+
overlay.flash([{ x: r.left, y: r.top, w: r.width, h: r.height }], "reattach", {
|
|
509
|
+
ms: 900,
|
|
510
|
+
label: `reattach ${scrollReset.some((s) => s.reset) ? "\xB7 scroll reset" : ""}${focusLost ? " \xB7 focus lost" : ""}`.trim()
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (lastFocused && !lastFocused.isConnected) {
|
|
517
|
+
const active = document.activeElement;
|
|
518
|
+
if (!active || active === document.body) {
|
|
519
|
+
bus.emit("focus.lost", { element: describeElement(lastFocused, false), cause: "element removed from document" }, { flush });
|
|
520
|
+
}
|
|
521
|
+
lastFocused = null;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });
|
|
525
|
+
function grabContext(el) {
|
|
526
|
+
const grab = window.__SOLID_GRAB__;
|
|
527
|
+
const desc = describeElement(el);
|
|
528
|
+
const ctx = grab?.inspect && el instanceof HTMLElement ? grab.inspect(el) : null;
|
|
529
|
+
return {
|
|
530
|
+
element: desc,
|
|
531
|
+
selector: toSelector(el),
|
|
532
|
+
pulseComponent: solid ? (() => {
|
|
533
|
+
for (const c of solid.components()) if (solid.elementsFor(c.id).includes(el)) return { id: c.id, name: c.name };
|
|
534
|
+
return null;
|
|
535
|
+
})() : null,
|
|
536
|
+
grab: ctx ? { formatted: ctx.formatted, elementSource: ctx.elementSource, components: ctx.components } : null,
|
|
537
|
+
html: el.outerHTML.length > 500 ? el.outerHTML.slice(0, 500) + "..." : el.outerHTML
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
controller.register(
|
|
541
|
+
{
|
|
542
|
+
name: "inspect.element",
|
|
543
|
+
summary: "Element \u2192 source context: data-solid-source/component, solid-grab formatted context when installed, pulse component attribution.",
|
|
544
|
+
args: { selector: "CSS selector (first match)", x: "viewport x (with y, instead of selector)", y: "viewport y" },
|
|
545
|
+
ui: "Grab tab \u203A Pick element (Alt+click via solid-grab)"
|
|
546
|
+
},
|
|
547
|
+
(a) => {
|
|
548
|
+
let el = null;
|
|
549
|
+
if (a.selector !== void 0) el = document.querySelector(String(a.selector));
|
|
550
|
+
else if (a.x !== void 0 && a.y !== void 0) {
|
|
551
|
+
const hit = document.elementsFromPoint(Number(a.x), Number(a.y)).find((e) => !isOwn(e));
|
|
552
|
+
el = hit ?? null;
|
|
553
|
+
}
|
|
554
|
+
if (!el) throw new Error("no element matched");
|
|
555
|
+
return grabContext(el);
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
controller.register(
|
|
559
|
+
{ name: "dom.highlight", summary: "Flash an outline around matching elements so a human can see what an agent is looking at.", args: { selector: "CSS selector", ms: "duration (default 1200)", all: "true = every match (max 20)" }, ui: "Grab tab \u203A Highlight" },
|
|
560
|
+
(a) => {
|
|
561
|
+
if (!overlay) throw new Error("overlay not mounted");
|
|
562
|
+
const all = a.all === true || a.all === "true";
|
|
563
|
+
const nodes = all ? [...document.querySelectorAll(String(a.selector))].slice(0, 20) : [document.querySelector(String(a.selector))].filter(Boolean);
|
|
564
|
+
const rects = nodes.map((n) => {
|
|
565
|
+
const r = n.getBoundingClientRect();
|
|
566
|
+
return { x: r.left, y: r.top, w: r.width, h: r.height };
|
|
567
|
+
});
|
|
568
|
+
overlay.flash(rects, "highlight", { ms: a.ms === void 0 ? 1200 : Number(a.ms), label: String(a.selector) });
|
|
569
|
+
return { matched: nodes.length, rects };
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
controller.register(
|
|
573
|
+
{ name: "inspect.focus", summary: "Active element and the last element that had focus.", ui: "Pulse tab \u203A footer" },
|
|
574
|
+
() => ({
|
|
575
|
+
active: document.activeElement && document.activeElement !== document.body ? describeElement(document.activeElement) : null,
|
|
576
|
+
lastFocused: lastFocused ? describeElement(lastFocused) : null
|
|
577
|
+
})
|
|
578
|
+
);
|
|
579
|
+
controller.register(
|
|
580
|
+
{ name: "inspect.scrollers", summary: "Elements that have scrolled recently with their last scrollTop.", ui: "Pulse tab \u203A footer" },
|
|
581
|
+
() => [...scrollTops].map(([el, top]) => ({ element: describeElement(el, false), selector: toSelector(el), scrollTop: top, connected: el.isConnected }))
|
|
582
|
+
);
|
|
583
|
+
return {
|
|
584
|
+
dispose() {
|
|
585
|
+
observer.disconnect();
|
|
586
|
+
clearInterval(focusPoll);
|
|
587
|
+
document.removeEventListener("scroll", onScroll, true);
|
|
588
|
+
document.removeEventListener("focusin", onFocusIn, true);
|
|
589
|
+
controller.unregister("inspect.element");
|
|
590
|
+
controller.unregister("dom.highlight");
|
|
591
|
+
controller.unregister("inspect.focus");
|
|
592
|
+
controller.unregister("inspect.scrollers");
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/solid/network.ts
|
|
598
|
+
var MAX_BODY_CHARS = 2e3;
|
|
599
|
+
var MAX_INDIVIDUAL_STREAM_MESSAGES = 200;
|
|
600
|
+
function bodySize(body) {
|
|
601
|
+
if (body == null) return 0;
|
|
602
|
+
if (typeof body === "string") return body.length;
|
|
603
|
+
if (body instanceof ArrayBuffer) return body.byteLength;
|
|
604
|
+
if (ArrayBuffer.isView(body)) return body.byteLength;
|
|
605
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return body.size;
|
|
606
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) return body.toString().length;
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
function messageType(data) {
|
|
610
|
+
if (typeof data !== "string" || data.length > 65536 || data[0] !== "{") return null;
|
|
611
|
+
const m = /"type"\s*:\s*"([^"]{1,80})"/.exec(data);
|
|
612
|
+
return m ? m[1] : null;
|
|
613
|
+
}
|
|
614
|
+
function preview(data, on) {
|
|
615
|
+
if (!on) return void 0;
|
|
616
|
+
if (typeof data === "string") return redactText(data.length > MAX_BODY_CHARS ? data.slice(0, MAX_BODY_CHARS) + "\u2026" : data);
|
|
617
|
+
return void 0;
|
|
618
|
+
}
|
|
619
|
+
function installNetwork(controller, solid, options = {}) {
|
|
620
|
+
const bus = controller.bus;
|
|
621
|
+
const ignored = (url) => options.ignoreUrl?.(url) === true;
|
|
622
|
+
let nextId = 1;
|
|
623
|
+
const g = globalThis;
|
|
624
|
+
const origFetch = g.fetch;
|
|
625
|
+
const NativeWebSocket = g.WebSocket;
|
|
626
|
+
const NativeEventSource = g.EventSource;
|
|
627
|
+
function wrapSse(res, id, url) {
|
|
628
|
+
if (!res.body) return res;
|
|
629
|
+
let count = 0;
|
|
630
|
+
let carry = "";
|
|
631
|
+
const decoder = new TextDecoder();
|
|
632
|
+
const startedAt = performance.now();
|
|
633
|
+
const reader = res.body.getReader();
|
|
634
|
+
bus.emit("net.sse.open", { id, url, transport: "fetch" });
|
|
635
|
+
const scan = (chunk) => {
|
|
636
|
+
carry += decoder.decode(chunk, { stream: true });
|
|
637
|
+
let idx;
|
|
638
|
+
while ((idx = carry.search(/\r?\n\r?\n/)) >= 0) {
|
|
639
|
+
const frame = carry.slice(0, idx);
|
|
640
|
+
carry = carry.slice(idx).replace(/^\r?\n\r?\n/, "");
|
|
641
|
+
if (!frame.trim() || frame.startsWith(":")) continue;
|
|
642
|
+
count++;
|
|
643
|
+
const evt = /^event:\s?(.*)$/m.exec(frame)?.[1] ?? "message";
|
|
644
|
+
if (count <= MAX_INDIVIDUAL_STREAM_MESSAGES || count % 50 === 0) {
|
|
645
|
+
bus.emit("net.sse.message", { id, url, event: evt, n: count, bytes: frame.length, transport: "fetch", preview: preview(frame, controller.isOn("captureBodies")) });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
const done = () => bus.emit("net.sse.close", { id, url, messages: count, ms: Math.round(performance.now() - startedAt), transport: "fetch" });
|
|
650
|
+
const body = new ReadableStream({
|
|
651
|
+
async pull(ctl) {
|
|
652
|
+
const { value, done: finished } = await reader.read();
|
|
653
|
+
if (finished) {
|
|
654
|
+
done();
|
|
655
|
+
ctl.close();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
scan(value);
|
|
659
|
+
ctl.enqueue(value);
|
|
660
|
+
},
|
|
661
|
+
cancel(reason) {
|
|
662
|
+
done();
|
|
663
|
+
return reader.cancel(reason);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
667
|
+
}
|
|
668
|
+
const pulseFetch = function pulseFetch2(input, init) {
|
|
669
|
+
if (!controller.isOn("network")) return origFetch.call(this, input, init);
|
|
670
|
+
const req = typeof Request !== "undefined" && input instanceof Request ? input : null;
|
|
671
|
+
const rawUrl = req ? req.url : input instanceof URL ? input.href : String(input);
|
|
672
|
+
if (ignored(rawUrl)) return origFetch.call(this, input, init);
|
|
673
|
+
const id = nextId++;
|
|
674
|
+
const url = redactUrl(rawUrl);
|
|
675
|
+
const method = (init?.method ?? req?.method ?? "GET").toUpperCase();
|
|
676
|
+
const start = performance.now();
|
|
677
|
+
const component = solid?.currentComponent() ?? null;
|
|
678
|
+
let aborted = false;
|
|
679
|
+
const signal = init?.signal ?? req?.signal;
|
|
680
|
+
if (signal) {
|
|
681
|
+
if (signal.aborted) aborted = true;
|
|
682
|
+
else signal.addEventListener("abort", () => aborted = true, { once: true });
|
|
683
|
+
}
|
|
684
|
+
bus.emit("net.fetch.start", { id, method, url, bodyBytes: bodySize(init?.body), preview: preview(init?.body, controller.isOn("captureBodies")) }, { component });
|
|
685
|
+
return origFetch.call(this, input, init).then(
|
|
686
|
+
(res) => {
|
|
687
|
+
const ms = Math.round((performance.now() - start) * 100) / 100;
|
|
688
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
689
|
+
const sse = ct.includes("text/event-stream");
|
|
690
|
+
bus.emit(
|
|
691
|
+
"net.fetch.end",
|
|
692
|
+
{ id, method, url, status: res.status, ok: res.ok, ms, contentType: ct, contentLength: res.headers.get("content-length"), sse, streaming: sse || !res.headers.has("content-length") && res.body !== null },
|
|
693
|
+
{ component }
|
|
694
|
+
);
|
|
695
|
+
return sse ? wrapSse(res, id, url) : res;
|
|
696
|
+
},
|
|
697
|
+
(err) => {
|
|
698
|
+
const ms = Math.round((performance.now() - start) * 100) / 100;
|
|
699
|
+
const e = err;
|
|
700
|
+
bus.emit("net.fetch.error", { id, method, url, ms, name: e?.name ?? "Error", message: redactText(String(e?.message ?? err)), aborted: aborted || e?.name === "AbortError" }, { component });
|
|
701
|
+
throw err;
|
|
702
|
+
}
|
|
703
|
+
);
|
|
704
|
+
};
|
|
705
|
+
pulseFetch.__solidPulse = true;
|
|
706
|
+
g.fetch = pulseFetch;
|
|
707
|
+
class PulseWebSocket extends NativeWebSocket {
|
|
708
|
+
constructor(url, protocols) {
|
|
709
|
+
super(url, protocols);
|
|
710
|
+
if (ignored(typeof url === "string" ? url : url.href)) return;
|
|
711
|
+
const id = nextId++;
|
|
712
|
+
const safeUrl = redactUrl(typeof url === "string" ? url : url.href);
|
|
713
|
+
const openedAt = performance.now();
|
|
714
|
+
let inbound = 0;
|
|
715
|
+
let outbound = 0;
|
|
716
|
+
const component = solid?.currentComponent() ?? null;
|
|
717
|
+
bus.emit("net.ws.open", { id, url: safeUrl, protocols: protocols ? [].concat(protocols) : [], state: "connecting" }, { component });
|
|
718
|
+
this.addEventListener("open", () => {
|
|
719
|
+
bus.emit("net.ws.open", { id, url: safeUrl, protocol: this.protocol, state: "open", ms: Math.round(performance.now() - openedAt) }, { component });
|
|
720
|
+
});
|
|
721
|
+
this.addEventListener("message", (ev) => {
|
|
722
|
+
inbound++;
|
|
723
|
+
if (!controller.isOn("network")) return;
|
|
724
|
+
if (inbound <= MAX_INDIVIDUAL_STREAM_MESSAGES || inbound % 50 === 0) {
|
|
725
|
+
const data = ev.data;
|
|
726
|
+
bus.emit("net.ws.message", { id, url: safeUrl, dir: "in", n: inbound, bytes: bodySize(data), type: messageType(data), preview: preview(data, controller.isOn("captureBodies")) });
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
this.addEventListener("close", (ev) => {
|
|
730
|
+
const e = ev;
|
|
731
|
+
bus.emit("net.ws.close", { id, url: safeUrl, code: e.code, reason: redactText(e.reason), wasClean: e.wasClean, inbound, outbound, ms: Math.round(performance.now() - openedAt) });
|
|
732
|
+
});
|
|
733
|
+
this.addEventListener("error", () => {
|
|
734
|
+
bus.emit("net.ws.error", { id, url: safeUrl, readyState: this.readyState });
|
|
735
|
+
});
|
|
736
|
+
const origSend = this.send.bind(this);
|
|
737
|
+
this.send = (data) => {
|
|
738
|
+
outbound++;
|
|
739
|
+
if (controller.isOn("network") && (outbound <= MAX_INDIVIDUAL_STREAM_MESSAGES || outbound % 50 === 0)) {
|
|
740
|
+
bus.emit("net.ws.message", { id, url: safeUrl, dir: "out", n: outbound, bytes: bodySize(data), type: messageType(data), preview: preview(data, controller.isOn("captureBodies")) });
|
|
741
|
+
}
|
|
742
|
+
return origSend(data);
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
PulseWebSocket.__solidPulse = true;
|
|
747
|
+
g.WebSocket = PulseWebSocket;
|
|
748
|
+
if (NativeEventSource) {
|
|
749
|
+
class PulseEventSource extends NativeEventSource {
|
|
750
|
+
constructor(url, init) {
|
|
751
|
+
super(url, init);
|
|
752
|
+
if (ignored(typeof url === "string" ? url : url.href)) return;
|
|
753
|
+
const id = nextId++;
|
|
754
|
+
const safeUrl = redactUrl(typeof url === "string" ? url : url.href);
|
|
755
|
+
const openedAt = performance.now();
|
|
756
|
+
let count = 0;
|
|
757
|
+
const seen = /* @__PURE__ */ new Set();
|
|
758
|
+
const component = solid?.currentComponent() ?? null;
|
|
759
|
+
const countType = (type) => {
|
|
760
|
+
if (seen.has(type)) return;
|
|
761
|
+
seen.add(type);
|
|
762
|
+
super.addEventListener(type, (ev) => {
|
|
763
|
+
count++;
|
|
764
|
+
if (!controller.isOn("network")) return;
|
|
765
|
+
if (count <= MAX_INDIVIDUAL_STREAM_MESSAGES || count % 50 === 0) {
|
|
766
|
+
const data = ev.data;
|
|
767
|
+
bus.emit("net.sse.message", { id, url: safeUrl, event: type, n: count, bytes: bodySize(data), transport: "EventSource", preview: preview(data, controller.isOn("captureBodies")) });
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
};
|
|
771
|
+
countType("message");
|
|
772
|
+
this.addEventListener("open", () => bus.emit("net.sse.open", { id, url: safeUrl, transport: "EventSource", ms: Math.round(performance.now() - openedAt) }, { component }));
|
|
773
|
+
this.addEventListener("error", () => bus.emit("net.sse.error", { id, url: safeUrl, readyState: this.readyState, transport: "EventSource" }));
|
|
774
|
+
const origAdd = this.addEventListener.bind(this);
|
|
775
|
+
this.addEventListener = ((type, listener, options2) => {
|
|
776
|
+
if (type !== "open" && type !== "error") countType(type);
|
|
777
|
+
return origAdd(type, listener, options2);
|
|
778
|
+
});
|
|
779
|
+
const origClose = this.close.bind(this);
|
|
780
|
+
this.close = () => {
|
|
781
|
+
bus.emit("net.sse.close", { id, url: safeUrl, messages: count, ms: Math.round(performance.now() - openedAt), transport: "EventSource" });
|
|
782
|
+
origClose();
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
PulseEventSource.__solidPulse = true;
|
|
787
|
+
g.EventSource = PulseEventSource;
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
dispose() {
|
|
791
|
+
g.fetch = origFetch;
|
|
792
|
+
g.WebSocket = NativeWebSocket;
|
|
793
|
+
if (NativeEventSource) g.EventSource = NativeEventSource;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/bridge/client.ts
|
|
799
|
+
function defaultUrl() {
|
|
800
|
+
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
|
801
|
+
return `${proto}//${location.host}${DEFAULT_PATH}/ws`;
|
|
802
|
+
}
|
|
803
|
+
function clientIdFor(explicit) {
|
|
804
|
+
if (explicit) return explicit;
|
|
805
|
+
try {
|
|
806
|
+
const existing = sessionStorage.getItem("solid-pulse:clientId");
|
|
807
|
+
if (existing) return existing;
|
|
808
|
+
const id = `tab-${Math.random().toString(36).slice(2, 8)}`;
|
|
809
|
+
sessionStorage.setItem("solid-pulse:clientId", id);
|
|
810
|
+
return id;
|
|
811
|
+
} catch {
|
|
812
|
+
return `tab-${Math.random().toString(36).slice(2, 8)}`;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
var BridgeClient = class {
|
|
816
|
+
constructor(controller, options = {}) {
|
|
817
|
+
this.controller = controller;
|
|
818
|
+
this.options = options;
|
|
819
|
+
this.url = options.url ?? defaultUrl();
|
|
820
|
+
this.clientId = clientIdFor(options.clientId);
|
|
821
|
+
this.NativeWebSocket = WebSocket;
|
|
822
|
+
}
|
|
823
|
+
controller;
|
|
824
|
+
options;
|
|
825
|
+
ws = null;
|
|
826
|
+
timer = null;
|
|
827
|
+
queue = [];
|
|
828
|
+
flushTimer = null;
|
|
829
|
+
unsubscribe = null;
|
|
830
|
+
closed = false;
|
|
831
|
+
NativeWebSocket;
|
|
832
|
+
url;
|
|
833
|
+
clientId;
|
|
834
|
+
connected = false;
|
|
835
|
+
connect() {
|
|
836
|
+
if (this.ws || this.closed) return;
|
|
837
|
+
try {
|
|
838
|
+
const ws = new this.NativeWebSocket(this.url);
|
|
839
|
+
this.ws = ws;
|
|
840
|
+
ws.onopen = () => {
|
|
841
|
+
this.connected = true;
|
|
842
|
+
const hello = {
|
|
843
|
+
type: "hello",
|
|
844
|
+
protocol: PROTOCOL_VERSION,
|
|
845
|
+
clientId: this.clientId,
|
|
846
|
+
url: location.href,
|
|
847
|
+
title: document.title,
|
|
848
|
+
userAgent: navigator.userAgent,
|
|
849
|
+
commands: this.controller.describe(),
|
|
850
|
+
startedWall: this.controller.startedWall
|
|
851
|
+
};
|
|
852
|
+
ws.send(JSON.stringify(hello));
|
|
853
|
+
this.queue = this.controller.bus.list({ limit: 2e3 });
|
|
854
|
+
this.scheduleFlush();
|
|
855
|
+
this.unsubscribe?.();
|
|
856
|
+
this.unsubscribe = this.controller.bus.subscribe((e) => {
|
|
857
|
+
this.queue.push(e);
|
|
858
|
+
this.scheduleFlush();
|
|
859
|
+
});
|
|
860
|
+
this.controller.bus.emit("pulse.note", { note: `bridge connected ${this.url}` });
|
|
861
|
+
};
|
|
862
|
+
ws.onmessage = (ev) => void this.onMessage(ev.data);
|
|
863
|
+
ws.onclose = () => {
|
|
864
|
+
this.connected = false;
|
|
865
|
+
this.ws = null;
|
|
866
|
+
this.unsubscribe?.();
|
|
867
|
+
this.unsubscribe = null;
|
|
868
|
+
if (!this.closed) this.timer = setTimeout(() => this.connect(), this.options.reconnectMs ?? 2e3);
|
|
869
|
+
};
|
|
870
|
+
ws.onerror = () => ws.close();
|
|
871
|
+
} catch {
|
|
872
|
+
this.timer = setTimeout(() => this.connect(), this.options.reconnectMs ?? 2e3);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
disconnect() {
|
|
876
|
+
this.closed = true;
|
|
877
|
+
if (this.timer) clearTimeout(this.timer);
|
|
878
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
879
|
+
this.unsubscribe?.();
|
|
880
|
+
this.ws?.close();
|
|
881
|
+
this.ws = null;
|
|
882
|
+
this.connected = false;
|
|
883
|
+
}
|
|
884
|
+
scheduleFlush() {
|
|
885
|
+
if (this.flushTimer) return;
|
|
886
|
+
this.flushTimer = setTimeout(() => {
|
|
887
|
+
this.flushTimer = null;
|
|
888
|
+
this.flush();
|
|
889
|
+
}, 50);
|
|
890
|
+
}
|
|
891
|
+
flush() {
|
|
892
|
+
if (!this.ws || this.ws.readyState !== this.NativeWebSocket.OPEN || this.queue.length === 0) return;
|
|
893
|
+
while (this.queue.length) {
|
|
894
|
+
const chunk = this.queue.splice(0, 200);
|
|
895
|
+
this.send({ type: "events", events: chunk });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
send(frame) {
|
|
899
|
+
if (!this.ws || this.ws.readyState !== this.NativeWebSocket.OPEN) return;
|
|
900
|
+
this.ws.send(JSON.stringify(frame));
|
|
901
|
+
}
|
|
902
|
+
async onMessage(raw) {
|
|
903
|
+
let frame;
|
|
904
|
+
try {
|
|
905
|
+
frame = JSON.parse(String(raw));
|
|
906
|
+
} catch {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (!isServerFrame(frame)) return;
|
|
910
|
+
if (frame.type === "command") {
|
|
911
|
+
const result = await this.controller.run(frame.name, frame.args ?? {});
|
|
912
|
+
this.send({ type: "result", id: frame.id, result });
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
// src/index.ts
|
|
918
|
+
var instance = null;
|
|
919
|
+
function getPulse() {
|
|
920
|
+
return instance;
|
|
921
|
+
}
|
|
922
|
+
function initPulse(options = {}) {
|
|
923
|
+
if (instance) return instance;
|
|
924
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
925
|
+
throw new Error("solid-pulse runs in the browser only");
|
|
926
|
+
}
|
|
927
|
+
const bus = new EventBus(options.bufferSize ?? 2e3);
|
|
928
|
+
const controller = new PulseController(bus, options.features);
|
|
929
|
+
const overlay = options.overlay === false ? null : new FlashOverlay();
|
|
930
|
+
let solid = null;
|
|
931
|
+
let dom = null;
|
|
932
|
+
let net = null;
|
|
933
|
+
let bridge = null;
|
|
934
|
+
const boot = () => {
|
|
935
|
+
overlay?.mount();
|
|
936
|
+
solid = installSolid(controller);
|
|
937
|
+
dom = installDom(controller, solid, overlay);
|
|
938
|
+
if (options.bridge) {
|
|
939
|
+
const opts = options.bridge === true ? {} : typeof options.bridge === "string" ? { url: options.bridge } : options.bridge;
|
|
940
|
+
bridge = new BridgeClient(controller, opts);
|
|
941
|
+
}
|
|
942
|
+
net = installNetwork(controller, solid, { ignoreUrl: (url) => url.includes("/__pulse/") });
|
|
943
|
+
bridge?.connect();
|
|
944
|
+
if (options.banner !== false) {
|
|
945
|
+
console.log(
|
|
946
|
+
"%c\u25C9 solid-pulse%c dev instrumentation on \xB7 window.__SOLID_PULSE__.run(cmd) \xB7 CLI: solid-pulse commands",
|
|
947
|
+
"color:#f59e0b;font-weight:bold",
|
|
948
|
+
"color:inherit"
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
const pulse = {
|
|
953
|
+
controller,
|
|
954
|
+
bus,
|
|
955
|
+
overlay,
|
|
956
|
+
get solid() {
|
|
957
|
+
return solid;
|
|
958
|
+
},
|
|
959
|
+
get bridge() {
|
|
960
|
+
return bridge;
|
|
961
|
+
},
|
|
962
|
+
run: (name, args) => controller.run(name, args),
|
|
963
|
+
destroy() {
|
|
964
|
+
bridge?.disconnect();
|
|
965
|
+
net?.dispose();
|
|
966
|
+
dom?.dispose();
|
|
967
|
+
solid?.dispose();
|
|
968
|
+
overlay?.unmount();
|
|
969
|
+
instance = null;
|
|
970
|
+
delete window.__SOLID_PULSE__;
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
instance = pulse;
|
|
974
|
+
window.__SOLID_PULSE__ = pulse;
|
|
975
|
+
boot();
|
|
976
|
+
return pulse;
|
|
977
|
+
}
|
|
978
|
+
export {
|
|
979
|
+
EventBus,
|
|
980
|
+
FlashOverlay,
|
|
981
|
+
PulseController,
|
|
982
|
+
describeElement,
|
|
983
|
+
getPulse,
|
|
984
|
+
initPulse,
|
|
985
|
+
toSelector
|
|
986
|
+
};
|
|
987
|
+
//# sourceMappingURL=index.js.map
|