@jsenv/core 27.3.3 → 27.5.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.
Files changed (89) hide show
  1. package/dist/js/autoreload.js +359 -0
  2. package/dist/js/execute_using_dynamic_import.js +1 -1
  3. package/dist/js/html_supervisor_installer.js +524 -147
  4. package/dist/js/html_supervisor_setup.js +3 -4
  5. package/dist/js/new_stylesheet.js +26 -58
  6. package/dist/js/server_events_client.js +307 -0
  7. package/dist/main.js +7699 -7307
  8. package/package.json +15 -15
  9. package/{README.md → readme.md} +18 -7
  10. package/src/build/build.js +16 -18
  11. package/src/build/start_build_server.js +24 -28
  12. package/src/dev/start_dev_server.js +30 -94
  13. package/src/execute/execute.js +17 -35
  14. package/src/execute/run.js +2 -0
  15. package/src/omega/errors.js +43 -9
  16. package/src/omega/kitchen.js +42 -25
  17. package/src/omega/omega_server.js +96 -74
  18. package/src/omega/server/file_service.js +256 -28
  19. package/src/omega/url_graph.js +39 -20
  20. package/src/plugins/autoreload/client/autoreload.js +201 -0
  21. package/src/plugins/autoreload/{dev_sse/client → client}/autoreload_preference.js +0 -0
  22. package/src/plugins/autoreload/{dev_sse/client → client}/reload.js +29 -10
  23. package/src/plugins/autoreload/{dev_sse/client → client}/url_helpers.js +0 -0
  24. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -8
  25. package/src/plugins/autoreload/{dev_sse/jsenv_plugin_dev_sse_client.js → jsenv_plugin_autoreload_client.js} +8 -8
  26. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +196 -0
  27. package/src/{dev/plugins → plugins}/explorer/client/explorer.html +0 -0
  28. package/src/{dev/plugins → plugins}/explorer/client/jsenv.png +0 -0
  29. package/src/{dev/plugins → plugins}/explorer/jsenv_plugin_explorer.js +1 -3
  30. package/src/plugins/html_supervisor/client/error_overlay.js +401 -0
  31. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +138 -23
  32. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +3 -4
  33. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +55 -23
  34. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +97 -117
  35. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +66 -58
  36. package/src/plugins/plugin_controller.js +102 -67
  37. package/src/plugins/plugins.js +10 -10
  38. package/src/{helpers/event_source/event_source.js → plugins/server_events/client/event_source_connection.js} +125 -33
  39. package/src/plugins/server_events/client/server_events_client.js +17 -0
  40. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +48 -0
  41. package/src/plugins/server_events/server_events_dispatcher.js +69 -0
  42. package/src/{dev/plugins → plugins}/toolbar/client/animation/toolbar_animation.js +0 -0
  43. package/src/{dev/plugins → plugins}/toolbar/client/eventsource/eventsource.css +0 -0
  44. package/src/{dev/plugins → plugins}/toolbar/client/eventsource/toolbar_eventsource.js +0 -0
  45. package/src/{dev/plugins → plugins}/toolbar/client/execution/execution.css +0 -0
  46. package/src/{dev/plugins → plugins}/toolbar/client/execution/toolbar_execution.js +0 -0
  47. package/src/{dev/plugins → plugins}/toolbar/client/focus/focus.css +0 -0
  48. package/src/{dev/plugins → plugins}/toolbar/client/focus/toolbar_focus.js +0 -0
  49. package/src/{dev/plugins → plugins}/toolbar/client/jsenv_logo.svg +0 -0
  50. package/src/{dev/plugins → plugins}/toolbar/client/notification/toolbar_notification.js +0 -0
  51. package/src/{dev/plugins → plugins}/toolbar/client/responsive/overflow_menu.css +0 -0
  52. package/src/{dev/plugins → plugins}/toolbar/client/responsive/toolbar_responsive.js +0 -0
  53. package/src/{dev/plugins → plugins}/toolbar/client/settings/settings.css +0 -0
  54. package/src/{dev/plugins → plugins}/toolbar/client/settings/toolbar_settings.js +0 -0
  55. package/src/{dev/plugins → plugins}/toolbar/client/theme/jsenv_theme.css +0 -0
  56. package/src/{dev/plugins → plugins}/toolbar/client/theme/light_theme.css +0 -0
  57. package/src/{dev/plugins → plugins}/toolbar/client/theme/toolbar_theme.js +0 -0
  58. package/src/{dev/plugins → plugins}/toolbar/client/toolbar.html +0 -0
  59. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_injector.js +0 -0
  60. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.css +0 -0
  61. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.js +0 -0
  62. package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.css +0 -0
  63. package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.js +0 -0
  64. package/src/{dev/plugins → plugins}/toolbar/client/util/animation.js +0 -0
  65. package/src/{dev/plugins → plugins}/toolbar/client/util/dom.js +0 -0
  66. package/src/{dev/plugins → plugins}/toolbar/client/util/fetch_using_xhr.js +0 -0
  67. package/src/{dev/plugins → plugins}/toolbar/client/util/fetching.js +0 -0
  68. package/src/{dev/plugins → plugins}/toolbar/client/util/iframe_to_parent_href.js +0 -0
  69. package/src/{dev/plugins → plugins}/toolbar/client/util/jsenv_logger.js +0 -0
  70. package/src/{dev/plugins → plugins}/toolbar/client/util/preferences.js +0 -0
  71. package/src/{dev/plugins → plugins}/toolbar/client/util/responsive.js +0 -0
  72. package/src/{dev/plugins → plugins}/toolbar/client/util/util.js +0 -0
  73. package/src/{dev/plugins → plugins}/toolbar/client/variant/variant.js +0 -0
  74. package/src/{dev/plugins → plugins}/toolbar/jsenv_plugin_toolbar.js +0 -0
  75. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +4 -3
  76. package/src/plugins/transpilation/babel/new_stylesheet/client/new_stylesheet.js +25 -55
  77. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +44 -24
  78. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +6 -1
  79. package/src/plugins/url_analysis/html/html_urls.js +8 -8
  80. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +3 -1
  81. package/src/test/execute_plan.js +36 -54
  82. package/src/test/execute_test_plan.js +2 -2
  83. package/src/test/logs_file_execution.js +60 -27
  84. package/src/test/logs_file_execution.test.mjs +41 -0
  85. package/dist/js/event_source_client.js +0 -528
  86. package/src/helpers/event_source/sse_service.js +0 -53
  87. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +0 -193
  88. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +0 -203
  89. package/src/plugins/html_supervisor/client/error_in_document.js +0 -198
