import type { Settings } from "../persistence/settings"; import type { Logger } from "../tracing/logger"; import { createPromise } from "../utils/create-promise"; import { SyncResetError } from "./sync-reset-error"; export class ConnectionStatus { private static readonly UNTIL_RESOLUTION = Symbol(); private canFetch: boolean; private until: Promise; private resolveUntil: (result: symbol) => void; private rejectUntil: (reason: unknown) => void; public constructor( settings: Settings, private readonly logger: Logger ) { this.canFetch = settings.getSettings().isSyncEnabled; [this.until, this.resolveUntil, this.rejectUntil] = createPromise(); settings.addOnSettingsChangeListener((newSettings, oldSettings) => { if (oldSettings.isSyncEnabled != newSettings.isSyncEnabled) { this.canFetch = newSettings.isSyncEnabled; this.resolveUntil(ConnectionStatus.UNTIL_RESOLUTION); [this.until, this.resolveUntil, this.rejectUntil] = createPromise(); } }); } private static getUrlFromInput(input: RequestInfo | URL): string { if (input instanceof URL) { return input.href; } if (typeof input === "string") { return input; } return input.url; } public startReset(): void { this.rejectUntil(new SyncResetError()); } public finishReset(): void { [this.until, this.resolveUntil, this.rejectUntil] = createPromise(); } public getFetchImplementation( logger: Logger, fetch: typeof globalThis.fetch = globalThis.fetch ): typeof globalThis.fetch { return async ( input: RequestInfo | URL, init?: RequestInit ): Promise => { while (!this.canFetch) { await this.until; } try { // https://github.com/jonbern/fetch-retry/blob/8684ef4e688375f623bd76f13add76dbc1d67cfb/index.js#L67C1-L70C21 const _input = typeof Request !== "undefined" && input instanceof Request ? input.clone() : input; const fetchPromise = fetch(_input, init); // We only want to catch rejections from `this.until` let result: symbol | Response | undefined = undefined; do { result = await Promise.race([this.until, fetchPromise]); } while (result === ConnectionStatus.UNTIL_RESOLUTION); const fetchResult: Response = result as Response; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion if (!fetchResult.ok) { this.logger.warn( `Fetch for ${ConnectionStatus.getUrlFromInput( input )}, got status ${fetchResult.status}` ); } return fetchResult; } catch (error) { logger.warn( `Fetch for ${ConnectionStatus.getUrlFromInput( input )}, got error: ${error}` ); throw error; } }; } }