After an APK reinstall (or an OS low-memory kill that START_STICKY didn't recover promptly), the VpnService dies but `filter_enabled` stays true. isVpnEffectivelyOn then reports vpn:true (from the flag) → tamperLock:true → lockedIn:true → the green "protection active" card with no toggles, while in reality nothing is filtering. New native reconcileVpn(): if `filter_enabled` && !RebreakVpnService.isRunning && VpnService.prepare()==null → startVpnService(). Wired into _layout.tsx enforceProtection() (runs on launch / foreground / 15s poll), called before reading combined state. No-op on iOS/web. If the VPN consent was revoked, isVpnEffectivelyOn already clears the flag, so that case self-resolves too. Net behavior: while `filter_enabled` is true (user hasn't exited via the cooldown), the app keeps the VPN alive. Exiting still goes through the cooldown → forceDisable → filter_enabled=false → reconcile leaves it off. DiGA-compliant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
import { NativeModule, requireNativeModule } from 'expo';
|
|
|
|
import type {
|
|
ActivateResult,
|
|
DeviceLayers,
|
|
DisableResult,
|
|
HealthProbeOpts,
|
|
HealthProbeResult,
|
|
RebreakProtectionEvents,
|
|
SyncBlocklistOpts,
|
|
SyncBlocklistResult,
|
|
SystemSettingsTarget,
|
|
} from './RebreakProtection.types';
|
|
|
|
declare class RebreakProtectionModule extends NativeModule<RebreakProtectionEvents> {
|
|
/**
|
|
* iOS: aktiviert NUR den NEFilter (URL-Filter Layer).
|
|
* Triggert iOS-Dialog "Filter-Konfiguration zulassen".
|
|
*/
|
|
activateUrlFilter(): Promise<{ enabled: boolean; error?: string }>;
|
|
|
|
/**
|
|
* iOS: aktiviert NUR Family Controls (Auth + denyAppRemoval = der Lock).
|
|
* Triggert iOS-Dialog "Bildschirmzeit verwalten".
|
|
* Sobald aktiv, kann der User den Schutz nur über Cooldown deaktivieren.
|
|
*/
|
|
activateFamilyControls(): Promise<{ enabled: boolean; error?: string }>;
|
|
|
|
/**
|
|
* Aktiviert ALLE Schutz-Layer in einem Call (legacy, beide Dialoge nacheinander).
|
|
* Bevorzugt activateUrlFilter() + activateFamilyControls() einzeln aufrufen.
|
|
*/
|
|
activate(): Promise<ActivateResult>;
|
|
|
|
/**
|
|
* Schaltet ALLE Schutz-Layer ab. NUR aufrufen wenn JS-Layer verifiziert
|
|
* hat dass der 24h-Cooldown abgelaufen ist. Native-Modul prüft das nicht
|
|
* — der Backend-Cooldown ist Single Source of Truth, das ist Aufgabe der
|
|
* JS-Schicht.
|
|
*/
|
|
disable(): Promise<DisableResult>;
|
|
|
|
/** Aktueller Device-State. Polling- und Health-Check-Pfad. */
|
|
getDeviceState(): Promise<DeviceLayers>;
|
|
|
|
/**
|
|
* Lädt blocklist.bin vom Server, schreibt atomisch in App-Group/internal
|
|
* storage, postet Reload-Notification an die Filter-Extension. Server
|
|
* respondet 304 wenn ETag matched → updated=false. Plan-aware:
|
|
* Free → nur personal-domains (≤5), Pro/Legend → 208k+ + personal.
|
|
*/
|
|
syncBlocklist(opts: SyncBlocklistOpts): Promise<SyncBlocklistResult>;
|
|
|
|
/**
|
|
* E2E-Verifikation: Hidden WebView lädt eine bekannte Gambling-Domain,
|
|
* prüft ob WebKit/Browser den Load aborted (Filter funktioniert) oder die
|
|
* Page lädt (Filter ist tot — Alarm).
|
|
*/
|
|
runHealthProbe(opts?: HealthProbeOpts): Promise<HealthProbeResult>;
|
|
|
|
/** Öffnet System-Settings auf dem entsprechenden Tab. */
|
|
openSystemSettings(target?: SystemSettingsTarget): Promise<void>;
|
|
|
|
// ─── Android-spezifische Methoden (auf iOS undefined zur Laufzeit) ───────
|
|
|
|
/** Android: Live-Check ob unser AccessibilityService aktuell als enabled
|
|
* registriert ist (Settings.Secure + AccessibilityManager). */
|
|
isAccessibilityEnabled(): Promise<{ enabled: boolean }>;
|
|
|
|
/** Android: Öffnet Settings → Bedienungshilfen, möglichst tief auf die
|
|
* Rebreak-Detail-Page (deep-link). Fallback: generelle A11y-Liste. */
|
|
openAccessibilitySettings(): Promise<{ opened: boolean }>;
|
|
|
|
/** Android: Aktiviert Tamper-Lock-Watchdog (Settings-Page-Blockade durch
|
|
* AccessibilityService). Wirft `preconditions_not_met` wenn VPN oder A11y
|
|
* nicht beide live. */
|
|
armTamperLock(): Promise<{ armed: boolean }>;
|
|
|
|
/** Android: Disarm Tamper-Lock. Schutz-Layers laufen weiter, aber Settings-
|
|
* Watchdog blockt nicht mehr. Im normalen Flow nur nach Cooldown-Ablauf. */
|
|
disarmTamperLock(): Promise<{ armed: boolean }>;
|
|
|
|
/** Android: kombinierter Status aller 3 Layers + Blocklist-Count. */
|
|
getProtectionStatus(): Promise<{
|
|
vpnEnabled: boolean;
|
|
accessibilityEnabled: boolean;
|
|
blocklistCount: number;
|
|
tamperArmed: boolean;
|
|
}>;
|
|
|
|
/** Android: Wenn der Schutz an sein soll (`filter_enabled`) der VpnService
|
|
* aber nicht läuft (Reinstall / Low-Mem-Kill) → neu starten. Bei App-Start /
|
|
* Foreground aufrufen, damit der State nicht „an aber tot" bleibt. */
|
|
reconcileVpn(): Promise<{ restarted: boolean; needsConsent?: boolean }>;
|
|
}
|
|
|
|
export default requireNativeModule<RebreakProtectionModule>('RebreakProtection');
|