diff --git a/frontend/obsidian-plugin/src/vault-link-plugin.ts b/frontend/obsidian-plugin/src/vault-link-plugin.ts index 12ba1060..7d91b9f5 100644 --- a/frontend/obsidian-plugin/src/vault-link-plugin.ts +++ b/frontend/obsidian-plugin/src/vault-link-plugin.ts @@ -136,10 +136,7 @@ export default class VaultLinkPlugin extends Plugin { ...(IS_DEBUG_BUILD ? { fetch: debugging.slowFetchFactory(1), - webSocket: debugging.slowWebSocketFactory( - 1, - new Logger() - ) + webSocket: debugging.slowWebSocketFactory(1, new Logger()) } : {}) }); @@ -174,7 +171,7 @@ export default class VaultLinkPlugin extends Plugin { this.registerEditorExtension([remoteCursorsTheme, remoteCursorsPlugin]); - client.addRemoteCursorsUpdateListener((cursors) => { + client.onRemoteCursorsUpdated.add((cursors) => { RemoteCursorsPluginValue.setCursors(cursors, this.app); renderCursorsInFileExplorer(cursors, this.app); }); diff --git a/frontend/obsidian-plugin/src/views/settings/settings-tab.ts b/frontend/obsidian-plugin/src/views/settings/settings-tab.ts index 0eeb166a..213c0d2c 100644 --- a/frontend/obsidian-plugin/src/views/settings/settings-tab.ts +++ b/frontend/obsidian-plugin/src/views/settings/settings-tab.ts @@ -41,30 +41,28 @@ export class SyncSettingsTab extends PluginSettingTab { this.editedToken = this.syncClient.getSettings().token; this.editedVaultName = this.syncClient.getSettings().vaultName; - this.syncClient.onSettingsChanged.add( - (newSettings, oldSettings) => { - let hasChanged = false; + this.syncClient.onSettingsChanged.add((newSettings, oldSettings) => { + let hasChanged = false; - if (newSettings.remoteUri !== oldSettings.remoteUri) { - this.editedServerUri = newSettings.remoteUri; - hasChanged = true; - } - - if (newSettings.token !== oldSettings.token) { - this.editedToken = newSettings.token; - hasChanged = true; - } - - if (newSettings.vaultName !== oldSettings.vaultName) { - this.editedVaultName = newSettings.vaultName; - hasChanged = true; - } - - if (hasChanged) { - this.display(); - } + if (newSettings.remoteUri !== oldSettings.remoteUri) { + this.editedServerUri = newSettings.remoteUri; + hasChanged = true; } - ); + + if (newSettings.token !== oldSettings.token) { + this.editedToken = newSettings.token; + hasChanged = true; + } + + if (newSettings.vaultName !== oldSettings.vaultName) { + this.editedVaultName = newSettings.vaultName; + hasChanged = true; + } + + if (hasChanged) { + this.display(); + } + }); } private get isApplyingChanges(): boolean { diff --git a/frontend/sync-client/src/persistence/database.ts b/frontend/sync-client/src/persistence/database.ts index 8e1cd61f..86b2845c 100644 --- a/frontend/sync-client/src/persistence/database.ts +++ b/frontend/sync-client/src/persistence/database.ts @@ -114,7 +114,7 @@ export class Database { i === 0 ? false : records[i - 1].parallelVersion === - current.parallelVersion + current.parallelVersion ) ) { throw new Error( diff --git a/frontend/sync-client/src/persistence/settings.ts b/frontend/sync-client/src/persistence/settings.ts index 234c99f6..d78170e6 100644 --- a/frontend/sync-client/src/persistence/settings.ts +++ b/frontend/sync-client/src/persistence/settings.ts @@ -33,13 +33,13 @@ export const DEFAULT_SETTINGS: SyncSettings = { }; export class Settings { - private settings: SyncSettings; - private readonly lock: Lock = new Lock(); - public readonly onSettingsChanged = new EventListeners< (newSettings: SyncSettings, oldSettings: SyncSettings) => unknown >(); + private settings: SyncSettings; + private readonly lock: Lock = new Lock(); + public constructor( private readonly logger: Logger, initialState: Partial | undefined, diff --git a/frontend/sync-client/src/sync-client.ts b/frontend/sync-client/src/sync-client.ts index 550ef096..1544a1e0 100644 --- a/frontend/sync-client/src/sync-client.ts +++ b/frontend/sync-client/src/sync-client.ts @@ -26,7 +26,7 @@ import { FixedSizeDocumentCache } from "./utils/data-structures/fix-sized-cache" import { setUpTelemetry } from "./utils/set-up-telemetry"; import { DIFF_CACHE_SIZE_MB } from "./consts"; import { ServerConfig } from "./services/server-config"; -import { EventListeners } from "./utils/data-structures/event-listeners"; +import type { EventListeners } from "./utils/data-structures/event-listeners"; export class SyncClient { private hasStartedOfflineSync = false; @@ -54,7 +54,7 @@ export class SyncClient { database: Partial; }> > - ) { } + ) {} public get documentCount(): number { return this.database.length; @@ -63,6 +63,42 @@ export class SyncClient { public get isWebSocketConnected(): boolean { return this.webSocketManager.isWebSocketConnected; } + + public get onSyncHistoryUpdated(): EventListeners< + (stats: HistoryStats) => unknown + > { + this.checkIfDestroyed("onSyncHistoryUpdated getter"); + return this.history.onHistoryUpdated; + } + + public get onSettingsChanged(): EventListeners< + (newSettings: SyncSettings, oldSettings: SyncSettings) => unknown + > { + this.checkIfDestroyed("onSettingsChanged getter"); + return this.settings.onSettingsChanged; + } + + public get onRemainingOperationsCountChanged(): EventListeners< + (remainingOperationsCount: number) => unknown + > { + this.checkIfDestroyed("onRemainingOperationsCountChanged getter"); + return this.syncer.onRemainingOperationsCountChanged; + } + + public get onWebSocketStatusChanged(): EventListeners< + (isConnected: boolean) => unknown + > { + this.checkIfDestroyed("onWebSocketStatusChanged getter"); + return this.webSocketManager.onWebSocketStatusChanged; + } + + public get onRemoteCursorsUpdated(): EventListeners< + (cursors: MaybeOutdatedClientCursors[]) => unknown + > { + this.checkIfDestroyed("onRemoteCursorsUpdated getter"); + return this.cursorTracker.onRemoteCursorsUpdated; + } + public static async create({ fs, persistence, @@ -228,9 +264,7 @@ export class SyncClient { } }); - this.settings.onSettingsChanged.add( - this.onSettingsChange.bind(this) - ); + this.settings.onSettingsChanged.add(this.onSettingsChange.bind(this)); if (this.settings.getSettings().isSyncEnabled) { this.logger.info("Starting SyncClient"); @@ -318,37 +352,6 @@ export class SyncClient { await this.settings.setSettings(value); } - public get onSyncHistoryUpdated(): EventListeners< - (stats: HistoryStats) => unknown - > { - this.checkIfDestroyed("onSyncHistoryUpdated getter"); - return this.history.onHistoryUpdated; - } - - - - - public get onSettingsChanged(): EventListeners< - (newSettings: SyncSettings, oldSettings: SyncSettings) => unknown - > { - this.checkIfDestroyed("onSettingsChanged getter"); - return this.settings.onSettingsChanged; - } - - public get onRemainingOperationsCountChanged(): EventListeners< - (remainingOperationsCount: number) => unknown - > { - this.checkIfDestroyed("onRemainingOperationsCountChanged getter"); - return this.syncer.onRemainingOperationsCountChanged; - } - - public get onWebSocketStatusChanged(): EventListeners< - (isConnected: boolean) => unknown - > { - this.checkIfDestroyed("onWebSocketStatusChanged getter"); - return this.webSocketManager.onWebSocketStatusChanged; - } - public async syncLocallyCreatedFile( relativePath: RelativePath ): Promise { @@ -414,14 +417,6 @@ export class SyncClient { await this.cursorTracker.sendLocalCursorsToServer(documentToCursors); } - - public get onRemoteCursorsUpdated(): EventListeners< - (cursors: MaybeOutdatedClientCursors[]) => unknown - > { - this.checkIfDestroyed("onRemoteCursorsUpdated getter"); - return this.cursorTracker.onRemoteCursorsUpdated; - } - public async waitUntilFinished(): Promise { this.checkIfDestroyed("waitUntilIdle"); await this.syncer.waitUntilFinished(); diff --git a/frontend/sync-client/src/sync-operations/cursor-tracker.ts b/frontend/sync-client/src/sync-operations/cursor-tracker.ts index f60cd588..bdd7d9b7 100644 --- a/frontend/sync-client/src/sync-operations/cursor-tracker.ts +++ b/frontend/sync-client/src/sync-operations/cursor-tracker.ts @@ -16,14 +16,14 @@ import { EventListeners } from "../utils/data-structures/event-listeners"; // known remote cursor positions, and for each document, tries to return the latest cursor positions that are // not from the future. export class CursorTracker { - private readonly updateLock = new Lock(); - // The returned position may be accurate, if it matches the document version, or outdated, in which case // the client has to heuristically guess it's current position based on the local edits. public readonly onRemoteCursorsUpdated = new EventListeners< (cursors: MaybeOutdatedClientCursors[]) => unknown >(); + private readonly updateLock = new Lock(); + private knownRemoteCursors: (ClientCursors & { upToDateness: DocumentUpToDateness; })[] = []; @@ -72,7 +72,6 @@ export class CursorTracker { } ); - this.fileChangeNotifier.onFileChanged.add(async (relativePath) => this.updateLock.withLock(async () => { for (const clientCursor of this.knownRemoteCursors) { @@ -156,7 +155,6 @@ export class CursorTracker { this.webSocketManager.updateLocalCursors({ documentsWithCursors }); } - public reset(): void { this.knownRemoteCursors = []; this.lastLocalCursorState = []; diff --git a/frontend/sync-client/src/sync-operations/syncer.ts b/frontend/sync-client/src/sync-operations/syncer.ts index 24b4a890..78cef699 100644 --- a/frontend/sync-client/src/sync-operations/syncer.ts +++ b/frontend/sync-client/src/sync-operations/syncer.ts @@ -24,16 +24,18 @@ import { awaitAll } from "../utils/await-all"; import { EventListeners } from "../utils/data-structures/event-listeners"; export class Syncer { - private readonly remoteDocumentsLock: Locks; public readonly onRemainingOperationsCountChanged = new EventListeners< (remainingOperations: number) => unknown >(); + private readonly remoteDocumentsLock: Locks; + // FIFO to limit the number of concurrent sync operations private readonly syncQueue: PQueue; private _isFirstSyncComplete = false; private runningScheduleSyncForOfflineChanges: Promise | undefined; + private previousRemainingOperationsCount = 0; public constructor( private readonly deviceId: string, @@ -58,17 +60,20 @@ export class Syncer { }); this.syncQueue.on("active", () => { - this.onRemainingOperationsCountChanged.trigger(this.syncQueue.size); + if (this.previousRemainingOperationsCount !== this.syncQueue.size) { + this.previousRemainingOperationsCount = this.syncQueue.size; + this.onRemainingOperationsCountChanged.trigger( + this.syncQueue.size + ); + } }); - this.webSocketManager.onWebSocketStatusChanged.add( - (isConnected) => { - if (isConnected) { - // The JS WebSocket API doesn't support setting headers, so we have to send the token as a message - this.sendHandshakeMessage(); - } + this.webSocketManager.onWebSocketStatusChanged.add((isConnected) => { + if (isConnected) { + // The JS WebSocket API doesn't support setting headers, so we have to send the token as a message + this.sendHandshakeMessage(); } - ); + }); this.webSocketManager.onRemoteVaultUpdateReceived.add( this.syncRemotelyUpdatedFile.bind(this) ); @@ -166,7 +171,7 @@ export class Syncer { // in that case, we mustn't move it again. if ( this.database.getLatestDocumentByRelativePath(relativePath) === - undefined || + undefined || this.database.getLatestDocumentByRelativePath(relativePath) ?.isDeleted === true ) { diff --git a/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts b/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts index 32cfb22a..0bef47d4 100644 --- a/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts +++ b/frontend/sync-client/src/sync-operations/unrestricted-syncer.ts @@ -333,7 +333,7 @@ export class UnrestrictedSyncer { const actualUpdateDetails: SyncUpdateDetails | SyncMovedDetails = oldPath !== undefined || - response.relativePath != originalRelativePath + response.relativePath != originalRelativePath ? { type: SyncType.MOVE, relativePath: response.relativePath, @@ -540,8 +540,9 @@ export class UnrestrictedSyncer { type: SyncType.SKIPPED, relativePath }, - message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${maxFileSizeMB - } MB` + message: `File size of ${sizeInMB} MB exceeds the maximum file size limit of ${ + maxFileSizeMB + } MB` }; } } diff --git a/frontend/sync-client/src/tracing/logger.ts b/frontend/sync-client/src/tracing/logger.ts index 6ac2b4e1..6d544fbc 100644 --- a/frontend/sync-client/src/tracing/logger.ts +++ b/frontend/sync-client/src/tracing/logger.ts @@ -20,15 +20,15 @@ export class LogLine { public constructor( public level: LogLevel, public message: string - ) { } + ) {} } export class Logger { - private readonly messages: LogLine[] = []; public readonly onLogEmitted = new EventListeners< (message: LogLine) => unknown >(); + private readonly messages: LogLine[] = []; public debug(message: string): void { this.pushMessage(message, LogLevel.DEBUG); diff --git a/frontend/sync-client/src/tracing/sync-history.ts b/frontend/sync-client/src/tracing/sync-history.ts index 5768296d..31f77283 100644 --- a/frontend/sync-client/src/tracing/sync-history.ts +++ b/frontend/sync-client/src/tracing/sync-history.ts @@ -70,18 +70,18 @@ export interface HistoryStats { } export class SyncHistory { - private readonly _entries: HistoryEntry[] = []; - public readonly onHistoryUpdated = new EventListeners< (status: HistoryStats) => unknown >(); + private readonly _entries: HistoryEntry[] = []; + private status: HistoryStats = { success: 0, error: 0 }; - public constructor(private readonly logger: Logger) { } + public constructor(private readonly logger: Logger) {} public get entries(): readonly HistoryEntry[] { return this._entries; @@ -114,8 +114,6 @@ export class SyncHistory { this.updateSuccessCount(historyEntry); } - - public reset(): void { this._entries.length = 0; this.status = { @@ -141,8 +139,8 @@ export class SyncHistory { candidate !== undefined && (this._entries[0] === candidate || candidate.timestamp.getTime() + - TIMEOUT_FOR_MERGING_HISTORY_ENTRIES_IN_SECONDS * 1000 > - entry.timestamp.getTime()) + TIMEOUT_FOR_MERGING_HISTORY_ENTRIES_IN_SECONDS * 1000 > + entry.timestamp.getTime()) ) { return candidate; } diff --git a/frontend/sync-client/src/utils/create-client-id.ts b/frontend/sync-client/src/utils/create-client-id.ts index 4da442c2..cfa132da 100644 --- a/frontend/sync-client/src/utils/create-client-id.ts +++ b/frontend/sync-client/src/utils/create-client-id.ts @@ -8,8 +8,8 @@ export function createClientId(): string { typeof navigator !== "undefined" ? navigator.platform // eslint-disable-line @typescript-eslint/no-deprecated : typeof process !== "undefined" - ? process.platform - : "unknown"; + ? process.platform + : "unknown"; return `vault-link/${packageVersion} (${uuidv4()}; ${platform})`; } diff --git a/frontend/sync-client/src/utils/data-structures/event-listeners.test.ts b/frontend/sync-client/src/utils/data-structures/event-listeners.test.ts index c3e5a483..a5f0cc7c 100644 --- a/frontend/sync-client/src/utils/data-structures/event-listeners.test.ts +++ b/frontend/sync-client/src/utils/data-structures/event-listeners.test.ts @@ -5,7 +5,8 @@ import { EventListeners } from "./event-listeners"; describe("EventListeners", () => { it("should add & remove listeners", () => { const listeners = new EventListeners<() => void>(); - const listener = () => { }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + const listener = (): void => {}; listeners.add(listener); @@ -16,10 +17,10 @@ describe("EventListeners", () => { assert.strictEqual(listeners.count, 0); }); - it("should remove listeners using unsubscribe function", () => { const listeners = new EventListeners<() => void>(); - const listener = () => { }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + const listener = (): void => {}; const unsubscribe = listeners.add(listener); unsubscribe(); @@ -29,7 +30,8 @@ describe("EventListeners", () => { it("should return false when removing non-existent listener", () => { const listeners = new EventListeners<() => void>(); - const listener = () => { }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + const listener = (): void => {}; const removed = listeners.remove(listener); @@ -38,9 +40,12 @@ describe("EventListeners", () => { it("should handle multiple listeners", () => { const listeners = new EventListeners<() => void>(); - const listener1 = () => { }; - const listener2 = () => { }; - const listener3 = () => { }; + // eslint-disable-next-line @typescript-eslint/no-empty-function + const listener1 = (): void => {}; + // eslint-disable-next-line @typescript-eslint/no-empty-function + const listener2 = (): void => {}; + // eslint-disable-next-line @typescript-eslint/no-empty-function + const listener3 = (): void => {}; listeners.add(listener1); listeners.add(listener2); @@ -82,10 +87,10 @@ describe("EventListeners", () => { let count1 = 0; let count2 = 0; - const listener1 = () => { + const listener1 = (): void => { count1++; }; - const listener2 = () => { + const listener2 = (): void => { count2++; }; @@ -127,12 +132,10 @@ describe("EventListeners", () => { assert.strictEqual(results.length, 3); }); - - it("should not trigger cleared listeners", () => { const listeners = new EventListeners<() => void>(); let called = false; - const listener = () => { + const listener = (): void => { called = true; }; diff --git a/frontend/sync-client/src/utils/data-structures/event-listeners.ts b/frontend/sync-client/src/utils/data-structures/event-listeners.ts index 008342e7..e08ca65e 100644 --- a/frontend/sync-client/src/utils/data-structures/event-listeners.ts +++ b/frontend/sync-client/src/utils/data-structures/event-listeners.ts @@ -2,11 +2,16 @@ import { removeFromArray } from "../remove-from-array"; import { awaitAll } from "../await-all"; /** -* A utility class for managing event listeners with type-safe add/remove operations. -*/ + * A utility class for managing event listeners with type-safe add/remove operations. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export class EventListeners any> { private readonly listeners: TListener[] = []; + public get count(): number { + return this.listeners.length; + } + /** * Adds a new listener to the collection. * @@ -51,6 +56,7 @@ export class EventListeners any> { await awaitAll( this.listeners .map((listener) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return listener(...args); }) .filter((result): result is Promise => { @@ -62,10 +68,4 @@ export class EventListeners any> { public clear(): void { this.listeners.length = 0; } - - public get count(): number { - return this.listeners.length; - } - - } diff --git a/frontend/test-client/src/agent/mock-agent.ts b/frontend/test-client/src/agent/mock-agent.ts index 604c3742..13d9928a 100644 --- a/frontend/test-client/src/agent/mock-agent.ts +++ b/frontend/test-client/src/agent/mock-agent.ts @@ -198,14 +198,14 @@ export class MockAgent extends MockClient { ); this.client.logger.info( "Local files: " + - Array.from(otherAgent.localFiles.keys()).join(", ") + Array.from(otherAgent.localFiles.keys()).join(", ") ); otherAgent.client.logger.info( "Local data: " + JSON.stringify(otherAgent.data, null, 2) ); otherAgent.client.logger.info( "Local files: " + - Array.from(otherAgent.localFiles.keys()).join(", ") + Array.from(otherAgent.localFiles.keys()).join(", ") ); throw e; diff --git a/scripts/check.sh b/scripts/check.sh index e8a40985..dd41fbcb 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -34,18 +34,15 @@ fi cd .. -# Use git ls-files to only check tracked files, respecting .gitignore -if [[ "$FIX_MODE" == true ]]; then - git ls-files | xargs npx eclint fix -else - git ls-files | xargs npx eclint check -fi - cd frontend npm run build npm run test npm run lint +# Use git ls-files to only check tracked files, respecting .gitignore +# We always run in fix mode and then check with git status +git ls-files | xargs npx eclint fix + if [[ "$FIX_MODE" == false ]] && [[ $(git status --porcelain) ]]; then git status --porcelain echo "Failing CI because the working directory is not clean after linting" diff --git a/scripts/update-api-types.sh b/scripts/update-api-types.sh index 5aa05d99..4b947ee8 100755 --- a/scripts/update-api-types.sh +++ b/scripts/update-api-types.sh @@ -11,5 +11,6 @@ cd - cp -r sync-server/bindings/* frontend/sync-client/src/services/types/ cd frontend -npm run lint || npx prettier --write sync-client/src/services/types/*.ts +npm run lint +git ls-files | xargs npx eclint fix cd -