@xstate/inspect
Advanced tools
Comparing version 0.3.0 to 0.4.0
# @xstate/inspect | ||
## 0.4.0 | ||
### Minor Changes | ||
- [`63ba888e`](https://github.com/davidkpiano/xstate/commit/63ba888e19bd2b72f9aad2c9cd36cde297e0ffe5) [#1770](https://github.com/davidkpiano/xstate/pull/1770) Thanks [@davidkpiano](https://github.com/davidkpiano)! - It is now easier for developers to create their own XState inspectors, and even inspect services offline. | ||
A **receiver** is an actor that receives inspector events from a source, such as `"service.register"`, `"service.state"`, `"service.event"`, etc. This update includes two receivers: | ||
- `createWindowReceiver` - listens to inspector events from a parent window (for both popup and iframe scenarios) | ||
- 🚧 `createWebSocketReceiver` (experimental) - listens to inspector events from a WebSocket server | ||
Here's how it works: | ||
**Application (browser) code** | ||
```js | ||
import { inspect } from '@xstate/inspect'; | ||
inspect(/* options */); | ||
// ... | ||
interpret(someMachine, { devTools: true }).start(); | ||
``` | ||
**Inspector code** | ||
```js | ||
import { createWindowReceiver } from '@xstate/inspect'; | ||
const windowReceiver = createWindowReceiver(/* options? */); | ||
windowReceiver.subscribe(event => { | ||
// here, you will receive events like: | ||
// { type: "service.register", machine: ..., state: ..., sessionId: ... } | ||
console.log(event); | ||
}); | ||
``` | ||
The events you will receive are `ParsedReceiverEvent` types: | ||
```ts | ||
export type ParsedReceiverEvent = | ||
| { | ||
type: 'service.register'; | ||
machine: StateMachine<any, any, any>; | ||
state: State<any, any>; | ||
id: string; | ||
sessionId: string; | ||
parent?: string; | ||
source?: string; | ||
} | ||
| { type: 'service.stop'; sessionId: string } | ||
| { | ||
type: 'service.state'; | ||
state: State<any, any>; | ||
sessionId: string; | ||
} | ||
| { type: 'service.event'; event: SCXML.Event<any>; sessionId: string }; | ||
``` | ||
Given these events, you can visualize the service machines and their states and events however you'd like. | ||
## 0.3.0 | ||
@@ -4,0 +67,0 @@ |
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Copyright (c) Microsoft Corporation. | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
@@ -15,0 +15,0 @@ |
@@ -1,27 +0,3 @@ | ||
import { Interpreter, EventObject } from 'xstate'; | ||
import { XStateDevInterface } from 'xstate/lib/devTools'; | ||
export declare type ServiceListener = (service: Interpreter<any>) => void; | ||
declare type MaybeLazy<T> = T | (() => T); | ||
export interface InspectorOptions { | ||
url: string; | ||
iframe: MaybeLazy<HTMLIFrameElement | null | false>; | ||
devTools: MaybeLazy<XStateDevInterface>; | ||
} | ||
export declare function createDevTools(): XStateDevInterface; | ||
export declare const createInspectMachine: (devTools?: XStateDevInterface) => import("xstate").StateMachine<{ | ||
client?: any; | ||
}, any, import("xstate").AnyEventObject, { | ||
value: any; | ||
context: { | ||
client?: any; | ||
}; | ||
}>; | ||
export declare function inspect(options?: Partial<InspectorOptions>): { | ||
send: (event: EventObject) => void; | ||
/** | ||
* Disconnects the inspector. | ||
*/ | ||
disconnect: () => void; | ||
}; | ||
export {}; | ||
export { inspect, createWindowReceiver, createWebSocketReceiver, createDevTools } from './browser'; | ||
export * from './types'; | ||
//# sourceMappingURL=index.d.ts.map |
217
es/index.js
@@ -1,216 +0,1 @@ | ||
import { __assign } from './_virtual/_tslib.js'; | ||
import { createMachine, assign, interpret } from 'xstate'; | ||
import { toSCXMLEvent, toEventObject } from 'xstate/lib/utils'; | ||
import safeStringify from 'fast-safe-stringify'; | ||
var serviceMap = new Map(); | ||
function createDevTools() { | ||
var services = new Set(); | ||
var serviceListeners = new Set(); | ||
return { | ||
services: services, | ||
register: function (service) { | ||
services.add(service); | ||
serviceMap.set(service.sessionId, service); | ||
serviceListeners.forEach(function (listener) { return listener(service); }); | ||
service.onStop(function () { | ||
services.delete(service); | ||
serviceMap.delete(service.sessionId); | ||
}); | ||
}, | ||
unregister: function (service) { | ||
services.delete(service); | ||
serviceMap.delete(service.sessionId); | ||
}, | ||
onRegister: function (listener) { | ||
serviceListeners.add(listener); | ||
services.forEach(function (service) { return listener(service); }); | ||
return { | ||
unsubscribe: function () { | ||
serviceListeners.delete(listener); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
var createInspectMachine = function (devTools) { | ||
if (devTools === void 0) { devTools = globalThis.__xstate__; } | ||
return createMachine({ | ||
initial: 'pendingConnection', | ||
context: { | ||
client: undefined | ||
}, | ||
states: { | ||
pendingConnection: {}, | ||
connected: { | ||
on: { | ||
'service.state': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'service.event': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'service.register': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'service.stop': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'xstate.event': { | ||
actions: function (_, e) { | ||
var event = e.event; | ||
var scxmlEventObject = JSON.parse(event); | ||
var service = serviceMap.get(scxmlEventObject.origin); | ||
service === null || service === void 0 ? void 0 : service.send(scxmlEventObject); | ||
} | ||
}, | ||
unload: { | ||
actions: function (ctx) { | ||
ctx.client.send({ type: 'xstate.disconnect' }); | ||
} | ||
}, | ||
disconnect: 'disconnected' | ||
} | ||
}, | ||
disconnected: { | ||
type: 'final' | ||
} | ||
}, | ||
on: { | ||
'xstate.inspecting': { | ||
target: '.connected', | ||
actions: [ | ||
assign({ client: function (_, e) { return e.client; } }), | ||
function (ctx) { | ||
devTools.services.forEach(function (service) { | ||
ctx.client.send({ | ||
type: 'service.register', | ||
machine: stringify(service.machine), | ||
state: stringify(service.state || service.initialState), | ||
sessionId: service.sessionId | ||
}); | ||
}); | ||
} | ||
] | ||
} | ||
} | ||
}); | ||
}; | ||
var defaultInspectorOptions = { | ||
url: 'https://statecharts.io/inspect', | ||
iframe: function () { | ||
return document.querySelector('iframe[data-xstate]'); | ||
}, | ||
devTools: function () { | ||
var devTools = createDevTools(); | ||
globalThis.__xstate__ = devTools; | ||
return devTools; | ||
} | ||
}; | ||
function getLazy(value) { | ||
return typeof value === 'function' ? value() : value; | ||
} | ||
function stringify(value) { | ||
try { | ||
return JSON.stringify(value); | ||
} | ||
catch (e) { | ||
return safeStringify(value); | ||
} | ||
} | ||
function inspect(options) { | ||
var _a = __assign(__assign({}, defaultInspectorOptions), options), iframe = _a.iframe, url = _a.url, devTools = _a.devTools; | ||
var resolvedDevTools = getLazy(devTools); | ||
var resolvedIframe = getLazy(iframe); | ||
var inspectMachine = createInspectMachine(resolvedDevTools); | ||
var targetWindow; | ||
var inspectService = interpret(inspectMachine).start(); | ||
if (resolvedIframe === null) { | ||
console.warn('No suitable <iframe> found to embed the inspector. Please pass an <iframe> element to `inspect(iframe)` or create an <iframe data-xstate></iframe> element.'); | ||
inspectService.send('disconnect'); | ||
return { send: function () { return void 0; }, disconnect: function () { return void 0; } }; | ||
} | ||
var client; | ||
var messageHandler = function (event) { | ||
if (typeof event.data === 'object' && | ||
event.data !== null && | ||
'type' in event.data) { | ||
if (resolvedIframe && !targetWindow) { | ||
targetWindow = resolvedIframe.contentWindow; | ||
} | ||
if (!client) { | ||
client = { | ||
send: function (e) { | ||
targetWindow.postMessage(e, url); | ||
} | ||
}; | ||
} | ||
inspectService.send(__assign(__assign({}, event.data), { client: client })); | ||
} | ||
}; | ||
window.addEventListener('message', messageHandler); | ||
window.addEventListener('unload', function () { | ||
inspectService.send({ type: 'unload' }); | ||
}); | ||
if (resolvedIframe === false) { | ||
targetWindow = window.open(url, 'xstateinspector'); | ||
} | ||
resolvedDevTools.onRegister(function (service) { | ||
var _a; | ||
inspectService.send({ | ||
type: 'service.register', | ||
machine: stringify(service.machine), | ||
state: stringify(service.state || service.initialState), | ||
sessionId: service.sessionId, | ||
id: service.id, | ||
parent: (_a = service.parent) === null || _a === void 0 ? void 0 : _a.sessionId | ||
}); | ||
inspectService.send({ | ||
type: 'service.event', | ||
event: stringify((service.state || service.initialState)._event), | ||
sessionId: service.sessionId | ||
}); | ||
// monkey-patch service.send so that we know when an event was sent | ||
// to a service *before* it is processed, since other events might occur | ||
// while the sent one is being processed, which throws the order off | ||
var originalSend = service.send.bind(service); | ||
service.send = function inspectSend(event, payload) { | ||
inspectService.send({ | ||
type: 'service.event', | ||
event: stringify(toSCXMLEvent(toEventObject(event, payload))), | ||
sessionId: service.sessionId | ||
}); | ||
return originalSend(event, payload); | ||
}; | ||
service.subscribe(function (state) { | ||
inspectService.send({ | ||
type: 'service.state', | ||
state: stringify(state), | ||
sessionId: service.sessionId | ||
}); | ||
}); | ||
service.onStop(function () { | ||
inspectService.send({ | ||
type: 'service.stop', | ||
sessionId: service.sessionId | ||
}); | ||
}); | ||
}); | ||
if (resolvedIframe) { | ||
resolvedIframe.addEventListener('load', function () { | ||
targetWindow = resolvedIframe.contentWindow; | ||
}); | ||
resolvedIframe.setAttribute('src', url); | ||
} | ||
return { | ||
send: function (event) { | ||
inspectService.send(event); | ||
}, | ||
disconnect: function () { | ||
inspectService.send('disconnect'); | ||
window.removeEventListener('message', messageHandler); | ||
} | ||
}; | ||
} | ||
export { createDevTools, createInspectMachine, inspect }; | ||
export { createDevTools, createWebSocketReceiver, createWindowReceiver, inspect } from './browser.js'; |
@@ -1,2 +0,8 @@ | ||
export declare function inspectServer(): void; | ||
import * as WebSocket from 'ws'; | ||
import { Inspector } from './types'; | ||
interface ServerInspectorOptions { | ||
server: WebSocket.Server; | ||
} | ||
export declare function inspect(options: ServerInspectorOptions): Inspector; | ||
export {}; | ||
//# sourceMappingURL=server.d.ts.map |
@@ -6,14 +6,14 @@ 'use strict'; | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Copyright (c) Microsoft Corporation. | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
@@ -20,0 +20,0 @@ |
@@ -1,27 +0,3 @@ | ||
import { Interpreter, EventObject } from 'xstate'; | ||
import { XStateDevInterface } from 'xstate/lib/devTools'; | ||
export declare type ServiceListener = (service: Interpreter<any>) => void; | ||
declare type MaybeLazy<T> = T | (() => T); | ||
export interface InspectorOptions { | ||
url: string; | ||
iframe: MaybeLazy<HTMLIFrameElement | null | false>; | ||
devTools: MaybeLazy<XStateDevInterface>; | ||
} | ||
export declare function createDevTools(): XStateDevInterface; | ||
export declare const createInspectMachine: (devTools?: XStateDevInterface) => import("xstate").StateMachine<{ | ||
client?: any; | ||
}, any, import("xstate").AnyEventObject, { | ||
value: any; | ||
context: { | ||
client?: any; | ||
}; | ||
}>; | ||
export declare function inspect(options?: Partial<InspectorOptions>): { | ||
send: (event: EventObject) => void; | ||
/** | ||
* Disconnects the inspector. | ||
*/ | ||
disconnect: () => void; | ||
}; | ||
export {}; | ||
export { inspect, createWindowReceiver, createWebSocketReceiver, createDevTools } from './browser'; | ||
export * from './types'; | ||
//# sourceMappingURL=index.d.ts.map |
222
lib/index.js
@@ -5,221 +5,9 @@ 'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var browser = require('./browser.js'); | ||
var _tslib = require('./_virtual/_tslib.js'); | ||
var xstate = require('xstate'); | ||
var utils = require('xstate/lib/utils'); | ||
var safeStringify = _interopDefault(require('fast-safe-stringify')); | ||
var serviceMap = new Map(); | ||
function createDevTools() { | ||
var services = new Set(); | ||
var serviceListeners = new Set(); | ||
return { | ||
services: services, | ||
register: function (service) { | ||
services.add(service); | ||
serviceMap.set(service.sessionId, service); | ||
serviceListeners.forEach(function (listener) { return listener(service); }); | ||
service.onStop(function () { | ||
services.delete(service); | ||
serviceMap.delete(service.sessionId); | ||
}); | ||
}, | ||
unregister: function (service) { | ||
services.delete(service); | ||
serviceMap.delete(service.sessionId); | ||
}, | ||
onRegister: function (listener) { | ||
serviceListeners.add(listener); | ||
services.forEach(function (service) { return listener(service); }); | ||
return { | ||
unsubscribe: function () { | ||
serviceListeners.delete(listener); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
var createInspectMachine = function (devTools) { | ||
if (devTools === void 0) { devTools = globalThis.__xstate__; } | ||
return xstate.createMachine({ | ||
initial: 'pendingConnection', | ||
context: { | ||
client: undefined | ||
}, | ||
states: { | ||
pendingConnection: {}, | ||
connected: { | ||
on: { | ||
'service.state': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'service.event': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'service.register': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'service.stop': { | ||
actions: function (ctx, e) { return ctx.client.send(e); } | ||
}, | ||
'xstate.event': { | ||
actions: function (_, e) { | ||
var event = e.event; | ||
var scxmlEventObject = JSON.parse(event); | ||
var service = serviceMap.get(scxmlEventObject.origin); | ||
service === null || service === void 0 ? void 0 : service.send(scxmlEventObject); | ||
} | ||
}, | ||
unload: { | ||
actions: function (ctx) { | ||
ctx.client.send({ type: 'xstate.disconnect' }); | ||
} | ||
}, | ||
disconnect: 'disconnected' | ||
} | ||
}, | ||
disconnected: { | ||
type: 'final' | ||
} | ||
}, | ||
on: { | ||
'xstate.inspecting': { | ||
target: '.connected', | ||
actions: [ | ||
xstate.assign({ client: function (_, e) { return e.client; } }), | ||
function (ctx) { | ||
devTools.services.forEach(function (service) { | ||
ctx.client.send({ | ||
type: 'service.register', | ||
machine: stringify(service.machine), | ||
state: stringify(service.state || service.initialState), | ||
sessionId: service.sessionId | ||
}); | ||
}); | ||
} | ||
] | ||
} | ||
} | ||
}); | ||
}; | ||
var defaultInspectorOptions = { | ||
url: 'https://statecharts.io/inspect', | ||
iframe: function () { | ||
return document.querySelector('iframe[data-xstate]'); | ||
}, | ||
devTools: function () { | ||
var devTools = createDevTools(); | ||
globalThis.__xstate__ = devTools; | ||
return devTools; | ||
} | ||
}; | ||
function getLazy(value) { | ||
return typeof value === 'function' ? value() : value; | ||
} | ||
function stringify(value) { | ||
try { | ||
return JSON.stringify(value); | ||
} | ||
catch (e) { | ||
return safeStringify(value); | ||
} | ||
} | ||
function inspect(options) { | ||
var _a = _tslib.__assign(_tslib.__assign({}, defaultInspectorOptions), options), iframe = _a.iframe, url = _a.url, devTools = _a.devTools; | ||
var resolvedDevTools = getLazy(devTools); | ||
var resolvedIframe = getLazy(iframe); | ||
var inspectMachine = createInspectMachine(resolvedDevTools); | ||
var targetWindow; | ||
var inspectService = xstate.interpret(inspectMachine).start(); | ||
if (resolvedIframe === null) { | ||
console.warn('No suitable <iframe> found to embed the inspector. Please pass an <iframe> element to `inspect(iframe)` or create an <iframe data-xstate></iframe> element.'); | ||
inspectService.send('disconnect'); | ||
return { send: function () { return void 0; }, disconnect: function () { return void 0; } }; | ||
} | ||
var client; | ||
var messageHandler = function (event) { | ||
if (typeof event.data === 'object' && | ||
event.data !== null && | ||
'type' in event.data) { | ||
if (resolvedIframe && !targetWindow) { | ||
targetWindow = resolvedIframe.contentWindow; | ||
} | ||
if (!client) { | ||
client = { | ||
send: function (e) { | ||
targetWindow.postMessage(e, url); | ||
} | ||
}; | ||
} | ||
inspectService.send(_tslib.__assign(_tslib.__assign({}, event.data), { client: client })); | ||
} | ||
}; | ||
window.addEventListener('message', messageHandler); | ||
window.addEventListener('unload', function () { | ||
inspectService.send({ type: 'unload' }); | ||
}); | ||
if (resolvedIframe === false) { | ||
targetWindow = window.open(url, 'xstateinspector'); | ||
} | ||
resolvedDevTools.onRegister(function (service) { | ||
var _a; | ||
inspectService.send({ | ||
type: 'service.register', | ||
machine: stringify(service.machine), | ||
state: stringify(service.state || service.initialState), | ||
sessionId: service.sessionId, | ||
id: service.id, | ||
parent: (_a = service.parent) === null || _a === void 0 ? void 0 : _a.sessionId | ||
}); | ||
inspectService.send({ | ||
type: 'service.event', | ||
event: stringify((service.state || service.initialState)._event), | ||
sessionId: service.sessionId | ||
}); | ||
// monkey-patch service.send so that we know when an event was sent | ||
// to a service *before* it is processed, since other events might occur | ||
// while the sent one is being processed, which throws the order off | ||
var originalSend = service.send.bind(service); | ||
service.send = function inspectSend(event, payload) { | ||
inspectService.send({ | ||
type: 'service.event', | ||
event: stringify(utils.toSCXMLEvent(utils.toEventObject(event, payload))), | ||
sessionId: service.sessionId | ||
}); | ||
return originalSend(event, payload); | ||
}; | ||
service.subscribe(function (state) { | ||
inspectService.send({ | ||
type: 'service.state', | ||
state: stringify(state), | ||
sessionId: service.sessionId | ||
}); | ||
}); | ||
service.onStop(function () { | ||
inspectService.send({ | ||
type: 'service.stop', | ||
sessionId: service.sessionId | ||
}); | ||
}); | ||
}); | ||
if (resolvedIframe) { | ||
resolvedIframe.addEventListener('load', function () { | ||
targetWindow = resolvedIframe.contentWindow; | ||
}); | ||
resolvedIframe.setAttribute('src', url); | ||
} | ||
return { | ||
send: function (event) { | ||
inspectService.send(event); | ||
}, | ||
disconnect: function () { | ||
inspectService.send('disconnect'); | ||
window.removeEventListener('message', messageHandler); | ||
} | ||
}; | ||
} | ||
exports.createDevTools = createDevTools; | ||
exports.createInspectMachine = createInspectMachine; | ||
exports.inspect = inspect; | ||
exports.createDevTools = browser.createDevTools; | ||
exports.createWebSocketReceiver = browser.createWebSocketReceiver; | ||
exports.createWindowReceiver = browser.createWindowReceiver; | ||
exports.inspect = browser.inspect; |
@@ -1,2 +0,8 @@ | ||
export declare function inspectServer(): void; | ||
import * as WebSocket from 'ws'; | ||
import { Inspector } from './types'; | ||
interface ServerInspectorOptions { | ||
server: WebSocket.Server; | ||
} | ||
export declare function inspect(options: ServerInspectorOptions): Inspector; | ||
export {}; | ||
//# sourceMappingURL=server.d.ts.map |
{ | ||
"name": "@xstate/inspect", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "XState inspection utilities", | ||
@@ -42,4 +42,4 @@ "keywords": [ | ||
"@types/ws": "^7.2.6", | ||
"rollup": "^1.26.3", | ||
"rollup-plugin-typescript2": "^0.25.2", | ||
"rollup": "^2.35.1", | ||
"rollup-plugin-typescript2": "^0.29.0", | ||
"typescript": "^4.1.2", | ||
@@ -46,0 +46,0 @@ "ws": "^7.3.1", |
@@ -86,2 +86,86 @@ # `@xstate/inspect` | ||
## Implementing | ||
You can implement your own inspector by creating a **receiver**. A **receiver** is an actor that receives inspector events from a source (like a parent window or a WebSocket connection): | ||
- `"service.register"` | ||
```ts | ||
{ | ||
type: 'service.register'; | ||
machine: StateMachine<any, any, any>; | ||
state: State<any, any>; | ||
id: string; | ||
sessionId: string; | ||
parent?: string; | ||
source?: string; | ||
} | ||
``` | ||
- `"service.stop"` | ||
```ts | ||
{ | ||
type: 'service.stop'; | ||
sessionId: string; | ||
} | ||
``` | ||
- `"service.state"` | ||
```ts | ||
{ | ||
type: 'service.state'; | ||
state: State<any, any>; | ||
sessionId: string; | ||
} | ||
``` | ||
- `"service.event"` | ||
```ts | ||
{ | ||
type: 'service.event'; | ||
event: SCXML.Event<any>; | ||
sessionId: string | ||
}; | ||
``` | ||
To listen to events from an inspected source, create a receiver with the appropriate `create*Receiver(...)` function; for example: | ||
```js | ||
import { createWindowReceiver } from '@xstate/inspect'; | ||
const windowReceiver = createWindowReceiver(/* options? */); | ||
windowReceiver.subscribe((event) => { | ||
// here, you will receive "service.*" events | ||
console.log(event); | ||
}); | ||
``` | ||
You can also send events to the receiver: | ||
```js | ||
// ... | ||
// This will send the event to the inspected service | ||
windowReceiver.send({ | ||
type: 'xstate.event', | ||
event: JSON.stringify({ type: 'someEvent' }), | ||
service: /* session ID of the service this event is sent to */ | ||
}); | ||
``` | ||
The typical inspection workflow is as follows: | ||
1. The `inspect(/* ... */)` call on the client opens the inspector (e.g., in a separate window, or creates a WebSocket connection) | ||
2. The receiver sends an `"xstate.inspecting"` event to the client | ||
3. The client sends `"service.register"` events to the receiver | ||
4. An inspector listening to the receiver (via `receiver.subscribe(...)`) registers the machine (`event.machine`) by its `event.sessionId` | ||
5. The machine is visually rendered, and its current state (`event.state`) is highlighted | ||
6. As the service at the source receives events and changes state, it will send the receiver `"service.event"` and `"service.state"` events, respectively | ||
7. The inspector can use those events to highlight the current state and keep a log of events sent to that service | ||
8. When the service stops, a `"service.stop"` event is sent to the receiver with the `event.sessionId` to identify the stopped service. | ||
## FAQs | ||
@@ -88,0 +172,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
53188
26
0
1158
183
1