fix(extension): harden chat and target tab lifecycle

This commit is contained in:
Timo
2026-07-31 09:48:22 +02:00
parent 0b6ae803c8
commit 8050748e61
26 changed files with 776 additions and 226 deletions
+50 -2
View File
@@ -1,10 +1,14 @@
import { describe, expect, it } from 'vitest';
import { CHAT_MESSAGE_RATE_LIMIT } from '../server/rate-limiter.js';
import {
CHAT_ECHO_TIMEOUT_MS,
CHAT_SEND_LIMIT,
createChatEchoTracker,
createChatSendLimiter,
createLatestTaskQueue,
MAX_ROOM_ID_LENGTH,
normalizeRoomId
normalizeRoomId,
shouldShowChatNotification
} from './chat-session.js';
describe('chat session boundaries', () => {
@@ -14,7 +18,8 @@ describe('chat session boundaries', () => {
expect(normalizeRoomId(null)).toBe('');
});
it('keeps client chat bursts below the relay disconnect threshold', () => {
it('allows ten-message client bursts without exceeding the relay threshold', () => {
expect(CHAT_SEND_LIMIT).toBe(CHAT_MESSAGE_RATE_LIMIT);
let current = 1000;
const limiter = createChatSendLimiter({ now: () => current });
for (let index = 0; index < CHAT_SEND_LIMIT; index++) {
@@ -27,6 +32,49 @@ describe('chat session boundaries', () => {
expect(limiter.take()).toEqual({ allowed: true, retryAfterMs: 0 });
});
it('waits for the matching relay echo and clears pending sends on reset or timeout', async () => {
const timers = new Map();
let nextTimerId = 1;
const tracker = createChatEchoTracker({
setTimer(callback, timeoutMs) {
const id = nextTimerId++;
timers.set(id, { callback, timeoutMs });
return id;
},
clearTimer(id) {
timers.delete(id);
}
});
const acknowledged = tracker.waitFor('ciphertext-1');
expect(tracker.acknowledge('ciphertext-1')).toBe(true);
await expect(acknowledged).resolves.toBe(true);
const reset = tracker.waitFor('ciphertext-2');
tracker.reset();
await expect(reset).resolves.toBe(false);
const timedOut = tracker.waitFor('ciphertext-3');
const timeout = [...timers.values()][0];
expect(timeout.timeoutMs).toBe(CHAT_ECHO_TIMEOUT_MS);
timeout.callback();
await expect(timedOut).resolves.toBe(false);
});
it('suppresses chat notifications only while the target tab and its window are focused', () => {
const focused = {
enabled: true,
targetTabId: 7,
tab: { id: 7, active: true, windowId: 3 },
windowInfo: { id: 3, focused: true }
};
expect(shouldShowChatNotification(focused)).toBe(false);
expect(shouldShowChatNotification({ ...focused, windowInfo: { id: 3, focused: false } })).toBe(true);
expect(shouldShowChatNotification({ ...focused, tab: { id: 7, active: false, windowId: 3 } })).toBe(true);
expect(shouldShowChatNotification({ ...focused, targetTabId: 8 })).toBe(true);
expect(shouldShowChatNotification({ ...focused, enabled: false })).toBe(false);
});
it('serializes join work and prevents an older request from winning storage races', async () => {
const queue = createLatestTaskQueue();
let releaseFirst;