107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
import { ref, type Ref } from 'vue';
|
|
import axios from 'axios';
|
|
import { chainConfigService } from '@/services/chainConfig';
|
|
|
|
const RETRY_BASE_DELAY_MS = 1_500;
|
|
const RETRY_MAX_DELAY_MS = 60_000;
|
|
|
|
export interface RetryManager {
|
|
schedule: () => void;
|
|
clear: () => void;
|
|
reset: () => void;
|
|
retryDelayMs: Ref<number>;
|
|
}
|
|
|
|
/**
|
|
* Creates a retry manager with exponential backoff for async operations.
|
|
*
|
|
* @param loadFn - The async function to retry (receives activeChainId as argument)
|
|
* @param activeChainId - Ref containing the current chain ID to pass to loadFn
|
|
* @param baseDelay - Initial retry delay in milliseconds (default: 1500ms)
|
|
* @param maxDelay - Maximum retry delay in milliseconds (default: 60000ms)
|
|
* @returns RetryManager with schedule, clear, and reset methods
|
|
*
|
|
* @example
|
|
* const retry = createRetryManager(loadPositions, activeChainId);
|
|
* try {
|
|
* await loadPositions(chainId);
|
|
* retry.reset(); // Reset on success
|
|
* } catch (error) {
|
|
* retry.schedule(); // Schedule retry with backoff
|
|
* }
|
|
*/
|
|
export function createRetryManager(
|
|
loadFn: (chainId: number) => Promise<void>,
|
|
activeChainId: Ref<number>,
|
|
baseDelay = RETRY_BASE_DELAY_MS,
|
|
maxDelay = RETRY_MAX_DELAY_MS
|
|
): RetryManager {
|
|
const retryDelayMs = ref(baseDelay);
|
|
let retryTimer: number | null = null;
|
|
|
|
function clear() {
|
|
if (retryTimer !== null) {
|
|
clearTimeout(retryTimer);
|
|
retryTimer = null;
|
|
}
|
|
}
|
|
|
|
function schedule() {
|
|
if (typeof window === 'undefined' || retryTimer !== null) {
|
|
return;
|
|
}
|
|
const delay = retryDelayMs.value;
|
|
retryTimer = window.setTimeout(async () => {
|
|
retryTimer = null;
|
|
await loadFn(activeChainId.value);
|
|
}, delay);
|
|
retryDelayMs.value = Math.min(retryDelayMs.value * 2, maxDelay);
|
|
}
|
|
|
|
function reset() {
|
|
retryDelayMs.value = baseDelay;
|
|
}
|
|
|
|
return { schedule, clear, reset, retryDelayMs };
|
|
}
|
|
|
|
/**
|
|
* Formats GraphQL/Axios errors into user-friendly messages.
|
|
*
|
|
* @param error - The error object from axios or other source
|
|
* @returns Formatted error message string
|
|
*/
|
|
export function formatGraphqlError(error: unknown): string {
|
|
if (axios.isAxiosError(error)) {
|
|
const responseErrors = (error.response?.data as { errors?: unknown[] })?.errors;
|
|
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
|
|
return responseErrors.map((err: unknown) => (err as { message?: string })?.message ?? 'GraphQL error').join(', ');
|
|
}
|
|
if (error.response?.status) {
|
|
return `GraphQL request failed with status ${error.response.status}`;
|
|
}
|
|
if (error.message) {
|
|
return error.message;
|
|
}
|
|
}
|
|
if (error instanceof Error && error.message) {
|
|
return error.message;
|
|
}
|
|
return 'Unknown GraphQL error';
|
|
}
|
|
|
|
/**
|
|
* Resolves a GraphQL endpoint URL for a given chain ID.
|
|
*
|
|
* @param chainId - The blockchain network ID
|
|
* @param endpointOverride - Optional endpoint URL to use instead of default
|
|
* @returns The GraphQL endpoint URL
|
|
* @throws Error if endpoint is not configured for the chain
|
|
*/
|
|
export function resolveGraphqlEndpoint(chainId: number, endpointOverride?: string): string {
|
|
const normalized = endpointOverride?.trim();
|
|
if (normalized) {
|
|
return normalized;
|
|
}
|
|
return chainConfigService.getEndpoint(chainId, 'graphql');
|
|
}
|