@xstate-devtools/adapter 0.1.4 → 0.1.6
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 +22 -1
- package/dist/chunk-M4XORXVP.js +307 -0
- package/dist/chunk-M4XORXVP.js.map +1 -0
- package/dist/chunk-W5QGSHOC.js +34 -0
- package/dist/chunk-W5QGSHOC.js.map +1 -0
- package/dist/index.cjs +177 -571
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -10
- package/dist/index.d.ts +2 -10
- package/dist/index.js +2 -2
- package/dist/react.cjs +232 -590
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +31 -5
- package/dist/react.d.ts +31 -5
- package/dist/react.js +61 -13
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +245 -681
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +10 -7
- package/dist/server.d.ts +10 -7
- package/dist/server.js +75 -146
- package/dist/server.js.map +1 -1
- package/package.json +13 -4
- package/dist/chunk-3GM2SFT4.js +0 -680
- package/dist/chunk-3GM2SFT4.js.map +0 -1
- package/dist/chunk-MHHRMHW5.js +0 -62
- package/dist/chunk-MHHRMHW5.js.map +0 -1
package/dist/server.cjs
CHANGED
|
@@ -33,115 +33,20 @@ __export(server_exports, {
|
|
|
33
33
|
createServerAdapter: () => createServerAdapter
|
|
34
34
|
});
|
|
35
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
36
|
|
|
133
37
|
// 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
38
|
function serializeGuard(guard) {
|
|
141
39
|
if (!guard) return void 0;
|
|
142
40
|
if (typeof guard === "string") return guard;
|
|
143
|
-
if (typeof guard === "function")
|
|
144
|
-
|
|
41
|
+
if (typeof guard === "function") {
|
|
42
|
+
const fn = guard;
|
|
43
|
+
if (Array.isArray(fn.guards) && typeof fn.check === "function") {
|
|
44
|
+
const parts = fn.guards.map((g) => serializeGuard(g) ?? "(inline)");
|
|
45
|
+
return `${fn.name || "(combinator)"}(${parts.join(", ")})`;
|
|
46
|
+
}
|
|
47
|
+
return fn.name || "(inline)";
|
|
48
|
+
}
|
|
49
|
+
if (typeof guard === "object") {
|
|
145
50
|
const g = guard;
|
|
146
51
|
return g.type ?? g.name ?? "(inline)";
|
|
147
52
|
}
|
|
@@ -157,214 +62,133 @@ function serializeAction(action) {
|
|
|
157
62
|
return String(action);
|
|
158
63
|
}
|
|
159
64
|
function serializeTransitionList(transitions) {
|
|
160
|
-
return transitions.
|
|
65
|
+
return transitions.map((t) => ({
|
|
161
66
|
eventType: t.eventType ?? "",
|
|
162
67
|
targets: (t.target ?? []).map((n) => n?.id ?? String(n)).filter(Boolean),
|
|
163
68
|
guard: serializeGuard(t.guard),
|
|
164
|
-
actions: (t.actions ?? []).
|
|
69
|
+
actions: (t.actions ?? []).map(serializeAction).filter(Boolean)
|
|
165
70
|
}));
|
|
166
71
|
}
|
|
167
72
|
function serializeInvokes(node) {
|
|
168
|
-
return node.invoke.
|
|
73
|
+
return node.invoke.map((inv) => ({
|
|
169
74
|
id: inv.id ?? "(unknown)",
|
|
170
75
|
src: typeof inv.src === "string" ? inv.src : inv.src?.id ?? inv.src?.name ?? "(inline)"
|
|
171
76
|
}));
|
|
172
77
|
}
|
|
173
|
-
function serializeNode(node
|
|
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;
|
|
78
|
+
function serializeNode(node) {
|
|
215
79
|
const allTransitions = [];
|
|
216
80
|
if (node.transitions instanceof Map) {
|
|
217
81
|
for (const [, tList] of node.transitions) {
|
|
218
|
-
if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) break;
|
|
219
82
|
allTransitions.push(...serializeTransitionList(tList));
|
|
220
|
-
if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) {
|
|
221
|
-
allTransitions.length = MAX_TRANSITIONS_PER_NODE;
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
83
|
}
|
|
225
84
|
}
|
|
226
85
|
const always = Array.isArray(node.always) ? serializeTransitionList(node.always) : [];
|
|
227
|
-
const childEntries = Object.entries(node.states ?? {}).slice(0, MAX_CHILD_STATES);
|
|
228
86
|
return {
|
|
229
87
|
id: node.id,
|
|
230
88
|
key: node.key,
|
|
231
89
|
type: node.type,
|
|
232
90
|
initial: node.initial?.target?.[0]?.key,
|
|
233
|
-
states: Object.fromEntries(
|
|
91
|
+
states: Object.fromEntries(
|
|
92
|
+
Object.entries(node.states ?? {}).map(([k, v]) => [k, serializeNode(v)])
|
|
93
|
+
),
|
|
234
94
|
on: allTransitions,
|
|
235
95
|
always,
|
|
236
|
-
entry: (node.entry ?? []).
|
|
237
|
-
exit: (node.exit ?? []).
|
|
238
|
-
invoke: serializeInvokes(node)
|
|
239
|
-
sourceLocation: node.config?.__xstateDevtoolsSource ?? void 0,
|
|
240
|
-
description: node.config?.description ?? void 0
|
|
96
|
+
entry: (node.entry ?? []).map(serializeAction).filter(Boolean),
|
|
97
|
+
exit: (node.exit ?? []).map(serializeAction).filter(Boolean),
|
|
98
|
+
invoke: serializeInvokes(node)
|
|
241
99
|
};
|
|
242
100
|
}
|
|
243
101
|
function serializeMachine(machine, sourceLocation) {
|
|
244
102
|
return {
|
|
245
103
|
id: machine.id,
|
|
246
|
-
root: serializeNode(machine.root
|
|
104
|
+
root: serializeNode(machine.root),
|
|
247
105
|
sourceLocation
|
|
248
106
|
};
|
|
249
107
|
}
|
|
250
108
|
|
|
251
|
-
// src/
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
109
|
+
// src/sanitize.ts
|
|
110
|
+
var MAX_DEPTH = 10;
|
|
111
|
+
var MAX_STRING_LENGTH = 500;
|
|
112
|
+
var MAX_ARRAY_LENGTH = 100;
|
|
113
|
+
var MAX_NODES = 1e4;
|
|
114
|
+
function sanitizeInner(value, ctx) {
|
|
115
|
+
if (ctx.depth > MAX_DEPTH) return "[MaxDepth]";
|
|
116
|
+
if (++ctx.budget.n > MAX_NODES) return "[Truncated]";
|
|
117
|
+
if (value === null || value === void 0) return value;
|
|
118
|
+
if (typeof value === "boolean" || typeof value === "number") return value;
|
|
119
|
+
if (typeof value === "string") {
|
|
120
|
+
return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + "\u2026" : value;
|
|
258
121
|
}
|
|
259
|
-
if ("
|
|
260
|
-
if ("
|
|
261
|
-
if (
|
|
262
|
-
|
|
122
|
+
if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
|
|
123
|
+
if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
|
|
124
|
+
if (typeof value === "bigint") return `[BigInt: ${value}]`;
|
|
125
|
+
if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
|
|
126
|
+
if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
|
|
127
|
+
if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
|
|
128
|
+
if (value instanceof Promise) return "[Promise]";
|
|
129
|
+
if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
|
|
130
|
+
if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
|
|
131
|
+
if (typeof Node !== "undefined" && value instanceof Node) {
|
|
132
|
+
return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
|
|
263
133
|
}
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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;
|
|
134
|
+
if (ctx.seen.has(value)) return "[Circular]";
|
|
135
|
+
ctx.seen.add(value);
|
|
136
|
+
const child = { ...ctx, depth: ctx.depth + 1 };
|
|
137
|
+
if (value instanceof Map) {
|
|
138
|
+
const entries = [];
|
|
139
|
+
for (const [k, v] of value) {
|
|
140
|
+
if (entries.length >= MAX_ARRAY_LENGTH) break;
|
|
141
|
+
entries.push([sanitizeInner(k, child), sanitizeInner(v, child)]);
|
|
311
142
|
}
|
|
143
|
+
return { __type: "Map", entries };
|
|
312
144
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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;
|
|
145
|
+
if (value instanceof Set) {
|
|
146
|
+
const values = [];
|
|
147
|
+
for (const v of value) {
|
|
148
|
+
if (values.length >= MAX_ARRAY_LENGTH) break;
|
|
149
|
+
values.push(sanitizeInner(v, child));
|
|
150
|
+
}
|
|
151
|
+
return { __type: "Set", values };
|
|
331
152
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (wrapped) {
|
|
348
|
-
return normalizedFrame.replace(wrapped[1], location);
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
const sliced = value.slice(0, MAX_ARRAY_LENGTH);
|
|
155
|
+
const result = sliced.map((v) => sanitizeInner(v, child));
|
|
156
|
+
if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
if (typeof value === "object") {
|
|
160
|
+
const result = {};
|
|
161
|
+
let count = 0;
|
|
162
|
+
for (const [k, v] of Object.entries(value)) {
|
|
163
|
+
if (count++ >= MAX_ARRAY_LENGTH) {
|
|
164
|
+
result["\u2026"] = "[truncated]";
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
result[k] = sanitizeInner(v, child);
|
|
349
168
|
}
|
|
350
|
-
return
|
|
169
|
+
return result;
|
|
351
170
|
}
|
|
352
|
-
return
|
|
171
|
+
return String(value);
|
|
172
|
+
}
|
|
173
|
+
function sanitize(value) {
|
|
174
|
+
return sanitizeInner(value, { depth: 0, budget: { n: 0 }, seen: /* @__PURE__ */ new WeakSet() });
|
|
353
175
|
}
|
|
354
|
-
|
|
176
|
+
|
|
177
|
+
// src/core.ts
|
|
178
|
+
function getSourceLocation() {
|
|
355
179
|
try {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return
|
|
180
|
+
const lines = new Error().stack?.split("\n") ?? [];
|
|
181
|
+
const callerLine = lines.find(
|
|
182
|
+
(l, i) => i > 3 && !l.includes("xstate") && !l.includes("adapter")
|
|
183
|
+
);
|
|
184
|
+
return callerLine?.trim().replace(/^at\s+/, "");
|
|
361
185
|
} catch {
|
|
362
186
|
return void 0;
|
|
363
187
|
}
|
|
364
188
|
}
|
|
365
189
|
function serializeSnapshot(snapshot) {
|
|
366
190
|
return {
|
|
367
|
-
value: snapshot?.value ?? null,
|
|
191
|
+
value: sanitize(snapshot?.value ?? null),
|
|
368
192
|
context: sanitize(snapshot?.context),
|
|
369
193
|
status: snapshot?.status ?? "active",
|
|
370
194
|
error: snapshot?.error ? sanitize(snapshot.error) : void 0
|
|
@@ -377,125 +201,18 @@ function safeSerializeSnapshot(actorRef) {
|
|
|
377
201
|
return { value: null, context: void 0, status: "active" };
|
|
378
202
|
}
|
|
379
203
|
}
|
|
380
|
-
function
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
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;
|
|
204
|
+
function safePersistedSnapshot(actorRef) {
|
|
205
|
+
const getPersisted = actorRef.getPersistedSnapshot;
|
|
206
|
+
if (typeof getPersisted !== "function") {
|
|
207
|
+
return { error: "Actor does not support getPersistedSnapshot." };
|
|
453
208
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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");
|
|
209
|
+
try {
|
|
210
|
+
const raw = getPersisted.call(actorRef);
|
|
211
|
+
if (raw === void 0) return { error: "No persisted snapshot available." };
|
|
212
|
+
return { persisted: JSON.parse(JSON.stringify(raw)) };
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return { error: `Snapshot is not JSON-serializable: ${e.message}` };
|
|
482
215
|
}
|
|
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
216
|
}
|
|
500
217
|
var SEQ_KEY = "__xstate_devtools_global_seq__";
|
|
501
218
|
function nextSeq() {
|
|
@@ -505,10 +222,10 @@ function nextSeq() {
|
|
|
505
222
|
g[SEQ_KEY] = next;
|
|
506
223
|
return next;
|
|
507
224
|
}
|
|
508
|
-
function createInspector(transport, source
|
|
225
|
+
function createInspector(transport, source) {
|
|
509
226
|
const actorRefs = /* @__PURE__ */ new Map();
|
|
510
|
-
const
|
|
511
|
-
const prefix =
|
|
227
|
+
const restoreHandlers = /* @__PURE__ */ new Map();
|
|
228
|
+
const prefix = source + ":";
|
|
512
229
|
const tag = (sessionId) => prefix + sessionId;
|
|
513
230
|
const tagOptional = (id) => id ? prefix + id : void 0;
|
|
514
231
|
const stripIfMine = (id) => id.startsWith(prefix) ? id.slice(prefix.length) : null;
|
|
@@ -520,300 +237,166 @@ function createInspector(transport, source, options = {}) {
|
|
|
520
237
|
return;
|
|
521
238
|
}
|
|
522
239
|
if (snap?.status !== "active") {
|
|
523
|
-
|
|
524
|
-
type: "XSTATE_ACTOR_STOPPED",
|
|
525
|
-
sessionId: tag(actorRef.sessionId)
|
|
526
|
-
};
|
|
527
|
-
debugLog2(source, "actor stopped; notifying transport", summarizeMessage(message));
|
|
528
|
-
transport.send(message);
|
|
240
|
+
transport.send({ type: "XSTATE_ACTOR_STOPPED", sessionId: tag(actorRef.sessionId) });
|
|
529
241
|
actorRefs.delete(actorRef.sessionId);
|
|
530
|
-
actorMachines.delete(actorRef.sessionId);
|
|
531
242
|
}
|
|
532
243
|
}
|
|
533
244
|
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
245
|
if (message.type === "XSTATE_DISPATCH") {
|
|
555
246
|
const local = stripIfMine(message.sessionId);
|
|
556
|
-
if (local === null)
|
|
557
|
-
debugLog2(source, "ignoring dispatch for different source", summarizeMessage(message));
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
247
|
+
if (local === null) return;
|
|
560
248
|
const ref = actorRefs.get(local);
|
|
561
249
|
if (ref) {
|
|
562
250
|
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
251
|
ref.send(message.event);
|
|
568
252
|
} catch (e) {
|
|
569
|
-
|
|
253
|
+
console.warn("[xstate-devtools] dispatch error:", e);
|
|
570
254
|
}
|
|
571
|
-
} else {
|
|
572
|
-
warnLog2(source, "received dispatch for unknown actor", {
|
|
573
|
-
sessionId: local,
|
|
574
|
-
knownActors: actorRefs.size
|
|
575
|
-
});
|
|
576
255
|
}
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
if (message.type === "XSTATE_SET_ACTIVE_STATE") {
|
|
256
|
+
} else if (message.type === "XSTATE_REQUEST_PERSISTED") {
|
|
580
257
|
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
|
-
}
|
|
258
|
+
if (local === null) return;
|
|
589
259
|
const ref = actorRefs.get(local);
|
|
590
|
-
if (!ref)
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
260
|
+
if (!ref) return;
|
|
261
|
+
const { persisted, error } = safePersistedSnapshot(ref);
|
|
262
|
+
transport.send({
|
|
263
|
+
type: "XSTATE_PERSISTED_SNAPSHOT",
|
|
264
|
+
sessionId: tag(local),
|
|
265
|
+
persisted,
|
|
266
|
+
error,
|
|
267
|
+
timestamp: Date.now()
|
|
268
|
+
});
|
|
269
|
+
} else if (message.type === "XSTATE_RESTORE") {
|
|
270
|
+
const local = stripIfMine(message.sessionId);
|
|
271
|
+
if (local === null) return;
|
|
272
|
+
const handler = restoreHandlers.get(local);
|
|
273
|
+
if (handler) {
|
|
274
|
+
try {
|
|
275
|
+
handler(message.persisted);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.warn("[xstate-devtools] restore error:", e);
|
|
278
|
+
}
|
|
596
279
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
const inspect = (inspectionEvent) => {
|
|
283
|
+
try {
|
|
284
|
+
if (inspectionEvent.type === "@xstate.actor") {
|
|
285
|
+
const actorRef = inspectionEvent.actorRef;
|
|
286
|
+
const sessionId = actorRef.sessionId;
|
|
287
|
+
actorRefs.set(sessionId, actorRef);
|
|
288
|
+
const logic = actorRef.logic;
|
|
289
|
+
const machine = logic?.root ? serializeMachine(logic, getSourceLocation()) : null;
|
|
290
|
+
transport.send({
|
|
291
|
+
type: "XSTATE_ACTOR_REGISTERED",
|
|
292
|
+
sessionId: tag(sessionId),
|
|
293
|
+
parentSessionId: tagOptional(actorRef._parent?.sessionId),
|
|
294
|
+
// actorRef.id is the invoke `id` for invoked actors — lets the debugger
|
|
295
|
+
// nest non-machine actors (promise/callback) under their state.
|
|
296
|
+
actorId: actorRef.id,
|
|
297
|
+
machine,
|
|
298
|
+
snapshot: safeSerializeSnapshot(actorRef),
|
|
299
|
+
globalSeq: nextSeq(),
|
|
300
|
+
timestamp: Date.now()
|
|
301
|
+
});
|
|
302
|
+
} else if (inspectionEvent.type === "@xstate.snapshot") {
|
|
303
|
+
transport.send({
|
|
601
304
|
type: "XSTATE_SNAPSHOT",
|
|
602
|
-
sessionId: tag(
|
|
603
|
-
snapshot:
|
|
305
|
+
sessionId: tag(inspectionEvent.actorRef.sessionId),
|
|
306
|
+
snapshot: serializeSnapshot(inspectionEvent.snapshot),
|
|
604
307
|
timestamp: Date.now(),
|
|
605
308
|
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
309
|
});
|
|
310
|
+
checkAndNotifyStop(inspectionEvent.actorRef);
|
|
311
|
+
} else if (inspectionEvent.type === "@xstate.event") {
|
|
312
|
+
transport.send({
|
|
313
|
+
type: "XSTATE_EVENT",
|
|
314
|
+
sessionId: tag(inspectionEvent.actorRef.sessionId),
|
|
315
|
+
event: inspectionEvent.event,
|
|
316
|
+
snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
|
|
317
|
+
timestamp: Date.now(),
|
|
318
|
+
globalSeq: nextSeq()
|
|
319
|
+
});
|
|
320
|
+
checkAndNotifyStop(inspectionEvent.actorRef);
|
|
619
321
|
}
|
|
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
|
-
);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.warn("[xstate-devtools] inspection failed, dropping event:", e.message);
|
|
699
324
|
}
|
|
700
325
|
};
|
|
326
|
+
function registerRestore(sessionId, handler) {
|
|
327
|
+
restoreHandlers.set(sessionId, handler);
|
|
328
|
+
return () => {
|
|
329
|
+
if (restoreHandlers.get(sessionId) === handler) restoreHandlers.delete(sessionId);
|
|
330
|
+
};
|
|
331
|
+
}
|
|
701
332
|
function dispose() {
|
|
702
|
-
infoLog2(source, "disposing inspector", { actorCount: actorRefs.size });
|
|
703
333
|
unsubscribe();
|
|
704
334
|
actorRefs.clear();
|
|
705
|
-
|
|
335
|
+
restoreHandlers.clear();
|
|
706
336
|
}
|
|
707
|
-
return { inspect, dispose };
|
|
337
|
+
return { inspect, dispose, registerRestore };
|
|
708
338
|
}
|
|
709
339
|
|
|
710
340
|
// 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
341
|
var OPEN_STATE = 1;
|
|
729
|
-
function
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
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;
|
|
342
|
+
function trackLive(server, message) {
|
|
343
|
+
switch (message.type) {
|
|
344
|
+
case "XSTATE_ACTOR_REGISTERED":
|
|
345
|
+
server.liveActors.set(message.sessionId, { reg: message, snapshot: message.snapshot });
|
|
346
|
+
break;
|
|
347
|
+
case "XSTATE_SNAPSHOT": {
|
|
348
|
+
const live = server.liveActors.get(message.sessionId);
|
|
349
|
+
if (live) {
|
|
350
|
+
live.snapshot = message.snapshot;
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "XSTATE_EVENT": {
|
|
355
|
+
const live = server.liveActors.get(message.sessionId);
|
|
356
|
+
if (live) {
|
|
357
|
+
live.snapshot = message.snapshotAfter;
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case "XSTATE_ACTOR_STOPPED":
|
|
362
|
+
server.liveActors.delete(message.sessionId);
|
|
363
|
+
break;
|
|
769
364
|
}
|
|
770
365
|
}
|
|
771
366
|
function createServerAdapter(options = {}) {
|
|
772
367
|
const port = options.port ?? (Number(process.env.XSTATE_DEVTOOLS_PORT) || 9301);
|
|
773
368
|
const host = options.host ?? "127.0.0.1";
|
|
774
369
|
const bufferSize = options.bufferSize ?? 200;
|
|
775
|
-
infoLog3("createServerAdapter called", { host, port, bufferSize });
|
|
776
370
|
const key = `__xstate_devtools_server_${port}__`;
|
|
777
371
|
const cache = globalThis[key];
|
|
778
372
|
let server;
|
|
779
373
|
if (cache) {
|
|
780
374
|
server = cache;
|
|
781
|
-
infoLog3("reusing cached WebSocket server", {
|
|
782
|
-
host,
|
|
783
|
-
port,
|
|
784
|
-
clientCount: server.clients.size,
|
|
785
|
-
bufferedMessages: server.buffer.length
|
|
786
|
-
});
|
|
787
375
|
if (bufferSize > server.bufferSize) server.bufferSize = bufferSize;
|
|
788
376
|
} else {
|
|
789
377
|
const clients = /* @__PURE__ */ new Set();
|
|
790
378
|
const dispatchHandlers = /* @__PURE__ */ new Set();
|
|
791
|
-
const
|
|
379
|
+
const liveActors = /* @__PURE__ */ new Map();
|
|
380
|
+
const recentEvents = [];
|
|
792
381
|
let wss = null;
|
|
793
382
|
let closed = false;
|
|
794
|
-
let portResolve;
|
|
795
|
-
let portReject;
|
|
796
|
-
const portPromise = new Promise((res, rej) => {
|
|
797
|
-
portResolve = res;
|
|
798
|
-
portReject = rej;
|
|
799
|
-
});
|
|
800
383
|
server = {
|
|
801
384
|
clients,
|
|
802
385
|
dispatchHandlers,
|
|
803
|
-
|
|
386
|
+
liveActors,
|
|
387
|
+
recentEvents,
|
|
804
388
|
bufferSize,
|
|
805
389
|
activated: false,
|
|
806
|
-
port: portPromise,
|
|
807
390
|
close: () => {
|
|
808
391
|
closed = true;
|
|
809
|
-
infoLog3("closing WebSocket server", { host, port, clientCount: clients.size });
|
|
810
392
|
try {
|
|
811
393
|
wss?.close();
|
|
812
394
|
} catch {
|
|
813
395
|
}
|
|
814
396
|
clients.clear();
|
|
815
397
|
dispatchHandlers.clear();
|
|
816
|
-
|
|
398
|
+
liveActors.clear();
|
|
399
|
+
recentEvents.length = 0;
|
|
817
400
|
delete globalThis[key];
|
|
818
401
|
}
|
|
819
402
|
};
|
|
@@ -822,112 +405,93 @@ function createServerAdapter(options = {}) {
|
|
|
822
405
|
const mod = await import("ws");
|
|
823
406
|
const WSServer = mod.WebSocketServer ?? mod.Server;
|
|
824
407
|
if (closed) return;
|
|
825
|
-
|
|
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 });
|
|
408
|
+
wss = new WSServer({ port, host });
|
|
831
409
|
wss.on("connection", (ws) => {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
410
|
+
for (const { reg, snapshot } of server.liveActors.values()) {
|
|
411
|
+
try {
|
|
412
|
+
ws.send(JSON.stringify({ ...reg, __xstateDevtools: true }));
|
|
413
|
+
} catch {
|
|
414
|
+
}
|
|
415
|
+
if (snapshot !== reg.snapshot) {
|
|
416
|
+
try {
|
|
417
|
+
ws.send(JSON.stringify({
|
|
418
|
+
type: "XSTATE_SNAPSHOT",
|
|
419
|
+
sessionId: reg.sessionId,
|
|
420
|
+
snapshot,
|
|
421
|
+
timestamp: reg.timestamp,
|
|
422
|
+
globalSeq: reg.globalSeq,
|
|
423
|
+
__xstateDevtools: true
|
|
424
|
+
}));
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
ws.send(JSON.stringify({
|
|
431
|
+
type: "XSTATE_REPLAY_DONE",
|
|
432
|
+
sessionIds: [...server.liveActors.keys()],
|
|
433
|
+
__xstateDevtools: true
|
|
434
|
+
}));
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
838
437
|
if (!server.activated) {
|
|
839
438
|
server.activated = true;
|
|
840
|
-
|
|
841
|
-
host,
|
|
842
|
-
port,
|
|
843
|
-
bufferedMessages: server.buffer.length
|
|
844
|
-
});
|
|
845
|
-
for (const payload of server.buffer) {
|
|
439
|
+
for (const payload of server.recentEvents) {
|
|
846
440
|
try {
|
|
847
441
|
ws.send(payload);
|
|
848
442
|
} catch {
|
|
849
443
|
}
|
|
850
444
|
}
|
|
851
|
-
server.
|
|
445
|
+
server.recentEvents.length = 0;
|
|
852
446
|
}
|
|
853
447
|
server.clients.add(ws);
|
|
854
448
|
ws.on("message", (raw) => {
|
|
855
449
|
try {
|
|
856
450
|
const text = typeof raw === "string" ? raw : raw.toString("utf8");
|
|
857
451
|
const msg = JSON.parse(text);
|
|
858
|
-
debugLog3("received dispatch from panel", summarizeMessage2(msg));
|
|
859
452
|
for (const cb of server.dispatchHandlers) cb(msg);
|
|
860
|
-
} catch
|
|
861
|
-
warnLog3("failed to parse panel message", { error });
|
|
453
|
+
} catch {
|
|
862
454
|
}
|
|
863
455
|
});
|
|
864
|
-
ws.on("close", () =>
|
|
865
|
-
|
|
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
|
-
});
|
|
456
|
+
ws.on("close", () => server.clients.delete(ws));
|
|
457
|
+
ws.on("error", () => server.clients.delete(ws));
|
|
876
458
|
});
|
|
877
459
|
wss.on("error", (err) => {
|
|
878
|
-
|
|
460
|
+
console.warn("[xstate-devtools] WS server error:", err.message);
|
|
879
461
|
});
|
|
880
462
|
} catch (e) {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
message: e.message
|
|
886
|
-
});
|
|
463
|
+
console.warn(
|
|
464
|
+
"[xstate-devtools] could not start server adapter \u2014 install `ws` to enable.",
|
|
465
|
+
e.message
|
|
466
|
+
);
|
|
887
467
|
}
|
|
888
468
|
})();
|
|
889
469
|
globalThis[key] = server;
|
|
890
470
|
}
|
|
891
471
|
const transport = {
|
|
892
472
|
send(message) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if (!server.activated) {
|
|
896
|
-
|
|
897
|
-
server.
|
|
898
|
-
debugLog3("buffered outgoing adapter message; no panel connected yet", {
|
|
899
|
-
bufferedMessages: server.buffer.length,
|
|
900
|
-
message: summarizeMessage2(message)
|
|
901
|
-
});
|
|
902
|
-
return;
|
|
473
|
+
trackLive(server, message);
|
|
474
|
+
const payload = JSON.stringify({ ...message, __xstateDevtools: true });
|
|
475
|
+
if (!server.activated && (message.type === "XSTATE_EVENT" || message.type === "XSTATE_SNAPSHOT")) {
|
|
476
|
+
server.recentEvents.push(payload);
|
|
477
|
+
if (server.recentEvents.length > server.bufferSize) server.recentEvents.shift();
|
|
903
478
|
}
|
|
904
|
-
let sentCount = 0;
|
|
905
479
|
for (const ws of server.clients) {
|
|
906
480
|
if (ws.readyState === OPEN_STATE) {
|
|
907
481
|
try {
|
|
908
482
|
ws.send(payload);
|
|
909
|
-
sentCount += 1;
|
|
910
483
|
} catch {
|
|
911
484
|
}
|
|
912
485
|
}
|
|
913
486
|
}
|
|
914
|
-
debugLog3("sent adapter message to connected panels", {
|
|
915
|
-
sentCount,
|
|
916
|
-
clientCount: server.clients.size,
|
|
917
|
-
message: summarizeMessage2(message)
|
|
918
|
-
});
|
|
919
487
|
},
|
|
920
488
|
subscribe(handler) {
|
|
921
489
|
server.dispatchHandlers.add(handler);
|
|
922
|
-
|
|
923
|
-
return () => {
|
|
924
|
-
server.dispatchHandlers.delete(handler);
|
|
925
|
-
debugLog3("removed dispatch handler", { handlerCount: server.dispatchHandlers.size });
|
|
926
|
-
};
|
|
490
|
+
return () => server.dispatchHandlers.delete(handler);
|
|
927
491
|
}
|
|
928
492
|
};
|
|
929
493
|
const inspector = createInspector(transport, "srv");
|
|
930
|
-
return { ...inspector, close: server.close
|
|
494
|
+
return { ...inspector, close: server.close };
|
|
931
495
|
}
|
|
932
496
|
// Annotate the CommonJS export names for ESM import in node:
|
|
933
497
|
0 && (module.exports = {
|