summaryrefslogtreecommitdiffhomepage
path: root/src/features/chat/adapter/pending-inputs.ts
blob: 5798fdba61f84a1ff1ba9cad0b7bf2bcb3a401e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
    },
  };
}