diff options
| author | Adam Malczewski <[email protected]> | 2026-10-21 19:39:47 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-10-21 19:39:47 +0900 |
| commit | 7b7d6d01cf94ecad1dcaaa4b87670d50fe15790a (patch) | |
| tree | 29bbfa52183b2a6639d96ad21d66806fb8ccea30 /src/features/chat/adapter | |
| parent | ac43e92d174ed1f081c387944e9044643ee9efc2 (diff) | |
| download | dispatch-web-7b7d6d01cf94ecad1dcaaa4b87670d50fe15790a.tar.gz dispatch-web-7b7d6d01cf94ecad1dcaaa4b87670d50fe15790a.zip | |
fix(chat): bound live subscriptions and preserve unconfirmed input
Diffstat (limited to 'src/features/chat/adapter')
| -rw-r--r-- | src/features/chat/adapter/pending-inputs.ts | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/src/features/chat/adapter/pending-inputs.ts b/src/features/chat/adapter/pending-inputs.ts new file mode 100644 index 0000000..5798fdb --- /dev/null +++ b/src/features/chat/adapter/pending-inputs.ts @@ -0,0 +1,49 @@ +import type { PendingInput, PendingInputStorage } from "../pending-inputs"; + +export function createPendingInputStorage( + storage: Storage, + conversationId: string, +): PendingInputStorage { + const prefix = `dispatch.pending-inputs:${conversationId}:`; + let known = new Map<string, string>(); + return { + load() { + const inputs: PendingInput[] = []; + const loaded = new Map<string, string>(); + for (let i = 0; i < storage.length; i++) { + const key = storage.key(i); + if (key === null || !key.startsWith(prefix)) continue; + const raw = storage.getItem(key); + if (raw === null) continue; + const input = JSON.parse(raw); + if ( + (input?.request?.type !== "chat.send" && input?.request?.type !== "chat.queue") || + input.request.conversationId !== conversationId || + typeof input.request.requestId !== "string" || + key !== prefix + input.request.requestId || + (input.request.type === "chat.send" + ? typeof input.request.message !== "string" + : typeof input.request.text !== "string") || + (input.rejection !== undefined && typeof input.rejection !== "string") + ) + throw new Error("Invalid saved input"); + inputs.push(input); + loaded.set(input.request.requestId, raw); + } + known = loaded; + return inputs; + }, + save(inputs) { + // Per-request keys prevent another browser tab's send/receipt from overwriting ours. + // Quota/security failures reach the composer BEFORE it clears its staged input. + const next = new Map(inputs.map((input) => [input.request.requestId, JSON.stringify(input)])); + for (const [id, raw] of next) { + if (known.get(id) !== raw) storage.setItem(prefix + id, raw); + } + for (const id of known.keys()) { + if (!next.has(id)) storage.removeItem(prefix + id); + } + known = next; + }, + }; +} |
