Published
Edited
Feb 1, 2022
Importers
Insert cell
Insert cell
Insert cell
{
reset()
console.log('starting test')
const test_clientA = get_client({
id: 'testUserA',
send: cb => msg => msg == 'ping' ? cb('pong') : console.log('A: Server sent:', msg)})
const test_clientB = get_client({id: 'testUserB', send: cb => msg => msg == 'ping' ? cb('pong') : console.log('B: Server sent:', msg)})
test_clientA({channel: 'test-channel', action: 'join'})
test_clientB({channel: 'test-channel', action: 'join'})
test_clientA({action: 'message', recipient: 'testUserB', body: 'hello from A'}) // check the console after running
test_clientA({channel: 'test-channel', action: 'leave'})
}
Insert cell
Insert cell
reset = () => {
Object.keys(sessions).map(k => delete sessions[k])
Object.keys(connections).map(k => delete connections[k])
set_heartbeat_timeout()
}
Insert cell
mutable serverPing = undefined
Insert cell
sessions = ({})
Insert cell
connections = ({}) // map from user id to connection info
Insert cell
no_ping = ({})
Insert cell
update_clients = (session) => {
Object.keys(session).map(id => connections[id] && connections[id]({ client_id: id, action: 'list', response: Object.keys(session)}))
}
Insert cell
set_heartbeat_timeout = (ms=3000) => {
if (mutable serverPing) clearInterval(mutable serverPing)
mutable serverPing = setInterval(() => {
let updated_sessions = ({})
// remove clients that have died
Object.keys(no_ping).map(client_id => {
console.log('Dropping', client_id)
delete connections[client_id]
Object.keys(sessions).map(s => {
updated_sessions[s] = true
delete sessions[s][client_id]
if (Object.keys(s).length == 0) delete sessions[s]
})
delete no_ping[client_id]
})
// notify living clients about dead clients
Object.keys(updated_sessions).map(s => sessions[s] && update_clients(sessions[s]))
// handle next iteration
Object.keys(connections).map(u => no_ping[u] = true)
Object.values(connections).map(c => setTimeout(() => c('ping')))
}, ms)
}
Insert cell
handle_request = (args) => { // handle client message
let {action, client_id, channel, name=channel, recipient, body, sender} = args
const err = msg => (console.log(msg), connections[client_id]({error: msg}))
console.log('Server: request:', args)
const K = Object.keys
const V = Object.values
const s = K(sessions).find(name1 => name1 == name)
|| (!name && V(sessions).find(s => s[client_id])) || undefined;
const R = args => {
console.log('responding', args)
connections[client_id]({action, response: args, client_id})
console.log('responded')
}
const session = sessions[s] || s
const ops = {
list: () =>
s ? R(K(session))
: R({sessions: K(sessions)
.map(s => ({name: s.name}))}),
join: () => {
if (!name) return err(`can't join with session name ${name}`)
if (!session) return sessions[name] = {[client_id] : true}
session[client_id] = true
update_clients(session)
},
leave: () => {
const session = sessions[s]
if (!s || !session || !session[client_id]) return err(`You're not in that session.`)
delete session[client_id] && update_clients(session)
if (Object.keys(session).length == 0) delete sessions[s]
return true
},
message: () =>
!s ? err("You need to be in a session to send a message.")
: !(session[client_id] && session[recipient]) ? err("Couldn't find that recipient in your session.")
: connections[recipient]({action, sender: client_id, name, body, recipient})
}
if (!Object.keys(ops).includes(action)) {console.log('Invalid action'); return}

ops[action]()
}
Insert cell
get_client = ({id, send}) => {
if (connections[id]) throw Error('That id exists.')
// TODO clarify the logic of send--what is the call doing??
const result = request => {
request == 'pong' ? delete no_ping[id] : handle_request({...request, client_id: id})
}
connections[id] = send(result)
return result
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more