mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-16 17:23:43 +08:00
Update
This commit is contained in:
+42
-43
@@ -2,11 +2,15 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { settingsStore } from '@/lib/store/settings-store';
|
||||
import { useSubscriptionSync } from '@/lib/hooks/useSubscriptionSync';
|
||||
import { Lock } from 'lucide-react';
|
||||
|
||||
const SESSION_UNLOCKED_KEY = 'kvideo-unlocked';
|
||||
|
||||
export function PasswordGate({ children, hasEnvPassword: initialHasEnvPassword }: { children: React.ReactNode, hasEnvPassword: boolean }) {
|
||||
// Enable background subscription syncing globally
|
||||
useSubscriptionSync();
|
||||
|
||||
const [isLocked, setIsLocked] = useState(true);
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
@@ -15,57 +19,52 @@ export function PasswordGate({ children, hasEnvPassword: initialHasEnvPassword }
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const settings = settingsStore.getSettings();
|
||||
const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true';
|
||||
let mounted = true;
|
||||
|
||||
// Determine initial lock state immediately
|
||||
// Lock if (local password enabled OR env password exists) AND not already unlocked in session
|
||||
const shouldBeLocked = (settings.passwordAccess || initialHasEnvPassword) && !isUnlocked;
|
||||
const init = async () => {
|
||||
const settings = settingsStore.getSettings();
|
||||
const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true';
|
||||
|
||||
setIsLocked(shouldBeLocked);
|
||||
setIsClient(true);
|
||||
// 1. Initial local check (fast)
|
||||
const localLocked = (settings.passwordAccess || initialHasEnvPassword) && !isUnlocked;
|
||||
if (mounted) setIsLocked(localLocked);
|
||||
if (mounted) setIsClient(true);
|
||||
|
||||
// Still run background checks to keep everything in sync
|
||||
checkEnvPasswordStatus();
|
||||
checkLockStatus();
|
||||
}, [initialHasEnvPassword]);
|
||||
// 2. Fetch remote config & sync
|
||||
try {
|
||||
const res = await fetch('/api/config');
|
||||
if (!res.ok) throw new Error('Failed to fetch config');
|
||||
|
||||
const checkEnvPasswordStatus = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/config');
|
||||
const data = await res.json();
|
||||
setHasEnvPassword(data.hasEnvPassword);
|
||||
const data = await res.json();
|
||||
|
||||
// Sync subscription sources if provided by environment
|
||||
if (data.subscriptionSources) {
|
||||
settingsStore.syncEnvSubscriptions(data.subscriptionSources);
|
||||
if (mounted) {
|
||||
setHasEnvPassword(data.hasEnvPassword);
|
||||
|
||||
// CRITICAL: Sync subscriptions immediately
|
||||
if (data.subscriptionSources) {
|
||||
console.log('Syncing env subscriptions:', data.subscriptionSources);
|
||||
settingsStore.syncEnvSubscriptions(data.subscriptionSources);
|
||||
}
|
||||
|
||||
// Re-evaluate lock status with confirmed server state
|
||||
// We only care about envPassword if we are not unlocked.
|
||||
// Access control logic:
|
||||
// Locked IF: (Local setting ON OR Env Password Exists) AND (Not Unlocked)
|
||||
const confirmLocked = (settings.passwordAccess || data.hasEnvPassword) && !isUnlocked;
|
||||
setIsLocked(confirmLocked);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("PasswordGate init failed:", e);
|
||||
// Fallback: rely on initial/local state which was already set
|
||||
}
|
||||
} catch {
|
||||
// Silently fail
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const checkLockStatus = async () => {
|
||||
const settings = settingsStore.getSettings();
|
||||
const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true';
|
||||
init();
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/config');
|
||||
const data = await res.json();
|
||||
// Note: envPasswordSet is not directly used for immediate locking if we want to rely on real-time settings
|
||||
// But we should respect the server config.
|
||||
// However, for the subscription fix, we mainly care about 'settings.passwordAccess' updating in real-time.
|
||||
const envPasswordSet = data.hasEnvPassword;
|
||||
|
||||
// Updated lock state check
|
||||
const currentlyLocked = (settings.passwordAccess || envPasswordSet) && !isUnlocked;
|
||||
setIsLocked(currentlyLocked);
|
||||
} catch {
|
||||
// Handle error by checking local settings
|
||||
const currentlyLocked = settings.passwordAccess && !isUnlocked;
|
||||
setIsLocked(currentlyLocked);
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [initialHasEnvPassword]);
|
||||
|
||||
// Subscribe to settings changes (real-time updates)
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,11 +2,9 @@ import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useSearchCache } from '@/lib/hooks/useSearchCache';
|
||||
import { useParallelSearch } from '@/lib/hooks/useParallelSearch';
|
||||
import { useSubscriptionSync } from '@/lib/hooks/useSubscriptionSync';
|
||||
import { settingsStore, type SortOption } from '@/lib/store/settings-store';
|
||||
|
||||
export function useHomePage() {
|
||||
useSubscriptionSync();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { loadFromCache, saveToCache } = useSearchCache();
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { settingsStore } from '@/lib/store/settings-store';
|
||||
import { fetchSourcesFromUrl, mergeSources } from '@/lib/utils/source-import-utils';
|
||||
|
||||
export function useSubscriptionSync() {
|
||||
const hasSyncedRef = useRef(false);
|
||||
const [subscriptions, setSubscriptions] = useState(() => settingsStore.getSettings().subscriptions);
|
||||
|
||||
// Subscribe to settings changes to detect when subscriptions are updated (e.g. from PasswordGate env sync)
|
||||
useEffect(() => {
|
||||
if (hasSyncedRef.current) return;
|
||||
hasSyncedRef.current = true;
|
||||
const unsubscribe = settingsStore.subscribe(() => {
|
||||
const currentSubs = settingsStore.getSettings().subscriptions;
|
||||
setSubscriptions(currentSubs);
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
// Effect to run the sync when subscriptions change
|
||||
useEffect(() => {
|
||||
const sync = async () => {
|
||||
const activeSubscriptions = subscriptions.filter((s: any) => s.autoRefresh !== false);
|
||||
if (activeSubscriptions.length === 0) return;
|
||||
|
||||
// We need to check if we actually need to sync.
|
||||
// If we just synced, or if nothing changed, maybe skip?
|
||||
// For now, let's rely on a simplified approach:
|
||||
// If the subscription list length/content changes, we might want to re-sync.
|
||||
// But be careful of infinite loops if we update sources inside this effect.
|
||||
|
||||
const settings = settingsStore.getSettings();
|
||||
const subscriptions = settings.subscriptions.filter(s => s.autoRefresh !== false);
|
||||
|
||||
if (subscriptions.length === 0) return;
|
||||
|
||||
let anyChanged = false;
|
||||
let currentSources = [...settings.sources];
|
||||
let currentPremiumSources = [...settings.premiumSources];
|
||||
let updatedSubscriptions = [...settings.subscriptions];
|
||||
// We use a local copy of subscriptions to avoid re-triggering this effect when we update 'lastUpdated'
|
||||
let updatedSubscriptions = [...subscriptions];
|
||||
|
||||
for (let i = 0; i < activeSubscriptions.length; i++) {
|
||||
const sub = activeSubscriptions[i];
|
||||
|
||||
// Optional: Check if we synced this recently (e.g. within 5 minutes) to avoid spamming on hot-reload/nav
|
||||
// const now = Date.now();
|
||||
// if (sub.lastUpdated && now - sub.lastUpdated < 5 * 60 * 1000) continue;
|
||||
|
||||
for (let i = 0; i < subscriptions.length; i++) {
|
||||
const sub = subscriptions[i];
|
||||
try {
|
||||
const result = await fetchSourcesFromUrl(sub.url);
|
||||
|
||||
@@ -58,6 +76,8 @@ export function useSubscriptionSync() {
|
||||
}
|
||||
};
|
||||
|
||||
sync();
|
||||
}, []);
|
||||
// Debounce slightly to avoid rapid-fire updates if multiple settings change
|
||||
const timeoutId = setTimeout(sync, 1000);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [subscriptions]); // Only re-run if subscriptions array reference changes (which happens on saveSettings)
|
||||
}
|
||||
|
||||
@@ -54,10 +54,16 @@ export function useVideoPlayer(
|
||||
isReversedRef.current = isReversed;
|
||||
}, [isReversed]);
|
||||
|
||||
|
||||
|
||||
const fetchVideoDetails = useCallback(async () => {
|
||||
if (!videoId || !source) return;
|
||||
|
||||
try {
|
||||
// Don't clear error immediately if we are just retrying silently,
|
||||
// but for manual retry or initial load we should.
|
||||
// Let's clear it to show loading state if we want, or keep it.
|
||||
// Standard behavior: clear error and show loading.
|
||||
setVideoError('');
|
||||
setLoading(true);
|
||||
|
||||
@@ -121,6 +127,33 @@ export function useVideoPlayer(
|
||||
}
|
||||
}, [videoId, source]);
|
||||
|
||||
// EFFECT: Retry logic when settings change (e.g., sources loaded from subscriptions)
|
||||
useEffect(() => {
|
||||
if (!videoId || !source || !videoError) return;
|
||||
|
||||
const unsubscribe = settingsStore.subscribe(() => {
|
||||
// If we are currently in an error state (likely "Invalid source configuration"),
|
||||
// and settings updated (likely new sources arrived), try fetching again.
|
||||
// We can be smarter: check if the source ID now exists in the store.
|
||||
const settings = settingsStore.getSettings();
|
||||
const allSources = [
|
||||
...settings.sources,
|
||||
...settings.premiumSources,
|
||||
...settings.subscriptions, // note: subscription items aren't usually video sources directly but let's check broadly
|
||||
];
|
||||
|
||||
// We really need to check if the specific source ID is now available
|
||||
// But since 'subscriptions' in store expands into 'sources'/'premiumSources',
|
||||
// we just check if any sources exist now.
|
||||
if (allSources.length > 0) {
|
||||
console.log("Settings updated, retrying video fetch...");
|
||||
fetchVideoDetails();
|
||||
}
|
||||
});
|
||||
|
||||
return () => unsubscribe();
|
||||
}, [videoId, source, videoError, fetchVideoDetails]);
|
||||
|
||||
// Sync state from params if they change externally (e.g. back/forward navigation)
|
||||
useEffect(() => {
|
||||
if (videoData?.episodes && episodeParam !== null) {
|
||||
|
||||
Reference in New Issue
Block a user