193 lines
7.0 KiB
JavaScript
193 lines
7.0 KiB
JavaScript
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
/**
|
|
* Expo Config-Plugin — wires the NEFilter Extension target into the iOS
|
|
* project at prebuild time.
|
|
*
|
|
* Was es macht:
|
|
* 1) Setzt die Entitlements der Haupt-App (family-controls, network-
|
|
* extension, app-groups).
|
|
* 2) Kopiert `modules/rebreak-protection/ios/RebreakURLFilter/` nach
|
|
* `ios/RebreakURLFilter/` (idempotent).
|
|
* 3) Fügt einen neuen Xcode-Target `RebreakURLFilter` (Bundle-ID
|
|
* `org.rebreak.app.RebreakURLFilter`) zum Projekt hinzu, mit:
|
|
* - Source-File: FilterControlProvider.swift
|
|
* - NetworkExtension.framework
|
|
* - Embed-App-Extensions Build-Phase im Haupt-Target
|
|
* - Entitlements via `RebreakURLFilter.entitlements`
|
|
*
|
|
* Wird aus app.config.ts via `plugins: ['./plugins/with-rebreak-protection-ios']`
|
|
* registriert. Idempotent — kann beliebig oft via `expo prebuild` laufen.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const {
|
|
withEntitlementsPlist,
|
|
withDangerousMod,
|
|
withXcodeProject,
|
|
} = require('@expo/config-plugins');
|
|
|
|
const APP_GROUP = 'group.org.rebreak.app';
|
|
const TARGET_NAME = 'RebreakURLFilter';
|
|
const EXT_BUNDLE_SUFFIX = 'RebreakURLFilter';
|
|
const MODULE_DIR = path.join(
|
|
__dirname,
|
|
'..',
|
|
'modules',
|
|
'rebreak-protection',
|
|
'ios',
|
|
TARGET_NAME,
|
|
);
|
|
|
|
// ─── 1) Haupt-App Entitlements ──────────────────────────────────────────────
|
|
|
|
function withMainAppEntitlements(config) {
|
|
return withEntitlementsPlist(config, (cfg) => {
|
|
cfg.modResults['com.apple.developer.networking.networkextension'] = [
|
|
'content-filter-provider',
|
|
];
|
|
cfg.modResults['com.apple.developer.family-controls'] = true;
|
|
const groups = cfg.modResults['com.apple.security.application-groups'] || [];
|
|
if (!groups.includes(APP_GROUP)) {
|
|
cfg.modResults['com.apple.security.application-groups'] = [...groups, APP_GROUP];
|
|
}
|
|
return cfg;
|
|
});
|
|
}
|
|
|
|
// ─── 2) Extension-Sources ins ios/-Verzeichnis kopieren ─────────────────────
|
|
|
|
function withCopyExtensionSources(config) {
|
|
return withDangerousMod(config, [
|
|
'ios',
|
|
async (cfg) => {
|
|
const platformProjectRoot = cfg.modRequest.platformProjectRoot;
|
|
const dest = path.join(platformProjectRoot, TARGET_NAME);
|
|
if (!fs.existsSync(MODULE_DIR)) {
|
|
throw new Error(
|
|
`[with-rebreak-protection-ios] Extension source dir missing: ${MODULE_DIR}`,
|
|
);
|
|
}
|
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
for (const file of fs.readdirSync(MODULE_DIR)) {
|
|
const srcFile = path.join(MODULE_DIR, file);
|
|
const destFile = path.join(dest, file);
|
|
fs.copyFileSync(srcFile, destFile);
|
|
}
|
|
return cfg;
|
|
},
|
|
]);
|
|
}
|
|
|
|
// ─── 3) Xcode-Target hinzufügen ──────────────────────────────────────────────
|
|
|
|
function withExtensionTarget(config) {
|
|
return withXcodeProject(config, async (cfg) => {
|
|
const proj = cfg.modResults;
|
|
|
|
// Idempotenz: skip wenn Target schon angelegt
|
|
if (proj.pbxTargetByName(TARGET_NAME)) {
|
|
return cfg;
|
|
}
|
|
|
|
const mainBundleId = cfg.ios?.bundleIdentifier;
|
|
if (!mainBundleId) {
|
|
throw new Error('[with-rebreak-protection-ios] ios.bundleIdentifier fehlt in app.config');
|
|
}
|
|
const extBundleId = `${mainBundleId}.${EXT_BUNDLE_SUFFIX}`;
|
|
|
|
// ── Target anlegen (Type: app_extension) ──
|
|
const target = proj.addTarget(TARGET_NAME, 'app_extension', TARGET_NAME, extBundleId);
|
|
|
|
// ── Build-Phasen: Sources + Frameworks + Resources ──
|
|
proj.addBuildPhase(
|
|
['FilterControlProvider.swift'],
|
|
'PBXSourcesBuildPhase',
|
|
'Sources',
|
|
target.uuid,
|
|
);
|
|
proj.addBuildPhase(
|
|
['NetworkExtension.framework'],
|
|
'PBXFrameworksBuildPhase',
|
|
'Frameworks',
|
|
target.uuid,
|
|
);
|
|
// Info.plist gehört NICHT als Resource — wird via INFOPLIST_FILE referenziert.
|
|
|
|
// ── PBXGroup für die Sources ──
|
|
const pbxGroup = proj.addPbxGroup(
|
|
['FilterControlProvider.swift', 'Info.plist', 'RebreakURLFilter.entitlements'],
|
|
TARGET_NAME,
|
|
TARGET_NAME,
|
|
);
|
|
// Group ans CustomTemplate-Group hängen damit sie im Project Navigator erscheint
|
|
const groups = proj.hash.project.objects.PBXGroup;
|
|
Object.keys(groups).forEach((key) => {
|
|
if (
|
|
groups[key].name === 'CustomTemplate' ||
|
|
(groups[key].name === undefined && groups[key].path === undefined)
|
|
) {
|
|
proj.addToPbxGroup(pbxGroup.uuid, key);
|
|
}
|
|
});
|
|
|
|
// ── Build-Settings auf der Target-Configuration anpassen ──
|
|
const configurations = proj.pbxXCBuildConfigurationSection();
|
|
Object.keys(configurations)
|
|
.filter((k) => typeof configurations[k] === 'object')
|
|
.forEach((k) => {
|
|
const buildSettingsObj = configurations[k].buildSettings;
|
|
if (
|
|
buildSettingsObj &&
|
|
buildSettingsObj.PRODUCT_NAME &&
|
|
buildSettingsObj.PRODUCT_NAME.replace(/"/g, '') === TARGET_NAME
|
|
) {
|
|
buildSettingsObj.INFOPLIST_FILE = `"${TARGET_NAME}/Info.plist"`;
|
|
buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `"${TARGET_NAME}/${TARGET_NAME}.entitlements"`;
|
|
buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = '15.1';
|
|
buildSettingsObj.SWIFT_VERSION = '5.9';
|
|
buildSettingsObj.TARGETED_DEVICE_FAMILY = '"1,2"';
|
|
buildSettingsObj.CODE_SIGN_STYLE = 'Automatic';
|
|
}
|
|
});
|
|
|
|
// ── Embed App Extensions Build-Phase im Haupt-Target ──
|
|
// Suche nach existierender CopyFilesBuildPhase mit Comment "Embed App Extensions"
|
|
const mainTargetUuid = proj.getFirstTarget().uuid;
|
|
const buildPhases = proj.hash.project.objects.PBXNativeTarget[mainTargetUuid].buildPhases;
|
|
const copyFilesPhases = proj.hash.project.objects.PBXCopyFilesBuildPhase || {};
|
|
const hasEmbedPhase = Object.keys(copyFilesPhases).some((key) => {
|
|
const phase = copyFilesPhases[key];
|
|
return (
|
|
typeof phase === 'object' &&
|
|
phase.dstSubfolderSpec === 13 && // 13 = PluginsAndFrameworks (App Extensions)
|
|
buildPhases.some((bp) => bp.value === key)
|
|
);
|
|
});
|
|
if (!hasEmbedPhase) {
|
|
proj.addBuildPhase(
|
|
[`${TARGET_NAME}.appex`],
|
|
'PBXCopyFilesBuildPhase',
|
|
'Embed App Extensions',
|
|
mainTargetUuid,
|
|
'app_extension', // dstSubfolderSpec=13
|
|
);
|
|
}
|
|
|
|
// ── Target-Dependency: Haupt-App muss Extension vor sich bauen ──
|
|
proj.addTargetDependency(mainTargetUuid, [target.uuid]);
|
|
|
|
return cfg;
|
|
});
|
|
}
|
|
|
|
// ─── Composition ────────────────────────────────────────────────────────────
|
|
|
|
module.exports = function withRebreakProtectionIos(config) {
|
|
config = withMainAppEntitlements(config);
|
|
config = withCopyExtensionSources(config);
|
|
config = withExtensionTarget(config);
|
|
return config;
|
|
};
|