From e5e052210d52e6deabdd4688ccd2d771eedd843c Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 14 Oct 2026 23:03:18 +0200 Subject: [PATCH] fix(server-storage): a keepalive answer cut off after its headers counts as offline A page-hide write whose headers said saved but whose body broke off was returned as saved with the read error dropped, so the instance stayed verified. Like readJson() for requests, it now reports offline (the outcome stays saved on an unknown version), and the instance is checked before the next token or write goes out. Co-Authored-By: Claude Opus 5.5 --- .../server-storage/server-api-client.spec.ts | 17 +++++++++++++++++ .../src/lib/server-storage/server-api-client.ts | 11 ++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/libs/services/src/lib/server-storage/server-api-client.spec.ts b/libs/services/src/lib/server-storage/server-api-client.spec.ts index fceb84af6..241437275 100644 --- a/libs/services/src/lib/server-storage/server-api-client.spec.ts +++ b/libs/services/src/lib/server-storage/server-api-client.spec.ts @@ -110,6 +110,23 @@ describe('ServerApiClient', () => { expect(status.reportOffline).toHaveBeenCalledTimes(1); }); + it('reports a keepalive answer cut off after its headers as offline', async () => { + const cutOff = jest.fn().mockResolvedValue({ + ...response(200), + json: async () => { + throw new TypeError('body cut off'); + }, + }); + + // Saved, on a version it cannot tell (see the write queue). + await expect( + client(cutOff).sendKeepalive('PUT', '/user-state/a/b', { + value: 1, + }) + ).resolves.toEqual({ status: 'saved', tag: undefined }); + expect(status.reportOffline).toHaveBeenCalled(); + }); + it('reports a gateway answering for a down backend on a keepalive write', async () => { const down = jest.fn().mockResolvedValue(response(502)); diff --git a/libs/services/src/lib/server-storage/server-api-client.ts b/libs/services/src/lib/server-storage/server-api-client.ts index 3b309380b..7261f59e1 100644 --- a/libs/services/src/lib/server-storage/server-api-client.ts +++ b/libs/services/src/lib/server-storage/server-api-client.ts @@ -201,11 +201,12 @@ export class ServerApiClient { signal: this.timeoutSignal(), }); if (response.ok) { - const body = (await response - .json() - .catch( - () => ({}) - )) as Partial; + const body = (await response.json().catch(() => { + // Saved, on a version it cannot tell: cut off after its + // headers, which counts as offline, as for requests. + this.options.status.reportOffline(); + return {}; + })) as Partial; return { status: 'saved', tag: body?.tag }; } if (GATEWAY_UNAVAILABLE.has(response.status)) {