Allow overriding WebSocket implementation and add flaky version for testing

This commit is contained in:
Andras Schmelczer 2025-04-07 23:13:45 +01:00
parent 74a8060246
commit 3ec6bd4d5b
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
8 changed files with 162 additions and 73 deletions

View file

@ -21,13 +21,13 @@ export class SyncService {
private static readonly NETWORK_RETRY_INTERVAL_MS = 1000;
private client: Client<paths>;
private pingClient: Client<paths>;
private _fetchImplementation: typeof globalThis.fetch = globalThis.fetch;
public constructor(
private readonly deviceId: string,
private readonly connectionStatus: ConnectionStatus,
private readonly settings: Settings,
private readonly logger: Logger
private readonly logger: Logger,
private readonly fetchImplementation: typeof globalThis.fetch = globalThis.fetch
) {
[this.client, this.pingClient] = this.createClient(
this.settings.getSettings().remoteUri
@ -44,13 +44,6 @@ export class SyncService {
});
}
public set fetchImplementation(fetch: typeof globalThis.fetch) {
this._fetchImplementation = fetch;
[this.client, this.pingClient] = this.createClient(
this.settings.getSettings().remoteUri
);
}
private static formatError(
error: components["schemas"]["SerializedError"]
): string {
@ -329,7 +322,7 @@ export class SyncService {
baseUrl: remoteUri,
fetch: this.connectionStatus.getFetchImplementation(
this.logger,
this._fetchImplementation
this.fetchImplementation
),
headers: {
authorization: `Bearer ${this.settings.getSettings().token}`
@ -337,7 +330,7 @@ export class SyncService {
}),
createClient<paths>({
baseUrl: remoteUri,
fetch: this._fetchImplementation,
fetch: this.fetchImplementation,
headers: {
authorization: `Bearer ${this.settings.getSettings().token}`
}

View file

@ -56,7 +56,8 @@ export class SyncClient {
public static async create({
fs,
persistence,
fetch = globalThis.fetch,
fetch,
webSocket,
nativeLineEndings = "\n"
}: {
fs: FileSystemOperations;
@ -67,6 +68,7 @@ export class SyncClient {
}>
>;
fetch?: typeof globalThis.fetch;
webSocket?: typeof globalThis.WebSocket;
nativeLineEndings?: string;
}): Promise<SyncClient> {
const logger = new Logger();
@ -113,9 +115,10 @@ export class SyncClient {
deviceId,
connectionStatus,
settings,
logger
logger,
fetch
);
syncService.fetchImplementation = fetch;
const fileOperations = new FileOperations(
logger,
database,
@ -137,7 +140,8 @@ export class SyncClient {
settings,
syncService,
fileOperations,
unrestrictedSyncer
unrestrictedSyncer,
webSocket
);
const client = new SyncClient(

View file

@ -31,6 +31,8 @@ export class Syncer {
| undefined;
private applyRemoteChangesWebSocket: WebSocket | undefined;
private readonly webSocketImplementation: typeof globalThis.WebSocket;
// eslint-disable-next-line @typescript-eslint/max-params
public constructor(
private readonly deviceId: string,
@ -39,12 +41,27 @@ export class Syncer {
private readonly settings: Settings,
private readonly syncService: SyncService,
private readonly operations: FileOperations,
private readonly internalSyncer: UnrestrictedSyncer
private readonly internalSyncer: UnrestrictedSyncer,
webSocketImplementation?: typeof globalThis.WebSocket
) {
this.syncQueue = new PQueue({
concurrency: settings.getSettings().syncConcurrency
});
if (webSocketImplementation) {
this.webSocketImplementation = webSocketImplementation;
} else {
if (
typeof globalThis !== "undefined" &&
typeof globalThis.WebSocket === "undefined"
) {
// eslint-disable-next-line
this.webSocketImplementation = require("ws"); // polyfill for WebSocket in Node.js
} else {
this.webSocketImplementation = WebSocket;
}
}
this.updateWebSocket(settings.getSettings());
this.remoteDocumentsLock = new Locks<DocumentId>(this.logger);
@ -74,7 +91,10 @@ export class Syncer {
}
public get isWebSocketConnected(): boolean {
return this.applyRemoteChangesWebSocket?.readyState === WebSocket.OPEN;
return (
this.applyRemoteChangesWebSocket?.readyState ===
this.webSocketImplementation.OPEN
);
}
public addRemainingOperationsListener(
@ -270,15 +290,9 @@ export class Syncer {
this.logger.info(`Connecting to WebSocket at ${wsUri.toString()}`);
if (
typeof globalThis !== "undefined" &&
typeof globalThis.WebSocket === "undefined"
) {
// eslint-disable-next-line
globalThis.WebSocket = require("ws"); // polyfill for WebSocket in Node.js
}
this.applyRemoteChangesWebSocket = new WebSocket(wsUri);
this.applyRemoteChangesWebSocket = new this.webSocketImplementation(
wsUri
);
this.applyRemoteChangesWebSocket.onmessage = (event): void =>
void this.syncRemotelyUpdatedFile(event.data).catch(
@ -316,7 +330,8 @@ export class Syncer {
private setWebSocketRefreshInterval(): void {
this.refreshApplyRemoteChangesWebSocketInterval = setInterval(() => {
if (
this.applyRemoteChangesWebSocket?.readyState === WebSocket.OPEN
this.applyRemoteChangesWebSocket?.readyState ===
this.webSocketImplementation.OPEN
) {
return;
}