Last active
April 1, 2017 00:48
-
-
Save bmeck/4e6b2464e3222c57d22a00ab2bdc7291 to your computer and use it in GitHub Desktop.
a chat server using generators to manage connection state.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/*:: | |
type Client = Object; | |
type Channel = string; | |
*/ | |
const channels/*: Map<Channel, Set<Client>> */ = new Map; | |
const clients/*: Map<Client, Set<Channel>> */ = new WeakMap; | |
const join = (name, client) => { | |
if (!channels.has(name)) { | |
channels.set(name, new Set); | |
} | |
channels.get(name).add(client); | |
if (!clients.has(client)) { | |
clients.set(client, new Set); | |
} | |
clients.get(client).add(name); | |
client.writeln(`joined ${name}`) | |
} | |
const leave = (name, client) => { | |
if (channels.has(name)) { | |
const members = channels.get(name); | |
members.delete(client); | |
if (members.size === 0) { | |
channels.delete(name); | |
} | |
} | |
if (clients.has(client)) { | |
const joined = clients.get(client); | |
joined.delete(name); | |
if (joined.size === 0) { | |
clients.delete(client); | |
} | |
} | |
client.writeln(`left ${name}`) | |
} | |
const send = (name, msg) => { | |
if (channels.has(name)) { | |
const members = channels.get(name); | |
for (let member of members) { | |
member.writeln(`${name}> ${msg}`); | |
} | |
} | |
} | |
module.exports = function* channel(client) { | |
let channel = 'global'; | |
try { | |
client.writeln(`Welcome to Chat use "help" if needed`); | |
join(channel, client); | |
while (true) { | |
let action = yield; | |
if (action === 'quit') return action; | |
if (action === 'help') { | |
client.writeln(`CURRENT CHANNEL: ${channel}`); | |
if (clients.has(client)) { | |
const joined = [...clients.get(client)]; | |
client.writeln(`IN CHANNELS: ${joined.join(', ')}`); | |
} | |
client.writeln(`ACTIONS: help, quit, say <msg>, into <channel>, drop <channel>`) | |
continue; | |
} | |
let say = /^say (.*$)/m.exec(action); | |
if (say) { | |
send(channel, say[1]); | |
continue; | |
} | |
let into = /^into (.*$)/m.exec(action); | |
if (into) { | |
join(into[1], client); | |
channel = into[1]; | |
continue; | |
} | |
let drop = /^drop (.*$)/m.exec(action); | |
if (drop) { | |
leave(drop[1], client); | |
if (!clients.has(client)) { | |
channel = 'global'; | |
client.writeln('not in any channel, joining "global" automatically') | |
join(channel, client); | |
} | |
continue; | |
} | |
} | |
} | |
finally { | |
if (clients.has(client)) { | |
for (let joined of clients.get(client)) { | |
leave(joined, client); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const chatting = require('./chatting'); | |
const server = require('net').createServer( | |
conn => { | |
const session = init(conn); | |
// startup the session, this is the one oddity of generators I find | |
session.next(); | |
// note: conn is the only thing keeping `session` alive | |
conn.on('data', buff => { | |
session.next(`${buff}`.trim()); | |
}); | |
const kill = () => session.next('quit'); | |
// it is safe to kill multiple times. | |
conn.on('error', kill); | |
conn.on('close', kill); | |
conn.on('end', kill); | |
conn.on('finish', kill); | |
} | |
).listen(1337); | |
function* init(conn) { | |
try { | |
const client = Object.freeze(Object.setPrototypeOf({ | |
writeln(msg) { | |
conn.write(`${msg}\n`); | |
} | |
}, null)); | |
// we don't pass around the cleanup | |
yield* chatting(client); | |
} | |
finally { | |
conn.end(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment