@cardql/react-native
Version:
CardQL SDK for React Native applications with mobile-optimized features
162 lines (136 loc) • 4.24 kB
text/typescript
import { useState, useEffect, useCallback } from "react";
import { useIsOnline } from "./useNetworkStatus";
import { useCardQLClient } from "../context";
import { storage } from "../storage";
export interface QueuedOperation {
id: string;
mutation: string;
variables: any;
timestamp: number;
retryCount: number;
}
export interface UseOfflineQueueOptions {
maxRetries?: number;
retryDelay?: number;
storageKey?: string;
}
export interface UseOfflineQueueResult {
queue: QueuedOperation[];
addToQueue: (mutation: string, variables: any) => Promise<void>;
processQueue: () => Promise<void>;
clearQueue: () => Promise<void>;
removeFromQueue: (id: string) => Promise<void>;
isProcessing: boolean;
}
const STORAGE_KEY = "cardql_offline_queue";
/**
* Hook for managing offline operations queue
*/
export function useOfflineQueue(
options: UseOfflineQueueOptions = {}
): UseOfflineQueueResult {
const {
maxRetries = 3,
retryDelay = 1000,
storageKey = STORAGE_KEY,
} = options;
const cardql = useCardQLClient();
const isOnline = useIsOnline();
const [queue, setQueue] = useState<QueuedOperation[]>([]);
const [isProcessing, setIsProcessing] = useState(false);
// Load queue from storage on mount
useEffect(() => {
const loadQueue = async () => {
try {
const storedQueue = await storage.getItem(storageKey);
if (storedQueue) {
setQueue(JSON.parse(storedQueue));
}
} catch (error) {
console.warn("Failed to load offline queue:", error);
}
};
loadQueue();
}, [storageKey]);
// Save queue to storage whenever it changes
useEffect(() => {
const saveQueue = async () => {
try {
await storage.setItem(storageKey, JSON.stringify(queue));
} catch (error) {
console.warn("Failed to save offline queue:", error);
}
};
saveQueue();
}, [queue, storageKey]);
// Process queue when coming back online
useEffect(() => {
if (isOnline && queue.length > 0 && !isProcessing) {
processQueue();
}
}, [isOnline, queue.length, isProcessing]);
const addToQueue = useCallback(async (mutation: string, variables: any) => {
const operation: QueuedOperation = {
id: `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
mutation,
variables,
timestamp: Date.now(),
retryCount: 0,
};
setQueue((prev) => [...prev, operation]);
}, []);
const removeFromQueue = useCallback(async (id: string) => {
setQueue((prev) => prev.filter((op) => op.id !== id));
}, []);
const processQueue = useCallback(async () => {
if (!isOnline || isProcessing || queue.length === 0) {
return;
}
setIsProcessing(true);
const processedIds: string[] = [];
for (const operation of queue) {
try {
await cardql.client.request(operation.mutation, operation.variables);
processedIds.push(operation.id);
} catch (error) {
console.warn("Failed to process queued operation:", error);
// Increment retry count
setQueue((prev) =>
prev.map((op) =>
op.id === operation.id
? { ...op, retryCount: op.retryCount + 1 }
: op
)
);
// Remove if max retries exceeded
if (operation.retryCount >= maxRetries) {
processedIds.push(operation.id);
console.warn("Max retries exceeded for operation:", operation.id);
}
// Wait before processing next operation
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
// Remove processed operations
if (processedIds.length > 0) {
setQueue((prev) => prev.filter((op) => !processedIds.includes(op.id)));
}
setIsProcessing(false);
}, [isOnline, isProcessing, queue, cardql, maxRetries, retryDelay]);
const clearQueue = useCallback(async () => {
setQueue([]);
try {
await storage.removeItem(storageKey);
} catch (error) {
console.warn("Failed to clear queue storage:", error);
}
}, [storageKey]);
return {
queue,
addToQueue,
processQueue,
clearQueue,
removeFromQueue,
isProcessing,
};
}