/* eslint-disable @typescript-eslint/no-var-requires */ /** * Expo Config-Plugin — wires the Android VpnService (DNS-Filter) + * AccessibilityService (URL filter Layer 2) into AndroidManifest.xml at * prebuild time. * * Was es macht: * 1) Sorgt für `xmlns:tools` auf . * 2) Registriert mit * foregroundServiceType="systemExempted" + intent-filter * android.net.VpnService + permission BIND_VPN_SERVICE. * (`systemExempted` ist seit Android 14 der korrekte Type für * VPN-/Filter-Foreground-Services — vorher war `specialUse`+content_filter * angedacht aber bringt mehr Probleme als Nutzen.) * 3) Registriert mit * android:permission=BIND_ACCESSIBILITY_SERVICE + intent-filter * android.accessibilityservice.AccessibilityService + meta-data * android.accessibilityservice → @xml/accessibility_service_config. * * Wird aus app.config.ts via `plugins: ['./plugins/with-rebreak-protection-android']` * registriert. Idempotent — kann beliebig oft via `expo prebuild` laufen. * * Native Source: `modules/rebreak-protection/android/src/main/java/expo/modules/rebreakprotection/` * - VpnService: expo.modules.rebreakprotection.vpn.RebreakVpnService * - AccessibilityService: expo.modules.rebreakprotection.accessibility.RebreakAccessibilityService */ const { withAndroidManifest, AndroidConfig, } = require('@expo/config-plugins'); const VPN_SERVICE_CLASS = 'expo.modules.rebreakprotection.vpn.RebreakVpnService'; const A11Y_SERVICE_CLASS = 'expo.modules.rebreakprotection.accessibility.RebreakAccessibilityService'; // ─── 1) tools-Namespace auf ────────────────────────────────────── function ensureToolsNamespace(manifest) { if (!manifest.manifest.$) manifest.manifest.$ = {}; if (!manifest.manifest.$['xmlns:tools']) { manifest.manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools'; } } // ─── 2) -Tag für RebreakVpnService ───────────────────────────────── function ensureVpnService(manifest) { const application = AndroidConfig.Manifest.getMainApplicationOrThrow(manifest); if (!application.service) application.service = []; const alreadyDeclared = application.service.some( (svc) => svc.$ && svc.$['android:name'] === VPN_SERVICE_CLASS, ); if (alreadyDeclared) return; application.service.push({ $: { 'android:name': VPN_SERVICE_CLASS, 'android:permission': 'android.permission.BIND_VPN_SERVICE', 'android:foregroundServiceType': 'systemExempted', 'android:exported': 'false', }, 'intent-filter': [ { action: [{ $: { 'android:name': 'android.net.VpnService' } }], }, ], }); } // ─── 3) -Tag für RebreakAccessibilityService ─────────────────────── function ensureAccessibilityService(manifest) { const application = AndroidConfig.Manifest.getMainApplicationOrThrow(manifest); if (!application.service) application.service = []; const alreadyDeclared = application.service.some( (svc) => svc.$ && svc.$['android:name'] === A11Y_SERVICE_CLASS, ); if (alreadyDeclared) return; application.service.push({ $: { 'android:name': A11Y_SERVICE_CLASS, 'android:permission': 'android.permission.BIND_ACCESSIBILITY_SERVICE', 'android:label': '@string/accessibility_service_summary', 'android:exported': 'true', }, 'intent-filter': [ { action: [ { $: { 'android:name': 'android.accessibilityservice.AccessibilityService', }, }, ], }, ], 'meta-data': [ { $: { 'android:name': 'android.accessibilityservice', 'android:resource': '@xml/accessibility_service_config', }, }, ], }); } // ─── Composition ──────────────────────────────────────────────────────────── function withRebreakProtectionAndroid(config) { return withAndroidManifest(config, (cfg) => { ensureToolsNamespace(cfg.modResults); ensureVpnService(cfg.modResults); ensureAccessibilityService(cfg.modResults); return cfg; }); } module.exports = withRebreakProtectionAndroid;