feat(blocker): reactivation only re-arms the VPN/filter; a11y setup is first-time-only
The a11y (App-Lock) permission flow now runs only the first time the user turns
protection on. Reactivating after a cooldown / external disable just re-starts the
VPN/DNS filter — no a11y system prompt, no modal loop ("a11y can't be activated…").
- blocker.tsx handleActivateFamilyControls: no error modal when error === 'accessibility_pending'
(we just opened the a11y settings — that's the feedback; tapping again re-opens, no loop).
- lib/protection.ts getCombinedState: "active" = urlFilter on (App-Lock is optional hardening,
not a precondition); "recoveringFromBypass" now means urlFilter is OFF while the backend
says it should be on (a real bypass), instead of "lock is off".
- blocker.tsx recoveringFromBypass alert: offers "turn back on" → activateUrlFilter (VPN),
not activateFamilyControls.
- _layout.tsx bypass re-arm (enforceProtection fallback + onBypassNotificationTap):
protection.activate() instead of activateFamilyControls().
- new i18n keys: blocker.protection_off_title / protection_off_message / reactivate_btn.
JS-only (hot-reloadable).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3c2aee7bda
commit
4492c7b265
@ -92,10 +92,12 @@ export default function AppLayout() {
|
|||||||
bypassNotifiedRef.current = true;
|
bypassNotifiedRef.current = true;
|
||||||
const notified = await notifyBypassDetected();
|
const notified = await notifyBypassDetected();
|
||||||
if (!notified) {
|
if (!notified) {
|
||||||
// Fallback wenn Notifications nicht erlaubt sind.
|
// Fallback wenn Notifications nicht erlaubt sind. Reaktivierung setzt
|
||||||
|
// NUR den Filter/VPN wieder — kein a11y-Prompt (das passiert nur beim
|
||||||
|
// ersten Einrichten).
|
||||||
rearmInFlightRef.current = true;
|
rearmInFlightRef.current = true;
|
||||||
router.replace('/blocker');
|
router.replace('/blocker');
|
||||||
await protection.activateFamilyControls().catch(() => ({ enabled: false }));
|
await protection.activate().catch(() => null);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
rearmInFlightRef.current = false;
|
rearmInFlightRef.current = false;
|
||||||
@ -107,7 +109,8 @@ export default function AppLayout() {
|
|||||||
rearmInFlightRef.current = true;
|
rearmInFlightRef.current = true;
|
||||||
try {
|
try {
|
||||||
router.replace('/blocker');
|
router.replace('/blocker');
|
||||||
await protection.activateFamilyControls().catch(() => ({ enabled: false }));
|
// Reaktivierung = nur Filter/VPN wieder setzen (a11y nur beim ersten Mal).
|
||||||
|
await protection.activate().catch(() => null);
|
||||||
} finally {
|
} finally {
|
||||||
rearmInFlightRef.current = false;
|
rearmInFlightRef.current = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -134,12 +134,17 @@ export default function BlockerScreen() {
|
|||||||
try {
|
try {
|
||||||
const result = await activateFamilyControls();
|
const result = await activateFamilyControls();
|
||||||
console.log('[blocker] activateFamilyControls:', result);
|
console.log('[blocker] activateFamilyControls:', result);
|
||||||
if (!result.enabled) {
|
// `accessibility_pending` = die a11y-Berechtigung fehlt noch und wir haben
|
||||||
|
// grad die System-Settings geöffnet → das IST das Feedback. Kein Fehler-
|
||||||
|
// Modal (sonst Modal-Loop bei jedem Tap). a11y wird nur beim ersten
|
||||||
|
// Einrichten geholt; danach ist das hier ein 1-Tap-Arm ohne Dialog.
|
||||||
|
if (!result.enabled && result.error !== 'accessibility_pending') {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t('blocker.activate_app_lock_failed_title'),
|
t('blocker.activate_app_lock_failed_title'),
|
||||||
result.error ?? t('blocker.activate_app_lock_failed_msg'),
|
result.error ?? t('blocker.activate_app_lock_failed_msg'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('[blocker] activateFamilyControls threw:', e);
|
console.error('[blocker] activateFamilyControls threw:', e);
|
||||||
Alert.alert(t('blocker.activation_failed_title'), e?.message ?? t('common.unknown_error'));
|
Alert.alert(t('blocker.activation_failed_title'), e?.message ?? t('common.unknown_error'));
|
||||||
@ -188,16 +193,15 @@ export default function BlockerScreen() {
|
|||||||
}
|
}
|
||||||
if (bypassAlertShownRef.current) return;
|
if (bypassAlertShownRef.current) return;
|
||||||
bypassAlertShownRef.current = true;
|
bypassAlertShownRef.current = true;
|
||||||
|
// Schutz-Filter ist aus, sollte aber an sein → Reaktivierung setzt NUR den
|
||||||
|
// VPN/Filter wieder (kein a11y-Prompt — das passiert nur beim ersten Mal).
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t('blocker.activate_app_lock_failed_title'),
|
t('blocker.protection_off_title'),
|
||||||
t('blocker.layers_app_lock_warning'),
|
t('blocker.protection_off_message'),
|
||||||
[{
|
[
|
||||||
text: t('common.ok'),
|
{ text: t('common.ok'), style: 'cancel' },
|
||||||
onPress: () => {
|
{ text: t('blocker.reactivate_btn'), onPress: () => { void handleActivateUrlFilter(); } },
|
||||||
void handleActivateFamilyControls();
|
],
|
||||||
},
|
|
||||||
}],
|
|
||||||
{ cancelable: false },
|
|
||||||
);
|
);
|
||||||
}, [state?.phase, t]);
|
}, [state?.phase, t]);
|
||||||
|
|
||||||
|
|||||||
@ -243,16 +243,17 @@ export const protection = {
|
|||||||
} as DeviceLayers)
|
} as DeviceLayers)
|
||||||
: rawLayers;
|
: rawLayers;
|
||||||
|
|
||||||
const allLayersOn = isAllLayersOn(layers);
|
// "Aktiv" = der eigentliche Schutz (URL-/DNS-Filter) läuft. Der App-Lock
|
||||||
const iosLockActive =
|
// (familyControls/tamperLock) ist optionales Hardening — er macht den Schutz
|
||||||
layers.appDeletionLock ?? layers.familyControls ?? false;
|
// schwerer abschaltbar, ist aber keine Voraussetzung für "geschützt". Er wird
|
||||||
|
// nur beim ersten Aktivieren eingerichtet; eine Reaktivierung setzt nur den
|
||||||
|
// Filter wieder. → "recoveringFromBypass" heißt deshalb: Filter ist aus,
|
||||||
|
// obwohl das Backend sagt er sollte an sein (= jemand hat den VPN extern aus).
|
||||||
const phase: ProtectionPhase = cooldown.active
|
const phase: ProtectionPhase = cooldown.active
|
||||||
? "cooldownActive"
|
? "cooldownActive"
|
||||||
: backend?.protectionShouldBeActive === true &&
|
: backend?.protectionShouldBeActive === true && layers.urlFilter !== true
|
||||||
layers.urlFilter === true &&
|
|
||||||
iosLockActive !== true
|
|
||||||
? "recoveringFromBypass"
|
? "recoveringFromBypass"
|
||||||
: allLayersOn
|
: layers.urlFilter === true
|
||||||
? "active"
|
? "active"
|
||||||
: "inactive";
|
: "inactive";
|
||||||
|
|
||||||
|
|||||||
@ -241,6 +241,9 @@
|
|||||||
"activate_url_failed_title": "URL-Filter konnte nicht aktiviert werden",
|
"activate_url_failed_title": "URL-Filter konnte nicht aktiviert werden",
|
||||||
"activate_url_failed_msg": "Unbekannter Fehler.\nDu kannst es nochmal versuchen oder System-Einstellungen prüfen.",
|
"activate_url_failed_msg": "Unbekannter Fehler.\nDu kannst es nochmal versuchen oder System-Einstellungen prüfen.",
|
||||||
"activate_settings_btn": "Einstellungen",
|
"activate_settings_btn": "Einstellungen",
|
||||||
|
"protection_off_title": "Schutz ist aus",
|
||||||
|
"protection_off_message": "Der Filter läuft gerade nicht, sollte aber an sein. Willst du ihn wieder einschalten?",
|
||||||
|
"reactivate_btn": "Wieder einschalten",
|
||||||
"activate_app_lock_failed_title": "App-Lock konnte nicht aktiviert werden",
|
"activate_app_lock_failed_title": "App-Lock konnte nicht aktiviert werden",
|
||||||
"activate_app_lock_failed_msg": "Die nötige Berechtigung wurde verweigert. Du kannst es nochmal versuchen.",
|
"activate_app_lock_failed_msg": "Die nötige Berechtigung wurde verweigert. Du kannst es nochmal versuchen.",
|
||||||
"sync_list_failed_title": "Filter-Liste konnte nicht geladen werden",
|
"sync_list_failed_title": "Filter-Liste konnte nicht geladen werden",
|
||||||
|
|||||||
@ -241,6 +241,9 @@
|
|||||||
"activate_url_failed_title": "Could not activate URL filter",
|
"activate_url_failed_title": "Could not activate URL filter",
|
||||||
"activate_url_failed_msg": "Unknown error.\nYou can try again or check System Settings.",
|
"activate_url_failed_msg": "Unknown error.\nYou can try again or check System Settings.",
|
||||||
"activate_settings_btn": "Settings",
|
"activate_settings_btn": "Settings",
|
||||||
|
"protection_off_title": "Protection is off",
|
||||||
|
"protection_off_message": "The filter isn't running but should be. Want to turn it back on?",
|
||||||
|
"reactivate_btn": "Turn back on",
|
||||||
"activate_app_lock_failed_title": "Could not activate App Lock",
|
"activate_app_lock_failed_title": "Could not activate App Lock",
|
||||||
"activate_app_lock_failed_msg": "The required permission was denied. You can try again.",
|
"activate_app_lock_failed_msg": "The required permission was denied. You can try again.",
|
||||||
"sync_list_failed_title": "Filter list could not be loaded",
|
"sync_list_failed_title": "Filter list could not be loaded",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user