import { useCallback, useState } from 'react'; import { Platform } from 'react-native'; import Constants from 'expo-constants'; import { supabase } from '../lib/supabase'; import { protection } from '../lib/protection'; type SyncResult = { ok: boolean; count?: number; plan?: string; error?: string }; /** * Synct die binary Blocklist (`blocklist.bin`) vom Server in die App-Group. * Die NEFilter-Extension memory-mapped diese Datei — ohne Sync = leere * Blocklist = nichts wird geblockt. * * Triggers: * - direkt nach activateUrlFilter() success * - nach Domain-Add/-Submit/-Delete * - bei App-Resume (in case Server-Updates kamen) * * Backend respondet 304 wenn ETag matched → kein Re-Download. * * iOS-Layer-2 / VIP: die kuratierte webContent-Domain-Liste wird über die * SEPARATE `syncWebContent`-Funktion gesynct — bewusst NICHT an `sync()` * gekoppelt. Layer 2 hängt an Family Controls, nicht am URL-Filter; der Sync * muss daher ungated laufen (auch wenn der URL-Filter aus ist). */ export function useBlocklistSync() { const [syncing, setSyncing] = useState(false); const [lastResult, setLastResult] = useState(null); // iOS-Layer-2 / VIP-Liste: kuratierte webContent-Domain-Liste vom Backend // syncen. ENTKOPPELT vom Blocklist-Sync — Layer 2 hängt an Family Controls, // NICHT am URL-Filter, also ungated aufrufbar. Best-effort: schlägt es fehl, // greift nativ der gebündelte Fallback (loadWebContentDomains). const syncWebContent = useCallback(async (): Promise => { if (Platform.OS !== 'ios') return; const baseURL = Constants.expoConfig?.extra?.apiUrl as string; const session = (await supabase.auth.getSession()).data.session; const authToken = session?.access_token; if (!baseURL || !authToken) { console.warn('[webcontent-sync] skipped — missing baseURL/token'); return; } try { const res = await protection.syncWebContentDomains({ baseURL, authToken }); console.log('[webcontent-sync] ok:', JSON.stringify(res)); } catch (e: any) { console.warn( '[webcontent-sync] failed (bundled fallback active):', e?.message ?? e, ); } }, []); const sync = useCallback(async (): Promise => { if (syncing) return { ok: false, error: 'already_syncing' }; setSyncing(true); try { const baseURL = Constants.expoConfig?.extra?.apiUrl as string; const session = (await supabase.auth.getSession()).data.session; const authToken = session?.access_token; if (!baseURL || !authToken) { const result = { ok: false, error: 'missing_baseURL_or_token' }; setLastResult(result); return result; } const res = await protection.syncBlocklist({ baseURL, authToken }); const result = { ok: true, count: res.count, plan: res.plan }; setLastResult(result); console.log('[blocklist-sync] ok:', res); return result; } catch (e: any) { const result = { ok: false, error: e?.message ?? 'sync_failed' }; setLastResult(result); console.error('[blocklist-sync] failed:', e); return result; } finally { setSyncing(false); } }, [syncing]); return { sync, syncWebContent, syncing, lastResult }; }