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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more