From e4273ffd945f16d6f74e9c64431808ea36148880 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 6 May 2025 01:07:54 -0400 Subject: Render "ghost" messages for unsent messages. There is a subtle race conditon in this code, which is likely not fixable without a protocol change: * Ghost messages can disappear before their "real" message replacement shows up, if the client finishes sending (i.e., receives an HTTP response on the POST) before the server delivers the real message. * Ghost messages can be duplicated briefly, if the client receives the real message before the client finishes sending. Both happen in practice; we make no ordering guarantees between requests. To aviod this, we'd to give clients a way to correlate pending sends with received messages. This would require fundamentally the same capabilities, like per-operation nonces, that preventing duplicate operations will require. --- ui/lib/state/remote/messages.svelte.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui/lib/state') diff --git a/ui/lib/state/remote/messages.svelte.js b/ui/lib/state/remote/messages.svelte.js index 576a74e..c6d31f0 100644 --- a/ui/lib/state/remote/messages.svelte.js +++ b/ui/lib/state/remote/messages.svelte.js @@ -1,7 +1,7 @@ import { DateTime } from 'luxon'; import { render } from '$lib/markdown.js'; -class Message { +export class Message { static boot({ id, at, channel, sender, body }) { return new Message({ id, -- cgit v1.2.3 From 92266a13bfabf7b29f08bc85d0e8efba467167da Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 8 May 2025 19:40:11 -0400 Subject: Rather than exploding a user into properties inside `runs`, use a helper method. --- ui/lib/runs.js | 11 ++--------- ui/lib/state/remote/state.svelte.js | 4 ++-- ui/lib/state/remote/users.svelte.js | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 13 deletions(-) (limited to 'ui/lib/state') diff --git a/ui/lib/runs.js b/ui/lib/runs.js index e3d4c20..de00e6a 100644 --- a/ui/lib/runs.js +++ b/ui/lib/runs.js @@ -1,4 +1,5 @@ import * as iter from './iterator.js'; +import { User } from './state/remote/users.svelte.js'; const RUN_COALESCE_MAX_INTERVAL = 10 /* min */ * 60 /* sec */ * 1000; /* ms */ @@ -21,13 +22,5 @@ function runKey(message) { } function continueRun([lastSender, lastAt], [newSender, newAt]) { - const { id: lastId, name: lastName } = lastSender; - const { id: newId, name: newName } = newSender; - if (lastId !== newId) { - return false; - } - if (lastName !== newName) { - return false; - } - return newAt - lastAt < RUN_COALESCE_MAX_INTERVAL; + return User.equal(lastSender, newSender) && newAt - lastAt < RUN_COALESCE_MAX_INTERVAL; } diff --git a/ui/lib/state/remote/state.svelte.js b/ui/lib/state/remote/state.svelte.js index 29831a0..e00d55c 100644 --- a/ui/lib/state/remote/state.svelte.js +++ b/ui/lib/state/remote/state.svelte.js @@ -1,4 +1,4 @@ -import { Users } from './users.svelte.js'; +import { User, Users } from './users.svelte.js'; import { Channels } from './channels.svelte.js'; import { Messages } from './messages.svelte.js'; @@ -10,7 +10,7 @@ export class State { static boot({ currentUser, heartbeat, users, channels, messages, resumePoint }) { return new State({ - currentUser, + currentUser: User.boot(currentUser), heartbeat, users: Users.boot(users), channels: Channels.boot(channels), diff --git a/ui/lib/state/remote/users.svelte.js b/ui/lib/state/remote/users.svelte.js index 617084f..a15d1da 100644 --- a/ui/lib/state/remote/users.svelte.js +++ b/ui/lib/state/remote/users.svelte.js @@ -1,10 +1,25 @@ import { SvelteMap } from 'svelte/reactivity'; +export class User { + static equal(a, b) { + return a.id === b.id && a.name === b.name; + } + + static boot({ id, name }) { + return new User(id, name); + } + + constructor(id, name) { + this.id = id; + this.name = name; + } +} + export class Users { all = $state(); static boot(users) { - const all = new SvelteMap(users.map((user) => [user.id, user])); + const all = new SvelteMap(users.map((user) => [user.id, User.boot(user)])); return new Users({ all }); } @@ -13,6 +28,6 @@ export class Users { } add({ id, name }) { - this.all.set(id, { id, name }); + this.all.set(id, new User(id, name)); } } -- cgit v1.2.3