diff --git a/plugin/package-lock.json b/plugin/package-lock.json index 04a2879d..e13c4099 100644 --- a/plugin/package-lock.json +++ b/plugin/package-lock.json @@ -18,6 +18,7 @@ "esbuild-sass-plugin": "^3.3.1", "eslint": "9.17.0", "eslint-plugin-unused-imports": "^4.1.4", + "fetch-retry": "^6.0.0", "obsidian": "1.7.2", "openapi-fetch": "0.13.3", "openapi-typescript": "7.4.4", @@ -2012,6 +2013,13 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-retry": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz", + "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", diff --git a/plugin/package.json b/plugin/package.json index 9b845901..8096c6d3 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -19,6 +19,7 @@ "dayjs": "^1.11.13", "esbuild": "0.24.0", "esbuild-plugin-wasm-pack": "^1.1.0", + "esbuild-sass-plugin": "^3.3.1", "eslint": "9.17.0", "eslint-plugin-unused-imports": "^4.1.4", "obsidian": "1.7.2", @@ -28,6 +29,6 @@ "tslib": "2.4.0", "typescript": "5.7.2", "typescript-eslint": "8.18.0", - "esbuild-sass-plugin": "^3.3.1" + "fetch-retry": "^6.0.0" } } \ No newline at end of file diff --git a/plugin/src/services/sync-service.ts b/plugin/src/services/sync-service.ts index 4713bedc..d5f4cbc7 100644 --- a/plugin/src/services/sync-service.ts +++ b/plugin/src/services/sync-service.ts @@ -11,7 +11,12 @@ import type { VaultUpdateId, } from "src/database/document-metadata"; import { Logger } from "src/tracing/logger.js"; +import { retriedFetch } from "src/utils/retried-fetch.js"; +export interface CheckConnectionResult { + isSuccessful: boolean; + message: string; +} export class SyncService { private client: Client; @@ -272,9 +277,32 @@ export class SyncService { return response.data; } + public async checkConnection(): Promise { + try { + const result = await this.ping(); + if (result.isAuthenticated) { + return { + isSuccessful: true, + message: `Successfully connected to server (version: ${result.serverVersion}) and authenticated.`, + }; + } + + return { + isSuccessful: false, + message: `Successfully connected to server (version: ${result.serverVersion}) but failed to authenticate.`, + }; + } catch (e) { + return { + isSuccessful: false, + message: `Failed to connect to server: ${e}`, + }; + } + } + private createClient(settings: SyncSettings): void { this.client = createClient({ baseUrl: settings.remoteUri, + fetch: retriedFetch, }); } } diff --git a/plugin/src/utils/retried-fetch.ts b/plugin/src/utils/retried-fetch.ts new file mode 100644 index 00000000..de40d3d4 --- /dev/null +++ b/plugin/src/utils/retried-fetch.ts @@ -0,0 +1,41 @@ +import * as fetchRetryFactory from "fetch-retry"; +import { Logger } from "src/tracing/logger"; + +const fetchWithRetry = fetchRetryFactory.default(fetch); + +export async function retriedFetch( + input: RequestInfo | URL, + init: RequestInit = {} +): Promise { + return fetchWithRetry(input, { + ...init, + retryOn: function (attempt, error, response) { + // retry on any network error, or 4xx or 5xx status codes + if (error !== null || !response || response.status >= 500) { + Logger.getInstance().warn( + `Retrying fetch attempt ${attempt} for ${getUrlFromInput( + input + )}` + ); + + return true; + } + return false; + }, + retries: 6, + retryDelay: function (attempt) { + Logger; + return Math.pow(1.5, attempt) * 500; + }, + }); +} + +function getUrlFromInput(input: RequestInfo | URL): string { + if (input instanceof URL) { + return input.href; + } + if (typeof input === "string") { + return input; + } + return input.url; +}