UNPKG

react-native-healthkit-bridge

Version:

A comprehensive React Native bridge for Apple HealthKit with TypeScript support, advanced authorization, and flexible data queries

143 lines (121 loc) 3.63 kB
import { HEALTHKIT_CONFIG } from '../config/healthkit.config'; export interface RetryConfig { maxAttempts: number; baseDelay: number; maxDelay: number; backoffMultiplier: number; retryableErrors?: string[]; } export interface RetryResult<T> { success: boolean; data?: T; error?: Error; attempts: number; totalTime: number; } export class RetryManager { private static instance: RetryManager; private defaultConfig: RetryConfig = { maxAttempts: HEALTHKIT_CONFIG.MAX_RETRIES, baseDelay: HEALTHKIT_CONFIG.RETRY_DELAY, maxDelay: 10000, // 10 seconds backoffMultiplier: 2, retryableErrors: [ 'ERR_QUERY', 'ERR_NETWORK', 'ERR_TIMEOUT', 'ERR_HEALTHKIT_UNAVAILABLE' ] }; private constructor() {} static getInstance(): RetryManager { if (!RetryManager.instance) { RetryManager.instance = new RetryManager(); } return RetryManager.instance; } async withRetry<T>( operation: () => Promise<T>, config?: Partial<RetryConfig> ): Promise<RetryResult<T>> { const finalConfig = { ...this.defaultConfig, ...config }; const startTime = Date.now(); let lastError: Error; for (let attempt = 1; attempt <= finalConfig.maxAttempts; attempt++) { try { const data = await operation(); return { success: true, data, attempts: attempt, totalTime: Date.now() - startTime }; } catch (error: any) { lastError = error; // Check if error is retryable if (!this.isRetryableError(error, finalConfig.retryableErrors)) { return { success: false, error, attempts: attempt, totalTime: Date.now() - startTime }; } // If this is the last attempt, don't wait if (attempt === finalConfig.maxAttempts) { break; } // Calculate delay with exponential backoff const delay = Math.min( finalConfig.baseDelay * Math.pow(finalConfig.backoffMultiplier, attempt - 1), finalConfig.maxDelay ); await this.sleep(delay); } } return { success: false, error: lastError!, attempts: finalConfig.maxAttempts, totalTime: Date.now() - startTime }; } private isRetryableError(error: any, retryableErrors?: string[]): boolean { if (!retryableErrors || retryableErrors.length === 0) { return true; // Retry all errors by default } const errorCode = error?.code || error?.message || ''; return retryableErrors.some(retryableError => errorCode.includes(retryableError) ); } private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } // Helper method for common retry scenarios async retryQuery<T>(operation: () => Promise<T>): Promise<T> { const result = await this.withRetry(operation, { retryableErrors: ['ERR_QUERY', 'ERR_TIMEOUT'] }); if (!result.success) { throw result.error; } return result.data!; } async retryAuth<T>(operation: () => Promise<T>): Promise<T> { const result = await this.withRetry(operation, { retryableErrors: ['ERR_AUTHORIZATION', 'ERR_HEALTHKIT_UNAVAILABLE'] }); if (!result.success) { throw result.error; } return result.data!; } } // Convenience function export function withRetry<T>( operation: () => Promise<T>, config?: Partial<RetryConfig> ): Promise<RetryResult<T>> { return RetryManager.getInstance().withRetry(operation, config); }