Pick up type changes
This commit is contained in:
parent
2a692ca669
commit
202576a03a
4 changed files with 53 additions and 25 deletions
|
|
@ -23,5 +23,5 @@ export class PermanentSyncError extends Error {}
|
||||||
|
|
||||||
export class LocalChangesDuringReconciliation 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 {}
|
export class ServerHistoryChangedError extends Error {}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ import {
|
||||||
ServerVersionMismatchError
|
ServerVersionMismatchError
|
||||||
} from "../errors/errors";
|
} from "../errors/errors";
|
||||||
import type { SyncService } from "./sync-service";
|
import type { SyncService } from "./sync-service";
|
||||||
import type { PingResponse } from "./types/PingResponse";
|
import type { ServerConfigResponse } from "./types/ServerConfigResponse";
|
||||||
|
|
||||||
export class ServerConfig {
|
export class ServerConfig {
|
||||||
private config?: Promise<PingResponse>;
|
private config?: Promise<ServerConfigResponse>;
|
||||||
|
|
||||||
public constructor(private readonly syncService: SyncService) {}
|
public constructor(private readonly syncService: SyncService) {}
|
||||||
|
|
||||||
private static validateConfig(config: PingResponse): void {
|
private static validateConfig(config: ServerConfigResponse): void {
|
||||||
if (config.supportedApiVersion !== SUPPORTED_API_VERSION) {
|
if (config.supportedApiVersion !== SUPPORTED_API_VERSION) {
|
||||||
const shouldUpgradeClient =
|
const shouldUpgradeClient =
|
||||||
config.supportedApiVersion > SUPPORTED_API_VERSION;
|
config.supportedApiVersion > SUPPORTED_API_VERSION;
|
||||||
|
|
@ -34,7 +34,7 @@ export class ServerConfig {
|
||||||
message: string;
|
message: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const result = await this.syncService.ping();
|
const result = await this.syncService.getServerConfig();
|
||||||
|
|
||||||
ServerConfig.validateConfig(result);
|
ServerConfig.validateConfig(result);
|
||||||
|
|
||||||
|
|
@ -50,10 +50,10 @@ export class ServerConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getConfig(): Promise<PingResponse> {
|
public async getConfig(): Promise<ServerConfigResponse> {
|
||||||
if (!this.config) {
|
if (!this.config) {
|
||||||
const pending = this.syncService
|
const pending = this.syncService
|
||||||
.ping()
|
.getServerConfig()
|
||||||
.then((config) => {
|
.then((config) => {
|
||||||
ServerConfig.validateConfig(config);
|
ServerConfig.validateConfig(config);
|
||||||
return config;
|
return config;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { HISTORY_HEADER } from "../consts";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
import { Settings } from "../persistence/settings";
|
import { Settings } from "../persistence/settings";
|
||||||
|
|
@ -25,7 +26,7 @@ test("paused sync sends no requests, while connection checks still work", async
|
||||||
requests++;
|
requests++;
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{ headEventId: 0, endEventId: 0, events: [] },
|
{ 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);
|
await assert.rejects(service.getEvents(0), SyncResetError);
|
||||||
assert.equal(requests, 0);
|
assert.equal(requests, 0);
|
||||||
await service.ping();
|
await service.getServerConfig();
|
||||||
assert.equal(requests, 1);
|
assert.equal(requests, 1);
|
||||||
service.resume();
|
service.resume();
|
||||||
assert.equal((await service.getEvents(0)).headEventId, 0);
|
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.promise
|
||||||
: Response.json(
|
: Response.json(
|
||||||
{ headEventId: 0, endEventId: 0, events: [] },
|
{ 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);
|
await service.getEvents(0);
|
||||||
assert.equal(signals[1]?.aborted, false);
|
assert.equal(signals[1]?.aborted, false);
|
||||||
response.resolve(
|
response.resolve(
|
||||||
Response.json(
|
Response.json({}, { headers: { [HISTORY_HEADER]: "99:abandoned" } })
|
||||||
{},
|
|
||||||
{ headers: { "X-Vault-Link-History": "99:abandoned" } }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
await new Promise((resolve) => setImmediate(resolve));
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
assert.deepEqual(saved, ["0:empty"]);
|
assert.deepEqual(saved, ["0:empty"]);
|
||||||
|
|
@ -101,7 +99,7 @@ for (const binary of [false, true]) {
|
||||||
bodyReady.resolve(controller);
|
bodyReady.resolve(controller);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{ headers: { "X-Vault-Link-History": "1:head" } }
|
{ headers: { [HISTORY_HEADER]: "1:head" } }
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
get: (): undefined => undefined,
|
get: (): undefined => undefined,
|
||||||
|
|
@ -139,7 +137,7 @@ test("pause waits for a checkpoint save already in progress", async (): Promise<
|
||||||
Response.json(
|
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 () =>
|
async () =>
|
||||||
Response.json(
|
Response.json(
|
||||||
{},
|
{},
|
||||||
{ headers: { "X-Vault-Link-History": responseCheckpoint } }
|
{ headers: { [HISTORY_HEADER]: responseCheckpoint } }
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
get: (): string | undefined => checkpoint,
|
get: (): string | undefined => checkpoint,
|
||||||
|
|
@ -212,7 +210,7 @@ for (const checkpoint of [undefined, ""]) {
|
||||||
headers:
|
headers:
|
||||||
checkpoint === undefined
|
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<void> => {
|
||||||
|
assert.fail("Config must not update the sync checkpoint");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.deepEqual(await service.getServerConfig(), config);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { HISTORY_HEADER, HISTORY_MISMATCH_HEADER } from "../consts";
|
||||||
import type { DocumentId, VaultUpdateId } from "../persistence/database";
|
import type { DocumentId, VaultUpdateId } from "../persistence/database";
|
||||||
import type { Settings } from "../persistence/settings";
|
import type { Settings } from "../persistence/settings";
|
||||||
import { abortable } from "../utils/abortable";
|
import { abortable } from "../utils/abortable";
|
||||||
|
|
@ -8,7 +9,7 @@ import type { FileManifestUpdateResponse } from "./types/FileManifestUpdateRespo
|
||||||
import type { PutFileContent } from "./types/PutFileContent";
|
import type { PutFileContent } from "./types/PutFileContent";
|
||||||
import type { PushFileManifest } from "./types/PushFileManifest";
|
import type { PushFileManifest } from "./types/PushFileManifest";
|
||||||
import type { VaultSnapshot } from "./types/VaultSnapshot";
|
import type { VaultSnapshot } from "./types/VaultSnapshot";
|
||||||
import type { PingResponse } from "./types/PingResponse";
|
import type { ServerConfigResponse } from "./types/ServerConfigResponse";
|
||||||
import {
|
import {
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
PermanentSyncError,
|
PermanentSyncError,
|
||||||
|
|
@ -45,8 +46,8 @@ export class SyncService {
|
||||||
await this.history.save(undefined);
|
await this.history.save(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ping(): Promise<PingResponse> {
|
public async getServerConfig(): Promise<ServerConfigResponse> {
|
||||||
return this.request("/ping", { ignoreAborts: true });
|
return this.request("/config", { ignoreAborts: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getVaultSnapshot(): Promise<VaultSnapshot> {
|
public async getVaultSnapshot(): Promise<VaultSnapshot> {
|
||||||
|
|
@ -93,9 +94,11 @@ export class SyncService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async recordHistory(response: Response): Promise<void> {
|
private async recordHistory(response: Response): Promise<void> {
|
||||||
const checkpoint = response.headers.get("x-vault-link-history");
|
const checkpoint = response.headers.get(HISTORY_HEADER);
|
||||||
if (checkpoint === null || checkpoint === "") {
|
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();
|
const previous = this.history.get();
|
||||||
|
|
@ -143,7 +146,7 @@ export class SyncService {
|
||||||
headers: {
|
headers: {
|
||||||
...(checkpoint === undefined
|
...(checkpoint === undefined
|
||||||
? {}
|
? {}
|
||||||
: { "X-Vault-Link-History": checkpoint }),
|
: { [HISTORY_HEADER]: checkpoint }),
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
"Device-Id": this.deviceId,
|
"Device-Id": this.deviceId,
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
|
@ -152,7 +155,7 @@ export class SyncService {
|
||||||
);
|
);
|
||||||
|
|
||||||
signal.throwIfAborted();
|
signal.throwIfAborted();
|
||||||
if (response.headers.get("x-vault-link-history-mismatch") === "1") {
|
if (response.headers.get(HISTORY_MISMATCH_HEADER) === "1") {
|
||||||
throw new ServerHistoryChangedError(
|
throw new ServerHistoryChangedError(
|
||||||
"Server history changed; recovering local work"
|
"Server history changed; recovering local work"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue