UNPKG

react-native-acoustic-connect-beta

Version:

BETA: React native plugin for Acoustic Connect

523 lines (522 loc) 28.1 kB
"use strict"; // Copyright (C) 2026 Acoustic, L.P. All rights reserved. // // NOTICE: This file contains material that is confidential and proprietary to // Acoustic, L.P. and/or other developers. No license is granted under any // intellectual or industrial property rights of Acoustic, L.P. except as may // be provided in an agreement with Acoustic, L.P. Any unauthorized copying or // distribution of content from this file is prohibited. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withConnectNSE = exports.withNSEPodfile = exports.withNSEXcodeProject = exports.getHostBuildSettingForConfig = exports.withNSEEntitlements = exports.injectPodfileBlock = exports.buildPodfileBlock = exports.buildConnectPodTargetBlock = exports.buildNSEEntitlements = exports.buildNSEInfoPlist = exports.substituteSwiftTemplate = exports.resolveAppGroupIdentifier = exports.assertValidAppGroup = void 0; const config_plugins_1 = require("@expo/config-plugins"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); // ─── App Group resolution ───────────────────────────────────────────────────── // Apple App Group identifiers are `group.` followed by a reverse-DNS string of // alphanumerics, dots, and hyphens. Validating against this both catches typos // early and guarantees the value is safe to interpolate into the entitlements // XML (no `<`, `>`, `&`, or quotes can appear). const APP_GROUP_PATTERN = /^group\.[A-Za-z0-9.-]+$/; /** * Validates an App Group identifier against Apple's format and returns it. * Throws an actionable error otherwise. */ function assertValidAppGroup(value, source) { if (!APP_GROUP_PATTERN.test(value)) { throw new Error(`[react-native-acoustic-connect-beta] Invalid iOS App Group identifier ` + `"${value}" (from ${source}).\n\n` + `It must start with "group." followed by letters, digits, dots, or ` + `hyphens — e.g. "group.com.example.app".`); } return value; } exports.assertValidAppGroup = assertValidAppGroup; /** * Resolves the iOS App Group identifier used by ConnectNSE. * * Priority: * 1. `iosAppGroupIdentifier` plugin prop in app.json (explicit override) * 2. `Connect.iOSAppGroupIdentifier` in `<projectRoot>/ConnectConfig.json` * * The resolved value is validated against Apple's App Group format. Throws a * clear, actionable error if neither source provides the value, or if the * provided value is malformed. */ function resolveAppGroupIdentifier(projectRoot, props) { if (props.iosAppGroupIdentifier) { return assertValidAppGroup(props.iosAppGroupIdentifier, 'app.json plugin prop'); } const configPath = path.join(projectRoot, 'ConnectConfig.json'); if (fs.existsSync(configPath)) { const raw = fs.readFileSync(configPath, 'utf8'); let parsed; try { parsed = JSON.parse(raw); } catch { throw new Error(`[react-native-acoustic-connect-beta] ConnectConfig.json at ${configPath} is not valid JSON.`); } const connect = parsed['Connect']; const groupId = connect === null || connect === void 0 ? void 0 : connect['iOSAppGroupIdentifier']; if (typeof groupId === 'string' && groupId.length > 0) { return assertValidAppGroup(groupId, 'Connect.iOSAppGroupIdentifier in ConnectConfig.json'); } } throw new Error(`[react-native-acoustic-connect-beta] Could not resolve iOS App Group identifier.\n\n` + `To fix this, do ONE of the following:\n` + ` 1. (Preferred) Add "iOSAppGroupIdentifier" to the "Connect" object in ` + `your project's ConnectConfig.json:\n` + ` { "Connect": { "iOSAppGroupIdentifier": "group.com.example.app" } }\n` + ` 2. Pass the identifier as a plugin prop in app.json:\n` + ` ["react-native-acoustic-connect-beta", { "iosAppGroupIdentifier": "group.com.example.app" }]\n`); } exports.resolveAppGroupIdentifier = resolveAppGroupIdentifier; // ─── Swift template substitution ───────────────────────────────────────────── const PLACEHOLDER = 'CONNECT_APP_GROUP_IDENTIFIER_PLACEHOLDER'; const GENERATED_HEADER = '// @generated by react-native-acoustic-connect-beta Expo Config Plugin — do not edit manually.\n'; /** * Returns the content of `plugin/swift/NotificationService.swift` with the * placeholder replaced by the resolved App Group identifier and a generated * header prepended. */ function substituteSwiftTemplate(templatePath, appGroupIdentifier) { const template = fs.readFileSync(templatePath, 'utf8'); // split/join replaces EVERY occurrence — the template mentions the // placeholder in a comment before the code occurrence, and a string // pattern to .replace() would only substitute that first mention. const substituted = template.split(PLACEHOLDER).join(appGroupIdentifier); return GENERATED_HEADER + substituted; } exports.substituteSwiftTemplate = substituteSwiftTemplate; // ─── Plist helpers ──────────────────────────────────────────────────────────── /** * Generates the Info.plist content for the ConnectNSE target. * * RCTNewArchEnabled is intentionally omitted: the NSE links the Connect SDK * only — no React Native runtime is present in the extension process. */ function buildNSEInfoPlist() { return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> \t<key>CFBundleDevelopmentRegion</key> \t<string>$(DEVELOPMENT_LANGUAGE)</string> \t<key>CFBundleDisplayName</key> \t<string>ConnectNSE</string> \t<key>CFBundleExecutable</key> \t<string>$(EXECUTABLE_NAME)</string> \t<key>CFBundleIdentifier</key> \t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> \t<key>CFBundleInfoDictionaryVersion</key> \t<string>6.0</string> \t<key>CFBundleName</key> \t<string>$(PRODUCT_NAME)</string> \t<key>CFBundlePackageType</key> \t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> \t<key>CFBundleShortVersionString</key> \t<string>$(MARKETING_VERSION)</string> \t<key>CFBundleVersion</key> \t<string>$(CURRENT_PROJECT_VERSION)</string> \t<key>NSExtension</key> \t<dict> \t\t<key>NSExtensionPointIdentifier</key> \t\t<string>com.apple.usernotifications.service</string> \t\t<key>NSExtensionPrincipalClass</key> \t\t<string>$(PRODUCT_MODULE_NAME).NotificationService</string> \t</dict> </dict> </plist> `; } exports.buildNSEInfoPlist = buildNSEInfoPlist; /** Generates the entitlements plist content for the ConnectNSE target. */ function buildNSEEntitlements(appGroupIdentifier) { return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> \t<key>com.apple.security.application-groups</key> \t<array> \t\t<string>${appGroupIdentifier}</string> \t</array> </dict> </plist> `; } exports.buildNSEEntitlements = buildNSEEntitlements; // ─── Podfile injection ──────────────────────────────────────────────────────── const PODFILE_MARKER = '# @generated ConnectNSE target (react-native-acoustic-connect-beta)'; /** * Builds the Ruby Podfile snippet shared by the NSE and NCE mods: a helper * that resolves the AcousticConnect pod (name + version requirements) from * ConnectConfig.json, plus an extension `target` that links it. Centralised * here so the NSE and NCE blocks have a single source of truth for the * resolution logic — they differ only in marker, helper name, and target name. * * Tolerates a missing ConnectConfig.json or a missing `Connect` key: when * the file or key is absent the block defaults to AcousticConnectDebug with * floor '>= 2.1.12' and no version pin, so prop-only setups (no * ConnectConfig.json) keep working. If the file exists but contains invalid * JSON, `pod install` will print a warning and use the same defaults rather * than crashing. */ function buildConnectPodTargetBlock(marker, helperName, targetName) { return ` ${marker} def ${helperName} config_path = File.join(__dir__, '..', 'ConnectConfig.json') connect_config = {} if File.exist?(config_path) begin parsed = JSON.parse(File.read(config_path)) connect_config = parsed['Connect'] || {} rescue JSON::ParserError => e warn "[react-native-acoustic-connect-beta] ConnectConfig.json is not valid JSON: #{e.message}. Using defaults." end end use_release = connect_config['useRelease'] || false ios_version = (connect_config['iOSVersion'] || '').to_s name = use_release ? 'AcousticConnect' : 'AcousticConnectDebug' floor = '>= 2.1.12' requirements = ios_version.empty? ? [floor] : [floor, ios_version] [name, requirements] end target '${targetName}' do connect_name, connect_requirements = ${helperName} pod connect_name, *connect_requirements end # @end ${targetName} target (react-native-acoustic-connect-beta) `; } exports.buildConnectPodTargetBlock = buildConnectPodTargetBlock; /** * Returns the Ruby snippet to inject into the Expo-generated Podfile for the * ConnectNSE target. Thin wrapper over {@link buildConnectPodTargetBlock}. */ function buildPodfileBlock() { return buildConnectPodTargetBlock(PODFILE_MARKER, 'acoustic_connect_pod_nse', 'ConnectNSE'); } exports.buildPodfileBlock = buildPodfileBlock; /** * Injects the ConnectNSE Podfile block into `podfileContent` if not already * present (idempotent, guarded by PODFILE_MARKER). */ function injectPodfileBlock(podfileContent) { if (podfileContent.includes(PODFILE_MARKER)) { return podfileContent; } return podfileContent + buildPodfileBlock(); } exports.injectPodfileBlock = injectPodfileBlock; // ─── Entitlements mod ───────────────────────────────────────────────────────── /** * Adds the HOST app's push entitlements. Merges idempotently: * - `aps-environment` — the APNs (Push Notifications) capability. Required for * the SDK's automatic `registerForRemoteNotifications()` to receive a device * token; without it iOS fails registration with "no valid aps-environment * entitlement string found", so no token is ever sent to the collector. * Mirrors the bare-workflow host entitlements. `development` targets the * sandbox APNs used by dev/`expo run:ios`/EAS `development` builds; * production/TestFlight builds need `production`. * - `com.apple.security.application-groups` — shared App Group for the host app * and the NSE/NCE extensions. Never clobbers an existing array. */ function withNSEEntitlements(config, appGroupIdentifier) { return (0, config_plugins_1.withEntitlementsPlist)(config, (c) => { var _a; if (!c.modResults['aps-environment']) { c.modResults['aps-environment'] = 'development'; } const existing = (_a = c.modResults['com.apple.security.application-groups']) !== null && _a !== void 0 ? _a : []; if (!existing.includes(appGroupIdentifier)) { c.modResults['com.apple.security.application-groups'] = [ ...existing, appGroupIdentifier, ]; } return c; }); } exports.withNSEEntitlements = withNSEEntitlements; // ─── Xcode project mod ─────────────────────────────────────────────────────── const NSE_TARGET_NAME = 'ConnectNSE'; /** * Extracts a build setting value scoped to the host native target's * configuration list for the given `configName` (e.g. "Debug" or "Release"). * Falls back to the other host config if the named one is absent, then to the * hardcoded `fallback`. * * Scoped to host configs only: configs whose CODE_SIGN_ENTITLEMENTS references * one of `skipTargetNames` (the extension targets this plugin generates) are * skipped, so values are never mirrored from a previously generated extension * target. Defaults to `[NSE_TARGET_NAME]`; the NCE mod (withConnectNCE) passes * both ConnectNSE and ConnectNCE so a re-run that already wrote the NSE target * never mirrors the sibling extension's settings. */ function getHostBuildSettingForConfig(xcodeProject, setting, configName, fallback, skipTargetNames = [NSE_TARGET_NAME]) { const allConfigs = xcodeProject.pbxXCBuildConfigurationSection(); // Two-pass resolution, both passes skipping the plugin's own NSE configs // (identified by CODE_SIGN_ENTITLEMENTS containing NSE_TARGET_NAME) so a // re-run never mirrors values from a previously generated extension target: // Pass 1 — exact match on the requested configName (Debug → Debug). // Pass 2 — graceful fallback to the sibling host config's value when the // matching config has no explicit setting; mirroring the host's // other config beats dropping to the hardcoded default. // Pass 1: exact configName match. for (const key of Object.keys(allConfigs)) { const entry = allConfigs[key]; if (entry && typeof entry === 'object' && entry.name === configName && entry.buildSettings) { // Skip NSE/NCE extension targets already written by this plugin const cse = entry.buildSettings['CODE_SIGN_ENTITLEMENTS']; if (typeof cse === 'string' && skipTargetNames.some((name) => cse.includes(name))) { continue; } const val = entry.buildSettings[setting]; if (typeof val === 'string') { return val; } } } // Pass 2: sibling host config fallback (any name), skip NSE/NCE targets. for (const key of Object.keys(allConfigs)) { const entry = allConfigs[key]; if (entry && entry.buildSettings) { const cse = entry.buildSettings['CODE_SIGN_ENTITLEMENTS']; if (typeof cse === 'string' && skipTargetNames.some((name) => cse.includes(name))) { continue; } const val = entry.buildSettings[setting]; if (typeof val === 'string') { return val; } } } return fallback; } exports.getHostBuildSettingForConfig = getHostBuildSettingForConfig; /** * Adds the ConnectNSE Xcode target (app_extension) to the project. * * Follows the OneSignal plugin pattern (withOneSignalNSE.ts) using the * `xcode` API exposed via `config.modResults`. * * Idempotent: if a target named ConnectNSE already exists, skips target * creation but still (re)writes the source files. */ function withNSEXcodeProject(config, appGroupIdentifier, swiftContent) { return (0, config_plugins_1.withXcodeProject)(config, (c) => { var _a, _b, _c, _d; const xcodeProject = c.modResults; // ios/ directory — modRequest.platformProjectRoot is the canonical // native project root. (xcodeProject.filepath points INSIDE the // .xcodeproj bundle, so deriving from it misplaces the files.) const iosDir = c.modRequest.platformProjectRoot; const nseDir = path.join(iosDir, NSE_TARGET_NAME); // Always write / overwrite the source files (idempotent) fs.mkdirSync(nseDir, { recursive: true }); fs.writeFileSync(path.join(nseDir, 'NotificationService.swift'), swiftContent, 'utf8'); fs.writeFileSync(path.join(nseDir, 'Info.plist'), buildNSEInfoPlist(), 'utf8'); fs.writeFileSync(path.join(nseDir, `${NSE_TARGET_NAME}.entitlements`), buildNSEEntitlements(appGroupIdentifier), 'utf8'); // Idempotency check — skip if target already registered in the pbxproj. // Note: pbxTargetByName misses targets on a REPARSED project because the // xcode lib stores written names with literal quotes ('"ConnectNSE"'), // so compare quote-stripped names across the native-target section. const nativeTargets = xcodeProject.pbxNativeTargetSection(); const targetExists = Object.values(nativeTargets).some((t) => typeof t === 'object' && typeof t.name === 'string' && t.name.replace(/"/g, '') === NSE_TARGET_NAME); if (targetExists) { return c; } // Derive the host bundle id — the NSE bundle id must be // `<hostBundleId>.ConnectNSE`. Fail fast rather than emit a placeholder // (a wrong bundle id breaks App Group pairing and App Store submission). const hostBundleId = (_a = c.ios) === null || _a === void 0 ? void 0 : _a.bundleIdentifier; if (!hostBundleId) { throw new Error(`[react-native-acoustic-connect-beta] ios.bundleIdentifier is not set.\n\n` + `The ConnectNSE extension bundle id is derived as ` + `"<ios.bundleIdentifier>.ConnectNSE", so the host bundle id must be ` + `defined in app.json before prebuild:\n` + ` { "expo": { "ios": { "bundleIdentifier": "com.example.app" } } }`); } // Add the native target (app_extension) const nseTarget = xcodeProject.addTarget(NSE_TARGET_NAME, 'app_extension', NSE_TARGET_NAME, `${hostBundleId}.ConnectNSE`); if (!nseTarget) { throw new Error(`[react-native-acoustic-connect-beta] Failed to add ConnectNSE Xcode target.`); } // Add the build phase for Swift sources xcodeProject.addBuildPhase(['NotificationService.swift'], 'PBXSourcesBuildPhase', 'Sources', nseTarget.uuid); // Add Resources build phase (empty, but required by Xcode) xcodeProject.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', nseTarget.uuid); // Add Frameworks build phase (empty — CocoaPods populates it) xcodeProject.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', nseTarget.uuid); // Make the host app target depend on ConnectNSE. CocoaPods resolves an // extension's host target through PBXTargetDependency (xcodeproj's // host_targets_for_embedded_target), NOT through the embed copy phase — // without this edge `pod install` fails with "Unable to find host // target(s) for ConnectNSE". // The xcode lib SILENTLY no-ops addTargetDependency when these sections // are absent from the parsed project (Expo templates ship without any // extension, so they are) — create them first. const objects = xcodeProject.hash.project.objects; objects['PBXTargetDependency'] = (_b = objects['PBXTargetDependency']) !== null && _b !== void 0 ? _b : {}; objects['PBXContainerItemProxy'] = (_c = objects['PBXContainerItemProxy']) !== null && _c !== void 0 ? _c : {}; const hostTargetUuid = xcodeProject.getFirstTarget().uuid; xcodeProject.addTargetDependency(hostTargetUuid, [nseTarget.uuid]); // Create a PBXGroup for ConnectNSE source files const nseGroup = xcodeProject.addPbxGroup([ 'NotificationService.swift', 'Info.plist', `${NSE_TARGET_NAME}.entitlements`, ], NSE_TARGET_NAME, NSE_TARGET_NAME); // Add the group to the main project group const mainGroup = xcodeProject.getFirstProject().firstProject.mainGroup; xcodeProject.addToPbxGroup(nseGroup.uuid, mainGroup); // Set per-configuration build settings — mirror each host configuration // individually so Debug gets Debug values and Release gets Release values. const buildConfigurations = xcodeProject.pbxXCConfigurationList(); const nseConfigListUuid = nseTarget.pbxNativeTarget.buildConfigurationList; if (nseConfigListUuid) { const configList = buildConfigurations[nseConfigListUuid]; if (configList && Array.isArray(configList .buildConfigurations)) { const configs = xcodeProject.pbxXCBuildConfigurationSection(); for (const entry of configList.buildConfigurations) { const buildConfig = configs[entry.value]; if (buildConfig && buildConfig.buildSettings) { // Determine which host config name to mirror (Debug → Debug, etc.) const configName = (_d = buildConfig.name) !== null && _d !== void 0 ? _d : 'Debug'; // M2: mirror per-configuration values; fall back to the other // config's value only when the matching one is absent. const deploymentTarget = getHostBuildSettingForConfig(xcodeProject, 'IPHONEOS_DEPLOYMENT_TARGET', configName, // Matches the AcousticConnect podspec floor (iOS >= 15.1) so the // NSE never demands a higher minimum than the host app. '15.1'); const swiftVersion = getHostBuildSettingForConfig(xcodeProject, 'SWIFT_VERSION', configName, '5.0'); const marketingVersion = getHostBuildSettingForConfig(xcodeProject, 'MARKETING_VERSION', configName, '1.0'); const currentProjectVersion = getHostBuildSettingForConfig(xcodeProject, 'CURRENT_PROJECT_VERSION', configName, '1'); Object.assign(buildConfig.buildSettings, { // C1: prevent Xcode from synthesising a second Info.plist that // would shadow the NSExtension dictionary in ours. GENERATE_INFOPLIST_FILE: 'NO', INFOPLIST_FILE: `${NSE_TARGET_NAME}/Info.plist`, // M1: extension must target the device SDK and restrict to // extension-safe APIs (mirrors bare-workflow reference target). SDKROOT: 'iphoneos', APPLICATION_EXTENSION_API_ONLY: 'YES', IPHONEOS_DEPLOYMENT_TARGET: deploymentTarget, SWIFT_VERSION: swiftVersion, MARKETING_VERSION: marketingVersion, CURRENT_PROJECT_VERSION: currentProjectVersion, CODE_SIGN_ENTITLEMENTS: `${NSE_TARGET_NAME}/${NSE_TARGET_NAME}.entitlements`, PRODUCT_NAME: NSE_TARGET_NAME, PRODUCT_BUNDLE_IDENTIFIER: `${hostBundleId}.ConnectNSE`, TARGETED_DEVICE_FAMILY: '"1,2"', CODE_SIGN_STYLE: 'Automatic', }); } } } } return c; }); } exports.withNSEXcodeProject = withNSEXcodeProject; // ─── Podfile mod ───────────────────────────────────────────────────────────── /** * Injects the ConnectNSE Podfile target block via withDangerousMod. * Guarded by a marker comment — re-runs are no-ops. * * M5: throws a clear, actionable error when the Podfile does not exist at * mod-execution time, instead of silently returning — a silent skip would * ship an NSE whose `import Connect` cannot resolve. */ function withNSEPodfile(config) { return (0, config_plugins_1.withDangerousMod)(config, [ 'ios', async (c) => { const podfilePath = path.join(c.modRequest.platformProjectRoot, 'Podfile'); if (!fs.existsSync(podfilePath)) { throw new Error(`[react-native-acoustic-connect-beta] ios/Podfile not found at ${podfilePath}.\n\n` + `This mod runs after prebuild generates the ios/ directory. ` + `If you are running this plugin outside of \`expo prebuild\`, ` + `ensure the Podfile exists before the dangerous mod phase executes.\n` + `Check prebuild ordering: withDangerousMod('ios') runs after ` + `withXcodeProject, so the ios/ directory should already be present.`); } const current = fs.readFileSync(podfilePath, 'utf8'); const updated = injectPodfileBlock(current); if (updated !== current) { fs.writeFileSync(podfilePath, updated, 'utf8'); } return c; }, ]); } exports.withNSEPodfile = withNSEPodfile; // ─── Composed mod ───────────────────────────────────────────────────────────── /** * Expo Config Plugin mod that provisions a Notification Service Extension * (NSE) Xcode target named `ConnectNSE`. * * Applies three mutations — all idempotent: * 1. Host app entitlements: merges App Group into * `com.apple.security.application-groups`. * 2. Xcode project: adds ConnectNSE target + files (skips if already present). * 3. Podfile: injects `target 'ConnectNSE'` block (guarded by marker comment). * * M4: `config._internal.projectRoot` is required. If absent (i.e. invoked * outside Expo CLI), an actionable error is thrown rather than silently * falling back to `process.cwd()`, which would misresolve ConnectConfig.json * in monorepo setups. */ const withConnectNSE = (config, props = {}) => { var _a; const projectRoot = (_a = config._internal) === null || _a === void 0 ? void 0 : _a.projectRoot; if (!projectRoot) { throw new Error(`[react-native-acoustic-connect-beta] config._internal.projectRoot is not set.\n\n` + `This value is injected by Expo CLI during \`expo prebuild\`. ` + `If you are calling this plugin outside of Expo CLI (e.g. in a test or ` + `custom script), set config._internal = { projectRoot: '/abs/path/to/project' } ` + `before invoking the plugin.`); } const appGroupIdentifier = resolveAppGroupIdentifier(projectRoot, props); const swiftTemplatePath = path.join(__dirname, '..', 'swift', 'NotificationService.swift'); const swiftContent = substituteSwiftTemplate(swiftTemplatePath, appGroupIdentifier); let result = withNSEEntitlements(config, appGroupIdentifier); result = withNSEXcodeProject(result, appGroupIdentifier, swiftContent); result = withNSEPodfile(result); return result; }; exports.withConnectNSE = withConnectNSE; //# sourceMappingURL=withConnectNSE.js.map