@jsenv/core 27.6.0 → 27.8.0
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/dist/js/autoreload.js +3 -6
- package/dist/js/html_supervisor_installer.js +42 -30
- package/dist/js/html_supervisor_setup.js +10 -2
- package/dist/js/server_events_client.js +249 -216
- package/dist/js/wrapper.mjs +4233 -0
- package/dist/main.js +21282 -21623
- package/package.json +4 -4
- package/src/build/build.js +19 -18
- package/src/dev/start_dev_server.js +7 -11
- package/src/execute/execute.js +2 -2
- package/src/execute/runtimes/browsers/chromium.js +1 -1
- package/src/execute/runtimes/browsers/firefox.js +1 -1
- package/src/execute/runtimes/browsers/webkit.js +1 -1
- package/src/omega/kitchen.js +9 -14
- package/src/omega/omega_server.js +13 -2
- package/src/omega/server/file_service.js +13 -29
- package/src/plugins/autoreload/client/autoreload.js +3 -4
- package/src/plugins/autoreload/jsenv_plugin_autoreload.js +0 -4
- package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +1 -1
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +1 -1
- package/src/plugins/autoreload/jsenv_plugin_hmr.js +1 -1
- package/src/plugins/bundling/jsenv_plugin_bundling.js +1 -3
- package/src/plugins/cache_control/jsenv_plugin_cache_control.js +2 -5
- package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
- package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -8
- package/src/plugins/html_supervisor/client/error_formatter.js +48 -35
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +1 -4
- package/src/plugins/html_supervisor/client/html_supervisor_setup.js +10 -2
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +28 -11
- package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +2 -2
- package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +18 -24
- package/src/plugins/importmap/jsenv_plugin_importmap.js +1 -1
- package/src/plugins/minification/jsenv_plugin_minification.js +1 -3
- package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +9 -7
- package/src/plugins/plugin_controller.js +17 -6
- package/src/plugins/plugins.js +0 -2
- package/src/plugins/server_events/client/connection_manager.js +165 -0
- package/src/plugins/server_events/client/event_source_connection.js +50 -256
- package/src/plugins/server_events/client/events_manager.js +75 -0
- package/src/plugins/server_events/client/server_events_client.js +12 -11
- package/src/plugins/server_events/client/web_socket_connection.js +81 -0
- package/src/plugins/server_events/server_events_dispatcher.js +70 -54
- package/src/plugins/toolbar/jsenv_plugin_toolbar.js +1 -3
- package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +1 -1
- package/src/plugins/url_analysis/html/html_urls.js +2 -2
- package/src/test/execute_plan.js +2 -2
- package/src/test/execute_test_plan.js +1 -1
- package/src/plugins/html_supervisor/client/error_site_remap.js +0 -85
|
@@ -1,227 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
CONNECTED: "connected",
|
|
4
|
-
DISCONNECTED: "disconnected",
|
|
5
|
-
}
|
|
1
|
+
import { createConnectionManager } from "./connection_manager.js"
|
|
2
|
+
import { createEventsManager } from "./events_manager.js"
|
|
6
3
|
|
|
7
4
|
export const createEventSourceConnection = (
|
|
8
5
|
eventSourceUrl,
|
|
9
6
|
{
|
|
10
|
-
|
|
11
|
-
retryAllocatedMs = Infinity,
|
|
7
|
+
withCredentials = true,
|
|
12
8
|
lastEventId,
|
|
13
9
|
useEventsToManageConnection = true,
|
|
10
|
+
retry = false,
|
|
11
|
+
retryMaxAttempt = Infinity,
|
|
12
|
+
retryAllocatedMs = Infinity,
|
|
14
13
|
} = {},
|
|
15
14
|
) => {
|
|
16
|
-
const { EventSource } = window
|
|
17
|
-
if (typeof EventSource !== "function") {
|
|
18
|
-
return () => {}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let eventSource
|
|
22
|
-
const listenersMap = new Map()
|
|
23
|
-
const callbacksMap = new Map()
|
|
24
15
|
const eventSourceOrigin = new URL(eventSourceUrl).origin
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
if (callbacks.length === 0) {
|
|
38
|
-
const eventListener = (e) => {
|
|
39
|
-
if (e.origin === eventSourceOrigin) {
|
|
40
|
-
if (e.lastEventId) {
|
|
41
|
-
lastEventId = e.lastEventId
|
|
42
|
-
}
|
|
43
|
-
callbacks.forEach((eventCallback) => {
|
|
44
|
-
eventCallback(e)
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
listenersMap.set(eventName, eventListener)
|
|
49
|
-
if (eventSource) {
|
|
50
|
-
eventSource.addEventListener(eventName, eventListener)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
callbacks.push(callback)
|
|
54
|
-
})
|
|
55
|
-
if (
|
|
56
|
-
useEventsToManageConnection &&
|
|
57
|
-
listenersMapSize === 0 &&
|
|
58
|
-
listenersMap.size > 0 &&
|
|
59
|
-
status.value !== STATUSES.CONNECTING &&
|
|
60
|
-
status.value !== STATUSES.CONNECTED
|
|
61
|
-
) {
|
|
62
|
-
_connect()
|
|
16
|
+
const attemptConnection = ({ onOpen, onClosed }) => {
|
|
17
|
+
const url = lastEventId
|
|
18
|
+
? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId)
|
|
19
|
+
: eventSourceUrl
|
|
20
|
+
let eventSource = new EventSource(url, { withCredentials })
|
|
21
|
+
eventSource.onerror = () => {
|
|
22
|
+
eventSource.onerror = null
|
|
23
|
+
eventSource.onopen = null
|
|
24
|
+
eventSource.onmessage = null
|
|
25
|
+
eventSource = null
|
|
26
|
+
onClosed()
|
|
63
27
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (removed) return
|
|
68
|
-
removed = true
|
|
69
|
-
listenersMapSize = listenersMap.size
|
|
70
|
-
Object.keys(namedCallbacks).forEach((eventName) => {
|
|
71
|
-
const callback = namedCallbacks[eventName]
|
|
72
|
-
const callbacks = callbacksMap.get(eventName)
|
|
73
|
-
if (callbacks) {
|
|
74
|
-
const index = callbacks.indexOf(callback)
|
|
75
|
-
if (index > -1) {
|
|
76
|
-
callbacks.splice(index, 1)
|
|
77
|
-
if (callbacks.length === 0) {
|
|
78
|
-
const listener = listenersMap.get(eventName)
|
|
79
|
-
if (listener) {
|
|
80
|
-
listenersMap.delete(listener)
|
|
81
|
-
if (eventSource) {
|
|
82
|
-
eventSource.removeEventListener(eventName, listener)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
namedCallbacks = null // allow garbage collect
|
|
90
|
-
if (
|
|
91
|
-
useEventsToManageConnection &&
|
|
92
|
-
listenersMapSize > 0 &&
|
|
93
|
-
listenersMap.size === 0 &&
|
|
94
|
-
(status.value === STATUSES.CONNECTING ||
|
|
95
|
-
status.value === STATUSES.CONNECTED)
|
|
96
|
-
) {
|
|
97
|
-
_disconnect()
|
|
98
|
-
}
|
|
28
|
+
eventSource.onopen = () => {
|
|
29
|
+
eventSource.onopen = null
|
|
30
|
+
onOpen()
|
|
99
31
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
32
|
+
eventSource.onmessage = (messageEvent) => {
|
|
33
|
+
if (messageEvent.origin === eventSourceOrigin) {
|
|
34
|
+
if (messageEvent.lastEventId) {
|
|
35
|
+
lastEventId = messageEvent.lastEventId
|
|
36
|
+
}
|
|
37
|
+
const event = JSON.parse(messageEvent.data)
|
|
38
|
+
eventsManager.triggerCallbacks(event)
|
|
107
39
|
}
|
|
108
|
-
status.value = value
|
|
109
|
-
status.onchange()
|
|
110
|
-
},
|
|
111
|
-
onchange: () => {},
|
|
112
|
-
}
|
|
113
|
-
let _disconnect = () => {}
|
|
114
|
-
|
|
115
|
-
const attemptConnection = (url) => {
|
|
116
|
-
if (
|
|
117
|
-
status.value === STATUSES.CONNECTING ||
|
|
118
|
-
status.value === STATUSES.CONNECTED
|
|
119
|
-
) {
|
|
120
|
-
return
|
|
121
40
|
}
|
|
122
|
-
|
|
123
|
-
withCredentials: true,
|
|
124
|
-
})
|
|
125
|
-
_disconnect = () => {
|
|
126
|
-
if (
|
|
127
|
-
status.value !== STATUSES.CONNECTING &&
|
|
128
|
-
status.value !== STATUSES.CONNECTED
|
|
129
|
-
) {
|
|
130
|
-
console.warn(
|
|
131
|
-
`disconnect() ignored because connection is ${status.value}`,
|
|
132
|
-
)
|
|
133
|
-
return
|
|
134
|
-
}
|
|
41
|
+
return () => {
|
|
135
42
|
if (eventSource) {
|
|
136
|
-
eventSource.onerror = undefined
|
|
137
43
|
eventSource.close()
|
|
138
|
-
listenersMap.forEach((listener, eventName) => {
|
|
139
|
-
eventSource.removeEventListener(eventName, listener)
|
|
140
|
-
})
|
|
141
44
|
}
|
|
142
|
-
eventSource = null
|
|
143
|
-
status.goTo(STATUSES.DISCONNECTED)
|
|
144
45
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} else {
|
|
158
|
-
const allRetryDuration = Date.now() - firstRetryMs
|
|
159
|
-
if (retryAllocatedMs && allRetryDuration > retryAllocatedMs) {
|
|
160
|
-
console.info(
|
|
161
|
-
`could not connect in less than ${retryAllocatedMs} ms`,
|
|
162
|
-
)
|
|
163
|
-
_disconnect()
|
|
164
|
-
return
|
|
165
|
-
}
|
|
46
|
+
}
|
|
47
|
+
const connectionManager = createConnectionManager(attemptConnection, {
|
|
48
|
+
retry,
|
|
49
|
+
retryMaxAttempt,
|
|
50
|
+
retryAllocatedMs,
|
|
51
|
+
})
|
|
52
|
+
const eventsManager = createEventsManager({
|
|
53
|
+
effect: () => {
|
|
54
|
+
if (useEventsToManageConnection) {
|
|
55
|
+
connectionManager.connect()
|
|
56
|
+
return () => {
|
|
57
|
+
connectionManager.disconnect()
|
|
166
58
|
}
|
|
167
|
-
|
|
168
|
-
retryCount++
|
|
169
|
-
status.goTo(STATUSES.CONNECTING)
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (errorEvent.target.readyState === EventSource.CLOSED) {
|
|
174
|
-
_disconnect()
|
|
175
|
-
return
|
|
176
59
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
status.goTo(STATUSES.CONNECTED)
|
|
180
|
-
}
|
|
181
|
-
listenersMap.forEach((listener, eventName) => {
|
|
182
|
-
eventSource.addEventListener(eventName, listener)
|
|
183
|
-
})
|
|
184
|
-
if (!listenersMap.has("welcome")) {
|
|
185
|
-
addEventCallbacks({
|
|
186
|
-
welcome: () => {}, // to update lastEventId
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
status.goTo(STATUSES.CONNECTING)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
let _connect = () => {
|
|
193
|
-
attemptConnection(eventSourceUrl)
|
|
194
|
-
_connect = () => {
|
|
195
|
-
attemptConnection(
|
|
196
|
-
lastEventId
|
|
197
|
-
? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId)
|
|
198
|
-
: eventSourceUrl,
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const removePageUnloadListener = listenPageUnload(() => {
|
|
204
|
-
if (
|
|
205
|
-
status.value === STATUSES.CONNECTING ||
|
|
206
|
-
status.value === STATUSES.CONNECTED
|
|
207
|
-
) {
|
|
208
|
-
_disconnect()
|
|
209
|
-
}
|
|
60
|
+
return null
|
|
61
|
+
},
|
|
210
62
|
})
|
|
211
63
|
|
|
212
|
-
const destroy = () => {
|
|
213
|
-
removePageUnloadListener()
|
|
214
|
-
_disconnect()
|
|
215
|
-
listenersMap.clear()
|
|
216
|
-
callbacksMap.clear()
|
|
217
|
-
}
|
|
218
|
-
|
|
219
64
|
return {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
destroy
|
|
65
|
+
readyState: connectionManager.readyState,
|
|
66
|
+
listenEvents: (namedCallbacks) => {
|
|
67
|
+
return eventsManager.addCallbacks(namedCallbacks)
|
|
68
|
+
},
|
|
69
|
+
destroy: () => {
|
|
70
|
+
connectionManager.destroy()
|
|
71
|
+
eventsManager.destroy()
|
|
72
|
+
},
|
|
225
73
|
}
|
|
226
74
|
}
|
|
227
75
|
|
|
@@ -233,57 +81,3 @@ const addLastEventIdIntoUrlSearchParams = (url, lastEventId) => {
|
|
|
233
81
|
}
|
|
234
82
|
return `${url}last-event-id=${encodeURIComponent(lastEventId)}`
|
|
235
83
|
}
|
|
236
|
-
|
|
237
|
-
// const listenPageMightFreeze = (callback) => {
|
|
238
|
-
// const removePageHideListener = listenEvent(window, "pagehide", (pageHideEvent) => {
|
|
239
|
-
// if (pageHideEvent.persisted === true) {
|
|
240
|
-
// callback(pageHideEvent)
|
|
241
|
-
// }
|
|
242
|
-
// })
|
|
243
|
-
// return removePageHideListener
|
|
244
|
-
// }
|
|
245
|
-
|
|
246
|
-
// const listenPageFreeze = (callback) => {
|
|
247
|
-
// const removeFreezeListener = listenEvent(document, "freeze", (freezeEvent) => {
|
|
248
|
-
// callback(freezeEvent)
|
|
249
|
-
// })
|
|
250
|
-
// return removeFreezeListener
|
|
251
|
-
// }
|
|
252
|
-
|
|
253
|
-
// const listenPageIsRestored = (callback) => {
|
|
254
|
-
// const removeResumeListener = listenEvent(document, "resume", (resumeEvent) => {
|
|
255
|
-
// removePageshowListener()
|
|
256
|
-
// callback(resumeEvent)
|
|
257
|
-
// })
|
|
258
|
-
// const removePageshowListener = listenEvent(window, "pageshow", (pageshowEvent) => {
|
|
259
|
-
// if (pageshowEvent.persisted === true) {
|
|
260
|
-
// removePageshowListener()
|
|
261
|
-
// removeResumeListener()
|
|
262
|
-
// callback(pageshowEvent)
|
|
263
|
-
// }
|
|
264
|
-
// })
|
|
265
|
-
// return () => {
|
|
266
|
-
// removeResumeListener()
|
|
267
|
-
// removePageshowListener()
|
|
268
|
-
// }
|
|
269
|
-
// }
|
|
270
|
-
|
|
271
|
-
const listenPageUnload = (callback) => {
|
|
272
|
-
const removePageHideListener = listenEvent(
|
|
273
|
-
window,
|
|
274
|
-
"pagehide",
|
|
275
|
-
(pageHideEvent) => {
|
|
276
|
-
if (pageHideEvent.persisted !== true) {
|
|
277
|
-
callback(pageHideEvent)
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
)
|
|
281
|
-
return removePageHideListener
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const listenEvent = (emitter, event, callback) => {
|
|
285
|
-
emitter.addEventListener(event, callback)
|
|
286
|
-
return () => {
|
|
287
|
-
emitter.removeEventListener(event, callback)
|
|
288
|
-
}
|
|
289
|
-
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const createEventsManager = ({ effect = () => {} } = {}) => {
|
|
2
|
+
const callbacksMap = new Map()
|
|
3
|
+
let cleanup
|
|
4
|
+
const addCallbacks = (namedCallbacks) => {
|
|
5
|
+
let callbacksMapSize = callbacksMap.size
|
|
6
|
+
Object.keys(namedCallbacks).forEach((eventName) => {
|
|
7
|
+
const callback = namedCallbacks[eventName]
|
|
8
|
+
const existingCallbacks = callbacksMap.get(eventName)
|
|
9
|
+
let callbacks
|
|
10
|
+
if (existingCallbacks) {
|
|
11
|
+
callbacks = existingCallbacks
|
|
12
|
+
} else {
|
|
13
|
+
callbacks = []
|
|
14
|
+
callbacksMap.set(eventName, callbacks)
|
|
15
|
+
}
|
|
16
|
+
callbacks.push(callback)
|
|
17
|
+
})
|
|
18
|
+
if (effect && callbacksMapSize === 0 && callbacksMapSize.size > 0) {
|
|
19
|
+
cleanup = effect()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let removed = false
|
|
23
|
+
return () => {
|
|
24
|
+
if (removed) return
|
|
25
|
+
removed = true
|
|
26
|
+
callbacksMapSize = callbacksMap.size
|
|
27
|
+
Object.keys(namedCallbacks).forEach((eventName) => {
|
|
28
|
+
const callback = namedCallbacks[eventName]
|
|
29
|
+
const callbacks = callbacksMap.get(eventName)
|
|
30
|
+
if (callbacks) {
|
|
31
|
+
const index = callbacks.indexOf(callback)
|
|
32
|
+
if (index > -1) {
|
|
33
|
+
callbacks.splice(index, 1)
|
|
34
|
+
if (callbacks.length === 0) {
|
|
35
|
+
callbacksMap.delete(eventName)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
namedCallbacks = null // allow garbage collect
|
|
41
|
+
if (
|
|
42
|
+
cleanup &&
|
|
43
|
+
typeof cleanup === "function" &&
|
|
44
|
+
callbacksMapSize > 0 &&
|
|
45
|
+
callbacksMapSize.size === 0
|
|
46
|
+
) {
|
|
47
|
+
cleanup()
|
|
48
|
+
cleanup = null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const triggerCallbacks = (event) => {
|
|
54
|
+
const callbacks = callbacksMap.get(event.type)
|
|
55
|
+
if (callbacks) {
|
|
56
|
+
callbacks.forEach((callback) => {
|
|
57
|
+
callback(event)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const destroy = () => {
|
|
63
|
+
callbacksMap.clear()
|
|
64
|
+
if (cleanup) {
|
|
65
|
+
cleanup()
|
|
66
|
+
cleanup = null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
addCallbacks,
|
|
72
|
+
triggerCallbacks,
|
|
73
|
+
destroy,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/* globals self */
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import { createWebSocketConnection } from "./web_socket_connection.js"
|
|
4
|
+
|
|
5
|
+
const websocketScheme = self.location.protocol === "https" ? "wss" : "ws"
|
|
6
|
+
const websocketUrl = `${websocketScheme}://${self.location.host}${self.location.pathname}${self.location.search}`
|
|
7
|
+
const websocketConnection = createWebSocketConnection(websocketUrl, {
|
|
8
|
+
retry: true,
|
|
9
|
+
retryAllocatedMs: 10_000,
|
|
10
|
+
})
|
|
11
|
+
const { readyState, connect, disconnect, listenEvents } = websocketConnection
|
|
11
12
|
window.__server_events__ = {
|
|
12
|
-
|
|
13
|
-
status,
|
|
13
|
+
readyState,
|
|
14
14
|
connect,
|
|
15
15
|
disconnect,
|
|
16
|
+
listenEvents,
|
|
16
17
|
}
|
|
17
18
|
connect()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createConnectionManager } from "./connection_manager.js"
|
|
2
|
+
import { createEventsManager } from "./events_manager.js"
|
|
3
|
+
|
|
4
|
+
export const createWebSocketConnection = (
|
|
5
|
+
websocketUrl,
|
|
6
|
+
{
|
|
7
|
+
protocols = ["jsenv"],
|
|
8
|
+
useEventsToManageConnection = true,
|
|
9
|
+
retry = false,
|
|
10
|
+
retryAfter = 1000,
|
|
11
|
+
retryMaxAttempt = Infinity,
|
|
12
|
+
retryAllocatedMs = Infinity,
|
|
13
|
+
} = {},
|
|
14
|
+
) => {
|
|
15
|
+
const connectionManager = createConnectionManager(
|
|
16
|
+
({ onClosed, onOpen }) => {
|
|
17
|
+
let socket = new WebSocket(websocketUrl, protocols)
|
|
18
|
+
let interval
|
|
19
|
+
const cleanup = () => {
|
|
20
|
+
if (socket) {
|
|
21
|
+
socket.onerror = null
|
|
22
|
+
socket.onopen = null
|
|
23
|
+
socket.onclose = null
|
|
24
|
+
socket.onmessage = null
|
|
25
|
+
socket = null
|
|
26
|
+
clearInterval(interval)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
socket.onerror = () => {
|
|
30
|
+
cleanup()
|
|
31
|
+
onClosed()
|
|
32
|
+
}
|
|
33
|
+
socket.onopen = () => {
|
|
34
|
+
socket.onopen = null
|
|
35
|
+
onOpen()
|
|
36
|
+
interval = setInterval(() => {
|
|
37
|
+
socket.send('{"type":"ping"}')
|
|
38
|
+
}, 30_000)
|
|
39
|
+
}
|
|
40
|
+
socket.onclose = () => {
|
|
41
|
+
cleanup()
|
|
42
|
+
onClosed()
|
|
43
|
+
}
|
|
44
|
+
socket.onmessage = (messageEvent) => {
|
|
45
|
+
const event = JSON.parse(messageEvent.data)
|
|
46
|
+
eventsManager.triggerCallbacks(event)
|
|
47
|
+
}
|
|
48
|
+
return () => {
|
|
49
|
+
if (socket) {
|
|
50
|
+
socket.close()
|
|
51
|
+
cleanup()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{ retry, retryAfter, retryMaxAttempt, retryAllocatedMs },
|
|
56
|
+
)
|
|
57
|
+
const eventsManager = createEventsManager({
|
|
58
|
+
effect: () => {
|
|
59
|
+
if (useEventsToManageConnection) {
|
|
60
|
+
connectionManager.connect()
|
|
61
|
+
return () => {
|
|
62
|
+
connectionManager.disconnect()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
readyState: connectionManager.readyState,
|
|
71
|
+
connect: connectionManager.connect,
|
|
72
|
+
disconnect: connectionManager.disconnect,
|
|
73
|
+
listenEvents: (namedCallbacks) => {
|
|
74
|
+
return eventsManager.addCallbacks(namedCallbacks)
|
|
75
|
+
},
|
|
76
|
+
destroy: () => {
|
|
77
|
+
connectionManager.destroy()
|
|
78
|
+
eventsManager.destroy()
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -1,69 +1,85 @@
|
|
|
1
|
-
import { createSSERoom } from "@jsenv/server"
|
|
2
|
-
import { createCallbackListNotifiedOnce } from "@jsenv/abort"
|
|
3
|
-
|
|
4
1
|
export const createServerEventsDispatcher = () => {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const sseRoomLimit = 100
|
|
2
|
+
const clients = []
|
|
3
|
+
const MAX_CLIENTS = 100
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
const addClient = (client) => {
|
|
6
|
+
clients.push(client)
|
|
7
|
+
if (clients.length >= MAX_CLIENTS) {
|
|
8
|
+
const firstClient = clients.shift()
|
|
9
|
+
firstClient.close()
|
|
10
|
+
}
|
|
11
|
+
return () => {
|
|
12
|
+
client.close()
|
|
13
|
+
const index = clients.indexOf(client)
|
|
14
|
+
if (index > -1) {
|
|
15
|
+
clients.splice(index, 1)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
return {
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
room.close()
|
|
37
|
-
const index = rooms.indexOf(room)
|
|
38
|
-
if (index > -1) {
|
|
39
|
-
rooms.splice(index, 1)
|
|
21
|
+
addWebsocket: (websocket, request) => {
|
|
22
|
+
const client = {
|
|
23
|
+
request,
|
|
24
|
+
getReadystate: () => {
|
|
25
|
+
return websocket.readyState
|
|
26
|
+
},
|
|
27
|
+
sendEvent: (event) => {
|
|
28
|
+
websocket.send(JSON.stringify(event))
|
|
29
|
+
},
|
|
30
|
+
close: (reason) => {
|
|
31
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
32
|
+
websocket.onclose = () => {
|
|
33
|
+
websocket.onclose = null
|
|
34
|
+
websocket.onerror = null
|
|
35
|
+
resolve()
|
|
36
|
+
}
|
|
37
|
+
websocket.onerror = (e) => {
|
|
38
|
+
websocket.onclose = null
|
|
39
|
+
websocket.onerror = null
|
|
40
|
+
reject(e)
|
|
40
41
|
}
|
|
41
|
-
}
|
|
42
|
+
})
|
|
43
|
+
websocket.close(reason)
|
|
44
|
+
return closePromise
|
|
42
45
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
destroy: () => {
|
|
47
|
+
websocket.terminate()
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
client.sendEvent({ type: "welcome" })
|
|
51
|
+
return addClient(client)
|
|
46
52
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
53
|
+
// we could add "addEventSource" and let clients connect using
|
|
54
|
+
// new WebSocket or new EventSource
|
|
55
|
+
// in practice the new EventSource won't be used
|
|
56
|
+
// so "serverEventsDispatcher.addEventSource" is not implemented
|
|
57
|
+
// addEventSource: (request) => {},
|
|
58
|
+
dispatch: (event) => {
|
|
59
|
+
clients.forEach((client) => {
|
|
60
|
+
if (client.getReadystate() === 1) {
|
|
61
|
+
client.sendEvent(event)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
54
64
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (predicate(
|
|
58
|
-
|
|
59
|
-
type,
|
|
60
|
-
data: JSON.stringify(data),
|
|
61
|
-
})
|
|
65
|
+
dispatchToClientsMatching: (event, predicate) => {
|
|
66
|
+
clients.forEach((client) => {
|
|
67
|
+
if (client.getReadystate() === 1 && predicate(client)) {
|
|
68
|
+
client.sendEvent(event)
|
|
62
69
|
}
|
|
63
70
|
})
|
|
64
71
|
},
|
|
72
|
+
close: async (reason) => {
|
|
73
|
+
await Promise.all(
|
|
74
|
+
clients.map(async (client) => {
|
|
75
|
+
await client.close(reason)
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
},
|
|
65
79
|
destroy: () => {
|
|
66
|
-
|
|
80
|
+
clients.forEach((client) => {
|
|
81
|
+
client.destroy()
|
|
82
|
+
})
|
|
67
83
|
},
|
|
68
84
|
}
|
|
69
85
|
}
|
|
@@ -17,9 +17,7 @@ export const jsenvPluginToolbar = ({ logs = false } = {}) => {
|
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
name: "jsenv:toolbar",
|
|
20
|
-
appliesDuring: {
|
|
21
|
-
dev: true,
|
|
22
|
-
},
|
|
20
|
+
appliesDuring: { dev: true, test: false },
|
|
23
21
|
transformUrlContent: {
|
|
24
22
|
html: ({ url, content }, { referenceUtils }) => {
|
|
25
23
|
if (url === toolbarHtmlClientFileUrl) {
|
|
@@ -43,7 +43,7 @@ export const jsenvPluginImportAssertions = ({
|
|
|
43
43
|
// We would have to tell rollup to ignore import with assertion
|
|
44
44
|
// - means rollup can bundle more js file together
|
|
45
45
|
// - means url versioning can work for css inlined in js
|
|
46
|
-
if (context.
|
|
46
|
+
if (context.scenarios.build) {
|
|
47
47
|
json = true
|
|
48
48
|
css = true
|
|
49
49
|
text = true
|