fix: say a connection is needed only when offline or the server is down, not at a start while online

This commit is contained in:
Linus Rath
2026-11-04 06:56:24 +01:00
parent 364fc516dc
commit a28617cbbb
2 changed files with 32 additions and 3 deletions
+25 -1
View File
@@ -8,7 +8,7 @@ import { DEFAULT_ORGANISATION_POLICY } from '@/lib/admin/organisation-policy';
import { setStoreBackend } from '@/lib/store/client/api';
import { FakeJmapMail } from '@/lib/store/__tests__/helpers/fake-jmap-mail';
import { InProcessStore } from '@/lib/store/__tests__/helpers/in-process-store';
import { startOfflineMode, wipeEverything } from '../offline-mode';
import { setOfflineNotifier, startOfflineMode, wipeEverything } from '../offline-mode';
import { forgetLegacyMove } from '../migrate-legacy';
import { useOfflineStore, writeEnabled } from '../state';
@@ -291,3 +291,27 @@ describe('what the store answers and is told', () => {
stop();
});
});
describe('saying a connection is needed', () => {
it('not while online with the server answering, as at a start with a draft waiting', async () => {
const { client, stop } = await ready();
const { getOfflineHooks } = await import('@/lib/jmap/offline-hooks');
const notices: { kind: string }[] = [];
setOfflineNotifier((notice) => notices.push(notice));
await getOfflineHooks()?.answer(client, [['Identity/set', { create: { x: { email: 'a@example.org' } } }, '0']]);
expect(notices.filter((n) => n.kind.startsWith('refused'))).toEqual([]);
stop();
});
it('when offline, once', async () => {
const { client, stop } = await ready();
const { getOfflineHooks } = await import('@/lib/jmap/offline-hooks');
const notices: { kind: string }[] = [];
setOfflineNotifier((notice) => notices.push(notice));
await offline(async () => {
await getOfflineHooks()?.answer(client, [['Identity/set', { create: { x: { email: 'a@example.org' } } }, '0']]);
});
expect(notices.filter((n) => n.kind === 'refused-other')).toHaveLength(1);
stop();
});
});
+7 -2
View File
@@ -319,7 +319,12 @@ function hasWholeText(email: { bodyValues?: Record<string, { isTruncated?: boole
const NEEDS_SERVER = /^(EmailSubmission|Email\/import|CalendarEvent\/set|ContactCard\/set|AddressBook\/set|Calendar\/set|Mailbox\/set|FileNode\/set|SieveScript\/set|VacationResponse\/set|Identity\/set)/;
let lastRefusalNotice = 0;
function noticeRefused(answered: OfflineResponse | null, methodCalls: OfflineMethodCall[]): void {
function noticeRefused(client: OfflineClientRef, answered: OfflineResponse | null, methodCalls: OfflineMethodCall[]): void {
// Online, with the server answering: the store only stood in for a moment (a draft
// waiting from last time, at a start). The outbox retries by itself; "this needs a
// connection" would be wrong.
const online = typeof navigator === 'undefined' || navigator.onLine !== false;
if (online && !useOfflineStore.getState().downServers.includes(serverKey(client.getServerUrl()))) return;
const refusedCall = methodCalls.some(([name, , id]) => NEEDS_SERVER.test(name)
&& (!answered || answered.methodResponses.some(([responseName, , responseId]) => responseId === id && responseName === 'error')));
const refusedChange = answered?.methodResponses.some(([name, result]) => name === 'Email/set' && result?.notUpdated) ?? false;
@@ -382,7 +387,7 @@ const hooks: OfflineHooks = {
} catch {
// The store couldn't start or lost its owner: as if nothing were kept.
}
noticeRefused(answered, methodCalls);
noticeRefused(client, answered, methodCalls);
if (answered && methodCalls.some(([name]) => QUEUEING_METHOD.test(name))) {
requestOutboxSync();
void refreshQueueState();