@xstate-devtools/adapter 0.1.2 → 0.1.4

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.
@@ -0,0 +1,936 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/server.ts
31
+ var server_exports = {};
32
+ __export(server_exports, {
33
+ createServerAdapter: () => createServerAdapter
34
+ });
35
+ module.exports = __toCommonJS(server_exports);
36
+ var import_node_net = require("net");
37
+
38
+ // src/logging.ts
39
+ function hasProcessEnv() {
40
+ return typeof process !== "undefined" && typeof process.env !== "undefined";
41
+ }
42
+ function isLoggingEnabled() {
43
+ if (globalThis.__XSTATE_DEVTOOLS_LOGGING__ === true) return true;
44
+ if (!hasProcessEnv()) return false;
45
+ const value = process.env.XSTATE_DEVTOOLS_LOGGING;
46
+ return value === "1" || value === "true";
47
+ }
48
+ function log(level, scope, message, details) {
49
+ if (!isLoggingEnabled()) return;
50
+ if (details === void 0) {
51
+ console[level](`[xstate-devtools:${scope}] ${message}`);
52
+ return;
53
+ }
54
+ console[level](`[xstate-devtools:${scope}] ${message}`, details);
55
+ }
56
+ function debugLog(scope, message, details) {
57
+ log("debug", scope, message, details);
58
+ }
59
+ function infoLog(scope, message, details) {
60
+ log("info", scope, message, details);
61
+ }
62
+ function warnLog(scope, message, details) {
63
+ log("warn", scope, message, details);
64
+ }
65
+
66
+ // src/sanitize.ts
67
+ var MAX_DEPTH = 10;
68
+ var MAX_STRING_LENGTH = 500;
69
+ var MAX_ARRAY_LENGTH = 100;
70
+ function sanitizeValue(value, depth, seen) {
71
+ if (depth > MAX_DEPTH) return "[MaxDepth]";
72
+ if (value === null || value === void 0) return value;
73
+ if (typeof value === "boolean" || typeof value === "number") return value;
74
+ if (typeof value === "string") {
75
+ return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}\u2026` : value;
76
+ }
77
+ if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
78
+ if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
79
+ if (typeof value === "bigint") return `[BigInt: ${value}]`;
80
+ if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
81
+ if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
82
+ if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
83
+ if (typeof value === "object") {
84
+ if (seen.has(value)) return "[Circular]";
85
+ seen.add(value);
86
+ }
87
+ if (value instanceof Map) {
88
+ const entries = [];
89
+ for (const [k, v] of value) {
90
+ if (entries.length >= MAX_ARRAY_LENGTH) break;
91
+ entries.push([sanitizeValue(k, depth + 1, seen), sanitizeValue(v, depth + 1, seen)]);
92
+ }
93
+ return { __type: "Map", entries };
94
+ }
95
+ if (value instanceof Set) {
96
+ const values = [];
97
+ for (const v of value) {
98
+ if (values.length >= MAX_ARRAY_LENGTH) break;
99
+ values.push(sanitizeValue(v, depth + 1, seen));
100
+ }
101
+ return { __type: "Set", values };
102
+ }
103
+ if (value instanceof Promise) return "[Promise]";
104
+ if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
105
+ if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
106
+ if (typeof Node !== "undefined" && value instanceof Node) {
107
+ return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
108
+ }
109
+ if (Array.isArray(value)) {
110
+ const sliced = value.slice(0, MAX_ARRAY_LENGTH);
111
+ const result = sliced.map((v) => sanitizeValue(v, depth + 1, seen));
112
+ if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
113
+ return result;
114
+ }
115
+ if (typeof value === "object") {
116
+ const result = {};
117
+ let count = 0;
118
+ for (const [k, v] of Object.entries(value)) {
119
+ if (count++ >= MAX_ARRAY_LENGTH) {
120
+ result["\u2026"] = "[truncated]";
121
+ break;
122
+ }
123
+ result[k] = sanitizeValue(v, depth + 1, seen);
124
+ }
125
+ return result;
126
+ }
127
+ return String(value);
128
+ }
129
+ function sanitize(value, depth = 0) {
130
+ return sanitizeValue(value, depth, /* @__PURE__ */ new WeakSet());
131
+ }
132
+
133
+ // src/serialize.ts
134
+ var MAX_SERIALIZED_NODES = 500;
135
+ var MAX_TRANSITIONS_PER_NODE = 100;
136
+ var MAX_CHILD_STATES = 100;
137
+ var MAX_ACTIONS_PER_TRANSITION = 20;
138
+ var MAX_ENTRY_EXIT_ACTIONS = 20;
139
+ var MAX_INVOKES_PER_NODE = 20;
140
+ function serializeGuard(guard) {
141
+ if (!guard) return void 0;
142
+ if (typeof guard === "string") return guard;
143
+ if (typeof guard === "function") return guard.name || "(inline)";
144
+ if (typeof guard === "object" && guard !== null) {
145
+ const g = guard;
146
+ return g.type ?? g.name ?? "(inline)";
147
+ }
148
+ return "(inline)";
149
+ }
150
+ function serializeAction(action) {
151
+ if (typeof action === "string") return action;
152
+ if (typeof action === "function") return action.name || "(anonymous)";
153
+ if (typeof action === "object" && action !== null) {
154
+ const a = action;
155
+ return a.type ?? a.name ?? String(action);
156
+ }
157
+ return String(action);
158
+ }
159
+ function serializeTransitionList(transitions) {
160
+ return transitions.slice(0, MAX_TRANSITIONS_PER_NODE).map((t) => ({
161
+ eventType: t.eventType ?? "",
162
+ targets: (t.target ?? []).map((n) => n?.id ?? String(n)).filter(Boolean),
163
+ guard: serializeGuard(t.guard),
164
+ actions: (t.actions ?? []).slice(0, MAX_ACTIONS_PER_TRANSITION).map(serializeAction).filter(Boolean)
165
+ }));
166
+ }
167
+ function serializeInvokes(node) {
168
+ return node.invoke.slice(0, MAX_INVOKES_PER_NODE).map((inv) => ({
169
+ id: inv.id ?? "(unknown)",
170
+ src: typeof inv.src === "string" ? inv.src : inv.src?.id ?? inv.src?.name ?? "(inline)"
171
+ }));
172
+ }
173
+ function serializeNode(node, state) {
174
+ if (!node || typeof node !== "object") {
175
+ return {
176
+ id: "(unknown)",
177
+ key: "(unknown)",
178
+ type: "atomic",
179
+ states: {},
180
+ on: [],
181
+ always: [],
182
+ entry: [],
183
+ exit: [],
184
+ invoke: []
185
+ };
186
+ }
187
+ if (state.count >= MAX_SERIALIZED_NODES) {
188
+ return {
189
+ id: node.id ?? "(truncated)",
190
+ key: node.key ?? "(truncated)",
191
+ type: node.type ?? "atomic",
192
+ states: {},
193
+ on: [],
194
+ always: [],
195
+ entry: [],
196
+ exit: [],
197
+ invoke: []
198
+ };
199
+ }
200
+ if (state.seen.has(node)) {
201
+ return {
202
+ id: node.id ?? "(circular)",
203
+ key: node.key ?? "(circular)",
204
+ type: node.type ?? "atomic",
205
+ states: {},
206
+ on: [],
207
+ always: [],
208
+ entry: [],
209
+ exit: [],
210
+ invoke: []
211
+ };
212
+ }
213
+ state.seen.add(node);
214
+ state.count += 1;
215
+ const allTransitions = [];
216
+ if (node.transitions instanceof Map) {
217
+ for (const [, tList] of node.transitions) {
218
+ if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) break;
219
+ allTransitions.push(...serializeTransitionList(tList));
220
+ if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) {
221
+ allTransitions.length = MAX_TRANSITIONS_PER_NODE;
222
+ break;
223
+ }
224
+ }
225
+ }
226
+ const always = Array.isArray(node.always) ? serializeTransitionList(node.always) : [];
227
+ const childEntries = Object.entries(node.states ?? {}).slice(0, MAX_CHILD_STATES);
228
+ return {
229
+ id: node.id,
230
+ key: node.key,
231
+ type: node.type,
232
+ initial: node.initial?.target?.[0]?.key,
233
+ states: Object.fromEntries(childEntries.map(([k, v]) => [k, serializeNode(v, state)])),
234
+ on: allTransitions,
235
+ always,
236
+ entry: (node.entry ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
237
+ exit: (node.exit ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
238
+ invoke: serializeInvokes(node),
239
+ sourceLocation: node.config?.__xstateDevtoolsSource ?? void 0,
240
+ description: node.config?.description ?? void 0
241
+ };
242
+ }
243
+ function serializeMachine(machine, sourceLocation) {
244
+ return {
245
+ id: machine.id,
246
+ root: serializeNode(machine.root, { seen: /* @__PURE__ */ new WeakSet(), count: 0 }),
247
+ sourceLocation
248
+ };
249
+ }
250
+
251
+ // src/core.ts
252
+ function summarizeMessage(message) {
253
+ const summary = { type: message.type };
254
+ if ("sessionId" in message) summary.sessionId = message.sessionId;
255
+ if ("stateNodeId" in message) summary.stateNodeId = message.stateNodeId;
256
+ if ("parentSessionId" in message && message.parentSessionId) {
257
+ summary.parentSessionId = message.parentSessionId;
258
+ }
259
+ if ("globalSeq" in message) summary.globalSeq = message.globalSeq;
260
+ if ("timestamp" in message) summary.timestamp = message.timestamp;
261
+ if ("event" in message && message.event && typeof message.event === "object" && "type" in message.event) {
262
+ summary.eventType = message.event.type;
263
+ }
264
+ return summary;
265
+ }
266
+ function summarizeInspectionEvent(event) {
267
+ return {
268
+ type: event?.type,
269
+ sessionId: event?.actorRef?.sessionId,
270
+ eventType: event?.type === "@xstate.event" && event?.event && typeof event.event === "object" ? event.event.type : void 0
271
+ };
272
+ }
273
+ function debugLog2(source, message, details) {
274
+ debugLog(`${source}:adapter`, message, details);
275
+ }
276
+ function infoLog2(source, message, details) {
277
+ infoLog(`${source}:adapter`, message, details);
278
+ }
279
+ function warnLog2(source, message, details) {
280
+ warnLog(`${source}:adapter`, message, details);
281
+ }
282
+ function isLibraryStackFrame(line) {
283
+ const normalized = line.replace(/\\/g, "/").toLowerCase();
284
+ return normalized.includes("/node_modules/xstate/") || normalized.includes("/node_modules/@xstate/") || normalized.includes("/@xstate-devtools/adapter/") || normalized.includes("/packages/adapter/");
285
+ }
286
+ function normalizeStackFrame(line) {
287
+ return line.trim().replace(/^at\s+/, "");
288
+ }
289
+ function extractStackFrameLocation(frame) {
290
+ return frame.match(/\((.*)\)$/)?.[1] ?? frame;
291
+ }
292
+ function isAnonymousOrEvalLocation(location) {
293
+ const normalized = location.trim().toLowerCase().replace(/^[./\\]+/, "");
294
+ return normalized === "<anonymous>" || normalized === "anonymous" || normalized === "(anonymous)" || normalized === "eval" || normalized === "<eval>" || normalized === "[native code]";
295
+ }
296
+ function hasFilesystemBackedPath(location) {
297
+ const trimmed = location.trim();
298
+ if (!trimmed) return false;
299
+ const match = trimmed.match(/^(.*?)(?::\d+)?(?::\d+)?$/);
300
+ const rawPath = (match?.[1] ?? trimmed).trim();
301
+ if (!rawPath || isAnonymousOrEvalLocation(rawPath)) return false;
302
+ if (/^[a-zA-Z]:[\\/]/.test(rawPath)) return true;
303
+ if (rawPath.startsWith("/") || rawPath.startsWith("./") || rawPath.startsWith("../")) return true;
304
+ if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(rawPath)) {
305
+ try {
306
+ const url = new URL(rawPath);
307
+ if (url.protocol === "file:") return true;
308
+ return url.pathname.startsWith("/@fs/");
309
+ } catch {
310
+ return false;
311
+ }
312
+ }
313
+ return false;
314
+ }
315
+ function remapWebUrlLocationToFsPath(location, source, options) {
316
+ if (source !== "web" || !options?.webSourceRoot) return location;
317
+ const match = location.trim().match(/^(.*?)(?::(\d+))?(?::(\d+))?$/);
318
+ if (!match) return location;
319
+ const [, rawPath, line, column] = match;
320
+ if (!rawPath || !/^https?:\/\//.test(rawPath)) return location;
321
+ try {
322
+ const url = new URL(rawPath);
323
+ const pathname = decodeURIComponent(url.pathname);
324
+ if (!pathname.startsWith("/app/")) return location;
325
+ const root = options.webSourceRoot.replace(/\/+$/, "");
326
+ const filePath = `${root}${pathname}`;
327
+ const suffix = line ? `:${line}${column ? `:${column}` : ""}` : "";
328
+ return `${filePath}${suffix}`;
329
+ } catch {
330
+ return location;
331
+ }
332
+ }
333
+ function getSourceLocationFromStack(stack, source = "web", options) {
334
+ const lines = stack?.split("\n") ?? [];
335
+ for (let i = 0; i < lines.length; i += 1) {
336
+ if (i <= 2) continue;
337
+ const line = lines[i];
338
+ if (isLibraryStackFrame(line)) continue;
339
+ const normalizedFrame = normalizeStackFrame(line);
340
+ const location = remapWebUrlLocationToFsPath(
341
+ extractStackFrameLocation(normalizedFrame),
342
+ source,
343
+ options
344
+ );
345
+ if (!hasFilesystemBackedPath(location)) continue;
346
+ const wrapped = normalizedFrame.match(/\((.*)\)$/);
347
+ if (wrapped) {
348
+ return normalizedFrame.replace(wrapped[1], location);
349
+ }
350
+ return location;
351
+ }
352
+ return void 0;
353
+ }
354
+ function getSourceLocation(source, options) {
355
+ try {
356
+ const oldLimit = Error.stackTraceLimit;
357
+ Error.stackTraceLimit = 50;
358
+ const stack = new Error().stack;
359
+ Error.stackTraceLimit = oldLimit;
360
+ return getSourceLocationFromStack(stack, source, options);
361
+ } catch {
362
+ return void 0;
363
+ }
364
+ }
365
+ function serializeSnapshot(snapshot) {
366
+ return {
367
+ value: snapshot?.value ?? null,
368
+ context: sanitize(snapshot?.context),
369
+ status: snapshot?.status ?? "active",
370
+ error: snapshot?.error ? sanitize(snapshot.error) : void 0
371
+ };
372
+ }
373
+ function safeSerializeSnapshot(actorRef) {
374
+ try {
375
+ return serializeSnapshot(actorRef.getSnapshot());
376
+ } catch {
377
+ return { value: null, context: void 0, status: "active" };
378
+ }
379
+ }
380
+ function getActorDisplayName(actorRef) {
381
+ const actor = actorRef;
382
+ const src = actor.src ?? actor.logic?.src;
383
+ if (typeof src === "string" && src.length > 0) return src;
384
+ if (typeof actor.logic?.id === "string" && actor.logic.id.length > 0) return actor.logic.id;
385
+ if (typeof actor.logic?.name === "string" && actor.logic.name.length > 0) return actor.logic.name;
386
+ if (src && typeof src === "object") {
387
+ const namedSrc = src;
388
+ if (typeof namedSrc.id === "string" && namedSrc.id.length > 0) return namedSrc.id;
389
+ if (typeof namedSrc.name === "string" && namedSrc.name.length > 0) return namedSrc.name;
390
+ }
391
+ return void 0;
392
+ }
393
+ function getNodeInitialChild(node) {
394
+ if (!node.states) return null;
395
+ if (typeof node.initial === "string") {
396
+ return node.states[node.initial] ?? null;
397
+ }
398
+ const target = Array.isArray(node.initial?.target) ? node.initial.target[0] : null;
399
+ return target ?? null;
400
+ }
401
+ function encodeChildValue(child, childValue) {
402
+ if (child.type === "atomic" || child.type === "final" || child.type === "history") {
403
+ return child.key;
404
+ }
405
+ return { [child.key]: childValue };
406
+ }
407
+ function getDefaultStateValue(node) {
408
+ if (node.type === "parallel") {
409
+ const value = {};
410
+ for (const child of Object.values(node.states ?? {})) {
411
+ value[child.key] = getDefaultSelectionValue(child);
412
+ }
413
+ return value;
414
+ }
415
+ const initialChild = getNodeInitialChild(node);
416
+ if (!initialChild) return {};
417
+ return encodeChildValue(initialChild, getDefaultStateValue(initialChild));
418
+ }
419
+ function getDefaultSelectionValue(node) {
420
+ if (node.type === "atomic" || node.type === "final" || node.type === "history") {
421
+ return node.key;
422
+ }
423
+ return getDefaultStateValue(node);
424
+ }
425
+ function getExistingChildValue(value, childKey) {
426
+ if (!value || typeof value !== "object") return void 0;
427
+ return value[childKey];
428
+ }
429
+ function getPathToRoot(target, root) {
430
+ const path = [];
431
+ let current = target;
432
+ while (current) {
433
+ path.unshift(current);
434
+ if (current.id === root.id) return path;
435
+ current = current.parent;
436
+ }
437
+ throw new Error(`State node '${target.id}' is not part of machine '${root.id}'`);
438
+ }
439
+ function buildTargetStateValue(node, path, currentValue) {
440
+ const [, ...restPath] = path;
441
+ if (restPath.length === 0) {
442
+ if (node.type === "parallel") {
443
+ const next = {};
444
+ for (const child2 of Object.values(node.states ?? {})) {
445
+ next[child2.key] = getExistingChildValue(currentValue, child2.key) ?? getDefaultSelectionValue(child2);
446
+ }
447
+ return next;
448
+ }
449
+ if (node.type === "compound") {
450
+ return getDefaultStateValue(node);
451
+ }
452
+ return node.key;
453
+ }
454
+ const child = restPath[0];
455
+ if (node.type === "parallel") {
456
+ const next = {};
457
+ for (const sibling of Object.values(node.states ?? {})) {
458
+ if (sibling.key === child.key) {
459
+ next[sibling.key] = buildTargetStateValue(
460
+ sibling,
461
+ restPath,
462
+ getExistingChildValue(currentValue, sibling.key)
463
+ );
464
+ } else {
465
+ next[sibling.key] = getExistingChildValue(currentValue, sibling.key) ?? getDefaultSelectionValue(sibling);
466
+ }
467
+ }
468
+ return next;
469
+ }
470
+ const childValue = buildTargetStateValue(
471
+ child,
472
+ restPath,
473
+ getExistingChildValue(currentValue, child.key)
474
+ );
475
+ return encodeChildValue(child, childValue);
476
+ }
477
+ function setActiveState(actorRef, stateNodeId) {
478
+ const mutableActorRef = actorRef;
479
+ const machine = mutableActorRef.logic;
480
+ if (!machine?.getStateNodeById || !machine.resolveState || typeof mutableActorRef.update !== "function") {
481
+ throw new Error("Actor does not expose machine state mutation internals");
482
+ }
483
+ const currentSnapshot = actorRef.getSnapshot();
484
+ const targetNode = machine.getStateNodeById(stateNodeId);
485
+ const path = getPathToRoot(targetNode, machine.root);
486
+ const targetValue = buildTargetStateValue(machine.root, path, currentSnapshot?.value);
487
+ const nextSnapshot = machine.resolveState({
488
+ value: targetValue,
489
+ context: currentSnapshot?.context,
490
+ status: currentSnapshot?.status,
491
+ output: currentSnapshot?.output,
492
+ error: currentSnapshot?.error,
493
+ historyValue: currentSnapshot?.historyValue
494
+ });
495
+ mutableActorRef.update(nextSnapshot, {
496
+ type: "xstate.devtools.set-active-state",
497
+ stateNodeId
498
+ });
499
+ }
500
+ var SEQ_KEY = "__xstate_devtools_global_seq__";
501
+ function nextSeq() {
502
+ const g = globalThis;
503
+ const cur = g[SEQ_KEY] ?? 0;
504
+ const next = cur + 1;
505
+ g[SEQ_KEY] = next;
506
+ return next;
507
+ }
508
+ function createInspector(transport, source, options = {}) {
509
+ const actorRefs = /* @__PURE__ */ new Map();
510
+ const actorMachines = /* @__PURE__ */ new Map();
511
+ const prefix = `${source}:`;
512
+ const tag = (sessionId) => prefix + sessionId;
513
+ const tagOptional = (id) => id ? prefix + id : void 0;
514
+ const stripIfMine = (id) => id.startsWith(prefix) ? id.slice(prefix.length) : null;
515
+ function checkAndNotifyStop(actorRef) {
516
+ let snap;
517
+ try {
518
+ snap = actorRef.getSnapshot();
519
+ } catch {
520
+ return;
521
+ }
522
+ if (snap?.status !== "active") {
523
+ const message = {
524
+ type: "XSTATE_ACTOR_STOPPED",
525
+ sessionId: tag(actorRef.sessionId)
526
+ };
527
+ debugLog2(source, "actor stopped; notifying transport", summarizeMessage(message));
528
+ transport.send(message);
529
+ actorRefs.delete(actorRef.sessionId);
530
+ actorMachines.delete(actorRef.sessionId);
531
+ }
532
+ }
533
+ const unsubscribe = transport.subscribe((message) => {
534
+ debugLog2(source, "received message from transport", summarizeMessage(message));
535
+ if (message.type === "XSTATE_PANEL_CONNECTED") {
536
+ infoLog2(source, "panel connected; resyncing actors", { actorCount: actorRefs.size });
537
+ actorRefs.forEach((actorRef, sessionId) => {
538
+ const machine = actorMachines.get(sessionId) ?? null;
539
+ const resyncMessage = {
540
+ type: "XSTATE_ACTOR_REGISTERED",
541
+ sessionId: tag(sessionId),
542
+ parentSessionId: tagOptional(actorRef._parent?.sessionId),
543
+ displayName: getActorDisplayName(actorRef),
544
+ machine,
545
+ snapshot: safeSerializeSnapshot(actorRef),
546
+ globalSeq: nextSeq(),
547
+ timestamp: Date.now()
548
+ };
549
+ debugLog2(source, "resyncing actor", summarizeMessage(resyncMessage));
550
+ transport.send(resyncMessage);
551
+ });
552
+ return;
553
+ }
554
+ if (message.type === "XSTATE_DISPATCH") {
555
+ const local = stripIfMine(message.sessionId);
556
+ if (local === null) {
557
+ debugLog2(source, "ignoring dispatch for different source", summarizeMessage(message));
558
+ return;
559
+ }
560
+ const ref = actorRefs.get(local);
561
+ if (ref) {
562
+ try {
563
+ debugLog2(source, "dispatching event to actor", {
564
+ sessionId: local,
565
+ eventType: message.event && typeof message.event === "object" && "type" in message.event ? message.event.type : void 0
566
+ });
567
+ ref.send(message.event);
568
+ } catch (e) {
569
+ warnLog2(source, "dispatch error", { error: e, sessionId: local });
570
+ }
571
+ } else {
572
+ warnLog2(source, "received dispatch for unknown actor", {
573
+ sessionId: local,
574
+ knownActors: actorRefs.size
575
+ });
576
+ }
577
+ return;
578
+ }
579
+ if (message.type === "XSTATE_SET_ACTIVE_STATE") {
580
+ const local = stripIfMine(message.sessionId);
581
+ if (local === null) {
582
+ debugLog2(
583
+ source,
584
+ "ignoring state activation for different source",
585
+ summarizeMessage(message)
586
+ );
587
+ return;
588
+ }
589
+ const ref = actorRefs.get(local);
590
+ if (!ref) {
591
+ warnLog2(source, "received state activation for unknown actor", {
592
+ sessionId: local,
593
+ knownActors: actorRefs.size
594
+ });
595
+ return;
596
+ }
597
+ try {
598
+ debugLog2(source, "setting active state on actor", summarizeMessage(message));
599
+ setActiveState(ref, message.stateNodeId);
600
+ const snapshotMessage = {
601
+ type: "XSTATE_SNAPSHOT",
602
+ sessionId: tag(local),
603
+ snapshot: safeSerializeSnapshot(ref),
604
+ timestamp: Date.now(),
605
+ globalSeq: nextSeq()
606
+ };
607
+ debugLog2(
608
+ source,
609
+ "sending snapshot after state activation",
610
+ summarizeMessage(snapshotMessage)
611
+ );
612
+ transport.send(snapshotMessage);
613
+ } catch (error) {
614
+ warnLog2(source, "failed to set active state", {
615
+ error,
616
+ sessionId: local,
617
+ stateNodeId: message.stateNodeId
618
+ });
619
+ }
620
+ }
621
+ });
622
+ transport.send({ type: "XSTATE_ADAPTER_READY" });
623
+ infoLog2(source, "inspector created");
624
+ const inspect = (inspectionEvent) => {
625
+ debugLog2(source, "inspect callback invoked", summarizeInspectionEvent(inspectionEvent));
626
+ if (inspectionEvent.type === "@xstate.actor") {
627
+ const actorRef = inspectionEvent.actorRef;
628
+ const sessionId = actorRef.sessionId;
629
+ const actorLogic = actorRef.logic;
630
+ const machine = actorLogic?.root ? serializeMachine(
631
+ actorLogic,
632
+ actorLogic.config?.__xstateDevtoolsSource ?? getSourceLocation(source, options)
633
+ ) : null;
634
+ actorRefs.set(sessionId, actorRef);
635
+ actorMachines.set(sessionId, machine);
636
+ const notifyStop = () => {
637
+ if (actorRefs.has(sessionId)) {
638
+ actorRefs.delete(sessionId);
639
+ actorMachines.delete(sessionId);
640
+ const stopMsg = {
641
+ type: "XSTATE_ACTOR_STOPPED",
642
+ sessionId: tag(sessionId)
643
+ };
644
+ debugLog2(source, "actor stopped; notifying transport", summarizeMessage(stopMsg));
645
+ transport.send(stopMsg);
646
+ }
647
+ };
648
+ try {
649
+ actorRef.subscribe({ complete: notifyStop, error: notifyStop });
650
+ } catch {
651
+ }
652
+ const message = {
653
+ type: "XSTATE_ACTOR_REGISTERED",
654
+ sessionId: tag(sessionId),
655
+ parentSessionId: tagOptional(actorRef._parent?.sessionId),
656
+ displayName: getActorDisplayName(actorRef),
657
+ machine,
658
+ snapshot: safeSerializeSnapshot(actorRef),
659
+ globalSeq: nextSeq(),
660
+ timestamp: Date.now()
661
+ };
662
+ infoLog2(source, "registering actor with transport", {
663
+ message: summarizeMessage(message),
664
+ actorCount: actorRefs.size,
665
+ hasMachine: machine !== null
666
+ });
667
+ transport.send(message);
668
+ } else if (inspectionEvent.type === "@xstate.snapshot") {
669
+ const message = {
670
+ type: "XSTATE_SNAPSHOT",
671
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
672
+ snapshot: serializeSnapshot(inspectionEvent.snapshot),
673
+ timestamp: Date.now(),
674
+ globalSeq: nextSeq()
675
+ };
676
+ debugLog2(source, "sending snapshot to transport", summarizeMessage(message));
677
+ transport.send(message);
678
+ checkAndNotifyStop(inspectionEvent.actorRef);
679
+ } else if (inspectionEvent.type === "@xstate.event") {
680
+ const message = {
681
+ type: "XSTATE_EVENT",
682
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
683
+ // sanitize() returns unknown; the inspected event keeps its { type, ... }
684
+ // shape, so it is a SerializedEvent after deep-sanitizing.
685
+ event: sanitize(inspectionEvent.event),
686
+ snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
687
+ timestamp: Date.now(),
688
+ globalSeq: nextSeq()
689
+ };
690
+ debugLog2(source, "sending event to transport", summarizeMessage(message));
691
+ transport.send(message);
692
+ checkAndNotifyStop(inspectionEvent.actorRef);
693
+ } else {
694
+ debugLog2(
695
+ source,
696
+ "ignoring unsupported inspection event",
697
+ summarizeInspectionEvent(inspectionEvent)
698
+ );
699
+ }
700
+ };
701
+ function dispose() {
702
+ infoLog2(source, "disposing inspector", { actorCount: actorRefs.size });
703
+ unsubscribe();
704
+ actorRefs.clear();
705
+ actorMachines.clear();
706
+ }
707
+ return { inspect, dispose };
708
+ }
709
+
710
+ // src/server.ts
711
+ function getAvailablePort(start) {
712
+ return new Promise((resolve, reject) => {
713
+ const probe = (0, import_node_net.createServer)();
714
+ probe.unref();
715
+ probe.on("error", (err) => {
716
+ if (err.code === "EADDRINUSE") {
717
+ resolve(getAvailablePort(start + 1));
718
+ } else {
719
+ reject(err);
720
+ }
721
+ });
722
+ probe.listen(start, "127.0.0.1", () => {
723
+ const port = probe.address().port;
724
+ probe.close(() => resolve(port));
725
+ });
726
+ });
727
+ }
728
+ var OPEN_STATE = 1;
729
+ function summarizeMessage2(message) {
730
+ const summary = { type: message.type };
731
+ if ("sessionId" in message) summary.sessionId = message.sessionId;
732
+ if ("parentSessionId" in message && message.parentSessionId) {
733
+ summary.parentSessionId = message.parentSessionId;
734
+ }
735
+ if ("globalSeq" in message) summary.globalSeq = message.globalSeq;
736
+ if ("timestamp" in message) summary.timestamp = message.timestamp;
737
+ if ("event" in message && message.event && typeof message.event === "object" && "type" in message.event) {
738
+ summary.eventType = message.event.type;
739
+ }
740
+ return summary;
741
+ }
742
+ function debugLog3(message, details) {
743
+ debugLog("server", message, details);
744
+ }
745
+ function infoLog3(message, details) {
746
+ infoLog("server", message, details);
747
+ }
748
+ function warnLog3(message, details) {
749
+ warnLog("server", message, details);
750
+ }
751
+ function stringifyOutgoingMessage(message) {
752
+ const payload = { ...message, __xstateDevtools: true };
753
+ try {
754
+ return JSON.stringify(payload);
755
+ } catch (error) {
756
+ warnLog3("failed to stringify adapter message; retrying with sanitized payload", {
757
+ error,
758
+ message: summarizeMessage2(message)
759
+ });
760
+ }
761
+ try {
762
+ return JSON.stringify(sanitize(payload));
763
+ } catch (error) {
764
+ warnLog3("dropping adapter message that could not be stringified", {
765
+ error,
766
+ message: summarizeMessage2(message)
767
+ });
768
+ return null;
769
+ }
770
+ }
771
+ function createServerAdapter(options = {}) {
772
+ const port = options.port ?? (Number(process.env.XSTATE_DEVTOOLS_PORT) || 9301);
773
+ const host = options.host ?? "127.0.0.1";
774
+ const bufferSize = options.bufferSize ?? 200;
775
+ infoLog3("createServerAdapter called", { host, port, bufferSize });
776
+ const key = `__xstate_devtools_server_${port}__`;
777
+ const cache = globalThis[key];
778
+ let server;
779
+ if (cache) {
780
+ server = cache;
781
+ infoLog3("reusing cached WebSocket server", {
782
+ host,
783
+ port,
784
+ clientCount: server.clients.size,
785
+ bufferedMessages: server.buffer.length
786
+ });
787
+ if (bufferSize > server.bufferSize) server.bufferSize = bufferSize;
788
+ } else {
789
+ const clients = /* @__PURE__ */ new Set();
790
+ const dispatchHandlers = /* @__PURE__ */ new Set();
791
+ const buffer = [];
792
+ let wss = null;
793
+ let closed = false;
794
+ let portResolve;
795
+ let portReject;
796
+ const portPromise = new Promise((res, rej) => {
797
+ portResolve = res;
798
+ portReject = rej;
799
+ });
800
+ server = {
801
+ clients,
802
+ dispatchHandlers,
803
+ buffer,
804
+ bufferSize,
805
+ activated: false,
806
+ port: portPromise,
807
+ close: () => {
808
+ closed = true;
809
+ infoLog3("closing WebSocket server", { host, port, clientCount: clients.size });
810
+ try {
811
+ wss?.close();
812
+ } catch {
813
+ }
814
+ clients.clear();
815
+ dispatchHandlers.clear();
816
+ buffer.length = 0;
817
+ delete globalThis[key];
818
+ }
819
+ };
820
+ void (async () => {
821
+ try {
822
+ const mod = await import("ws");
823
+ const WSServer = mod.WebSocketServer ?? mod.Server;
824
+ if (closed) return;
825
+ const actualPort = await getAvailablePort(port);
826
+ if (closed) return;
827
+ wss = new WSServer({ port: actualPort, host });
828
+ process.env.XSTATE_DEVTOOLS_PORT = String(actualPort);
829
+ portResolve(actualPort);
830
+ infoLog3("WebSocket server listening", { host, port: actualPort });
831
+ wss.on("connection", (ws) => {
832
+ infoLog3("panel connected to WebSocket server", {
833
+ host,
834
+ port,
835
+ activated: server.activated,
836
+ bufferedMessages: server.buffer.length
837
+ });
838
+ if (!server.activated) {
839
+ server.activated = true;
840
+ infoLog3("flushing bootstrap buffer to first panel", {
841
+ host,
842
+ port,
843
+ bufferedMessages: server.buffer.length
844
+ });
845
+ for (const payload of server.buffer) {
846
+ try {
847
+ ws.send(payload);
848
+ } catch {
849
+ }
850
+ }
851
+ server.buffer.length = 0;
852
+ }
853
+ server.clients.add(ws);
854
+ ws.on("message", (raw) => {
855
+ try {
856
+ const text = typeof raw === "string" ? raw : raw.toString("utf8");
857
+ const msg = JSON.parse(text);
858
+ debugLog3("received dispatch from panel", summarizeMessage2(msg));
859
+ for (const cb of server.dispatchHandlers) cb(msg);
860
+ } catch (error) {
861
+ warnLog3("failed to parse panel message", { error });
862
+ }
863
+ });
864
+ ws.on("close", () => {
865
+ server.clients.delete(ws);
866
+ infoLog3("panel disconnected from WebSocket server", {
867
+ host,
868
+ port,
869
+ clientCount: server.clients.size
870
+ });
871
+ });
872
+ ws.on("error", (error) => {
873
+ server.clients.delete(ws);
874
+ warnLog3("WebSocket client error", { error });
875
+ });
876
+ });
877
+ wss.on("error", (err) => {
878
+ warnLog3("WS server error", { host, port, message: err.message });
879
+ });
880
+ } catch (e) {
881
+ portReject(e);
882
+ warnLog3("could not start server adapter \u2014 install `ws` to enable", {
883
+ host,
884
+ port,
885
+ message: e.message
886
+ });
887
+ }
888
+ })();
889
+ globalThis[key] = server;
890
+ }
891
+ const transport = {
892
+ send(message) {
893
+ const payload = stringifyOutgoingMessage(message);
894
+ if (payload === null) return;
895
+ if (!server.activated) {
896
+ if (server.buffer.length >= server.bufferSize) server.buffer.shift();
897
+ server.buffer.push(payload);
898
+ debugLog3("buffered outgoing adapter message; no panel connected yet", {
899
+ bufferedMessages: server.buffer.length,
900
+ message: summarizeMessage2(message)
901
+ });
902
+ return;
903
+ }
904
+ let sentCount = 0;
905
+ for (const ws of server.clients) {
906
+ if (ws.readyState === OPEN_STATE) {
907
+ try {
908
+ ws.send(payload);
909
+ sentCount += 1;
910
+ } catch {
911
+ }
912
+ }
913
+ }
914
+ debugLog3("sent adapter message to connected panels", {
915
+ sentCount,
916
+ clientCount: server.clients.size,
917
+ message: summarizeMessage2(message)
918
+ });
919
+ },
920
+ subscribe(handler) {
921
+ server.dispatchHandlers.add(handler);
922
+ debugLog3("registered dispatch handler", { handlerCount: server.dispatchHandlers.size });
923
+ return () => {
924
+ server.dispatchHandlers.delete(handler);
925
+ debugLog3("removed dispatch handler", { handlerCount: server.dispatchHandlers.size });
926
+ };
927
+ }
928
+ };
929
+ const inspector = createInspector(transport, "srv");
930
+ return { ...inspector, close: server.close, port: server.port };
931
+ }
932
+ // Annotate the CommonJS export names for ESM import in node:
933
+ 0 && (module.exports = {
934
+ createServerAdapter
935
+ });
936
+ //# sourceMappingURL=server.cjs.map