Refactor & lint

This commit is contained in:
Andras Schmelczer 2025-12-07 15:46:00 +00:00
parent e47d8a8179
commit 6608804d34
16 changed files with 126 additions and 133 deletions

View file

@ -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);
});

View file

@ -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 {

View file

@ -114,7 +114,7 @@ export class Database {
i === 0
? false
: records[i - 1].parallelVersion ===
current.parallelVersion
current.parallelVersion
)
) {
throw new Error(

View file

@ -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<SyncSettings> | undefined,

View file

@ -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<StoredDatabase>;
}>
>
) { }
) {}
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<void> {
@ -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<void> {
this.checkIfDestroyed("waitUntilIdle");
await this.syncer.waitUntilFinished();

View file

@ -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 = [];

View file

@ -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<DocumentId>;
public readonly onRemainingOperationsCountChanged = new EventListeners<
(remainingOperations: number) => unknown
>();
private readonly remoteDocumentsLock: Locks<DocumentId>;
// FIFO to limit the number of concurrent sync operations
private readonly syncQueue: PQueue;
private _isFirstSyncComplete = false;
private runningScheduleSyncForOfflineChanges: Promise<void> | 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
) {

View file

@ -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`
};
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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})`;
}

View file

@ -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;
};

View file

@ -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<TListener extends (...args: any[]) => 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<TListener extends (...args: any[]) => any> {
await awaitAll(
this.listeners
.map((listener) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return listener(...args);
})
.filter((result): result is Promise<unknown> => {
@ -62,10 +68,4 @@ export class EventListeners<TListener extends (...args: any[]) => any> {
public clear(): void {
this.listeners.length = 0;
}
public get count(): number {
return this.listeners.length;
}
}

View file

@ -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;