diff --git a/src/readline.ts b/src/readline.ts new file mode 100644 index 0000000..2a9c91f --- /dev/null +++ b/src/readline.ts @@ -0,0 +1,19 @@ +import { Turn, Entity, Facet } from './actor.js'; +import readline from 'readline'; + +export function attachReadline(t: Turn, entity: Partial): Facet { + const ref = t.ref(entity); + return t.facet(t => { + let rl: readline.Interface | null = + readline.createInterface({ input: process.stdin, output: process.stdout }); + function shutdown(_t: Turn) { + rl?.close(); + rl = null; + } + t.assert(ref, true); + t.activeFacet.actor.atExit(shutdown); + t.activeFacet.onStop(shutdown); + rl.on('line', (line: string) => t.freshen(t => t.message(ref, line))); + rl.on('close', () => t.freshen(t => t.stop())); + }); +} diff --git a/src/secure-chat-client.ts b/src/secure-chat-client.ts index 53775c5..9bae5fd 100644 --- a/src/secure-chat-client.ts +++ b/src/secure-chat-client.ts @@ -1,7 +1,7 @@ import { $joinedUser, $says, $user, asJoin, asSays, asUserInfo, fromNickClaim, fromSays, NickClaim, Says, UserId } from "./gen/secure-chat-protocol.js"; import { during, observe } from "./dataspace.js"; import { Assertion, Ref, Turn } from "./actor.js"; -import readline from 'readline'; +import { attachReadline } from './readline.js'; export default function (t: Turn, ds: Ref) { observe(t, ds, $joinedUser, { @@ -52,15 +52,14 @@ function runSession(t: Turn, uid: UserId, session: Ref) { }, }); - const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - - rl.on('line', (line: string) => t.freshen(t => { - if (line.toLowerCase().startsWith('/nick ')) { - updateUsername(t, line.slice(5).trimLeft()); - } else { - t.message(session, fromSays(Says({ who: uid, what: line }))); - } - })); - - rl.on('close', () => t.freshen(t => t.stopActor())); + attachReadline(t, { + retract(t) { t.stop(); }, + message(t, line: string) { + if (line.toLowerCase().startsWith('/nick ')) { + updateUsername(t, line.slice(5).trimLeft()); + } else { + t.message(session, fromSays(Says({ who: uid, what: line }))); + } + }, + }); } diff --git a/src/secure-chat-moderator.ts b/src/secure-chat-moderator.ts index cd76dd0..2fb7be4 100644 --- a/src/secure-chat-moderator.ts +++ b/src/secure-chat-moderator.ts @@ -6,37 +6,33 @@ export default function (t: Turn, ds: Ref) { let nextUserId: UserId = 0; const nicks = new Map(); - const users = new Map(); - - observe(t, ds, $claimNick, { - assert(t: Turn, c0: Assertion): void { - const c = asNickClaim(c0); - if (nicks.has(c.name)) { - t.message(c.k, fromNickConflict(NickConflict())); - } else { - t.message(c.k, true); - let u = users.get(c.uid); - if (!u) { - u = { infoHandle: void 0, nick: void 0 }; - users.set(c.uid, u); - } - if (u.nick !== void 0) nicks.delete(u.nick); - u.infoHandle = t.replace(ds, u.infoHandle, fromUserInfo(UserInfo(c))); - u.nick = c.name; - nicks.set(c.name, c.uid); - } - } - }); observe(t, ds, $Observe, during((t, o0) => { const o = asObserve(o0); if (o.label !== $joinedUser) return null; const uid: UserId = nextUserId++; - const h = t.assert(o.observer, fromJoin(Join({ - uid, - handle: ds, - }))); - return t => t.retract(h); + const f = t.facet(t => { + t.assert(o.observer, fromJoin(Join({ uid, handle: ds }))); + let infoHandle: Handle | undefined; + let nick: string | undefined; + observe(t, ds, $claimNick, { + assert(t: Turn, c0: Assertion): void { + const c = asNickClaim(c0); + if (c.uid !== uid) return; + if (nicks.has(c.name)) { + t.message(c.k, fromNickConflict(NickConflict())); + } else { + t.message(c.k, true); + if (nick !== void 0) nicks.delete(nick); + infoHandle = t.replace(ds, infoHandle, fromUserInfo(UserInfo(c))); + nick = c.name; + nicks.set(nick, uid); + } + } + }); + }); + + return t => t.stop(f); })); } diff --git a/src/simple-chat.ts b/src/simple-chat.ts index 75bb0b9..7ca746f 100644 --- a/src/simple-chat.ts +++ b/src/simple-chat.ts @@ -1,7 +1,7 @@ import { $Present, $Says, asPresent, asSays, fromPresent, fromSays, Present, Says } from "./gen/simple-chat-protocol.js"; import { during, observe } from "./dataspace.js"; import { Assertion, Handle, Ref, Turn } from "./actor.js"; -import readline from 'readline'; +import { attachReadline } from './readline.js'; export default function (t: Turn, ds: Ref) { let username = ''; @@ -26,16 +26,14 @@ export default function (t: Turn, ds: Ref) { }, }); - const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - t.activeFacet.actor.atExit(_t => rl.close()); - - rl.on('line', (line: string) => t.freshen(t => { - if (line.toLowerCase().startsWith('/nick ')) { - updateUsername(t, line.slice(5).trimLeft()); - } else { - t.message(ds, fromSays(Says({ who: username, what: line }))); - } - })); - - rl.on('close', () => t.freshen(t => t.stopActor())); + attachReadline(t, { + retract(t) { t.stop(); }, + message(t, line: string) { + if (line.toLowerCase().startsWith('/nick ')) { + updateUsername(t, line.slice(5).trimLeft()); + } else { + t.message(ds, fromSays(Says({ who: username, what: line }))); + } + }, + }); }