11import type { StandardSchemaV1 } from '@standard-schema/spec'
2+
23interface DevtoolsEvent < TEventName extends string , TPayload = any > {
34 type : TEventName
45 payload : TPayload
@@ -11,9 +12,9 @@ export type EventMap<TEventPrefix extends string> = Record<
1112>
1213
1314type AllDevtoolsEvents < TEventMap extends Record < string , any > > = {
14- [ K in keyof TEventMap ] : DevtoolsEvent <
15- K & string ,
16- StandardSchemaV1 . InferOutput < TEventMap [ K ] >
15+ [ Key in keyof TEventMap ] : DevtoolsEvent <
16+ Key & string ,
17+ StandardSchemaV1 . InferOutput < TEventMap [ Key ] >
1718 >
1819} [ keyof TEventMap ]
1920
@@ -32,7 +33,7 @@ export class DevtoolsPlugin<
3233 #globalListeners: Set < ( msg : DevtoolsEvent < string , any > ) => void >
3334 #eventListeners: Map < string , Set < ( msg : DevtoolsEvent < string , any > ) => void > > =
3435 new Map ( )
35-
36+ #eventSource: EventSource | null
3637 constructor ( {
3738 port = 42069 ,
3839 pluginId,
@@ -42,12 +43,28 @@ export class DevtoolsPlugin<
4243 pluginId : TPluginId
4344 eventTarget ?: EventTarget
4445 } ) {
46+ this . #eventSource = null
4547 this . #port = port
4648 this . #socket = null
4749 this . #globalListeners = new Set ( )
4850 this . #pluginId = pluginId
49- this . #eventTarget =
50- eventTarget || ( globalThis as any ) . __EVENT_TARGET__ || new EventTarget ( )
51+ this . #eventTarget = this . getGlobalTarget ( eventTarget )
52+ }
53+
54+ private connectSSE ( ) {
55+ this . #eventSource = new EventSource (
56+ `http://localhost:${ this . #port} /__devtools/sse` ,
57+ )
58+ this . #eventSource. onmessage = ( e ) => this . handleEventReceived ( e . data )
59+ }
60+ private getGlobalTarget ( eventTarget ?: EventTarget ) {
61+ if ( typeof window !== 'undefined' ) {
62+ return window
63+ }
64+ if ( typeof globalThis !== 'undefined' && globalThis . __EVENT_TARGET__ ) {
65+ return globalThis . __EVENT_TARGET__
66+ }
67+ return eventTarget || new EventTarget ( )
5168 }
5269
5370 private connectWebSocket ( ) {
@@ -63,10 +80,13 @@ export class DevtoolsPlugin<
6380 }
6481
6582 connect ( ) {
66- if ( typeof window === 'undefined' ) return
6783 try {
6884 this . connectWebSocket ( )
69- } catch { }
85+ } catch {
86+ // Do not try to connect to SSE if we're on the server side
87+ if ( typeof window === 'undefined' ) return
88+ this . connectSSE ( )
89+ }
7090 }
7191
7292 private emitToGlobalListeners ( event : DevtoolsEvent < string , any > ) {
@@ -94,35 +114,51 @@ export class DevtoolsPlugin<
94114
95115 private emitEventToBus ( event : DevtoolsEvent < string , any > ) {
96116 const json = JSON . stringify ( event )
117+ // try to emit it to the event bus first
97118 if ( this . #socket && this . #socket. readyState === WebSocket . OPEN ) {
98119 this . #socket. send ( json )
120+ // try to emit to SSE if WebSocket is not available (this will only happen on the client side)
121+ } else if ( this . #eventSource) {
122+ fetch ( `http://localhost:${ this . #port} /__devtools/send` , {
123+ method : 'POST' ,
124+ headers : { 'Content-Type' : 'application/json' } ,
125+ body : json ,
126+ } ) . catch ( ( ) => { } )
127+ // otherwise, emit it to the event target
128+ } else {
129+ this . emitEventToEventTarget ( event )
99130 }
100131 }
101132
102- emit < K extends keyof TEventMap > (
133+ private emitEventToEventTarget ( event : DevtoolsEvent < string , any > ) {
134+ this . #eventTarget. dispatchEvent (
135+ new CustomEvent ( event . type , { detail : event } ) ,
136+ )
137+ }
138+
139+ emit < TKey extends keyof TEventMap > (
103140 event : DevtoolsEvent <
104- K & string ,
105- StandardSchemaV1 . InferOutput < TEventMap [ K ] >
141+ TKey & string ,
142+ StandardSchemaV1 . InferOutput < TEventMap [ TKey ] >
106143 > ,
107144 ) {
108145 this . emitEventToBus ( event )
109146 }
110147
111- on < K extends keyof TEventMap > (
112- eventName : K ,
148+ on < TKey extends keyof TEventMap > (
149+ eventName : TKey ,
113150 cb : (
114151 event : DevtoolsEvent <
115- K & string ,
116- StandardSchemaV1 . InferOutput < TEventMap [ K ] >
152+ TKey & string ,
153+ StandardSchemaV1 . InferOutput < TEventMap [ TKey ] >
117154 > ,
118155 ) => void ,
119156 ) {
120157 const handler = ( e : Event ) => cb ( ( e as CustomEvent ) . detail )
121158 this . #eventTarget. addEventListener ( eventName as string , handler )
122- this . #globalListeners . add ( cb as any )
159+
123160 return ( ) => {
124161 this . #eventTarget. removeEventListener ( eventName as string , handler )
125- this . #globalListeners. delete ( cb as any )
126162 }
127163 }
128164
0 commit comments