Skip to content

Instantly share code, notes, and snippets.

@gearsdigital
Forked from bmeck/chatting.js
Created March 31, 2017 20:53
Show Gist options
  • Save gearsdigital/0e497f8e50e236c0883bf44c508ec502 to your computer and use it in GitHub Desktop.
Save gearsdigital/0e497f8e50e236c0883bf44c508ec502 to your computer and use it in GitHub Desktop.
a chat server using generators to manage connection state.
'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);
}
}
}
}
'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