From 202576a03a4244883a6dba3c577ee69f559c254d Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 27 Sep 2026 21:40:12 +0100 Subject: [PATCH] Pick up type changes --- frontend/sync-client/src/errors/errors.ts | 2 +- .../sync-client/src/services/server-config.ts | 12 ++--- .../src/services/sync-service.test.ts | 47 ++++++++++++++----- .../sync-client/src/services/sync-service.ts | 17 ++++--- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/frontend/sync-client/src/errors/errors.ts b/frontend/sync-client/src/errors/errors.ts index 1169453d..b00271f2 100644 --- a/frontend/sync-client/src/errors/errors.ts +++ b/frontend/sync-client/src/errors/errors.ts @@ -23,5 +23,5 @@ export class PermanentSyncError extends Error {} export class LocalChangesDuringReconciliation extends Error {} -/** The server rejected a checkpoint from a discarded/restored history. */ +// The server rejected a checkpoint from a discarded/restored history. export class ServerHistoryChangedError extends Error {} diff --git a/frontend/sync-client/src/services/server-config.ts b/frontend/sync-client/src/services/server-config.ts index 3f980744..82e650e0 100644 --- a/frontend/sync-client/src/services/server-config.ts +++ b/frontend/sync-client/src/services/server-config.ts @@ -4,14 +4,14 @@ import { ServerVersionMismatchError } from "../errors/errors"; import type { SyncService } from "./sync-service"; -import type { PingResponse } from "./types/PingResponse"; +import type { ServerConfigResponse } from "./types/ServerConfigResponse"; export class ServerConfig { - private config?: Promise; + private config?: Promise; public constructor(private readonly syncService: SyncService) {} - private static validateConfig(config: PingResponse): void { + private static validateConfig(config: ServerConfigResponse): void { if (config.supportedApiVersion !== SUPPORTED_API_VERSION) { const shouldUpgradeClient = config.supportedApiVersion > SUPPORTED_API_VERSION; @@ -34,7 +34,7 @@ export class ServerConfig { message: string; }> { try { - const result = await this.syncService.ping(); + const result = await this.syncService.getServerConfig(); ServerConfig.validateConfig(result); @@ -50,10 +50,10 @@ export class ServerConfig { } } - public async getConfig(): Promise { + public async getConfig(): Promise { if (!this.config) { const pending = this.syncService - .ping() + .getServerConfig() .then((config) => { ServerConfig.validateConfig(config); return config; diff --git a/frontend/sync-client/src/services/sync-service.test.ts b/frontend/sync-client/src/services/sync-service.test.ts index f3603974..59207ac9 100644 --- a/frontend/sync-client/src/services/sync-service.test.ts +++ b/frontend/sync-client/src/services/sync-service.test.ts @@ -1,3 +1,4 @@ +import { HISTORY_HEADER } from "../consts"; import assert from "node:assert/strict"; import { test } from "node:test"; import { Settings } from "../persistence/settings"; @@ -25,7 +26,7 @@ test("paused sync sends no requests, while connection checks still work", async requests++; return Response.json( { headEventId: 0, endEventId: 0, events: [] }, - { headers: { "X-Vault-Link-History": "0:empty" } } + { headers: { [HISTORY_HEADER]: "0:empty" } } ); }, { @@ -35,7 +36,7 @@ test("paused sync sends no requests, while connection checks still work", async ); await assert.rejects(service.getEvents(0), SyncResetError); assert.equal(requests, 0); - await service.ping(); + await service.getServerConfig(); assert.equal(requests, 1); service.resume(); assert.equal((await service.getEvents(0)).headEventId, 0); @@ -56,7 +57,7 @@ test("pause aborts requests and fences late responses from an injected fetch", a ? response.promise : Response.json( { headEventId: 0, endEventId: 0, events: [] }, - { headers: { "X-Vault-Link-History": "0:empty" } } + { headers: { [HISTORY_HEADER]: "0:empty" } } ); }, { @@ -75,10 +76,7 @@ test("pause aborts requests and fences late responses from an injected fetch", a await service.getEvents(0); assert.equal(signals[1]?.aborted, false); response.resolve( - Response.json( - {}, - { headers: { "X-Vault-Link-History": "99:abandoned" } } - ) + Response.json({}, { headers: { [HISTORY_HEADER]: "99:abandoned" } }) ); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(saved, ["0:empty"]); @@ -101,7 +99,7 @@ for (const binary of [false, true]) { bodyReady.resolve(controller); } }), - { headers: { "X-Vault-Link-History": "1:head" } } + { headers: { [HISTORY_HEADER]: "1:head" } } ), { get: (): undefined => undefined, @@ -139,7 +137,7 @@ test("pause waits for a checkpoint save already in progress", async (): Promise< Response.json( {}, { - headers: { "X-Vault-Link-History": "1:head" } + headers: { [HISTORY_HEADER]: "1:head" } } ), { @@ -174,7 +172,7 @@ test("history saves only new checkpoints, including a changed token at the same async () => Response.json( {}, - { headers: { "X-Vault-Link-History": responseCheckpoint } } + { headers: { [HISTORY_HEADER]: responseCheckpoint } } ), { get: (): string | undefined => checkpoint, @@ -212,7 +210,7 @@ for (const checkpoint of [undefined, ""]) { headers: checkpoint === undefined ? {} - : { "X-Vault-Link-History": checkpoint } + : { [HISTORY_HEADER]: checkpoint } } ), { @@ -229,3 +227,30 @@ for (const checkpoint of [undefined, ""]) { ); }); } + +test("server configuration uses GET /config even when synchronization is paused", async () => { + const config = { + supportedApiVersion: 4, + serverVersion: "test", + isAuthenticated: true, + mergeableFileExtensions: ["md"] + }; + const service = new SyncService( + "device", + settings(), + async (input, init) => { + assert.equal(input, "http://test/vaults/test/config"); + assert.equal(init?.method, "GET"); + assert.equal(init.body, undefined); + assert.equal(new Headers(init.headers).has(HISTORY_HEADER), false); + return Response.json(config); + }, + { + get: (): string => "10:old-history", + save: async (): Promise => { + assert.fail("Config must not update the sync checkpoint"); + } + } + ); + assert.deepEqual(await service.getServerConfig(), config); +}); diff --git a/frontend/sync-client/src/services/sync-service.ts b/frontend/sync-client/src/services/sync-service.ts index 5f91e1e8..f34a9793 100644 --- a/frontend/sync-client/src/services/sync-service.ts +++ b/frontend/sync-client/src/services/sync-service.ts @@ -1,3 +1,4 @@ +import { HISTORY_HEADER, HISTORY_MISMATCH_HEADER } from "../consts"; import type { DocumentId, VaultUpdateId } from "../persistence/database"; import type { Settings } from "../persistence/settings"; import { abortable } from "../utils/abortable"; @@ -8,7 +9,7 @@ import type { FileManifestUpdateResponse } from "./types/FileManifestUpdateRespo import type { PutFileContent } from "./types/PutFileContent"; import type { PushFileManifest } from "./types/PushFileManifest"; import type { VaultSnapshot } from "./types/VaultSnapshot"; -import type { PingResponse } from "./types/PingResponse"; +import type { ServerConfigResponse } from "./types/ServerConfigResponse"; import { AuthenticationError, PermanentSyncError, @@ -45,8 +46,8 @@ export class SyncService { await this.history.save(undefined); } - public async ping(): Promise { - return this.request("/ping", { ignoreAborts: true }); + public async getServerConfig(): Promise { + return this.request("/config", { ignoreAborts: true }); } public async getVaultSnapshot(): Promise { @@ -93,9 +94,11 @@ export class SyncService { } private async recordHistory(response: Response): Promise { - const checkpoint = response.headers.get("x-vault-link-history"); + const checkpoint = response.headers.get(HISTORY_HEADER); if (checkpoint === null || checkpoint === "") { - throw new PermanentSyncError("Missing server history checkpoint HTTP header"); + throw new PermanentSyncError( + "Missing server history checkpoint HTTP header" + ); } const previous = this.history.get(); @@ -143,7 +146,7 @@ export class SyncService { headers: { ...(checkpoint === undefined ? {} - : { "X-Vault-Link-History": checkpoint }), + : { [HISTORY_HEADER]: checkpoint }), Authorization: `Bearer ${token}`, "Device-Id": this.deviceId, "Content-Type": "application/json" @@ -152,7 +155,7 @@ export class SyncService { ); signal.throwIfAborted(); - if (response.headers.get("x-vault-link-history-mismatch") === "1") { + if (response.headers.get(HISTORY_MISMATCH_HEADER) === "1") { throw new ServerHistoryChangedError( "Server history changed; recovering local work" );