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 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-14 23:03:18 +02:00
1 parent 752ef756d6
commit e5e052210d
2 files changed
+23 -5

No files matched your search

@@ -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));
@@ -201,11 +201,12 @@ export class ServerApiClient {
signal: this.timeoutSignal(),
});
if (response.ok) {
const body = (await response
.json()
.catch(
() => ({})
)) as Partial<ServerUserStateWriteResponse>;
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<ServerUserStateWriteResponse>;
return { status: 'saved', tag: body?.tag };
}
if (GATEWAY_UNAVAILABLE.has(response.status)) {