git » sdk » commit 20d0dbc

Dedupe versions on my own edits in a MUC

author Stephen Paul Weber
2026-10-05 19:14:30 UTC
committer Stephen Paul Weber
2026-10-05 19:14:30 UTC
parent e350363246f2de85932f22193accc75368eb674b

Dedupe versions on my own edits in a MUC

The sent version has no serverId, then the reflection of that edit does
have a serverId, so we cannot dedupe by serverId. But we trust our own
localIds so we can dedupe that way.

borogove/persistence/IDB.js +7 -0
test/persistence-tests.ts +66 -0

diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js
index b04f6c4..61dab44 100644
--- a/borogove/persistence/IDB.js
+++ b/borogove/persistence/IDB.js
@@ -635,6 +635,13 @@ export default async (dbname, media, tokenize, stemmer) => {
 		const dedup = {};
 		const versions = [];
 		for (const version of newVersions.concat(storedVersions)) {
+			if (
+				version.direction === enums.borogove_MessageDirection.MessageSent &&
+				version.localId
+			) {
+				if (dedup[version.localId]) continue;
+				dedup[version.localId] = true;
+			}
 			if (version.serverId) {
 				const key = version.serverId + "\n" + version.serverIdBy;
 				if (dedup[key]) continue;
diff --git a/test/persistence-tests.ts b/test/persistence-tests.ts
index 6c2ace1..0d1a01b 100644
--- a/test/persistence-tests.ts
+++ b/test/persistence-tests.ts
@@ -624,6 +624,72 @@ export function sharedPersistenceTests(test) {
 		expect(stored.versions[1].sortId).toBe("a0");
 	});
 
+	test("dedupes unreflected and reflected correction versions when editing our own message", async ({
+		borogove,
+		persistence,
+	}) => {
+		const account = "alice@example.com";
+		const chatId = "room@example.com";
+		const senderId = "room@example.com/alice";
+
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "orig-srv",
+			serverIdBy: chatId,
+			localId: "orig-loc",
+			senderId,
+			direction: borogove.MessageDirection.MessageSent,
+			timeReceived: "2026-08-26T12:00:00Z",
+		});
+		builder.sortId = "a0";
+		builder.text = "Original message";
+		builder.type = borogove.MessageType.MessageChannel;
+		builder.to = borogove.JID.parse(chatId);
+		builder.from = borogove.JID.parse(senderId);
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.to];
+		const original = builder.build();
+
+		await persistence.storeMessages(account, [original]);
+
+		// Local edit: store a version with no serverId
+		builder.serverId = null;
+		builder.serverIdBy = null;
+		builder.localId = "edit-loc";
+		builder.text = "Corrected message";
+		builder.timeReceived = "2026-08-26T12:01:00Z";
+		const localVersion = builder.build();
+
+		builder.localId = original.localId;
+		builder.versions = [localVersion];
+		await persistence.storeMessages(account, [builder.build()]);
+
+		// Channel reflects: store a duplicate version that now has a serverId
+		builder.serverId = "edit-srv";
+		builder.serverIdBy = chatId;
+		builder.localId = "edit-loc";
+		builder.versions = [];
+		const reflectedVersion = builder.build();
+
+		builder.localId = original.localId;
+		builder.versions = [reflectedVersion];
+		const [stored] = await persistence.storeMessages(account, [
+			builder.build(),
+		]);
+
+		const [fetched] = await persistence.getMessagesBefore(account, chatId);
+
+		expect(stored.versions.length).toBe(2);
+		expect(stored.versions.map((v) => v.text)).toEqual([
+			"Corrected message",
+			"Original message",
+		]);
+		expect(fetched.versions.length).toBe(2);
+		expect(fetched.versions.map((v) => v.text)).toEqual([
+			"Corrected message",
+			"Original message",
+		]);
+	});
+
 	test("storeReaction", async ({ borogove, persistence }) => {
 		const builder = new borogove.ChatMessageBuilder({
 			serverId: "srv1",