From f2326b846ea4a3309cbb163c01b85beaadbab87b Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 19 Sep 2026 20:03:45 +0100 Subject: [PATCH] Small FE improvements --- frontend/sync-client/src/index.ts | 21 ++++++++++++++++--- .../src/persistence/persistence.ts | 4 ++++ .../src/services/fetch-controller.test.ts | 2 +- .../src/services/fetch-controller.ts | 2 +- .../sync-client/src/services/server-config.ts | 13 +++++++++--- .../src/services/websocket-manager.ts | 6 +++--- .../sync-client/src/utils/set-up-telemetry.ts | 2 ++ 7 files changed, 39 insertions(+), 11 deletions(-) diff --git a/frontend/sync-client/src/index.ts b/frontend/sync-client/src/index.ts index cfcc5071..b639f635 100644 --- a/frontend/sync-client/src/index.ts +++ b/frontend/sync-client/src/index.ts @@ -22,13 +22,18 @@ export { Logger, LogLevel, LogLine } from "./tracing/logger"; export { type SyncSettings, DEFAULT_SETTINGS } from "./persistence/settings"; export { rateLimit } from "./utils/rate-limit"; export type { RelativePath, StoredDatabase } from "./persistence/database"; -export type { FileSystemOperations } from "./file-operations/filesystem-operations"; +export type { + FileSystemOperations, + FileSnapshot +} from "./file-operations/filesystem-operations"; export type { PersistenceProvider } from "./persistence/persistence"; export type { CursorSpan } from "./services/types/CursorSpan"; export type { ClientCursors } from "./services/types/ClientCursors"; export type { NetworkConnectionStatus } from "./types/network-connection-status"; -export type { ServerVersionMismatchError } from "./services/server-version-mismatch-error"; -export type { AuthenticationError } from "./services/authentication-error"; +export type { + ServerVersionMismatchError, + AuthenticationError +} from "./errors/errors"; export type { MaybeOutdatedClientCursors } from "./types/maybe-outdated-client-cursors"; export { DocumentSyncStatus } from "./types/document-sync-status"; export { SyncClient } from "./sync-client"; @@ -47,3 +52,13 @@ export const utils = { awaitAll, removeFromArray }; + +export type { + FileManifest, + FileManifestEntries, + EventRecord, + EventBatch, + VaultSnapshot, + PutFileContent, + PushFileManifest +} from "./services/protocol"; diff --git a/frontend/sync-client/src/persistence/persistence.ts b/frontend/sync-client/src/persistence/persistence.ts index d226e611..f879d06b 100644 --- a/frontend/sync-client/src/persistence/persistence.ts +++ b/frontend/sync-client/src/persistence/persistence.ts @@ -1,3 +1,7 @@ +/** save() atomically replaces the entire value and resolves only after both the + * data and its directory entry are durable across power loss. Failed saves must + * leave either complete value readable; callers retain their recovery journal. + */ export interface PersistenceProvider { load: () => Promise; save: (data: T) => Promise; diff --git a/frontend/sync-client/src/services/fetch-controller.test.ts b/frontend/sync-client/src/services/fetch-controller.test.ts index 94fa8424..a0a70d80 100644 --- a/frontend/sync-client/src/services/fetch-controller.test.ts +++ b/frontend/sync-client/src/services/fetch-controller.test.ts @@ -3,7 +3,7 @@ import { describe, it, mock, beforeEach, afterEach } from "node:test"; import assert from "node:assert"; import { FetchController } from "./fetch-controller"; import { Logger } from "../tracing/logger"; -import { SyncResetError } from "./sync-reset-error"; +import { SyncResetError } from "../errors/errors"; import { sleep } from "../utils/sleep"; describe("FetchController", () => { diff --git a/frontend/sync-client/src/services/fetch-controller.ts b/frontend/sync-client/src/services/fetch-controller.ts index 77b87e3a..c1bd29c9 100644 --- a/frontend/sync-client/src/services/fetch-controller.ts +++ b/frontend/sync-client/src/services/fetch-controller.ts @@ -1,6 +1,6 @@ import type { Logger } from "../tracing/logger"; import { createPromise } from "../utils/create-promise"; -import { SyncResetError } from "./sync-reset-error"; +import { SyncResetError } from "../errors/errors"; /** * Offers a resettable fetch implementation that waits until syncing is enabled diff --git a/frontend/sync-client/src/services/server-config.ts b/frontend/sync-client/src/services/server-config.ts index 309c637c..fcd8adb9 100644 --- a/frontend/sync-client/src/services/server-config.ts +++ b/frontend/sync-client/src/services/server-config.ts @@ -1,6 +1,8 @@ import { SUPPORTED_API_VERSION } from "../consts"; -import { AuthenticationError } from "./authentication-error"; -import { ServerVersionMismatchError } from "./server-version-mismatch-error"; +import { + AuthenticationError, + ServerVersionMismatchError +} from "../errors/errors"; import type { SyncService } from "./sync-service"; import type { PingResponse } from "./types/PingResponse"; @@ -74,7 +76,12 @@ export class ServerConfig { public async getConfig(): Promise { if (!this.config) { this.response ??= this.syncService.ping(); - this.config = await this.response; + try { + this.config = await this.response; + } catch (error) { + this.response = undefined; + throw error; + } } ServerConfig.validateConfig(this.config); diff --git a/frontend/sync-client/src/services/websocket-manager.ts b/frontend/sync-client/src/services/websocket-manager.ts index 09787bce..8d62fb2b 100644 --- a/frontend/sync-client/src/services/websocket-manager.ts +++ b/frontend/sync-client/src/services/websocket-manager.ts @@ -5,7 +5,7 @@ import type { WebSocketClientMessage } from "./types/WebSocketClientMessage"; import type { CursorPositionFromClient } from "./types/CursorPositionFromClient"; import type { ClientCursors } from "./types/ClientCursors"; import { createPromise } from "../utils/create-promise"; -import type { WebSocketVaultUpdate } from "./types/WebSocketVaultUpdate"; +import type { EventBatch } from "./protocol"; import { WEBSOCKET_DISCONNECT_TIMEOUT_IN_S } from "../consts"; import { removeFromArray } from "../utils/remove-from-array"; import { EventListeners } from "../utils/data-structures/event-listeners"; @@ -17,7 +17,7 @@ export class WebSocketManager { >(); public readonly onRemoteVaultUpdateReceived = new EventListeners< - (update: WebSocketVaultUpdate) => Promise + (update: EventBatch) => Promise >(); public readonly onRemoteCursorsUpdateReceived = new EventListeners< @@ -252,7 +252,7 @@ export class WebSocketManager { private async handleWebSocketMessage( message: WebSocketServerMessage ): Promise { - if (message.type === "vaultUpdate") { + if (message.type === "vaultEvents") { await this.onRemoteVaultUpdateReceived.triggerAsync(message); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition diff --git a/frontend/sync-client/src/utils/set-up-telemetry.ts b/frontend/sync-client/src/utils/set-up-telemetry.ts index d9e73a79..d3704ee1 100644 --- a/frontend/sync-client/src/utils/set-up-telemetry.ts +++ b/frontend/sync-client/src/utils/set-up-telemetry.ts @@ -24,11 +24,13 @@ export const setUpTelemetry = (): (() => void) => { } }); }; + window.addEventListener("error", onError); const onUnhandledRejection = (event: PromiseRejectionEvent): void => { Sentry.captureException(event.reason); }; + window.addEventListener("unhandledrejection", onUnhandledRejection); return (): void => {