@jsenv/core 27.4.0 → 27.5.2
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 +359 -0
- package/dist/js/execute_using_dynamic_import.js +1 -1
- package/dist/js/html_supervisor_installer.js +469 -254
- package/dist/js/html_supervisor_setup.js +3 -4
- package/dist/js/new_stylesheet.js +26 -58
- package/dist/js/server_events_client.js +307 -0
- package/dist/main.js +7558 -7311
- package/package.json +12 -10
- package/{README.md → readme.md} +8 -9
- package/src/build/build.js +12 -16
- package/src/build/start_build_server.js +24 -28
- package/src/dev/start_dev_server.js +34 -96
- package/src/execute/execute.js +17 -35
- package/src/omega/errors.js +20 -18
- package/src/omega/kitchen.js +7 -6
- package/src/omega/omega_server.js +96 -127
- package/src/omega/server/file_service.js +247 -46
- package/src/omega/url_graph.js +33 -20
- package/src/plugins/autoreload/client/autoreload.js +201 -0
- package/src/plugins/autoreload/{dev_sse/client → client}/autoreload_preference.js +0 -0
- package/src/plugins/autoreload/{dev_sse/client → client}/reload.js +29 -10
- package/src/plugins/autoreload/{dev_sse/client → client}/url_helpers.js +0 -0
- package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -4
- package/src/plugins/autoreload/{dev_sse/jsenv_plugin_dev_sse_client.js → jsenv_plugin_autoreload_client.js} +8 -8
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +196 -0
- package/src/{dev/plugins → plugins}/explorer/client/explorer.html +0 -0
- package/src/{dev/plugins → plugins}/explorer/client/jsenv.png +0 -0
- package/src/{dev/plugins → plugins}/explorer/jsenv_plugin_explorer.js +1 -3
- package/src/plugins/html_supervisor/client/error_formatter.js +300 -0
- package/src/plugins/html_supervisor/client/error_overlay.js +172 -0
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +124 -54
- package/src/plugins/html_supervisor/client/html_supervisor_setup.js +3 -4
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +72 -27
- package/src/plugins/inline/jsenv_plugin_html_inline_content.js +97 -117
- package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +66 -58
- package/src/plugins/plugin_controller.js +102 -67
- package/src/plugins/plugins.js +10 -8
- package/src/{helpers/event_source/event_source.js → plugins/server_events/client/event_source_connection.js} +102 -31
- package/src/plugins/server_events/client/server_events_client.js +17 -0
- package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +48 -0
- package/src/plugins/server_events/server_events_dispatcher.js +69 -0
- package/src/{dev/plugins → plugins}/toolbar/client/animation/toolbar_animation.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/eventsource/eventsource.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/eventsource/toolbar_eventsource.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/execution/execution.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/execution/toolbar_execution.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/focus/focus.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/focus/toolbar_focus.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/jsenv_logo.svg +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/notification/toolbar_notification.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/responsive/overflow_menu.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/responsive/toolbar_responsive.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/settings/settings.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/settings/toolbar_settings.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/theme/jsenv_theme.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/theme/light_theme.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/theme/toolbar_theme.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar.html +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar_injector.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.css +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/animation.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/dom.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/fetch_using_xhr.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/fetching.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/iframe_to_parent_href.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/jsenv_logger.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/preferences.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/responsive.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/util/util.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/client/variant/variant.js +0 -0
- package/src/{dev/plugins → plugins}/toolbar/jsenv_plugin_toolbar.js +0 -0
- package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +4 -3
- package/src/plugins/transpilation/babel/new_stylesheet/client/new_stylesheet.js +25 -55
- package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +44 -24
- package/src/plugins/transpilation/jsenv_plugin_transpilation.js +6 -1
- package/src/plugins/url_analysis/html/html_urls.js +8 -8
- package/src/test/execute_plan.js +36 -54
- package/src/test/execute_test_plan.js +2 -2
- package/dist/js/event_source_client.js +0 -549
- package/src/helpers/event_source/sse_service.js +0 -53
- package/src/plugins/autoreload/dev_sse/client/event_source_client.js +0 -193
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +0 -192
- package/src/plugins/html_supervisor/client/error_in_document.js +0 -345
|
@@ -1,44 +1,63 @@
|
|
|
1
1
|
import { timeStart } from "@jsenv/server"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
const HOOK_NAMES = [
|
|
4
|
+
"init",
|
|
5
|
+
"serve", // is called only during dev/tests
|
|
6
|
+
"resolveUrl",
|
|
7
|
+
"redirectUrl",
|
|
8
|
+
"fetchUrlContent",
|
|
9
|
+
"transformUrlContent",
|
|
10
|
+
"transformUrlSearchParams",
|
|
11
|
+
"formatUrl",
|
|
12
|
+
"finalizeUrlContent",
|
|
13
|
+
"bundle", // is called only during build
|
|
14
|
+
"optimizeUrlContent", // is called only during build
|
|
15
|
+
"cooked",
|
|
16
|
+
"augmentResponse", // is called only during dev/tests
|
|
17
|
+
"destroy",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
export const createPluginController = ({ plugins, scenario }) => {
|
|
21
|
+
const flatPlugins = flattenAndFilterPlugins(plugins, { scenario })
|
|
22
|
+
// precompute a list of hooks per hookName for one major reason:
|
|
22
23
|
// - When debugging, there is less iteration
|
|
23
|
-
//
|
|
24
|
+
// also it should increase perf as there is less work to do
|
|
25
|
+
|
|
24
26
|
const hookGroups = {}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const addPlugin = (plugin, { position = "start" }) => {
|
|
28
|
+
Object.keys(plugin).forEach((key) => {
|
|
29
|
+
if (key === "name" || key === "appliesDuring" || key === "serverEvents") {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
const isHook = HOOK_NAMES.includes(key)
|
|
33
|
+
if (!isHook) {
|
|
34
|
+
console.warn(`Unexpected "${key}" property on "${plugin.name}" plugin`)
|
|
35
|
+
}
|
|
36
|
+
const hookName = key
|
|
37
|
+
const hookValue = plugin[hookName]
|
|
38
|
+
if (hookValue) {
|
|
39
|
+
const group = hookGroups[hookName] || (hookGroups[hookName] = [])
|
|
40
|
+
const hook = {
|
|
31
41
|
plugin,
|
|
32
|
-
hookName,
|
|
33
|
-
value:
|
|
34
|
-
}
|
|
42
|
+
name: hookName,
|
|
43
|
+
value: hookValue,
|
|
44
|
+
}
|
|
45
|
+
if (position === "start") {
|
|
46
|
+
group.push(hook)
|
|
47
|
+
} else {
|
|
48
|
+
group.unshift(hook)
|
|
49
|
+
}
|
|
35
50
|
}
|
|
36
51
|
})
|
|
37
|
-
hookGroups[hookName] = hooks
|
|
38
|
-
return hooks
|
|
39
52
|
}
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
const pushPlugin = (plugin) => {
|
|
54
|
+
addPlugin(plugin, { position: "start" })
|
|
55
|
+
}
|
|
56
|
+
const unshiftPlugin = (plugin) => {
|
|
57
|
+
addPlugin(plugin, { position: "end" })
|
|
58
|
+
}
|
|
59
|
+
flatPlugins.forEach((plugin) => {
|
|
60
|
+
pushPlugin(plugin)
|
|
42
61
|
})
|
|
43
62
|
|
|
44
63
|
let currentPlugin = null
|
|
@@ -49,15 +68,18 @@ export const createPluginController = ({
|
|
|
49
68
|
return null
|
|
50
69
|
}
|
|
51
70
|
currentPlugin = hook.plugin
|
|
52
|
-
currentHookName = hook.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
currentHookName = hook.name
|
|
72
|
+
let timeEnd
|
|
73
|
+
if (info.timing) {
|
|
74
|
+
timeEnd = timeStart(
|
|
75
|
+
`${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
56
78
|
let valueReturned = hookFn(info, context)
|
|
57
79
|
if (info.timing) {
|
|
58
80
|
Object.assign(info.timing, timeEnd())
|
|
59
81
|
}
|
|
60
|
-
valueReturned = assertAndNormalizeReturnValue(hook.
|
|
82
|
+
valueReturned = assertAndNormalizeReturnValue(hook.name, valueReturned)
|
|
61
83
|
currentPlugin = null
|
|
62
84
|
currentHookName = null
|
|
63
85
|
return valueReturned
|
|
@@ -68,15 +90,18 @@ export const createPluginController = ({
|
|
|
68
90
|
return null
|
|
69
91
|
}
|
|
70
92
|
currentPlugin = hook.plugin
|
|
71
|
-
currentHookName = hook.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
currentHookName = hook.name
|
|
94
|
+
let timeEnd
|
|
95
|
+
if (info.timing) {
|
|
96
|
+
timeEnd = timeStart(
|
|
97
|
+
`${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
75
100
|
let valueReturned = await hookFn(info, context)
|
|
76
101
|
if (info.timing) {
|
|
77
102
|
Object.assign(info.timing, timeEnd())
|
|
78
103
|
}
|
|
79
|
-
valueReturned = assertAndNormalizeReturnValue(hook.
|
|
104
|
+
valueReturned = assertAndNormalizeReturnValue(hook.name, valueReturned)
|
|
80
105
|
currentPlugin = null
|
|
81
106
|
currentHookName = null
|
|
82
107
|
return valueReturned
|
|
@@ -84,36 +109,45 @@ export const createPluginController = ({
|
|
|
84
109
|
|
|
85
110
|
const callHooks = (hookName, info, context, callback) => {
|
|
86
111
|
const hooks = hookGroups[hookName]
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
if (hooks) {
|
|
113
|
+
for (const hook of hooks) {
|
|
114
|
+
const returnValue = callHook(hook, info, context)
|
|
115
|
+
if (returnValue && callback) {
|
|
116
|
+
callback(returnValue)
|
|
117
|
+
}
|
|
91
118
|
}
|
|
92
119
|
}
|
|
93
120
|
}
|
|
94
121
|
const callAsyncHooks = async (hookName, info, context, callback) => {
|
|
95
122
|
const hooks = hookGroups[hookName]
|
|
96
|
-
|
|
97
|
-
await previous
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
123
|
+
if (hooks) {
|
|
124
|
+
await hooks.reduce(async (previous, hook) => {
|
|
125
|
+
await previous
|
|
126
|
+
const returnValue = await callAsyncHook(hook, info, context)
|
|
127
|
+
if (returnValue && callback) {
|
|
128
|
+
await callback(returnValue)
|
|
129
|
+
}
|
|
130
|
+
}, Promise.resolve())
|
|
131
|
+
}
|
|
103
132
|
}
|
|
104
133
|
|
|
105
134
|
const callHooksUntil = (hookName, info, context) => {
|
|
106
135
|
const hooks = hookGroups[hookName]
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
136
|
+
if (hooks) {
|
|
137
|
+
for (const hook of hooks) {
|
|
138
|
+
const returnValue = callHook(hook, info, context)
|
|
139
|
+
if (returnValue) {
|
|
140
|
+
return returnValue
|
|
141
|
+
}
|
|
111
142
|
}
|
|
112
143
|
}
|
|
113
144
|
return null
|
|
114
145
|
}
|
|
115
146
|
const callAsyncHooksUntil = (hookName, info, context) => {
|
|
116
147
|
const hooks = hookGroups[hookName]
|
|
148
|
+
if (!hooks) {
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
117
151
|
if (hooks.length === 0) {
|
|
118
152
|
return null
|
|
119
153
|
}
|
|
@@ -136,8 +170,9 @@ export const createPluginController = ({
|
|
|
136
170
|
}
|
|
137
171
|
|
|
138
172
|
return {
|
|
139
|
-
plugins,
|
|
140
|
-
|
|
173
|
+
plugins: flatPlugins,
|
|
174
|
+
pushPlugin,
|
|
175
|
+
unshiftPlugin,
|
|
141
176
|
getHookFunction,
|
|
142
177
|
callHook,
|
|
143
178
|
callAsyncHook,
|
|
@@ -152,8 +187,8 @@ export const createPluginController = ({
|
|
|
152
187
|
}
|
|
153
188
|
}
|
|
154
189
|
|
|
155
|
-
const flattenAndFilterPlugins = (
|
|
156
|
-
const
|
|
190
|
+
const flattenAndFilterPlugins = (plugins, { scenario }) => {
|
|
191
|
+
const flatPlugins = []
|
|
157
192
|
const visitPluginEntry = (pluginEntry) => {
|
|
158
193
|
if (Array.isArray(pluginEntry)) {
|
|
159
194
|
pluginEntry.forEach((value) => visitPluginEntry(value))
|
|
@@ -169,7 +204,7 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
|
|
|
169
204
|
return
|
|
170
205
|
}
|
|
171
206
|
if (appliesDuring === "*") {
|
|
172
|
-
|
|
207
|
+
flatPlugins.push(pluginEntry)
|
|
173
208
|
return
|
|
174
209
|
}
|
|
175
210
|
if (typeof appliesDuring === "string") {
|
|
@@ -179,7 +214,7 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
|
|
|
179
214
|
)
|
|
180
215
|
}
|
|
181
216
|
if (appliesDuring === scenario) {
|
|
182
|
-
|
|
217
|
+
flatPlugins.push(pluginEntry)
|
|
183
218
|
}
|
|
184
219
|
return
|
|
185
220
|
}
|
|
@@ -189,7 +224,7 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
|
|
|
189
224
|
)
|
|
190
225
|
}
|
|
191
226
|
if (appliesDuring[scenario]) {
|
|
192
|
-
|
|
227
|
+
flatPlugins.push(pluginEntry)
|
|
193
228
|
return
|
|
194
229
|
}
|
|
195
230
|
if (pluginEntry.destroy) {
|
|
@@ -199,8 +234,8 @@ const flattenAndFilterPlugins = (pluginsRaw, { scenario }) => {
|
|
|
199
234
|
}
|
|
200
235
|
throw new Error(`plugin must be objects, got ${pluginEntry}`)
|
|
201
236
|
}
|
|
202
|
-
|
|
203
|
-
return
|
|
237
|
+
plugins.forEach((plugin) => visitPluginEntry(plugin))
|
|
238
|
+
return flatPlugins
|
|
204
239
|
}
|
|
205
240
|
|
|
206
241
|
const getHookFunction = (
|
package/src/plugins/plugins.js
CHANGED
|
@@ -19,16 +19,17 @@ import { jsenvPluginMinification } from "./minification/jsenv_plugin_minificatio
|
|
|
19
19
|
import { jsenvPluginImportMetaHot } from "./import_meta_hot/jsenv_plugin_import_meta_hot.js"
|
|
20
20
|
import { jsenvPluginAutoreload } from "./autoreload/jsenv_plugin_autoreload.js"
|
|
21
21
|
import { jsenvPluginCacheControl } from "./cache_control/jsenv_plugin_cache_control.js"
|
|
22
|
+
// dev only
|
|
23
|
+
import { jsenvPluginExplorer } from "./explorer/jsenv_plugin_explorer.js"
|
|
22
24
|
|
|
23
25
|
export const getCorePlugins = ({
|
|
24
26
|
rootDirectoryUrl,
|
|
25
|
-
urlGraph,
|
|
26
27
|
scenario,
|
|
27
28
|
runtimeCompat,
|
|
28
29
|
|
|
29
30
|
urlAnalysis = {},
|
|
30
31
|
htmlSupervisor,
|
|
31
|
-
nodeEsmResolution,
|
|
32
|
+
nodeEsmResolution = true,
|
|
32
33
|
fileSystemMagicResolution,
|
|
33
34
|
directoryReferenceAllowed,
|
|
34
35
|
transpilation = true,
|
|
@@ -38,6 +39,7 @@ export const getCorePlugins = ({
|
|
|
38
39
|
clientAutoreload = false,
|
|
39
40
|
clientFileChangeCallbackList,
|
|
40
41
|
clientFilesPruneCallbackList,
|
|
42
|
+
explorer,
|
|
41
43
|
} = {}) => {
|
|
42
44
|
if (htmlSupervisor === true) {
|
|
43
45
|
htmlSupervisor = {}
|
|
@@ -45,9 +47,13 @@ export const getCorePlugins = ({
|
|
|
45
47
|
if (nodeEsmResolution === true) {
|
|
46
48
|
nodeEsmResolution = {}
|
|
47
49
|
}
|
|
50
|
+
if (fileSystemMagicResolution === true) {
|
|
51
|
+
fileSystemMagicResolution = {}
|
|
52
|
+
}
|
|
48
53
|
if (clientAutoreload === true) {
|
|
49
54
|
clientAutoreload = {}
|
|
50
55
|
}
|
|
56
|
+
|
|
51
57
|
return [
|
|
52
58
|
jsenvPluginUrlAnalysis({ rootDirectoryUrl, ...urlAnalysis }),
|
|
53
59
|
jsenvPluginTranspilation(transpilation),
|
|
@@ -63,12 +69,7 @@ export const getCorePlugins = ({
|
|
|
63
69
|
jsenvPluginHttpUrls(),
|
|
64
70
|
jsenvPluginLeadingSlash(),
|
|
65
71
|
// before url resolution to handle "js_import_export" resolution
|
|
66
|
-
jsenvPluginNodeEsmResolution(
|
|
67
|
-
rootDirectoryUrl,
|
|
68
|
-
urlGraph,
|
|
69
|
-
runtimeCompat,
|
|
70
|
-
...nodeEsmResolution,
|
|
71
|
-
}),
|
|
72
|
+
jsenvPluginNodeEsmResolution(nodeEsmResolution),
|
|
72
73
|
jsenvPluginUrlResolution(),
|
|
73
74
|
jsenvPluginUrlVersion(),
|
|
74
75
|
jsenvPluginCommonJsGlobals(),
|
|
@@ -90,5 +91,6 @@ export const getCorePlugins = ({
|
|
|
90
91
|
]
|
|
91
92
|
: []),
|
|
92
93
|
jsenvPluginCacheControl(),
|
|
94
|
+
...(explorer ? [jsenvPluginExplorer(explorer)] : []),
|
|
93
95
|
]
|
|
94
96
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-env browser */
|
|
2
|
-
|
|
3
1
|
const STATUSES = {
|
|
4
2
|
CONNECTING: "connecting",
|
|
5
3
|
CONNECTED: "connected",
|
|
@@ -8,7 +6,12 @@ const STATUSES = {
|
|
|
8
6
|
|
|
9
7
|
export const createEventSourceConnection = (
|
|
10
8
|
eventSourceUrl,
|
|
11
|
-
{
|
|
9
|
+
{
|
|
10
|
+
retryMaxAttempt = Infinity,
|
|
11
|
+
retryAllocatedMs = Infinity,
|
|
12
|
+
lastEventId,
|
|
13
|
+
useEventsToManageConnection = true,
|
|
14
|
+
} = {},
|
|
12
15
|
) => {
|
|
13
16
|
const { EventSource } = window
|
|
14
17
|
if (typeof EventSource !== "function") {
|
|
@@ -16,25 +19,85 @@ export const createEventSourceConnection = (
|
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
let eventSource
|
|
19
|
-
const
|
|
22
|
+
const listenersMap = new Map()
|
|
23
|
+
const callbacksMap = new Map()
|
|
20
24
|
const eventSourceOrigin = new URL(eventSourceUrl).origin
|
|
21
|
-
const addEventCallbacks = (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const addEventCallbacks = (namedCallbacks) => {
|
|
26
|
+
let listenersMapSize = listenersMap.size
|
|
27
|
+
Object.keys(namedCallbacks).forEach((eventName) => {
|
|
28
|
+
const callback = namedCallbacks[eventName]
|
|
29
|
+
const existingCallbacks = callbacksMap.get(eventName)
|
|
30
|
+
let callbacks
|
|
31
|
+
if (existingCallbacks) {
|
|
32
|
+
callbacks = existingCallbacks
|
|
33
|
+
} else {
|
|
34
|
+
callbacks = []
|
|
35
|
+
callbacksMap.set(eventName, callbacks)
|
|
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
|
+
})
|
|
28
46
|
}
|
|
29
|
-
|
|
47
|
+
}
|
|
48
|
+
listenersMap.set(eventName, eventListener)
|
|
49
|
+
if (eventSource) {
|
|
50
|
+
eventSource.addEventListener(eventName, eventListener)
|
|
30
51
|
}
|
|
31
52
|
}
|
|
32
|
-
|
|
33
|
-
eventSource.addEventListener(eventName, events[eventName])
|
|
34
|
-
}
|
|
53
|
+
callbacks.push(callback)
|
|
35
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()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let removed = false
|
|
66
|
+
return () => {
|
|
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
|
+
}
|
|
99
|
+
}
|
|
36
100
|
}
|
|
37
|
-
addEventCallbacks(events)
|
|
38
101
|
|
|
39
102
|
const status = {
|
|
40
103
|
value: "default",
|
|
@@ -50,6 +113,12 @@ export const createEventSourceConnection = (
|
|
|
50
113
|
let _disconnect = () => {}
|
|
51
114
|
|
|
52
115
|
const attemptConnection = (url) => {
|
|
116
|
+
if (
|
|
117
|
+
status.value === STATUSES.CONNECTING ||
|
|
118
|
+
status.value === STATUSES.CONNECTED
|
|
119
|
+
) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
53
122
|
eventSource = new EventSource(url, {
|
|
54
123
|
withCredentials: true,
|
|
55
124
|
})
|
|
@@ -63,11 +132,13 @@ export const createEventSourceConnection = (
|
|
|
63
132
|
)
|
|
64
133
|
return
|
|
65
134
|
}
|
|
66
|
-
eventSource
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
135
|
+
if (eventSource) {
|
|
136
|
+
eventSource.onerror = undefined
|
|
137
|
+
eventSource.close()
|
|
138
|
+
listenersMap.forEach((listener, eventName) => {
|
|
139
|
+
eventSource.removeEventListener(eventName, listener)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
71
142
|
eventSource = null
|
|
72
143
|
status.goTo(STATUSES.DISCONNECTED)
|
|
73
144
|
}
|
|
@@ -107,22 +178,20 @@ export const createEventSourceConnection = (
|
|
|
107
178
|
eventSource.onopen = () => {
|
|
108
179
|
status.goTo(STATUSES.CONNECTED)
|
|
109
180
|
}
|
|
110
|
-
|
|
111
|
-
eventSource.addEventListener(eventName,
|
|
181
|
+
listenersMap.forEach((listener, eventName) => {
|
|
182
|
+
eventSource.addEventListener(eventName, listener)
|
|
112
183
|
})
|
|
113
|
-
if (!
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
lastEventId = e.lastEventId
|
|
117
|
-
}
|
|
184
|
+
if (!listenersMap.has("welcome")) {
|
|
185
|
+
addEventCallbacks({
|
|
186
|
+
welcome: () => {}, // to update lastEventId
|
|
118
187
|
})
|
|
119
188
|
}
|
|
120
189
|
status.goTo(STATUSES.CONNECTING)
|
|
121
190
|
}
|
|
122
191
|
|
|
123
|
-
let
|
|
192
|
+
let _connect = () => {
|
|
124
193
|
attemptConnection(eventSourceUrl)
|
|
125
|
-
|
|
194
|
+
_connect = () => {
|
|
126
195
|
attemptConnection(
|
|
127
196
|
lastEventId
|
|
128
197
|
? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId)
|
|
@@ -143,11 +212,13 @@ export const createEventSourceConnection = (
|
|
|
143
212
|
const destroy = () => {
|
|
144
213
|
removePageUnloadListener()
|
|
145
214
|
_disconnect()
|
|
215
|
+
listenersMap.clear()
|
|
216
|
+
callbacksMap.clear()
|
|
146
217
|
}
|
|
147
218
|
|
|
148
219
|
return {
|
|
149
220
|
status,
|
|
150
|
-
connect,
|
|
221
|
+
connect: () => _connect(),
|
|
151
222
|
addEventCallbacks,
|
|
152
223
|
disconnect: () => _disconnect(),
|
|
153
224
|
destroy,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createEventSourceConnection } from "./event_source_connection.js"
|
|
2
|
+
|
|
3
|
+
const eventsourceConnection = createEventSourceConnection(
|
|
4
|
+
document.location.href,
|
|
5
|
+
{
|
|
6
|
+
retryMaxAttempt: Infinity,
|
|
7
|
+
retryAllocatedMs: 20 * 1000,
|
|
8
|
+
},
|
|
9
|
+
)
|
|
10
|
+
const { status, connect, addEventCallbacks, disconnect } = eventsourceConnection
|
|
11
|
+
window.__server_events__ = {
|
|
12
|
+
addEventCallbacks,
|
|
13
|
+
status,
|
|
14
|
+
connect,
|
|
15
|
+
disconnect,
|
|
16
|
+
}
|
|
17
|
+
connect()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This plugin is very special because it is here
|
|
3
|
+
* to provide "serverEvents" used by other plugins
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
parseHtmlString,
|
|
8
|
+
stringifyHtmlAst,
|
|
9
|
+
injectScriptNodeAsEarlyAsPossible,
|
|
10
|
+
createHtmlNode,
|
|
11
|
+
} from "@jsenv/ast"
|
|
12
|
+
|
|
13
|
+
const serverEventsClientFileUrl = new URL(
|
|
14
|
+
"./client/server_events_client.js",
|
|
15
|
+
import.meta.url,
|
|
16
|
+
).href
|
|
17
|
+
|
|
18
|
+
export const jsenvPluginServerEventsClientInjection = () => {
|
|
19
|
+
return {
|
|
20
|
+
name: "jsenv:server_events_client_injection",
|
|
21
|
+
appliesDuring: "*",
|
|
22
|
+
transformUrlContent: {
|
|
23
|
+
html: (htmlUrlInfo, context) => {
|
|
24
|
+
const htmlAst = parseHtmlString(htmlUrlInfo.content)
|
|
25
|
+
const [serverEventsClientFileReference] = context.referenceUtils.inject(
|
|
26
|
+
{
|
|
27
|
+
type: "script_src",
|
|
28
|
+
expectedType: "js_module",
|
|
29
|
+
specifier: serverEventsClientFileUrl,
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
injectScriptNodeAsEarlyAsPossible(
|
|
33
|
+
htmlAst,
|
|
34
|
+
createHtmlNode({
|
|
35
|
+
"tagName": "script",
|
|
36
|
+
"type": "module",
|
|
37
|
+
"src": serverEventsClientFileReference.generatedSpecifier,
|
|
38
|
+
"injected-by": "jsenv:server_events",
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
const htmlModified = stringifyHtmlAst(htmlAst)
|
|
42
|
+
return {
|
|
43
|
+
content: htmlModified,
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createSSERoom } from "@jsenv/server"
|
|
2
|
+
import { createCallbackListNotifiedOnce } from "@jsenv/abort"
|
|
3
|
+
|
|
4
|
+
export const createServerEventsDispatcher = () => {
|
|
5
|
+
const destroyCallbackList = createCallbackListNotifiedOnce()
|
|
6
|
+
const rooms = []
|
|
7
|
+
const sseRoomLimit = 100
|
|
8
|
+
|
|
9
|
+
destroyCallbackList.add(() => {
|
|
10
|
+
rooms.forEach((room) => {
|
|
11
|
+
room.close()
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
addRoom: (request) => {
|
|
17
|
+
const existingRoom = rooms.find(
|
|
18
|
+
(roomCandidate) =>
|
|
19
|
+
roomCandidate.request.ressource === request.ressource,
|
|
20
|
+
)
|
|
21
|
+
if (existingRoom) {
|
|
22
|
+
return existingRoom
|
|
23
|
+
}
|
|
24
|
+
const room = createSSERoom({
|
|
25
|
+
retryDuration: 2000,
|
|
26
|
+
historyLength: 100,
|
|
27
|
+
welcomeEventEnabled: true,
|
|
28
|
+
effect: () => {
|
|
29
|
+
rooms.push(room)
|
|
30
|
+
if (rooms.length >= sseRoomLimit) {
|
|
31
|
+
const firstRoom = rooms.shift()
|
|
32
|
+
firstRoom.close()
|
|
33
|
+
}
|
|
34
|
+
return () => {
|
|
35
|
+
// when the last client leaves the room it is closed and removed from the list
|
|
36
|
+
room.close()
|
|
37
|
+
const index = rooms.indexOf(room)
|
|
38
|
+
if (index > -1) {
|
|
39
|
+
rooms.splice(index, 1)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
room.request = request
|
|
45
|
+
return room
|
|
46
|
+
},
|
|
47
|
+
dispatch: ({ type, data }) => {
|
|
48
|
+
rooms.forEach((room) =>
|
|
49
|
+
room.sendEventToAllClients({
|
|
50
|
+
type,
|
|
51
|
+
data: JSON.stringify(data),
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
54
|
+
},
|
|
55
|
+
dispatchToRoomsMatching: ({ type, data }, predicate) => {
|
|
56
|
+
rooms.forEach((room) => {
|
|
57
|
+
if (predicate(room)) {
|
|
58
|
+
room.sendEventToAllClients({
|
|
59
|
+
type,
|
|
60
|
+
data: JSON.stringify(data),
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
},
|
|
65
|
+
destroy: () => {
|
|
66
|
+
destroyCallbackList.notify()
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|