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