From ef18a38e601f50ac63a5407f8cbe4cfa958a2abb Mon Sep 17 00:00:00 2001 From: MotyRose Date: Wed, 13 Aug 2025 17:51:27 +0300 Subject: [PATCH 1/2] Add retry mechanism for CloudKit authentication Implement exponential backoff retry logic to handle intermittent CloudKit authentication failures, particularly 421 status codes and UNKNOWN_ERROR responses. The mechanism retries up to 3 times with increasing delays (1s, 2s, 4s) to improve the reliability of CloudKit integration. - Add a retry loop with exponential backoff in the setupAuth function - Handle specific error types (421 status, UNKNOWN_ERROR) - Preserve original authentication flow for successful cases - Add wide error logging and final error handling --- src/components/Cloudkit.ts | 78 ++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/src/components/Cloudkit.ts b/src/components/Cloudkit.ts index 3894a14..28b96b6 100644 --- a/src/components/Cloudkit.ts +++ b/src/components/Cloudkit.ts @@ -35,19 +35,83 @@ export function useCloudkit() { }; }, []); + // Fixed authentication effect with cleanup and abort handling useEffect(() => { + let isMounted = true; + let timeoutId: NodeJS.Timeout | null = null; + const setupAuth = async (ck: CloudKit) => { - const appleId = await ck.getDefaultContainer().setUpAuth(); - if (appleId) { - setAppleSignedIn(true); - } else { - setAppleSignedIn(false); + const MAX_RETRIES = 3; + const MAX_DELAY = 8000; // Cap at 8 seconds + let attempt = 0; + let delay = 1000; // Start with 1 second + + while (isMounted && attempt < MAX_RETRIES) { + attempt++; + try { + const appleId = await ck.getDefaultContainer().setUpAuth(); + + // Check if component is still mounted before state update + if (!isMounted) return; + + if (appleId) { + setAppleSignedIn(true); + return; // Success + } + + // If appleId is null but no error, it's a valid state (user not signed in) + setAppleSignedIn(false); + return; + } catch (error: any) { + // Check if component is still mounted before continuing + if (!isMounted) return; + + const isRetryableError = error?.status === 421 || error?.ckErrorCode === "UNKNOWN_ERROR"; + const isLastAttempt = attempt >= MAX_RETRIES; + + console.warn(`CloudKit auth attempt ${attempt}/${MAX_RETRIES} failed:`, error); + + if (isRetryableError && !isLastAttempt) { + console.log(`Retrying CloudKit auth in ${delay}ms due to transient error...`); + + // Use a cancellable timeout + await new Promise((resolve) => { + timeoutId = setTimeout(() => { + timeoutId = null; + resolve(); + }, delay); + }); + + // Check again after timeout completes + if (!isMounted) return; + + delay = Math.min(delay * 2, MAX_DELAY); // Exponential backoff with cap + } else { + // Either non-retryable error or last attempt failed + setAppleSignedIn(false); + throw error; + } + } } }; if (cloudkit) { - setupAuth(cloudkit); + setupAuth(cloudkit).catch((err) => { + // Only log if component is still mounted + if (isMounted) { + console.error("CloudKit authentication setup failed:", err); + } + }); } + + // Cleanup function - prevents memory leaks and race conditions + return () => { + isMounted = false; + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }; }, [cloudkit]); useEffect(() => { @@ -66,5 +130,5 @@ export function useCloudkit() { } }, [appleSignedIn]); - return { cloudkit, appleSignedIn }; + return { cloudkit, appleSignedIn, setAppleSignedIn }; } From bf4e8134c5e5ca06880fd8071ca15f5c750cd758 Mon Sep 17 00:00:00 2001 From: Moty Rose Date: Sun, 19 Oct 2025 18:00:51 +0300 Subject: [PATCH 2/2] Enhance CloudKit authentication logging Add detailed diagnostic and error logging to the CloudKit authentication process. This includes logging the environment details, authentication attempts, and error specifics to improve debugging and monitoring capabilities. The changes aim to provide better insights into the authentication flow and facilitate troubleshooting of issues. --- src/components/Cloudkit.ts | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/components/Cloudkit.ts b/src/components/Cloudkit.ts index 28b96b6..2a73c22 100644 --- a/src/components/Cloudkit.ts +++ b/src/components/Cloudkit.ts @@ -46,8 +46,26 @@ export function useCloudkit() { let attempt = 0; let delay = 1000; // Start with 1 second + // Enhanced diagnostic logging + console.log('[CloudKit Debug] Environment:', { + userAgent: navigator.userAgent, + platform: navigator.platform, + timestamp: new Date().toISOString(), + containerConfig: { + id: ENV_CONFIG.CLOUDKIT_CONTAINER_ID, + env: ENV_CONFIG.CLOUDKIT_ENV, + }, + connection: { + online: navigator.onLine, + effectiveType: (navigator as any).connection?.effectiveType, + downlink: (navigator as any).connection?.downlink, + } + }); + while (isMounted && attempt < MAX_RETRIES) { attempt++; + console.log(`[CloudKit] Auth attempt ${attempt}/${MAX_RETRIES} starting...`); + try { const appleId = await ck.getDefaultContainer().setUpAuth(); @@ -55,11 +73,13 @@ export function useCloudkit() { if (!isMounted) return; if (appleId) { + console.log('[CloudKit] Authentication successful:', { appleId }); setAppleSignedIn(true); return; // Success } // If appleId is null but no error, it's a valid state (user not signed in) + console.log('[CloudKit] User not signed in (no appleId returned)'); setAppleSignedIn(false); return; } catch (error: any) { @@ -69,10 +89,21 @@ export function useCloudkit() { const isRetryableError = error?.status === 421 || error?.ckErrorCode === "UNKNOWN_ERROR"; const isLastAttempt = attempt >= MAX_RETRIES; - console.warn(`CloudKit auth attempt ${attempt}/${MAX_RETRIES} failed:`, error); + // Enhanced error logging + console.error('[CloudKit Error] Detailed error information:', { + attempt: `${attempt}/${MAX_RETRIES}`, + status: error?.status, + statusText: error?.statusText, + ckErrorCode: error?.ckErrorCode, + message: error?.message, + isRetryableError, + isLastAttempt, + errorObject: JSON.stringify(error, null, 2), + timestamp: new Date().toISOString(), + }); if (isRetryableError && !isLastAttempt) { - console.log(`Retrying CloudKit auth in ${delay}ms due to transient error...`); + console.log(`[CloudKit] Retrying in ${delay}ms due to transient error (${error?.status || error?.ckErrorCode})...`); // Use a cancellable timeout await new Promise((resolve) => { @@ -88,6 +119,11 @@ export function useCloudkit() { delay = Math.min(delay * 2, MAX_DELAY); // Exponential backoff with cap } else { // Either non-retryable error or last attempt failed + console.error('[CloudKit] Authentication failed permanently:', { + isRetryableError, + isLastAttempt, + finalError: error, + }); setAppleSignedIn(false); throw error; }