fix(outbox): two tabs no longer overwrite each other's queue (#268) #430

Merged
jared merged 1 commits from fix/outbox-multi-tab into lotus 2026-10-19 15:31:01 -04:00
+12 -7
View File
@@ -94,10 +94,15 @@ export function OutboxFeature() {
OFFLINE.has(mx.getSyncState()) || OFFLINE.has(mx.getSyncState()) ||
(typeof navigator !== 'undefined' && navigator.onLine === false); (typeof navigator !== 'undefined' && navigator.onLine === false);
const persist = (next: OutboxEntry[]) => { // [Gitea #268] Read-modify-write: another tab may have queued or cleared
if (next === entries) return; // entries since we last looked, and saving our own copy over the key
// would drop theirs. Entries are added/removed by txnId, so applying our
// change to the stored list merges cleanly.
const persist = (update: (list: OutboxEntry[]) => OutboxEntry[]) => {
const stored = loadOutbox(userId);
const next = update(stored);
entries = next; entries = next;
saveOutbox(userId, entries); if (next !== stored) saveOutbox(userId, next);
}; };
/** /**
@@ -123,8 +128,8 @@ export function OutboxFeature() {
if (!shouldKeepInOutbox(event.getType(), event.getContent())) return; if (!shouldKeepInOutbox(event.getType(), event.getContent())) return;
live.set(txnId, { event, room }); live.set(txnId, { event, room });
if (event.status === EventStatus.NOT_SENT) scheduleBlipRetry(event); if (event.status === EventStatus.NOT_SENT) scheduleBlipRetry(event);
persist( persist((list) =>
withEntry(entries, { withEntry(list, {
txnId, txnId,
roomId: room.roomId, roomId: room.roomId,
threadId: event.threadRootId ?? null, threadId: event.threadRootId ?? null,
@@ -138,7 +143,7 @@ export function OutboxFeature() {
// SENT, CANCELLED, or the remote echo replaced it (status null). // SENT, CANCELLED, or the remote echo replaced it (status null).
live.delete(txnId); live.delete(txnId);
autoRetries.delete(txnId); autoRetries.delete(txnId);
persist(withoutEntry(entries, txnId)); persist((list) => withoutEntry(list, txnId));
}; };
/** Re-send network failures, oldest first; a room stops at its first failure. */ /** Re-send network failures, oldest first; a room stops at its first failure. */
@@ -191,7 +196,7 @@ export function OutboxFeature() {
return !!room && isDelivered(room, entry.txnId); return !!room && isDelivered(room, entry.txnId);
}, },
); );
plan.drop.forEach((entry) => persist(withoutEntry(entries, entry.txnId))); plan.drop.forEach((entry) => persist((list) => withoutEntry(list, entry.txnId)));
plan.restore.forEach((entry) => { plan.restore.forEach((entry) => {
const room = mx.getRoom(entry.roomId); const room = mx.getRoom(entry.roomId);
if (!room || live.has(entry.txnId)) return; if (!room || live.has(entry.txnId)) return;