import crypto from 'crypto' const async_build_js_script = async function (path_js_entry_script, build_options) { const result = await Bun.build({ entrypoints: [ path_js_entry_script ], env: 'disable', minify: false, ...build_options, }) const str_out = await result.outputs[0].text() return str_out } const get_siteroot_html = function ({ page_title }) {return ` ${page_title || 'Page Title'} `.trim()} const start_server = function ({ context_meta, config, jsbuild_app_frontend }) { const server = Bun.serve({ hostname: config.host || '127.0.0.1', port : config.port || 8800, async fetch (req, server) { const url = new URL(req.url) let resp if (url.pathname === '/') { resp = new Response(get_siteroot_html({ page_title: config.page_title }), { status: 200, headers: { 'Content-Type': 'text/html' } }) } else if (url.pathname === '/index.js') { const str_js = await jsbuild_app_frontend() resp = new Response( await async_build_js_script(import.meta.dir + '/../frontend/index.js', { define: { JS_APP_FRONTEND: JSON.stringify(str_js), } }), { status: 200, headers: { 'Content-Type': 'text/javascript' } } ) } else if (url.pathname === '/ws') { const uid = crypto.randomUUID() if (server.upgrade(req, { data: { uid, }, })) { // do not return a Response if upgrade succeeds resp = undefined } resp = new Response('Upgrade failed', { status: 500 }) } else { resp = new Response(null, { status: 404 }) } return resp }, 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) {}, }, }) 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 ({ config, jsbuild_app_frontend, }) { const context_meta = { ws_map: new Map(), } const server = start_server({ context_meta, config, jsbuild_app_frontend }) console.info(server) process.on('SIGINT', async () => { 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() }) return } export { async_run }