mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-23 16:05:24 +00:00
course decryption logic in its own hook
This commit is contained in:
parent
c73d6eb8bb
commit
f526913f30
151
src/hooks/encryption/useCourseDecryption.js
Normal file
151
src/hooks/encryption/useCourseDecryption.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||||
|
import { useDecryptContent } from './useDecryptContent';
|
||||||
|
|
||||||
|
const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, router) => {
|
||||||
|
const [decryptedLessonIds, setDecryptedLessonIds] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { decryptContent } = useDecryptContent();
|
||||||
|
const processingRef = useRef(false);
|
||||||
|
const lastLessonIdRef = useRef(null);
|
||||||
|
const retryCountRef = useRef({});
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
|
||||||
|
// Get the current active lesson
|
||||||
|
const currentLessonIndex = router.query.active ? parseInt(router.query.active, 10) : 0;
|
||||||
|
const currentLesson = lessons.length > 0 ? lessons[currentLessonIndex] : null;
|
||||||
|
const currentLessonId = currentLesson?.id;
|
||||||
|
|
||||||
|
// Check if the current lesson has been decrypted
|
||||||
|
const isCurrentLessonDecrypted =
|
||||||
|
!paidCourse ||
|
||||||
|
(currentLessonId && decryptedLessonIds[currentLessonId]);
|
||||||
|
|
||||||
|
// Check user access
|
||||||
|
const hasAccess = useMemo(() => {
|
||||||
|
if (!session?.user || !paidCourse || !course) return false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
session.user.purchased?.some(purchase => purchase.courseId === course?.d) ||
|
||||||
|
session.user?.role?.subscribed ||
|
||||||
|
session.user?.pubkey === course?.pubkey
|
||||||
|
);
|
||||||
|
}, [session, paidCourse, course]);
|
||||||
|
|
||||||
|
// Reset retry count when lesson changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentLessonId && lastLessonIdRef.current !== currentLessonId) {
|
||||||
|
retryCountRef.current[currentLessonId] = 0;
|
||||||
|
}
|
||||||
|
}, [currentLessonId]);
|
||||||
|
|
||||||
|
// Simplified decrypt function
|
||||||
|
const decryptCurrentLesson = useCallback(async () => {
|
||||||
|
if (!currentLesson || !hasAccess || !paidCourse) return;
|
||||||
|
if (processingRef.current) return;
|
||||||
|
if (decryptedLessonIds[currentLesson.id]) return;
|
||||||
|
if (!currentLesson.content) return;
|
||||||
|
|
||||||
|
// Check retry count
|
||||||
|
if (!retryCountRef.current[currentLesson.id]) {
|
||||||
|
retryCountRef.current[currentLesson.id] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit maximum retries
|
||||||
|
if (retryCountRef.current[currentLesson.id] >= MAX_RETRIES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment retry count
|
||||||
|
retryCountRef.current[currentLesson.id]++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
processingRef.current = true;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Start the decryption process
|
||||||
|
const decryptionPromise = decryptContent(currentLesson.content);
|
||||||
|
|
||||||
|
// Add safety timeout to prevent infinite processing
|
||||||
|
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([
|
||||||
|
decryptionPromise,
|
||||||
|
timeoutPromise
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Clear the timeout if decryption wins
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
// If timeout or network error, schedule a retry
|
||||||
|
setTimeout(() => {
|
||||||
|
processingRef.current = false;
|
||||||
|
decryptCurrentLesson();
|
||||||
|
}, 5000);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decryptedContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the lessons array with decrypted content
|
||||||
|
const updatedLessons = lessons.map(lesson =>
|
||||||
|
lesson.id === currentLesson.id
|
||||||
|
? { ...lesson, content: decryptedContent }
|
||||||
|
: lesson
|
||||||
|
);
|
||||||
|
|
||||||
|
setLessons(updatedLessons);
|
||||||
|
|
||||||
|
// Mark this lesson as decrypted
|
||||||
|
setDecryptedLessonIds(prev => ({
|
||||||
|
...prev,
|
||||||
|
[currentLesson.id]: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Reset retry counter on success
|
||||||
|
retryCountRef.current[currentLesson.id] = 0;
|
||||||
|
} catch (error) {
|
||||||
|
// Silent error handling to prevent UI disruption
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
processingRef.current = false;
|
||||||
|
}
|
||||||
|
}, [currentLesson, hasAccess, paidCourse, decryptContent, lessons, setLessons, decryptedLessonIds]);
|
||||||
|
|
||||||
|
// Run decryption when lesson changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentLessonId) return;
|
||||||
|
|
||||||
|
// Skip if the lesson hasn't changed, unless it failed decryption previously
|
||||||
|
if (lastLessonIdRef.current === currentLessonId && decryptedLessonIds[currentLessonId]) return;
|
||||||
|
|
||||||
|
// Update the last processed lesson id
|
||||||
|
lastLessonIdRef.current = currentLessonId;
|
||||||
|
|
||||||
|
if (hasAccess && paidCourse && !decryptedLessonIds[currentLessonId]) {
|
||||||
|
decryptCurrentLesson();
|
||||||
|
}
|
||||||
|
}, [currentLessonId, hasAccess, paidCourse, decryptedLessonIds, decryptCurrentLesson]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
decryptionPerformed: isCurrentLessonDecrypted,
|
||||||
|
loading,
|
||||||
|
decryptedLessonIds
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCourseDecryption;
|
@ -12,6 +12,7 @@ import { nip19 } from 'nostr-tools';
|
|||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
import { useDecryptContent } from '@/hooks/encryption/useDecryptContent';
|
import { useDecryptContent } from '@/hooks/encryption/useDecryptContent';
|
||||||
|
import useCourseDecryption from '@/hooks/encryption/useCourseDecryption';
|
||||||
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||||
import appConfig from '@/config/appConfig';
|
import appConfig from '@/config/appConfig';
|
||||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
import useWindowWidth from '@/hooks/useWindowWidth';
|
||||||
@ -135,153 +136,6 @@ const useLessons = (ndk, fetchAuthor, lessonIds, pubkey) => {
|
|||||||
return { lessons, uniqueLessons, setLessons };
|
return { lessons, uniqueLessons, setLessons };
|
||||||
};
|
};
|
||||||
|
|
||||||
const useDecryption = (session, paidCourse, course, lessons, setLessons, router) => {
|
|
||||||
const [decryptedLessonIds, setDecryptedLessonIds] = useState({});
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { decryptContent } = useDecryptContent();
|
|
||||||
const processingRef = useRef(false);
|
|
||||||
const lastLessonIdRef = useRef(null);
|
|
||||||
const retryCountRef = useRef({});
|
|
||||||
const MAX_RETRIES = 3;
|
|
||||||
|
|
||||||
// Get the current active lesson
|
|
||||||
const currentLessonIndex = router.query.active ? parseInt(router.query.active, 10) : 0;
|
|
||||||
const currentLesson = lessons.length > 0 ? lessons[currentLessonIndex] : null;
|
|
||||||
const currentLessonId = currentLesson?.id;
|
|
||||||
|
|
||||||
// Check if the current lesson has been decrypted
|
|
||||||
const isCurrentLessonDecrypted =
|
|
||||||
!paidCourse ||
|
|
||||||
(currentLessonId && decryptedLessonIds[currentLessonId]);
|
|
||||||
|
|
||||||
// Check user access
|
|
||||||
const hasAccess = useMemo(() => {
|
|
||||||
if (!session?.user || !paidCourse || !course) return false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
session.user.purchased?.some(purchase => purchase.courseId === course?.d) ||
|
|
||||||
session.user?.role?.subscribed ||
|
|
||||||
session.user?.pubkey === course?.pubkey
|
|
||||||
);
|
|
||||||
}, [session, paidCourse, course]);
|
|
||||||
|
|
||||||
// Reset retry count when lesson changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentLessonId && lastLessonIdRef.current !== currentLessonId) {
|
|
||||||
retryCountRef.current[currentLessonId] = 0;
|
|
||||||
}
|
|
||||||
}, [currentLessonId]);
|
|
||||||
|
|
||||||
// Simplified decrypt function
|
|
||||||
const decryptCurrentLesson = useCallback(async () => {
|
|
||||||
if (!currentLesson || !hasAccess || !paidCourse) return;
|
|
||||||
if (processingRef.current) return;
|
|
||||||
if (decryptedLessonIds[currentLesson.id]) return;
|
|
||||||
if (!currentLesson.content) return;
|
|
||||||
|
|
||||||
// Check retry count
|
|
||||||
if (!retryCountRef.current[currentLesson.id]) {
|
|
||||||
retryCountRef.current[currentLesson.id] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit maximum retries
|
|
||||||
if (retryCountRef.current[currentLesson.id] >= MAX_RETRIES) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment retry count
|
|
||||||
retryCountRef.current[currentLesson.id]++;
|
|
||||||
|
|
||||||
try {
|
|
||||||
processingRef.current = true;
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// Start the decryption process
|
|
||||||
const decryptionPromise = decryptContent(currentLesson.content);
|
|
||||||
|
|
||||||
// Add safety timeout to prevent infinite processing
|
|
||||||
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([
|
|
||||||
decryptionPromise,
|
|
||||||
timeoutPromise
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Clear the timeout if decryption wins
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
} catch (error) {
|
|
||||||
// If timeout or network error, schedule a retry
|
|
||||||
setTimeout(() => {
|
|
||||||
processingRef.current = false;
|
|
||||||
decryptCurrentLesson();
|
|
||||||
}, 5000);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!decryptedContent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the lessons array with decrypted content
|
|
||||||
const updatedLessons = lessons.map(lesson =>
|
|
||||||
lesson.id === currentLesson.id
|
|
||||||
? { ...lesson, content: decryptedContent }
|
|
||||||
: lesson
|
|
||||||
);
|
|
||||||
|
|
||||||
setLessons(updatedLessons);
|
|
||||||
|
|
||||||
// Mark this lesson as decrypted
|
|
||||||
setDecryptedLessonIds(prev => ({
|
|
||||||
...prev,
|
|
||||||
[currentLesson.id]: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Reset retry counter on success
|
|
||||||
retryCountRef.current[currentLesson.id] = 0;
|
|
||||||
} catch (error) {
|
|
||||||
// Silent error handling to prevent UI disruption
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
processingRef.current = false;
|
|
||||||
}
|
|
||||||
}, [currentLesson, hasAccess, paidCourse, decryptContent, lessons, setLessons, decryptedLessonIds]);
|
|
||||||
|
|
||||||
// Run decryption when lesson changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentLessonId) return;
|
|
||||||
|
|
||||||
// Skip if the lesson hasn't changed, unless it failed decryption previously
|
|
||||||
if (lastLessonIdRef.current === currentLessonId && decryptedLessonIds[currentLessonId]) return;
|
|
||||||
|
|
||||||
// Update the last processed lesson id
|
|
||||||
lastLessonIdRef.current = currentLessonId;
|
|
||||||
|
|
||||||
if (hasAccess && paidCourse && !decryptedLessonIds[currentLessonId]) {
|
|
||||||
decryptCurrentLesson();
|
|
||||||
}
|
|
||||||
}, [currentLessonId, hasAccess, paidCourse, decryptedLessonIds, decryptCurrentLesson]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
decryptionPerformed: isCurrentLessonDecrypted,
|
|
||||||
loading,
|
|
||||||
decryptedLessonIds
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Course = () => {
|
const Course = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { ndk, addSigner } = useNDKContext();
|
const { ndk, addSigner } = useNDKContext();
|
||||||
@ -371,7 +225,7 @@ const Course = () => {
|
|||||||
course?.pubkey
|
course?.pubkey
|
||||||
);
|
);
|
||||||
|
|
||||||
const { decryptionPerformed, loading: decryptionLoading, decryptedLessonIds } = useDecryption(
|
const { decryptionPerformed, loading: decryptionLoading, decryptedLessonIds } = useCourseDecryption(
|
||||||
session,
|
session,
|
||||||
paidCourse,
|
paidCourse,
|
||||||
course,
|
course,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user