feat(debug,protection): Force Reset for Android screenshot-capture
Bug-context: user reports nach Cooldown-Disable auf v0.2.1 Android-Build reactiviert sich Schutz auto → a11y-Settings bleibt blockiert → keine Screenshots möglich. v0.3.0 hat den Backend-protectionDisabledAt-Guard der das verhindert, aber Test-Devices brauchen ein direktes Reset-Tool für Multi-Locale-Screenshots. Backend: - POST /api/protection/dev-force-disabled — sets protectionDisabledAt=NOW() ohne Cooldown-Vorlauf. Production-Guard (rebreak.org-non-staging → 403). Frontend: - /debug Android-Section refactored: "Force Reset + Settings öffnen" Button - Bundle aus 3 Steps: 1. native forceDisable (VPN stop + tamper disarm + filter_enabled=false) 2. backend dev-force-disabled (Anti-Auto-Reactivation-Mark) 3. Settings → Bedienungshilfen öffnen - Danach: User toggled ReBreak-Service in Android-Settings manuell off → frischer a11y-deep-link-Trigger für nächste Screenshot-Iteration Also: fix /onboarding/welcome → /onboarding (Duo-Rewrite hat den alten Pfad gelöscht). Route 404 auf Android sichtbar wenn User in debug-toggle 'welcome' oder 'nickname' tappt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
77bb7b84dc
commit
56bb59915d
@ -7,6 +7,7 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
Clipboard,
|
Clipboard,
|
||||||
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
@ -15,7 +16,7 @@ import { useColors } from '../lib/theme';
|
|||||||
import { useMe, invalidateMe, type Plan } from '../hooks/useMe';
|
import { useMe, invalidateMe, type Plan } from '../hooks/useMe';
|
||||||
import { apiFetch } from '../lib/api';
|
import { apiFetch } from '../lib/api';
|
||||||
import { PlanChangeSheet } from '../components/plan/PlanChangeSheet';
|
import { PlanChangeSheet } from '../components/plan/PlanChangeSheet';
|
||||||
import { getCooldownTestMode, setCooldownTestMode } from '../lib/protection';
|
import { getCooldownTestMode, setCooldownTestMode, protection } from '../lib/protection';
|
||||||
import { useRealtimeDebugStore, type LogEntry } from '../stores/realtimeDebug';
|
import { useRealtimeDebugStore, type LogEntry } from '../stores/realtimeDebug';
|
||||||
import { supabase } from '../lib/supabase';
|
import { supabase } from '../lib/supabase';
|
||||||
|
|
||||||
@ -115,6 +116,8 @@ export default function DebugScreen() {
|
|||||||
|
|
||||||
<CooldownTestModeToggle />
|
<CooldownTestModeToggle />
|
||||||
|
|
||||||
|
{Platform.OS === 'android' ? <AndroidA11yResetToggle colors={colors} /> : null}
|
||||||
|
|
||||||
<RealtimeStatusCard />
|
<RealtimeStatusCard />
|
||||||
<RealtimeLogCard />
|
<RealtimeLogCard />
|
||||||
|
|
||||||
@ -690,9 +693,13 @@ function OnboardingResetToggle({
|
|||||||
});
|
});
|
||||||
invalidateMe();
|
invalidateMe();
|
||||||
if (step === 'welcome') {
|
if (step === 'welcome') {
|
||||||
router.replace('/onboarding/welcome');
|
// /onboarding/welcome existiert nicht mehr (Duo-Rewrite → /onboarding/index.tsx).
|
||||||
|
// Korrekter Pfad ist /onboarding (Expo Router löst index.tsx auto auf).
|
||||||
|
router.replace('/onboarding');
|
||||||
} else if (step === 'nickname') {
|
} else if (step === 'nickname') {
|
||||||
router.replace('/profile/edit');
|
// Legacy-Stage — Duo-Flow navigiert intern; /onboarding triggert via
|
||||||
|
// slideFromStep den Resume zur Nickname-Slide.
|
||||||
|
router.replace('/onboarding');
|
||||||
} else if (step === 'block') {
|
} else if (step === 'block') {
|
||||||
router.replace('/(app)/blocker');
|
router.replace('/(app)/blocker');
|
||||||
} else if (step === 'done') {
|
} else if (step === 'done') {
|
||||||
@ -783,6 +790,117 @@ function OnboardingResetToggle({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Android: A11y-Settings Quick-Open ──────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android-only Dev-Helper: öffnet Settings → Bedienungshilfen damit User den
|
||||||
|
* ReBreak-Service manuell off-toggeln kann. Wird gebraucht für Screenshot-
|
||||||
|
* Capture vom a11y-Settings-Page in 4 Sprachen — nach Cooldown disarmt
|
||||||
|
* forceDisable() den tamper-lock, aber das System-Switch in den Einstellungen
|
||||||
|
* bleibt programmatisch un-modifizierbar (Android-OS-Restriction).
|
||||||
|
*/
|
||||||
|
function AndroidA11yResetToggle({
|
||||||
|
colors,
|
||||||
|
}: {
|
||||||
|
colors: import('../lib/theme').ColorScheme;
|
||||||
|
}) {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
async function forceResetAndOpen() {
|
||||||
|
if (busy) return;
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
// Step 1: native forceDisable — stoppt VPN, disarmt tamper-lock, setzt
|
||||||
|
// filter_enabled=false → a11y-service wird passiv → blockt nichts mehr.
|
||||||
|
await protection.forceDisable();
|
||||||
|
|
||||||
|
// Step 2: backend protection-state auf "explizit disabled" markieren →
|
||||||
|
// enforceProtection-Loop feuert KEINE Auto-Reactivation in den nächsten
|
||||||
|
// Pollings. Sonst wäre der a11y-Service-Off-Toggle zwecklos weil die
|
||||||
|
// App den Filter sofort wieder hochfährt.
|
||||||
|
await apiFetch('/api/protection/dev-force-disabled', { method: 'POST' });
|
||||||
|
invalidateMe();
|
||||||
|
|
||||||
|
// Step 3: Android-Settings → Bedienungshilfen öffnen — User toggelt
|
||||||
|
// ReBreak-Service manuell off (Android-OS-Restriction: programmatisch
|
||||||
|
// nicht setzbar). Danach Screenshot-Capture frischer Trigger möglich.
|
||||||
|
await protection.openSystemSettings('accessibility');
|
||||||
|
} catch (e: unknown) {
|
||||||
|
Alert.alert('Fehler', e instanceof Error ? e.message : String(e));
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
borderRadius: 14,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(0,0,0,0.05)',
|
||||||
|
padding: 14,
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 11,
|
||||||
|
backgroundColor: colors.surfaceElevated,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons name="accessibility-outline" size={18} color={colors.textMuted} />
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={{ fontSize: 14, color: colors.text, fontFamily: 'Nunito_700Bold' }}>
|
||||||
|
Force Reset (Android)
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.textMuted,
|
||||||
|
fontFamily: 'Nunito_400Regular',
|
||||||
|
marginTop: 3,
|
||||||
|
lineHeight: 17,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
VPN stoppen + tamper-lock disarmen + Backend als disabled markieren
|
||||||
|
+ Settings öffnen — Anti-Auto-Reactivation für Screenshot-Capture.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={forceResetAndOpen}
|
||||||
|
disabled={busy}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.brandOrange,
|
||||||
|
borderRadius: 10,
|
||||||
|
paddingVertical: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
opacity: busy ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: 'Nunito_700Bold',
|
||||||
|
color: '#ffffff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{busy ? 'Reset läuft...' : 'Force Reset + Settings öffnen'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Cooldown Test Mode ────────────────────────────────────────────────────
|
// ─── Cooldown Test Mode ────────────────────────────────────────────────────
|
||||||
|
|
||||||
function CooldownTestModeToggle() {
|
function CooldownTestModeToggle() {
|
||||||
|
|||||||
37
backend/server/api/protection/dev-force-disabled.post.ts
Normal file
37
backend/server/api/protection/dev-force-disabled.post.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { requireUser } from "../../utils/auth";
|
||||||
|
import { usePrisma } from "../../utils/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/protection/dev-force-disabled
|
||||||
|
*
|
||||||
|
* DEV/STAGING-ONLY: Setzt protectionDisabledAt = NOW() ohne Cooldown-Vorlauf.
|
||||||
|
* Frontend-Debug-Button für Screenshot-Capture (Android-a11y-reset-flow).
|
||||||
|
*
|
||||||
|
* Production-Guard: appUrl enthält "rebreak.org" aber NICHT "staging" → 403.
|
||||||
|
*
|
||||||
|
* Sobald gesetzt:
|
||||||
|
* - /api/protection/state gibt protectionShouldBeActive=false zurück
|
||||||
|
* - Frontend's enforceProtection-Loop feuert KEINE Auto-Reactivation mehr
|
||||||
|
* - User kann a11y-Settings öffnen und manuell den ReBreak-Service off-toggeln
|
||||||
|
*
|
||||||
|
* Zum Wiedereinschalten: POST /api/protection/mark-active (clear flag).
|
||||||
|
*/
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const user = await requireUser(event);
|
||||||
|
|
||||||
|
const config = useRuntimeConfig(event);
|
||||||
|
const appUrl = (config.public?.appUrl as string) ?? "";
|
||||||
|
const isProductionUrl =
|
||||||
|
appUrl.includes("rebreak.org") && !appUrl.includes("staging");
|
||||||
|
if (isProductionUrl) {
|
||||||
|
throw createError({ statusCode: 403, message: "dev-only" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = usePrisma();
|
||||||
|
await db.profile.update({
|
||||||
|
where: { id: user.id },
|
||||||
|
data: { protectionDisabledAt: new Date() },
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user