@jsenv/core 27.5.3 → 27.7.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 (30) hide show
  1. package/dist/js/autoreload.js +10 -6
  2. package/dist/js/html_supervisor_installer.js +272 -163
  3. package/dist/js/html_supervisor_setup.js +10 -2
  4. package/dist/js/server_events_client.js +249 -216
  5. package/dist/js/wrapper.mjs +4233 -0
  6. package/dist/main.js +21342 -21629
  7. package/package.json +4 -4
  8. package/src/build/build.js +15 -13
  9. package/src/dev/start_dev_server.js +10 -10
  10. package/src/execute/runtimes/browsers/chromium.js +1 -1
  11. package/src/execute/runtimes/browsers/firefox.js +1 -1
  12. package/src/execute/runtimes/browsers/webkit.js +1 -1
  13. package/src/omega/kitchen.js +13 -15
  14. package/src/omega/omega_server.js +15 -0
  15. package/src/omega/server/file_service.js +11 -84
  16. package/src/omega/url_graph/url_graph_load.js +0 -2
  17. package/src/omega/url_graph/url_info_transformations.js +27 -0
  18. package/src/omega/url_graph.js +1 -0
  19. package/src/plugins/autoreload/client/autoreload.js +10 -4
  20. package/src/plugins/html_supervisor/client/error_formatter.js +187 -66
  21. package/src/plugins/html_supervisor/client/error_overlay.js +29 -31
  22. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +22 -67
  23. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +10 -2
  24. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +96 -25
  25. package/src/plugins/server_events/client/connection_manager.js +165 -0
  26. package/src/plugins/server_events/client/event_source_connection.js +50 -256
  27. package/src/plugins/server_events/client/events_manager.js +75 -0
  28. package/src/plugins/server_events/client/server_events_client.js +12 -11
  29. package/src/plugins/server_events/client/web_socket_connection.js +81 -0
  30. package/src/plugins/server_events/server_events_dispatcher.js +70 -54
@@ -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
- import { createEventSourceConnection } from "./event_source_connection.js"
1
+ /* globals self */
2
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
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
- addEventCallbacks,
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 destroyCallbackList = createCallbackListNotifiedOnce()
6
- const rooms = []
7
- const sseRoomLimit = 100
2
+ const clients = []
3
+ const MAX_CLIENTS = 100
8
4
 
9
- destroyCallbackList.add(() => {
10
- rooms.forEach((room) => {
11
- room.close()
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
- 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)
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
- room.request = request
45
- return room
46
+ destroy: () => {
47
+ websocket.terminate()
48
+ },
49
+ }
50
+ client.sendEvent({ type: "welcome" })
51
+ return addClient(client)
46
52
  },
47
- dispatch: ({ type, data }) => {
48
- rooms.forEach((room) =>
49
- room.sendEventToAllClients({
50
- type,
51
- data: JSON.stringify(data),
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
- dispatchToRoomsMatching: ({ type, data }, predicate) => {
56
- rooms.forEach((room) => {
57
- if (predicate(room)) {
58
- room.sendEventToAllClients({
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
- destroyCallbackList.notify()
80
+ clients.forEach((client) => {
81
+ client.destroy()
82
+ })
67
83
  },
68
84
  }
69
85
  }