chahinebrini af87893eb9 fix(android): self-heal — restart VpnService if it should be running but isn't
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>
2026-05-11 20:10:43 +02:00

73 lines
1.7 KiB
TypeScript

/**
* Web-Stub. Ergibt im Browser keinen funktionalen Schutz — der Filter ist
* inhärent device-bound. Verhindert nur dass Imports auf Web crashen.
*/
import { registerWebModule, NativeModule } from 'expo';
import type {
ActivateResult,
DeviceLayers,
DisableResult,
HealthProbeResult,
RebreakProtectionEvents,
SyncBlocklistResult,
} from './RebreakProtection.types';
class RebreakProtectionModuleWeb extends NativeModule<RebreakProtectionEvents> {
async activate(): Promise<ActivateResult> {
return { allLayersOn: false, missingLayers: [] };
}
async disable(): Promise<DisableResult> {
return { allLayersOff: true };
}
async getDeviceState(): Promise<DeviceLayers> {
return { blocklistCount: 0, blocklistLastSyncAt: null };
}
async syncBlocklist(): Promise<SyncBlocklistResult> {
return { updated: false, count: 0 };
}
async runHealthProbe(): Promise<HealthProbeResult> {
return {
outcome: 'offline',
reason: 'web_stub',
durationMs: 0,
target: '',
};
}
async openSystemSettings(): Promise<void> {
// no-op
}
// Android-only stubs (Web nutzt keinen davon, aber Type-Compat).
async isAccessibilityEnabled() {
return { enabled: false };
}
async openAccessibilitySettings() {
return { opened: false };
}
async armTamperLock() {
return { armed: false };
}
async disarmTamperLock() {
return { armed: false };
}
async getProtectionStatus() {
return {
vpnEnabled: false,
accessibilityEnabled: false,
blocklistCount: 0,
tamperArmed: false,
};
}
async reconcileVpn() {
return { restarted: false };
}
}
export default registerWebModule(RebreakProtectionModuleWeb, 'RebreakProtection');