| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-19 20:35:12 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-04-19 20:54:15 UTC |
| parent | 152b8abf0c21bdc1f15e4004d0e21c296abb3af4 |
| borogove/persistence/IDB.js | +5 | -1 |
| test/idb.spec.ts | +76 | -0 |
diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js index d2f0361..f176f22 100644 --- a/borogove/persistence/IDB.js +++ b/borogove/persistence/IDB.js @@ -321,7 +321,11 @@ export default async (dbname, media, tokenize, stemmer) => { const message = hydrateMessageSync(value); const tx = db.transaction(["messages"], "readonly"); const store = tx.objectStore("messages"); - const replyToMessage = value.replyToMessage && value.replyToMessage[1] !== message.serverId && value.replyToMessage[3] !== message.localId && await hydrateMessage((await promisifyRequest(store.openCursor(IDBKeyRange.only(value.replyToMessage))))?.value); + if (value.replyToMessage && !value.replyToMessage[2]) value.replyToMessage[2] = value.serverIdBy ?? value.chatId; + const range = value.replyToMessage && value.replyToMessage[1] !== message.serverId && value.replyToMessage[3] !== message.localId && (!value.replyToMessage[3] ? + IDBKeyRange.bound(value.replyToMessage.slice(0, 3), [...value.replyToMessage.slice(0, 3), []]) + : IDBKeyRange.only(value.replyToMessage)); + const replyToMessage = range && await hydrateMessage((await promisifyRequest(store.openCursor(range)))?.value); message.replyToMessage = replyToMessage; message.versions = await Promise.all((value.versions || []).map(hydrateMessage)); diff --git a/test/idb.spec.ts b/test/idb.spec.ts index 7bafca0..6edd3de 100644 --- a/test/idb.spec.ts +++ b/test/idb.spec.ts @@ -774,3 +774,79 @@ test("media storage functions", async ({ page }) => { expect(result.hasBefore).toBe(true); expect(result.hasAfter).toBe(false); }); + +test("hydrate message with incomplete replyToMessage", async ({ page }) => { + page.route("https://localhost/", route => route.fulfill({ body: "<html></html>" })); + const code = fs.readFileSync("playwright/.cache/borogove.js", "utf8"); + await page.goto("https://localhost/"); + const result = await page.evaluate(async (code) => { + const blob = new Blob([code], { type: 'text/javascript' }); + const borogove = await import(URL.createObjectURL(blob)); + + const mediaStore = await borogove.persistence.MediaStoreCache("snikket"); + const persistence = await borogove.persistence.IDB("snikket", mediaStore); + + const builder = new borogove.ChatMessageBuilder({ + serverId: "parent", + serverIdBy: "hatter@example.com", + localId: "loc1", + senderId: "hatter@example.com", + direction: 0, + }); + builder.sortId = "a0"; + builder.to = borogove.JID.parse("alice@example.com"); + builder.from = borogove.JID.parse("hatter@example.com"); + builder.recipients = [builder.to]; + builder.replyTo = [builder.from]; + const parentMsg = builder.build(); + + const builder2 = new borogove.ChatMessageBuilder({ + serverId: "child", + serverIdBy: "hatter@example.com", + localId: "loc2", + senderId: "hatter@example.com", + direction: 0, + }); + builder2.sortId = "a1"; + builder2.to = borogove.JID.parse("alice@example.com"); + builder2.from = borogove.JID.parse("hatter@example.com"); + builder2.recipients = [builder2.to]; + builder2.replyTo = [builder2.from]; + builder2.replyToMessage = parentMsg; + const childMsg = builder2.build(); + + await persistence.storeMessages("alice@example.com", [parentMsg, childMsg]); + + const db = await new Promise((resolve, reject) => { + const req = indexedDB.open("snikket"); + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + const tx = db.transaction(["messages"], "readwrite"); + const store = tx.objectStore("messages"); + const key = ["alice@example.com", "child", "hatter@example.com", "loc2"]; + const rawChild = await new Promise((resolve) => { + const req = store.get(key); + req.onsuccess = () => resolve(req.result); + }); + + rawChild.replyToMessage = ["alice@example.com", "parent", "", ""]; + + await new Promise((resolve) => { + const req = store.put(rawChild); + req.onsuccess = () => resolve(); + }); + await new Promise((resolve) => { + tx.oncomplete = () => resolve(); + }); + + const retrievedChild = await persistence.getMessage("alice@example.com", "hatter@example.com", "child", "loc2"); + return { + hasReply: !!retrievedChild.replyToMessage, + replyServerId: retrievedChild.replyToMessage ? retrievedChild.replyToMessage.serverId : null + }; + }, code); + + expect(result.hasReply).toBe(true); + expect(result.replyServerId).toBe("parent"); +});