Lint & format

This commit is contained in:
Andras Schmelczer 2025-06-07 22:24:27 +01:00
parent 7e3f972531
commit 57b2b76932
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
21 changed files with 89 additions and 74 deletions

View file

@ -1,4 +1,4 @@
import { Editor } from "obsidian";
import type { Editor } from "obsidian";
import { lineAndColumnToPosition } from "./line-and-column-to-position";
export interface Cursor {

View file

@ -0,0 +1,9 @@
export function getRandomColor(name: string): string {
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = (hash << 5) - hash + name.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
const normalised = hash / 0x7fffffff;
return `hsl(${Math.abs(normalised * 360)}, 70%, 30%)`; // HSL color
}

View file

@ -9,13 +9,13 @@ import type { Settings } from "../persistence/settings";
import type { ConnectionStatus } from "./connection-status";
import { sleep } from "../utils/sleep";
import { SyncResetError } from "./sync-reset-error";
import { SerializedError } from "./types/SerializedError";
import { DocumentVersionWithoutContent } from "./types/DocumentVersionWithoutContent";
import { DocumentUpdateResponse } from "./types/DocumentUpdateResponse";
import { DocumentVersion } from "./types/DocumentVersion";
import { FetchLatestDocumentsResponse } from "./types/FetchLatestDocumentsResponse";
import { PingResponse } from "./types/PingResponse";
import { DeleteDocumentVersion } from "./types/DeleteDocumentVersion";
import type { SerializedError } from "./types/SerializedError";
import type { DocumentVersionWithoutContent } from "./types/DocumentVersionWithoutContent";
import type { DocumentUpdateResponse } from "./types/DocumentUpdateResponse";
import type { DocumentVersion } from "./types/DocumentVersion";
import type { FetchLatestDocumentsResponse } from "./types/FetchLatestDocumentsResponse";
import type { PingResponse } from "./types/PingResponse";
import type { DeleteDocumentVersion } from "./types/DeleteDocumentVersion";
export interface CheckConnectionResult {
isSuccessful: boolean;
@ -24,8 +24,8 @@ export interface CheckConnectionResult {
export class SyncService {
private static readonly NETWORK_RETRY_INTERVAL_MS = 1000;
private client: typeof globalThis.fetch;
private pingClient: typeof globalThis.fetch;
private readonly client: typeof globalThis.fetch;
private readonly pingClient: typeof globalThis.fetch;
public constructor(
private readonly deviceId: string,
@ -35,7 +35,7 @@ export class SyncService {
fetchImplementation: typeof globalThis.fetch = globalThis.fetch
) {
// ensure that if it's called a method, `this` won't be bound to the instance
const unboundFetch: typeof globalThis.fetch = (...args) =>
const unboundFetch: typeof globalThis.fetch = async (...args) =>
fetchImplementation(...args);
this.client = this.connectionStatus.getFetchImplementation(
@ -45,12 +45,6 @@ export class SyncService {
this.pingClient = unboundFetch;
}
private getUrl(path: string): string {
let { vaultName, remoteUri } = this.settings.getSettings();
remoteUri = remoteUri.replace(/\/+$/, "");
return `${remoteUri}/vaults/${vaultName}${path}`;
}
private static formatError(error: SerializedError): string {
let result = error.message;
if (error.causes.length > 0) {
@ -85,7 +79,9 @@ export class SyncService {
});
const result: SerializedError | DocumentVersionWithoutContent =
await response.json();
(await response.json()) as // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
| SerializedError
| DocumentVersionWithoutContent;
if ("errorType" in result) {
throw new Error(
@ -133,7 +129,9 @@ export class SyncService {
);
const result: SerializedError | DocumentUpdateResponse =
await response.json();
(await response.json()) as // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
| SerializedError
| DocumentUpdateResponse;
if ("errorType" in result) {
throw new Error(
@ -175,7 +173,9 @@ export class SyncService {
);
const result: SerializedError | DocumentVersionWithoutContent =
await response.json();
(await response.json()) as // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
| SerializedError
| DocumentVersionWithoutContent;
if ("errorType" in result) {
throw new Error(
@ -205,7 +205,7 @@ export class SyncService {
);
const result: SerializedError | DocumentVersion =
await response.json();
(await response.json()) as SerializedError | DocumentVersion; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
if ("errorType" in result) {
throw new Error(
@ -234,7 +234,9 @@ export class SyncService {
});
const result: SerializedError | FetchLatestDocumentsResponse =
await response.json();
(await response.json()) as // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
| SerializedError
| FetchLatestDocumentsResponse;
if ("errorType" in result) {
throw new Error(
@ -256,7 +258,7 @@ export class SyncService {
headers: this.getDefaultHeaders()
});
const result: PingResponse | SerializedError =
await response.json();
(await response.json()) as PingResponse | SerializedError; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
if ("errorType" in result) {
throw new Error(
@ -283,6 +285,12 @@ export class SyncService {
}
}
private getUrl(path: string): string {
const { vaultName, remoteUri } = this.settings.getSettings();
const safeRemoteUri = remoteUri.replace(/\/+$/, "");
return `${safeRemoteUri}/vaults/${vaultName}${path}`;
}
private getDefaultHeaders(): Record<string, string> {
return {
"device-id": this.deviceId,

View file

@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CursorSpan } from "./CursorSpan";
export type ClientCursors = { userName: string, deviceId: string, cursors: { [key in string]?: Array<CursorSpan> }, };
export interface ClientCursors { userName: string, deviceId: string, cursors: Partial<Record<string, CursorSpan[]>>, }

View file

@ -1,10 +1,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CreateDocumentVersion = {
export interface CreateDocumentVersion {
/**
* The client can decide the document id (if it wishes to) in order
* to help with syncing. If the client does not provide a document id,
* the server will generate one. If the client provides a document id
* it must not already exist in the database.
*/
document_id: string | null, relative_path: string, content: Array<number>, };
document_id: string | null, relative_path: string, content: number[], }

View file

@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CursorSpan } from "./CursorSpan";
export type CursorPositionFromClient = { documentToCursors: { [key in string]?: Array<CursorSpan> }, };
export interface CursorPositionFromClient { documentToCursors: Partial<Record<string, CursorSpan[]>>, }

View file

@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ClientCursors } from "./ClientCursors";
export type CursorPositionFromServer = { clients: Array<ClientCursors>, };
export interface CursorPositionFromServer { clients: ClientCursors[], }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CursorSpan = { start: number, end: number, };
export interface CursorSpan { start: number, end: number, }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type DeleteDocumentVersion = { relativePath: string, };
export interface DeleteDocumentVersion { relativePath: string, }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type DocumentVersion = { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, contentBase64: string, isDeleted: boolean, userId: string, deviceId: string, };
export interface DocumentVersion { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, contentBase64: string, isDeleted: boolean, userId: string, deviceId: string, }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type DocumentVersionWithoutContent = { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, isDeleted: boolean, userId: string, deviceId: string, contentSize: number, };
export interface DocumentVersionWithoutContent { vaultUpdateId: number, documentId: string, relativePath: string, updatedDate: string, isDeleted: boolean, userId: string, deviceId: string, contentSize: number, }

View file

@ -4,8 +4,8 @@ import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutCont
/**
* Response to a fetch latest documents request.
*/
export type FetchLatestDocumentsResponse = { latestDocuments: Array<DocumentVersionWithoutContent>,
export interface FetchLatestDocumentsResponse { latestDocuments: DocumentVersionWithoutContent[],
/**
* The update ID of the latest document in the response.
*/
lastUpdateId: bigint, };
lastUpdateId: bigint, }

View file

@ -3,7 +3,7 @@
/**
* Response to a ping request.
*/
export type PingResponse = {
export interface PingResponse {
/**
* Semantic version of the server.
*/
@ -12,4 +12,4 @@ serverVersion: string,
* Whether the client is authenticated based on the sent Authorization
* header.
*/
isAuthenticated: boolean, };
isAuthenticated: boolean, }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SerializedError = { errorType: string, message: string, causes: Array<string>, };
export interface SerializedError { errorType: string, message: string, causes: string[], }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UpdateDocumentVersion = { parent_version_id: bigint, relative_path: string, content: Array<number>, };
export interface UpdateDocumentVersion { parent_version_id: bigint, relative_path: string, content: number[], }

View file

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WebSocketHandshake = { token: string, deviceId: string, lastSeenVaultUpdateId: number | null, };
export interface WebSocketHandshake { token: string, deviceId: string, lastSeenVaultUpdateId: number | null, }

View file

@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DocumentVersionWithoutContent } from "./DocumentVersionWithoutContent";
export type WebSocketVaultUpdate = { documents: Array<DocumentVersionWithoutContent>, isInitialSync: boolean, };
export interface WebSocketVaultUpdate { documents: DocumentVersionWithoutContent[], isInitialSync: boolean, }

View file

@ -1,11 +1,11 @@
import type { Database } from "../persistence/database";
import type { Logger } from "../tracing/logger";
import type { Settings, SyncSettings } from "../persistence/settings";
import { WebSocketServerMessage } from "./types/WebSocketServerMessage";
import { Syncer } from "../sync-operations/syncer";
import { WebSocketClientMessage } from "./types/WebSocketClientMessage";
import { CursorPositionFromClient } from "./types/CursorPositionFromClient";
import { ClientCursors } from "./types/ClientCursors";
import type { WebSocketServerMessage } from "./types/WebSocketServerMessage";
import type { Syncer } from "../sync-operations/syncer";
import type { WebSocketClientMessage } from "./types/WebSocketClientMessage";
import type { CursorPositionFromClient } from "./types/CursorPositionFromClient";
import type { ClientCursors } from "./types/ClientCursors";
export class WebSocketManager {
private readonly webSocketStatusChangeListeners: (() => unknown)[] = [];
@ -19,7 +19,6 @@ export class WebSocketManager {
private readonly webSocketFactoryImplementation: typeof globalThis.WebSocket;
// eslint-disable-next-line @typescript-eslint/max-params
public constructor(
private readonly deviceId: string,
private readonly logger: Logger,
@ -90,6 +89,23 @@ export class WebSocketManager {
}
}
public updateLocalCursors(cursorPositions: CursorPositionFromClient): void {
if (!this.isWebSocketConnected) {
this.logger.warn(
"WebSocket is not connected, cannot send cursor positions"
);
return;
}
const message: WebSocketClientMessage = {
type: "cursorPositions",
...cursorPositions
};
this.webSocket?.send(JSON.stringify(message));
this.logger.info(
`Sent cursor positions: ${JSON.stringify(cursorPositions)}`
);
}
private updateWebSocket(settings: SyncSettings): void {
try {
this.webSocket?.close();
@ -134,6 +150,7 @@ export class WebSocketManager {
`Failed to sync remotely updated file: ${e}`
);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (message.type === "cursorPositions") {
this.logger.info(
`Received cursor positions for ${JSON.stringify(message.clients)}`
@ -159,7 +176,7 @@ export class WebSocketManager {
listener();
});
let message: WebSocketClientMessage = {
const message: WebSocketClientMessage = {
type: "handshake",
deviceId: this.deviceId,
token: settings.token,
@ -178,23 +195,6 @@ export class WebSocketManager {
};
}
public updateLocalCursors(cursorPositions: CursorPositionFromClient): void {
if (!this.isWebSocketConnected) {
this.logger.warn(
"WebSocket is not connected, cannot send cursor positions"
);
return;
}
let message: WebSocketClientMessage = {
type: "cursorPositions",
...cursorPositions
};
this.webSocket?.send(JSON.stringify(message));
this.logger.info(
`Sent cursor positions: ${JSON.stringify(cursorPositions)}`
);
}
private setWebSocketRefreshInterval(): void {
this.refreshWebSocketInterval = setInterval(() => {
if (

View file

@ -19,8 +19,8 @@ import type { NetworkConnectionStatus } from "./types/network-connection-status"
import { DocumentUpdateStatus } from "./types/document-update-status";
import { WebSocketManager } from "./services/websocket-manager";
import { createClientId } from "./utils/create-client-id";
import { CursorSpan } from "./services/types/CursorSpan";
import { ClientCursors } from "./services/types/ClientCursors";
import type { CursorSpan } from "./services/types/CursorSpan";
import type { ClientCursors } from "./services/types/ClientCursors";
export class SyncClient {
private static readonly MINIMUM_SAVE_INTERVAL_MS = 1000;
@ -275,10 +275,8 @@ export class SyncClient {
});
}
public async updateLocalCursors(documentToCursors: {
[path: RelativePath]: CursorSpan[];
}): Promise<void> {
return this.webSocketManager.updateLocalCursors({ documentToCursors });
public async updateLocalCursors(documentToCursors: Record<RelativePath, CursorSpan[]>): Promise<void> {
this.webSocketManager.updateLocalCursors({ documentToCursors });
}
public addRemoteCursorsUpdateListener(

View file

@ -16,7 +16,7 @@ import type { UnrestrictedSyncer } from "./unrestricted-syncer";
import { createPromise } from "../utils/create-promise";
import { SyncResetError } from "../services/sync-reset-error";
import { Locks } from "../utils/locks";
import { DocumentVersionWithoutContent } from "../services/types/DocumentVersionWithoutContent";
import type { DocumentVersionWithoutContent } from "../services/types/DocumentVersionWithoutContent";
export class Syncer {
private readonly remoteDocumentsLock: Locks<DocumentId>;

View file

@ -24,9 +24,9 @@ import { createPromise } from "../utils/create-promise";
import { FileNotFoundError } from "../file-operations/file-not-found-error";
import { SyncResetError } from "../services/sync-reset-error";
import { globsToRegexes } from "../utils/globs-to-regexes";
import { DocumentVersion } from "../services/types/DocumentVersion";
import { DocumentUpdateResponse } from "../services/types/DocumentUpdateResponse";
import { DocumentVersionWithoutContent } from "../services/types/DocumentVersionWithoutContent";
import type { DocumentVersion } from "../services/types/DocumentVersion";
import type { DocumentUpdateResponse } from "../services/types/DocumentUpdateResponse";
import type { DocumentVersionWithoutContent } from "../services/types/DocumentVersionWithoutContent";
export class UnrestrictedSyncer {
private ignorePatterns: RegExp[];