websocket sessions now get a UID + graceful close/cleanup, server graceful shutdown

This commit is contained in:
dab 2025-08-13 17:12:05 +00:00
parent e9d3ab17ca
commit f049888aa7
2 changed files with 61 additions and 7 deletions

View file

@ -1,3 +1,5 @@
import crypto from 'crypto'
const async_build_js_script = async function (path_js_entry_script, build_options) { const async_build_js_script = async function (path_js_entry_script, build_options) {
const result = await Bun.build({ const result = await Bun.build({
entrypoints: [ path_js_entry_script ], entrypoints: [ path_js_entry_script ],
@ -25,7 +27,7 @@ const get_siteroot_html = function ({ page_title }) {return `
</html> </html>
`.trim()} `.trim()}
const start_server = function ({ config, jsbuild_app_frontend }) { const start_server = function ({ context_meta, config, jsbuild_app_frontend }) {
const server = Bun.serve({ const server = Bun.serve({
hostname: config.host || '127.0.0.1', hostname: config.host || '127.0.0.1',
port : config.port || 8800, port : config.port || 8800,
@ -50,8 +52,13 @@ const start_server = function ({ config, jsbuild_app_frontend }) {
) )
} }
else if (url.pathname === '/ws') { else if (url.pathname === '/ws') {
if (server.upgrade(req)) { const uid = crypto.randomUUID()
// do not return a Response if upgrade fails if (server.upgrade(req, {
data: {
uid,
},
})) {
// do not return a Response if upgrade succeeds
resp = undefined resp = undefined
} }
resp = new Response('Upgrade failed', { status: 500 }) resp = new Response('Upgrade failed', { status: 500 })
@ -62,20 +69,67 @@ const start_server = function ({ config, jsbuild_app_frontend }) {
return resp return resp
}, },
websocket: { websocket: {
open: function (ws) {
const map_obj = {
ws,
closed_by_server: false,
}
context_meta.ws_map.set(ws.data.uid, map_obj)
return
},
close: function (ws, code, message) {
const map_obj = context_meta.ws_map.get(ws.data.uid)
if (map_obj === undefined) {
console.warn(['Unexpected: ws_map missing uid', ws.data.uid])
}
else {
if (map_obj.closed_by_server === true) {
console.info(['ws closed by server', ws.data.uid])
// cleanup will happen in outer context
}
else {
console.info(['ws closed by client', ws.data.uid, { code, message }])
// cleanup from this context
context_meta.ws_map.delete(ws.data.uid)
}
}
return
},
message: function (ws, message) {}, message: function (ws, message) {},
}, },
}) })
return server return server
} }
const close_active_ws_sessions = function ({ context_meta }) {
const keys_to_delete = []
for (const [ uid, map_obj ] of context_meta.ws_map) {
const code = 1001
const reason = 'Going Away'
map_obj.closed_by_server = true
map_obj.ws.close(code, reason)
keys_to_delete.push(uid)
}
for (let key of keys_to_delete) {
context_meta.ws_map.delete(key)
}
return
}
const async_run = async function ({ const async_run = async function ({
config, config,
jsbuild_app_frontend, jsbuild_app_frontend,
}) { }) {
const server = start_server({ config, jsbuild_app_frontend }) const context_meta = {
ws_map: new Map(),
}
const server = start_server({ context_meta, config, jsbuild_app_frontend })
console.info(server) console.info(server)
process.on('SIGINT', () => { process.on('SIGINT', async () => {
console.log('SIGINT intercepted') console.log('SIGINT intercepted')
close_active_ws_sessions({ context_meta })
// NOTE: wait for a second for teardown to fully complete
await Bun.sleep(2000)
process.exit() process.exit()
}) })
return return

View file

@ -42,9 +42,9 @@ class FramerockUtils {
on_open && on_open() on_open && on_open()
return return
} }
const func_on_close = () => { const func_on_close = (event) => {
console.info('WS CLOSE') console.info('WS CLOSE')
on_close && on_close() on_close && on_close(event)
return return
} }
const func_on_message = (e) => { const func_on_message = (e) => {