v0 refactor, will rebuild from this baseline
This commit is contained in:
parent
5eb15b509b
commit
e9d3ab17ca
2 changed files with 61 additions and 488 deletions
309
backend/index.js
309
backend/index.js
|
@ -1,45 +1,22 @@
|
||||||
import crypto from 'crypto'
|
const async_build_js_script = async function (path_js_entry_script, build_options) {
|
||||||
|
|
||||||
const APP_CONSTANTS = {
|
|
||||||
EVENTTYPES: {
|
|
||||||
HBPING : 1,
|
|
||||||
HBPONG : 2,
|
|
||||||
STREAM_JOIN : 3,
|
|
||||||
STREAM_LEAVE : 4,
|
|
||||||
EVENTPAIR_RESP: 5,
|
|
||||||
EVENTSUB_MSG : 6,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const make_kvsafe_stable_key_for_ns = function (str_ns, x) {
|
|
||||||
const hash = crypto.createHash('md5').update(x).digest('hex')
|
|
||||||
const out_key = str_ns + ':' + hash
|
|
||||||
return out_key
|
|
||||||
}
|
|
||||||
|
|
||||||
const PAGE_TITLE = Bun.env['PAGE_TITLE']
|
|
||||||
|
|
||||||
const POLLING_SLEEP_MAINLOOP = 1000
|
|
||||||
|
|
||||||
const async_build_js_script = async (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 ],
|
||||||
...build_options,
|
|
||||||
env: 'disable',
|
env: 'disable',
|
||||||
minify: false,
|
minify: false,
|
||||||
|
...build_options,
|
||||||
})
|
})
|
||||||
const str_out = await result.outputs[0].text()
|
const str_out = await result.outputs[0].text()
|
||||||
return str_out
|
return str_out
|
||||||
}
|
}
|
||||||
|
|
||||||
const get_siteroot_html = function () {return `
|
const get_siteroot_html = function ({ page_title }) {return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang=en>
|
<html lang=en>
|
||||||
<head>
|
<head>
|
||||||
<meta charset=utf-8>
|
<meta charset=utf-8>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>${PAGE_TITLE}</title>
|
<title>${page_title || 'Page Title'}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="text/javascript" src="/index.js">
|
<script type="text/javascript" src="/index.js">
|
||||||
|
@ -48,45 +25,22 @@ const get_siteroot_html = function () {return `
|
||||||
</html>
|
</html>
|
||||||
`.trim()}
|
`.trim()}
|
||||||
|
|
||||||
|
const start_server = function ({ config, jsbuild_app_frontend }) {
|
||||||
const WS_SESSIONS_MAP = {}
|
|
||||||
|
|
||||||
|
|
||||||
const wrap_send_event = (ws, subevt_type, subevt_data) => {
|
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
|
||||||
ws.send(new TextEncoder().encode(JSON.stringify([ subevt_type, subevt_data ])))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(['ws readyState !== OPEN : dropping event send', { subevt_type, subevt_data }])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const start_server = function ({ handlers, jsbuild_on_ws_connect }) {
|
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
hostname: Bun.env['CTX_WEBSERVER_HOST'] || '127.0.0.1',
|
hostname: config.host || '127.0.0.1',
|
||||||
port : Bun.env['CTX_WEBSERVER_PORT'] ? parseInt(Bun.env['CTX_WEBSERVER_PORT']) : 8800,
|
port : config.port || 8800,
|
||||||
async fetch (req, server) {
|
async fetch (req, server) {
|
||||||
|
|
||||||
const tstamp_handled = Date.now()
|
|
||||||
|
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
|
|
||||||
let resp
|
let resp
|
||||||
if (url.pathname === '/') {
|
if (url.pathname === '/') {
|
||||||
resp = new Response(get_siteroot_html(), { status: 200, headers: { 'Content-Type': 'text/html' } })
|
resp = new Response(get_siteroot_html({ page_title: config.page_title }), { status: 200, headers: { 'Content-Type': 'text/html' } })
|
||||||
}
|
}
|
||||||
else if (url.pathname === '/index.js') {
|
else if (url.pathname === '/index.js') {
|
||||||
|
const str_js = await jsbuild_app_frontend()
|
||||||
const str_js = await jsbuild_on_ws_connect()
|
|
||||||
|
|
||||||
resp = new Response(
|
resp = new Response(
|
||||||
await async_build_js_script(import.meta.dir + '/../frontend/index.js', {
|
await async_build_js_script(import.meta.dir + '/../frontend/index.js', {
|
||||||
define: {
|
define: {
|
||||||
APP_CONSTANTS : JSON.stringify(APP_CONSTANTS),
|
JS_APP_FRONTEND: JSON.stringify(str_js),
|
||||||
JS_ON_WS_CONNECT: JSON.stringify(str_js),
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -94,260 +48,37 @@ const start_server = function ({ handlers, jsbuild_on_ws_connect }) {
|
||||||
headers: { 'Content-Type': 'text/javascript' }
|
headers: { 'Content-Type': 'text/javascript' }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (url.pathname === '/ws') {
|
else if (url.pathname === '/ws') {
|
||||||
|
if (server.upgrade(req)) {
|
||||||
const uid_session = crypto.randomUUID()
|
// do not return a Response if upgrade fails
|
||||||
|
|
||||||
const ws_data = {
|
|
||||||
uid_session,
|
|
||||||
force_close: false,
|
|
||||||
active_subs: {},
|
|
||||||
pending_teardowns: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server.upgrade(req, {
|
|
||||||
data: ws_data,
|
|
||||||
})) {
|
|
||||||
// do not return a Response
|
|
||||||
resp = undefined
|
resp = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = new Response('Upgrade failed', { status: 500 })
|
resp = new Response('Upgrade failed', { status: 500 })
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resp = new Response(null, { status: 404 })
|
resp = new Response(null, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
},
|
},
|
||||||
websocket: {
|
websocket: {
|
||||||
message (ws, message) {
|
message: function (ws, message) {},
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const [ uid_envelope, evt_type, evt_data ] = JSON.parse(new TextDecoder().decode(message))
|
|
||||||
|
|
||||||
if (evt_type === APP_CONSTANTS.EVENTTYPES.HBPONG) {
|
|
||||||
|
|
||||||
// distinctly does NOT need a "response" (b/c it already IS a response, to Ping)
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (evt_type === APP_CONSTANTS.EVENTTYPES.STREAM_JOIN) {
|
|
||||||
|
|
||||||
const { uid_sub, init_params, stream_key } = evt_data
|
|
||||||
|
|
||||||
console.info({ uid_sub, init_params, stream_key })
|
|
||||||
|
|
||||||
const func_stream_handle = handlers[stream_key]
|
|
||||||
|
|
||||||
if (func_stream_handle !== undefined) {
|
|
||||||
|
|
||||||
// NOTE: allow client to set "uid_sub", BUT don't "trust" as safe key for our map
|
|
||||||
const uid_safe_sub = make_kvsafe_stable_key_for_ns('safesub', uid_sub)
|
|
||||||
|
|
||||||
const handler_meta = {
|
|
||||||
init_params,
|
|
||||||
|
|
||||||
do_stop: false,
|
|
||||||
|
|
||||||
func_sub_send: (sub_msg) => {
|
|
||||||
|
|
||||||
const is_final_message = false
|
|
||||||
|
|
||||||
wrap_send_event(ws, APP_CONSTANTS.EVENTTYPES.EVENTSUB_MSG, [ uid_sub, { is_final_message, sub_msg } ])
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func_stream_handle(handler_meta).then(() => {
|
|
||||||
|
|
||||||
console.info('(stream handler finished)')
|
|
||||||
|
|
||||||
const is_final_message = true
|
|
||||||
const sub_msg = null
|
|
||||||
|
|
||||||
wrap_send_event(ws, APP_CONSTANTS.EVENTTYPES.EVENTSUB_MSG, [ uid_sub, { is_final_message, sub_msg } ])
|
|
||||||
|
|
||||||
delete ws.data.active_subs[uid_safe_sub]
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
}).catch(console.error)
|
|
||||||
|
|
||||||
ws.data.active_subs[uid_safe_sub] = {
|
|
||||||
func_teardown: () => {
|
|
||||||
handler_meta.do_stop = true
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap_send_event(ws, APP_CONSTANTS.EVENTTYPES.EVENTPAIR_RESP, [ uid_envelope, { success: true } ])
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wrap_send_event(ws, APP_CONSTANTS.EVENTTYPES.EVENTPAIR_RESP, [ uid_envelope, { success: false } ])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (evt_type === APP_CONSTANTS.EVENTTYPES.STREAM_LEAVE) {
|
|
||||||
|
|
||||||
const { uid_sub } = evt_data
|
|
||||||
// (re-make hashed key)
|
|
||||||
const uid_safe_sub = make_kvsafe_stable_key_for_ns('safesub', uid_sub)
|
|
||||||
|
|
||||||
const sub_info = ws.data.active_subs[uid_safe_sub]
|
|
||||||
if (sub_info !== undefined) {
|
|
||||||
sub_info.func_teardown()
|
|
||||||
delete ws.data.active_subs[uid_safe_sub]
|
|
||||||
console.info(['tore down (gracefully) Active Sub', { uid_safe_sub }])
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(['no sub_info for uid_safe_sub', { uid_safe_sub }])
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap_send_event(ws, APP_CONSTANTS.EVENTTYPES.EVENTPAIR_RESP, [ uid_envelope, { success: true } ])
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
console.warn([ 'UNEXPECTED evt_type', evt_type ])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
},
|
|
||||||
open (ws) {
|
|
||||||
|
|
||||||
console.info('websocket OPEN')
|
|
||||||
|
|
||||||
const { uid_session } = ws.data
|
|
||||||
|
|
||||||
WS_SESSIONS_MAP[uid_session] = {
|
|
||||||
ws,
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
},
|
|
||||||
close (ws, code, message) {
|
|
||||||
|
|
||||||
console.info('websocket CLOSE')
|
|
||||||
|
|
||||||
const asyncfunc = async () => {
|
|
||||||
// NOTE: blocking
|
|
||||||
for (let func_teardown of ws.data.pending_teardowns) {
|
|
||||||
await func_teardown()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
asyncfunc().then(()=>{}).catch(console.error)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
},
|
|
||||||
drain (ws) {
|
|
||||||
console.info('websocket DRAIN')
|
|
||||||
return
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return server
|
return server
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const async_run = async ({
|
const async_run = async function ({
|
||||||
handlers,
|
config,
|
||||||
jsbuild_on_ws_connect,
|
jsbuild_app_frontend,
|
||||||
}) => {
|
}) {
|
||||||
|
const server = start_server({ config, jsbuild_app_frontend })
|
||||||
const server = start_server({ handlers, jsbuild_on_ws_connect })
|
|
||||||
console.info(server)
|
console.info(server)
|
||||||
|
|
||||||
const ctx_obj = {
|
|
||||||
do_shutdown: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
|
console.log('SIGINT intercepted')
|
||||||
console.info('Received SIGINT')
|
process.exit()
|
||||||
|
|
||||||
// NOTE: force close all WS
|
|
||||||
for (let [ uid_session, { ws } ] of Object.entries(WS_SESSIONS_MAP)) {
|
|
||||||
ws.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// let the loop "handle" whatever remains, then get out
|
|
||||||
setTimeout(() => {
|
|
||||||
ctx_obj.do_shutdown = true
|
|
||||||
return
|
|
||||||
}, 3 * 1000)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let loop_idx = 0
|
|
||||||
|
|
||||||
while (ctx_obj.do_shutdown === false) {
|
|
||||||
|
|
||||||
loop_idx += 1
|
|
||||||
|
|
||||||
//console.info(`MAINLOOP : ${loop_idx}`)
|
|
||||||
|
|
||||||
// process Clients
|
|
||||||
|
|
||||||
// copy beforehand so can delete while iterating
|
|
||||||
const lst_entries = [...Object.entries(WS_SESSIONS_MAP)]
|
|
||||||
|
|
||||||
for (let [ uid_session, { ws } ] of lst_entries) {
|
|
||||||
|
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
|
||||||
|
|
||||||
if (ws.data.force_close === true) {
|
|
||||||
console.warn([ 'FORCE CLOSING', ws.data.uid_session ])
|
|
||||||
ws.close()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wrap_send_event(ws, APP_CONSTANTS.EVENTTYPES.HBPING, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (ws.readyState === WebSocket.CLOSED) {
|
|
||||||
|
|
||||||
// handle Active Subs
|
|
||||||
for (let [ uid_sub, sub_info ] of Object.entries(ws.data.active_subs)) {
|
|
||||||
sub_info.func_teardown()
|
|
||||||
console.info(['tore down hanging Active Sub', { uid_sub }])
|
|
||||||
}
|
|
||||||
ws.data.active_subs = {}
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
delete WS_SESSIONS_MAP[uid_session]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
await Bun.sleep(POLLING_SLEEP_MAINLOOP)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { async_run }
|
export { async_run }
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
// provided by external "define": APP_CONSTANTS, JS_ON_WS_CONNECT
|
// provided by outer context: APP_CONSTANTS, JS_APP_FRONTEND
|
||||||
|
|
||||||
const get_probably_unique_idstr_32len = function () {
|
|
||||||
return (Date.now().toString() + Math.random().toString().replaceAll('.','')).padEnd(32, 'x').slice(0, 32)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setup_safer_eval = function (lst_idents) {
|
const setup_safer_eval = function (lst_idents) {
|
||||||
const runCodeWithCustomFunction = (obj) => {
|
const runCodeWithCustomFunction = (obj) => {
|
||||||
|
@ -22,218 +18,64 @@ return
|
||||||
return the_eval_result
|
return the_eval_result
|
||||||
}
|
}
|
||||||
|
|
||||||
const render_app = function ({ APP_CONSTANTS, func_send_event, handle_event_response }) {
|
class FramerockUtils {
|
||||||
|
constructor () {
|
||||||
const uidsub_map = {}
|
|
||||||
const register_streamjoin = (stream_key, init_params, func_callback) => {
|
|
||||||
|
|
||||||
const uid_sub = get_probably_unique_idstr_32len()
|
this._ws = undefined
|
||||||
|
|
||||||
console.info(['register_streamjoin', { stream_key, init_params, uid_sub }])
|
this.setup_transport = this._setup_transport.bind(this)
|
||||||
|
this.teardown_transport = this._teardown_transport.bind(this)
|
||||||
|
|
||||||
uidsub_map[uid_sub] = { func_callback }
|
return
|
||||||
|
|
||||||
func_send_event(APP_CONSTANTS.EVENTTYPES.STREAM_JOIN, { uid_sub, stream_key, init_params }, (full_result) => {
|
}
|
||||||
console.info(['STREAM_JOIN', { full_result }])
|
_setup_transport ({ on_open, on_close }) {
|
||||||
|
|
||||||
|
console.info('SETUP TRANSPORT')
|
||||||
|
|
||||||
|
this._ws = new WebSocket(`ws://${window.location.host}/ws`)
|
||||||
|
const ws = this._ws
|
||||||
|
ws.binaryType = 'arraybuffer'
|
||||||
|
|
||||||
|
const func_on_open = () => {
|
||||||
|
console.info('WS OPEN')
|
||||||
|
on_open && on_open()
|
||||||
return
|
return
|
||||||
})
|
}
|
||||||
|
const func_on_close = () => {
|
||||||
const func_teardown = () => {
|
console.info('WS CLOSE')
|
||||||
|
on_close && on_close()
|
||||||
func_send_event(APP_CONSTANTS.EVENTTYPES.STREAM_LEAVE, { uid_sub }, (full_result) => {
|
return
|
||||||
console.info(['STREAM_LEAVE', { full_result }])
|
}
|
||||||
return
|
const func_on_message = (e) => {
|
||||||
})
|
console.info('WS MESSAGE')
|
||||||
|
return
|
||||||
delete uidsub_map[uid_sub]
|
}
|
||||||
|
const func_on_error = () => {
|
||||||
|
console.info('WS ERROR')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return func_teardown
|
ws.addEventListener('open' , func_on_open)
|
||||||
|
ws.addEventListener('close' , func_on_close)
|
||||||
}
|
ws.addEventListener('message', func_on_message)
|
||||||
|
ws.addEventListener('error' , func_on_error)
|
||||||
const func_eval_appscript = () => {
|
|
||||||
|
|
||||||
console.info('EVAL APPSCRIPT')
|
|
||||||
|
|
||||||
do_safereval_strscript({ register_streamjoin }, JS_ON_WS_CONNECT)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
_teardown_transport () {
|
||||||
|
|
||||||
const func_handle_event = (evt_type, evt_data) => {
|
|
||||||
|
|
||||||
//console.info(['HANDLING EVENT!', [ evt_type, evt_data ]])
|
console.info('TEARDOWN TRANSPORT')
|
||||||
|
|
||||||
if (evt_type === APP_CONSTANTS.EVENTTYPES.HBPING) {
|
// NOTE: has no effect if ws already closed
|
||||||
func_send_event(APP_CONSTANTS.EVENTTYPES.HBPONG, true)
|
this._ws.close()
|
||||||
}
|
|
||||||
else if (evt_type === APP_CONSTANTS.EVENTTYPES.EVENTPAIR_RESP) {
|
|
||||||
handle_event_response(evt_data)
|
|
||||||
}
|
|
||||||
else if (evt_type === APP_CONSTANTS.EVENTTYPES.EVENTSUB_MSG) {
|
|
||||||
|
|
||||||
const [ uid_sub, stream_data ] = evt_data
|
|
||||||
|
|
||||||
const sub_info = uidsub_map[uid_sub]
|
|
||||||
|
|
||||||
if (sub_info === undefined) {
|
|
||||||
console.warn(['EVENTSUB_MSG but no handler', { uid_sub }])
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
const { func_callback } = sub_info
|
|
||||||
|
|
||||||
func_callback(stream_data.sub_msg, stream_data.is_final_message)
|
|
||||||
|
|
||||||
// NOTE: automatic teardown
|
|
||||||
if (stream_data.is_final_message === true) {
|
|
||||||
console.info(['auto-teardown!', uid_sub])
|
|
||||||
delete uidsub_map[uid_sub]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(['unhandled evt_type', { evt_type }])
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
const func_handle_wsopen = () => {
|
|
||||||
func_eval_appscript()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const func_handle_wsclose = () => {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return { func_handle_event, func_handle_wsopen, func_handle_wsclose }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const async_main = async function () {
|
const FRAMEROCK_UTILS = new FramerockUtils()
|
||||||
|
|
||||||
let ref_renderapp
|
|
||||||
|
|
||||||
const ws = new WebSocket(`ws://${window.location.host}/ws`)
|
|
||||||
ws.binaryType = 'arraybuffer'
|
|
||||||
|
|
||||||
const event_pair_map = {}
|
|
||||||
|
|
||||||
const func_send_event = (subevt_type, subevt_data, func_callback) => {
|
|
||||||
|
|
||||||
//console.info(['SEND EVENT', { subevt_type, subevt_data }])
|
|
||||||
|
|
||||||
let uid_envelope
|
|
||||||
if (func_callback) {
|
|
||||||
uid_envelope = get_probably_unique_idstr_32len()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// things might not "need" envelope/response
|
|
||||||
uid_envelope = null
|
|
||||||
}
|
|
||||||
|
|
||||||
event_pair_map[uid_envelope] = {
|
|
||||||
handle_response: (x) => {
|
|
||||||
if (func_callback) {
|
|
||||||
func_callback(x)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(['handle_response but no callback', { subevt_type, subevt_data }])
|
|
||||||
}
|
|
||||||
// clean up self
|
|
||||||
delete event_pair_map[uid_envelope]
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
|
||||||
ws.send(new TextEncoder().encode(JSON.stringify([ uid_envelope, subevt_type, subevt_data ])))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(['ws readyState !== OPEN : dropping event send', { subevt_type, subevt_data }])
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const handle_event_response = (evt_data) => {
|
|
||||||
|
|
||||||
//console.info(['HANDLE EVENT', { evt_data }])
|
|
||||||
|
|
||||||
const [ uid_envelope, result ] = evt_data
|
|
||||||
|
|
||||||
const { handle_response } = event_pair_map[uid_envelope]
|
|
||||||
|
|
||||||
if (handle_response) {
|
|
||||||
handle_response(result)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(['missing ref to handle_response', { uid_envelope, result }])
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const func_on_open = () => {
|
|
||||||
console.info('WS OPEN')
|
|
||||||
// forward to App
|
|
||||||
ref_renderapp && ref_renderapp.func_handle_wsopen()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const func_on_close = () => {
|
|
||||||
console.info('WS CLOSE')
|
|
||||||
// forward to App
|
|
||||||
ref_renderapp && ref_renderapp.func_handle_wsclose()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const func_on_message = (e) => {
|
|
||||||
try {
|
|
||||||
const [ evt_type, evt_data ] = JSON.parse(new TextDecoder().decode(e.data))
|
|
||||||
|
|
||||||
//console.info(['MESSAGE', { evt_type, evt_data }])
|
|
||||||
|
|
||||||
// forward to App
|
|
||||||
ref_renderapp && ref_renderapp.func_handle_event(evt_type, evt_data)
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const func_on_error = () => {
|
|
||||||
console.info('WS ERROR')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.addEventListener('open' , func_on_open)
|
|
||||||
ws.addEventListener('close' , func_on_close)
|
|
||||||
ws.addEventListener('message', func_on_message)
|
|
||||||
ws.addEventListener('error' , func_on_error)
|
|
||||||
|
|
||||||
ref_renderapp = render_app({ APP_CONSTANTS, func_send_event, handle_event_response })
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let after_page_ready
|
do_safereval_strscript({ FRAMEROCK_UTILS }, JS_APP_FRONTEND)
|
||||||
after_page_ready = async function () {
|
|
||||||
async_main().then(()=>{}).catch(console.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', after_page_ready)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
after_page_ready()
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue