1
1
import type { StandardSchemaV1 } from '@standard-schema/spec'
2
+
2
3
interface DevtoolsEvent < TEventName extends string , TPayload = any > {
3
4
type : TEventName
4
5
payload : TPayload
@@ -11,9 +12,9 @@ export type EventMap<TEventPrefix extends string> = Record<
11
12
>
12
13
13
14
type 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 ] >
17
18
>
18
19
} [ keyof TEventMap ]
19
20
@@ -32,7 +33,7 @@ export class DevtoolsPlugin<
32
33
#globalListeners: Set < ( msg : DevtoolsEvent < string , any > ) => void >
33
34
#eventListeners: Map < string , Set < ( msg : DevtoolsEvent < string , any > ) => void > > =
34
35
new Map ( )
35
-
36
+ #eventSource: EventSource | null
36
37
constructor ( {
37
38
port = 42069 ,
38
39
pluginId,
@@ -42,12 +43,28 @@ export class DevtoolsPlugin<
42
43
pluginId : TPluginId
43
44
eventTarget ?: EventTarget
44
45
} ) {
46
+ this . #eventSource = null
45
47
this . #port = port
46
48
this . #socket = null
47
49
this . #globalListeners = new Set ( )
48
50
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 ( )
51
68
}
52
69
53
70
private connectWebSocket ( ) {
@@ -63,10 +80,13 @@ export class DevtoolsPlugin<
63
80
}
64
81
65
82
connect ( ) {
66
- if ( typeof window === 'undefined' ) return
67
83
try {
68
84
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
+ }
70
90
}
71
91
72
92
private emitToGlobalListeners ( event : DevtoolsEvent < string , any > ) {
@@ -94,35 +114,51 @@ export class DevtoolsPlugin<
94
114
95
115
private emitEventToBus ( event : DevtoolsEvent < string , any > ) {
96
116
const json = JSON . stringify ( event )
117
+ // try to emit it to the event bus first
97
118
if ( this . #socket && this . #socket. readyState === WebSocket . OPEN ) {
98
119
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 )
99
130
}
100
131
}
101
132
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 > (
103
140
event : DevtoolsEvent <
104
- K & string ,
105
- StandardSchemaV1 . InferOutput < TEventMap [ K ] >
141
+ TKey & string ,
142
+ StandardSchemaV1 . InferOutput < TEventMap [ TKey ] >
106
143
> ,
107
144
) {
108
145
this . emitEventToBus ( event )
109
146
}
110
147
111
- on < K extends keyof TEventMap > (
112
- eventName : K ,
148
+ on < TKey extends keyof TEventMap > (
149
+ eventName : TKey ,
113
150
cb : (
114
151
event : DevtoolsEvent <
115
- K & string ,
116
- StandardSchemaV1 . InferOutput < TEventMap [ K ] >
152
+ TKey & string ,
153
+ StandardSchemaV1 . InferOutput < TEventMap [ TKey ] >
117
154
> ,
118
155
) => void ,
119
156
) {
120
157
const handler = ( e : Event ) => cb ( ( e as CustomEvent ) . detail )
121
158
this . #eventTarget. addEventListener ( eventName as string , handler )
122
- this . #globalListeners . add ( cb as any )
159
+
123
160
return ( ) => {
124
161
this . #eventTarget. removeEventListener ( eventName as string , handler )
125
- this . #globalListeners. delete ( cb as any )
126
162
}
127
163
}
128
164
0 commit comments