The template notes a seat's address at most once in ten minutes, so a seat open on two networks writes no line per request

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
Eric WagonerandClaude Fable 5.1 committed 2026-11-10 21:32:27 -05:00
1 parent 1a46d611a0
commit 210db2d5c3
2 files changed
+13 -2

No files matched your search

+2
View File
@@ -337,6 +337,8 @@ describe('a seat a browser lost', () => {
expect(room.seats.find((s) => s.id === 'B')?.addr).toBe('home'); expect(room.seats.find((s) => s.id === 'B')?.addr).toBe('home');
expect(room.seq).toBe(seq); expect(room.seq).toBe(seq);
rooms.seen(room, 'no-such-token', 'elsewhere'); rooms.seen(room, 'no-such-token', 'elsewhere');
// A seat open on two networks at once: the second address is not written on its heels.
rooms.seen(room, bo, 'office');
expect(new Store(dir).read(room.id).filter((l) => l.t === 'seen')).toHaveLength(1); expect(new Store(dir).read(room.id).filter((l) => l.t === 'seen')).toHaveLength(1);
expect(new Rooms(game, new Store(dir)).get(room.id).seats.find((s) => s.id === 'B')?.addr).toBe('home'); expect(new Rooms(game, new Store(dir)).get(room.id).seats.find((s) => s.id === 'B')?.addr).toBe('home');
}); });
+11 -2
View File
@@ -36,6 +36,8 @@ export interface Seat {
/** A table whose ledger has not been written in this long is not looked through for a lost seat. */ /** A table whose ledger has not been written in this long is not looked through for a lost seat. */
const LOST_SEAT_LOOKBACK_MS = 90 * 24 * 60 * 60 * 1000; const LOST_SEAT_LOOKBACK_MS = 90 * 24 * 60 * 60 * 1000;
/** A seat's address is noted at most this often: one seat open on two networks at once must not write a line per request. */
const SEEN_EVERY_MS = 10 * 60 * 1000;
function hashToken(token: string): string { function hashToken(token: string): string {
return createHash('sha256').update(token).digest('hex'); return createHash('sha256').update(token).digest('hex');
@@ -323,7 +325,10 @@ export class Rooms<State, Input> {
return { phrase, expiresAt }; return { phrase, expiresAt };
} }
/** The address a seat is used from, noted when it changes. Not a change the table need hear of: nothing wakes for it. */ /** When each seat's address was last noted, by room and seat. */
private seenAt = new Map<string, number>();
/** The address a seat is used from, noted when it changes, and not more often than SEEN_EVERY_MS. Not a change the table need hear of: nothing wakes for it. */
seen(room: Room<State>, token: string | undefined, addr: string): void { seen(room: Room<State>, token: string | undefined, addr: string): void {
let seat: Seat; let seat: Seat;
try { try {
@@ -332,7 +337,11 @@ export class Rooms<State, Input> {
return; return;
} }
if (seat.addr === addr) return; if (seat.addr === addr) return;
const line: LedgerLine = { t: 'seen', at: Date.now(), id: seat.id, addr }; const key = `${room.id}/${seat.id}`;
const now = Date.now();
if ((this.seenAt.get(key) ?? 0) > now - SEEN_EVERY_MS) return;
this.seenAt.set(key, now);
const line: LedgerLine = { t: 'seen', at: now, id: seat.id, addr };
this.store.append(room.id, line); this.store.append(room.id, line);
this.apply(room, line); this.apply(room, line);
} }