239 lines
5.6 KiB
JavaScript
239 lines
5.6 KiB
JavaScript
// provided by external "define": APP_CONSTANTS, JS_ON_WS_CONNECT
|
|
|
|
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 runCodeWithCustomFunction = (obj) => {
|
|
return Function(...lst_idents.map(x=>x[0]), `"use strict";return (${obj});`.trim())(...lst_idents.map(x=>x[1]))
|
|
}
|
|
return runCodeWithCustomFunction
|
|
}
|
|
const do_safereval_strscript = function (obj_eval_defines, the_strscript) {
|
|
const lst_idents = Object.entries(obj_eval_defines).map(([k,v])=>[k,v]).toSorted((a,b)=>(a[0]-b[0]))
|
|
const the_eval_func = setup_safer_eval(lst_idents)
|
|
const the_eval_result = the_eval_func(`
|
|
(function(){
|
|
${the_strscript}
|
|
return
|
|
})()
|
|
`.trim())
|
|
return the_eval_result
|
|
}
|
|
|
|
const render_app = function ({ APP_CONSTANTS, func_send_event, handle_event_response }) {
|
|
|
|
const uidsub_map = {}
|
|
const register_streamjoin = (stream_key, init_params, func_callback) => {
|
|
|
|
const uid_sub = get_probably_unique_idstr_32len()
|
|
|
|
console.info(['register_streamjoin', { stream_key, init_params, uid_sub }])
|
|
|
|
uidsub_map[uid_sub] = { func_callback }
|
|
|
|
func_send_event(APP_CONSTANTS.EVENTTYPES.STREAM_JOIN, { uid_sub, stream_key, init_params }, (full_result) => {
|
|
console.info(['STREAM_JOIN', { full_result }])
|
|
return
|
|
})
|
|
|
|
const func_teardown = () => {
|
|
|
|
func_send_event(APP_CONSTANTS.EVENTTYPES.STREAM_LEAVE, { uid_sub }, (full_result) => {
|
|
console.info(['STREAM_LEAVE', { full_result }])
|
|
return
|
|
})
|
|
|
|
delete uidsub_map[uid_sub]
|
|
|
|
return
|
|
}
|
|
|
|
return func_teardown
|
|
|
|
}
|
|
|
|
const func_eval_appscript = () => {
|
|
|
|
console.info('EVAL APPSCRIPT')
|
|
|
|
do_safereval_strscript({ register_streamjoin }, JS_ON_WS_CONNECT)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
const func_handle_event = (evt_type, evt_data) => {
|
|
|
|
//console.info(['HANDLING EVENT!', [ evt_type, evt_data ]])
|
|
|
|
if (evt_type === APP_CONSTANTS.EVENTTYPES.HBPING) {
|
|
func_send_event(APP_CONSTANTS.EVENTTYPES.HBPONG, true)
|
|
}
|
|
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
|
|
|
|
}
|
|
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 () {
|
|
|
|
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
|
|
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()
|
|
}
|