@@ -1,44 +1,63 @@
1
1
  import { timeStart } from "@jsenv/server"
2
2
 
3
- export const createPluginController = ({
4
- plugins,
5
- scenario,
6
- hooks = [
7
- "init",
8
- "resolveUrl",
9
- "redirectUrl",
10
- "fetchUrlContent",
11
- "transformUrlContent",
12
- "transformUrlSearchParams",
13
- "formatUrl",
14
- "finalizeUrlContent",
15
- "cooked",
16
- "destroy",
17
- ],
18
- }) => {
19
- plugins = flattenAndFilterPlugins(plugins, { scenario })
20
- // precompute a list of hooks per hookName
21
- // For one major reason:
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
- // And also it should increase perf as there is less work to do
24
+ // also it should increase perf as there is less work to do
25
+
24
26
  const hookGroups = {}
25
- const addHook = (hookName) => {
26
- const hooks = []
27
- plugins.forEach((plugin) => {
28
- const hook = plugin[hookName]
29
- if (hook) {
30
- hooks.push({
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: hook,
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
- hooks.forEach((hookName) => {
41
- addHook(hookName)
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.hookName
53
- const timeEnd = timeStart(
54
- `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
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.hookName, valueReturned)
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.hookName
72
- const timeEnd = timeStart(
73
- `${currentHookName}-${currentPlugin.name.replace("jsenv:", "")}`,
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.hookName, valueReturned)
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
- for (const hook of hooks) {
88
- const returnValue = callHook(hook, info, context)
89
- if (returnValue) {
90
- callback(returnValue)
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
- await hooks.reduce(async (previous, hook) => {
97
- await previous
98
- const returnValue = await callAsyncHook(hook, info, context)
99
- if (returnValue && callback) {
100
- await callback(returnValue)
101
- }
102
- }, Promise.resolve())
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
- for (const hook of hooks) {
108
- const returnValue = callHook(hook, info, context)
109
- if (returnValue) {
110
- return returnValue
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
- addHook,
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 = (pluginsRaw, { scenario }) => {
156
- const plugins = []
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
- plugins.push(pluginEntry)
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
- plugins.push(pluginEntry)
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
- plugins.push(pluginEntry)
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
- pluginsRaw.forEach((plugin) => visitPluginEntry(plugin))
203
- return plugins
237
+ plugins.forEach((plugin) => visitPluginEntry(plugin))
238
+ return flatPlugins
204
239
  }
205
240
 
206
241
  const getHookFunction = (
@@ -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(),
@@ -83,8 +84,6 @@ export const getCorePlugins = ({
83
84
  ? [
84
85
  jsenvPluginAutoreload({
85
86
  ...clientAutoreload,
86
- rootDirectoryUrl,
87
- urlGraph,
88
87
  scenario,
89
88
  clientFileChangeCallbackList,
90
89
  clientFilesPruneCallbackList,
@@ -92,5 +91,6 @@ export const getCorePlugins = ({
92
91
  ]
93
92
  : []),
94
93
  jsenvPluginCacheControl(),
94
+ ...(explorer ? [jsenvPluginExplorer(explorer)] : []),
95
95
  ]
96
96
  }
@@ -1,27 +1,103 @@
1
- /* eslint-env browser */
1
+ const STATUSES = {
2
+ CONNECTING: "connecting",
3
+ CONNECTED: "connected",
4
+ DISCONNECTED: "disconnected",
5
+ }
2
6
 
3
7
  export const createEventSourceConnection = (
4
8
  eventSourceUrl,
5
- events = {},
6
- { retryMaxAttempt = Infinity, retryAllocatedMs = Infinity, lastEventId } = {},
9
+ {
10
+ retryMaxAttempt = Infinity,
11
+ retryAllocatedMs = Infinity,
12
+ lastEventId,
13
+ useEventsToManageConnection = true,
14
+ } = {},
7
15
  ) => {
8
16
  const { EventSource } = window
9
17
  if (typeof EventSource !== "function") {
10
18
  return () => {}
11
19
  }
12
20
 
21
+ let eventSource
22
+ const listenersMap = new Map()
23
+ const callbacksMap = new Map()
13
24
  const eventSourceOrigin = new URL(eventSourceUrl).origin
14
- Object.keys(events).forEach((eventName) => {
15
- const eventCallback = events[eventName]
16
- events[eventName] = (e) => {
17
- if (e.origin === eventSourceOrigin) {
18
- if (e.lastEventId) {
19
- lastEventId = e.lastEventId
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
+ })
46
+ }
47
+ }
48
+ listenersMap.set(eventName, eventListener)
49
+ if (eventSource) {
50
+ eventSource.addEventListener(eventName, eventListener)
20
51
  }
21
- eventCallback(e)
22
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()
23
63
  }
24
- })
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
+ }
100
+ }
25
101
 
26
102
  const status = {
27
103
  value: "default",
@@ -37,22 +113,34 @@ export const createEventSourceConnection = (
37
113
  let _disconnect = () => {}
38
114
 
39
115
  const attemptConnection = (url) => {
40
- const eventSource = new EventSource(url, {
116
+ if (
117
+ status.value === STATUSES.CONNECTING ||
118
+ status.value === STATUSES.CONNECTED
119
+ ) {
120
+ return
121
+ }
122
+ eventSource = new EventSource(url, {
41
123
  withCredentials: true,
42
124
  })
43
125
  _disconnect = () => {
44
- if (status.value !== "connecting" && status.value !== "connected") {
126
+ if (
127
+ status.value !== STATUSES.CONNECTING &&
128
+ status.value !== STATUSES.CONNECTED
129
+ ) {
45
130
  console.warn(
46
131
  `disconnect() ignored because connection is ${status.value}`,
47
132
  )
48
133
  return
49
134
  }
50
- eventSource.onerror = undefined
51
- eventSource.close()
52
- Object.keys(events).forEach((eventName) => {
53
- eventSource.removeEventListener(eventName, events[eventName])
54
- })
55
- status.goTo("disconnected")
135
+ if (eventSource) {
136
+ eventSource.onerror = undefined
137
+ eventSource.close()
138
+ listenersMap.forEach((listener, eventName) => {
139
+ eventSource.removeEventListener(eventName, listener)
140
+ })
141
+ }
142
+ eventSource = null
143
+ status.goTo(STATUSES.DISCONNECTED)
56
144
  }
57
145
  let retryCount = 0
58
146
  let firstRetryMs = Date.now()
@@ -78,7 +166,7 @@ export const createEventSourceConnection = (
78
166
  }
79
167
 
80
168
  retryCount++
81
- status.goTo("connecting")
169
+ status.goTo(STATUSES.CONNECTING)
82
170
  return
83
171
  }
84
172
 
@@ -88,24 +176,22 @@ export const createEventSourceConnection = (
88
176
  }
89
177
  }
90
178
  eventSource.onopen = () => {
91
- status.goTo("connected")
179
+ status.goTo(STATUSES.CONNECTED)
92
180
  }
93
- Object.keys(events).forEach((eventName) => {
94
- eventSource.addEventListener(eventName, events[eventName])
181
+ listenersMap.forEach((listener, eventName) => {
182
+ eventSource.addEventListener(eventName, listener)
95
183
  })
96
- if (!events.hasOwnProperty("welcome")) {
97
- eventSource.addEventListener("welcome", (e) => {
98
- if (e.origin === eventSourceOrigin && e.lastEventId) {
99
- lastEventId = e.lastEventId
100
- }
184
+ if (!listenersMap.has("welcome")) {
185
+ addEventCallbacks({
186
+ welcome: () => {}, // to update lastEventId
101
187
  })
102
188
  }
103
- status.goTo("connecting")
189
+ status.goTo(STATUSES.CONNECTING)
104
190
  }
105
191
 
106
- let connect = () => {
192
+ let _connect = () => {
107
193
  attemptConnection(eventSourceUrl)
108
- connect = () => {
194
+ _connect = () => {
109
195
  attemptConnection(
110
196
  lastEventId
111
197
  ? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId)
@@ -115,7 +201,10 @@ export const createEventSourceConnection = (
115
201
  }
116
202
 
117
203
  const removePageUnloadListener = listenPageUnload(() => {
118
- if (status.value === "connecting" || status.value === "connected") {
204
+ if (
205
+ status.value === STATUSES.CONNECTING ||
206
+ status.value === STATUSES.CONNECTED
207
+ ) {
119
208
  _disconnect()
120
209
  }
121
210
  })
@@ -123,11 +212,14 @@ export const createEventSourceConnection = (
123
212
  const destroy = () => {
124
213
  removePageUnloadListener()
125
214
  _disconnect()
215
+ listenersMap.clear()
216
+ callbacksMap.clear()
126
217
  }
127
218
 
128
219
  return {
129
220
  status,
130
- connect,
221
+ connect: () => _connect(),
222
+ addEventCallbacks,
131
223
  disconnect: () => _disconnect(),
132
224
  destroy,
133
225
  }
@@ -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
+ }