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
text/typescript
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);
}