Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 107 additions & 7 deletions src/components/Cloudkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,119 @@ 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

// 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();

// Check if component is still mounted before state update
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) {
// 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;

// 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(`[CloudKit] Retrying in ${delay}ms due to transient error (${error?.status || error?.ckErrorCode})...`);

// Use a cancellable timeout
await new Promise<void>((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
console.error('[CloudKit] Authentication failed permanently:', {
isRetryableError,
isLastAttempt,
finalError: error,
});
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(() => {
Expand All @@ -66,5 +166,5 @@ export function useCloudkit() {
}
}, [appleSignedIn]);

return { cloudkit, appleSignedIn };
return { cloudkit, appleSignedIn, setAppleSignedIn };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cloudkit Hook Authentication State Vulnerability

Exporting setAppleSignedIn from the Cloudkit hook breaks encapsulation. This allows external code to directly manipulate the authentication state, bypassing the hook's internal retry logic and user sign-in/sign-out listeners. This can lead to inconsistencies or race conditions in the authentication state.

Fix in Cursor Fix in Web

}