diff --git a/src/hooks/encryption/useDecryptContent.js b/src/hooks/encryption/useDecryptContent.js index 2eae6f9..3d46f0a 100644 --- a/src/hooks/encryption/useDecryptContent.js +++ b/src/hooks/encryption/useDecryptContent.js @@ -29,17 +29,25 @@ export const useDecryptContent = () => { return await inProgressMap.current.get(cacheKey); } catch (error) { // If the existing promise rejects, we'll try again below - console.warn('Previous decryption attempt failed, retrying'); + if (error.name !== 'AbortError') { + console.warn('Previous decryption attempt failed, retrying'); + } } } + // Create abort controller for this request + const abortController = new AbortController(); + // Create a new decryption promise for this content const decryptPromise = (async () => { try { setIsLoading(true); setError(null); - const response = await axios.post('/api/decrypt', { encryptedContent }); + const response = await axios.post('/api/decrypt', + { encryptedContent }, + { signal: abortController.signal } + ); if (response.status !== 200) { throw new Error(`Failed to decrypt: ${response.statusText}`); @@ -52,6 +60,11 @@ export const useDecryptContent = () => { return decryptedContent; } catch (error) { + // Handle abort errors specifically + if (axios.isCancel(error)) { + throw new DOMException('Decryption aborted', 'AbortError'); + } + setError(error.message || 'Decryption failed'); // Re-throw to signal failure to awaiter throw error; @@ -62,9 +75,19 @@ export const useDecryptContent = () => { } })(); - // Store the promise in our map + // Store the promise and abort controller in our map + const abortablePromise = { + promise: decryptPromise, + abort: () => abortController.abort() + }; + inProgressMap.current.set(cacheKey, decryptPromise); + // Function to handle timeouts from parent callers + decryptPromise.cancel = () => { + abortController.abort(); + }; + // Return the promise try { return await decryptPromise; diff --git a/src/pages/course/[slug]/index.js b/src/pages/course/[slug]/index.js index 4a92a1e..b03be74 100644 --- a/src/pages/course/[slug]/index.js +++ b/src/pages/course/[slug]/index.js @@ -196,19 +196,32 @@ const useDecryption = (session, paidCourse, course, lessons, setLessons, router) processingRef.current = true; setLoading(true); + // Start the decryption process + const decryptionPromise = decryptContent(currentLesson.content); + // Add safety timeout to prevent infinite processing - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Decryption timeout')), 10000) - ); + let timeoutId; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + // Cancel the in-flight request when timeout occurs + if (decryptionPromise.cancel) { + decryptionPromise.cancel(); + } + reject(new Error('Decryption timeout')); + }, 10000); + }); // Use a separate try-catch for the race let decryptedContent; try { // Race between decryption and timeout decryptedContent = await Promise.race([ - decryptContent(currentLesson.content), + decryptionPromise, timeoutPromise ]); + + // Clear the timeout if decryption wins + clearTimeout(timeoutId); } catch (error) { // If timeout or network error, schedule a retry setTimeout(() => {