import { WebSocketServer } from 'ws' ; import { ActorRef , EventData , EventObject , interpret , Interpreter } from 'xstate' ; import { toEventObject , toSCXMLEvent } from 'xstate/lib/utils' ; import { createInspectMachine } from './inspectMachine' ; import { Inspector , Replacer } from './types' ; import { stringify } from './utils' ; const services = new Set < Interpreter < any > > ( ) ; const serviceMap = new Map < string , Interpreter < any > > ( ) ; const serviceListeners = new Set < any > ( ) ; function createDevTools ( ) { globalThis . __xstate__ = { services , register : ( service ) => { services . add ( service ) ; serviceMap . set ( service . sessionId , service ) ; serviceListeners . forEach ( ( listener ) => listener ( service ) ) ; service . onStop ( ( ) => { services . delete ( service ) ; serviceMap . delete ( service . sessionId ) ; } ) ; } , onRegister : ( listener ) => { serviceListeners . add ( listener ) ; services . forEach ( ( service ) => listener ( service ) ) ; return { unsubscribe : ( ) => { serviceListeners . delete ( listener ) ; } } ; } } ; } interface ServerInspectorOptions { server : WebSocketServer ; serialize ? : Replacer | undefined ; } export function inspect ( options ) { const { server } = options ; createDevTools ( ) ; const inspectService = interpret ( createInspectMachine ( globalThis . __xstate__ , options ) ) . start ( ) ; let client = { id : '@@xstate/ws-client' , send : ( event ) => { server . clients . forEach ( ( wsClient ) => { if ( wsClient . readyState === wsClient . OPEN ) { wsClient . send ( JSON . stringify ( event ) ) ; } } ) ; } , subscribe : ( ) => { return { unsubscribe : ( ) => void 0 } ; } , getSnapshot : ( ) => undefined } ; server . on ( 'connection' , function connection ( wsClient ) { wsClient . on ( 'message' , function incoming ( data , isBinary ) { if ( isBinary ) { return ; } const jsonMessage = JSON . parse ( data . toString ( ) ) ; inspectService . send ( { ... jsonMessage , client } ) ; } ) ; } ) ; globalThis . __xstate__ . onRegister ( ( service ) => { inspectService . send ( { type : 'service.register' , machine : JSON . stringify ( service . machine ) , state : JSON . stringify ( service . state || service . initialState ) , id : service . id , sessionId : service . sessionId } ) ; inspectService . send ( { type : 'service.event' , event : stringify ( ( service . state || service . initialState ) . _event ) , sessionId : service . sessionId } ) ; const originalSend = service . send . bind ( service ) ; service . send = function inspectSend ( event , payload ? ) { inspectService . send ( { type : 'service.event' , event : stringify ( toSCXMLEvent ( toEventObject ( event as EventObject , payload ) ) ) , sessionId : service . sessionId } ) ; return originalSend ( event , payload ) ; } ; service . subscribe ( ( state ) => { inspectService . send ( { type : 'service.state' , state : stringify ( state ) , sessionId : service . sessionId } ) ; } ) ; service . onStop ( ( ) => { inspectService . send ( { type : 'service.stop' , sessionId : service . sessionId } ) ; } ) ; } ) ; const inspector = { id : '@@xstate/inspector' , send : ( event ) => { inspectService . send ( event ) ; } , subscribe : ( ) => { return { unsubscribe : ( ) => void 0 } ; } , disconnect : ( ) => { server . close ( ) ; inspectService . stop ( ) ; } , getSnapshot : ( ) => undefined } ; server . on ( 'close' , ( ) => { inspectService . stop ( ) ; server . clients . forEach ( ( client ) => client . terminate ( ) ) ; } ) ; return inspector ; }