react-native-acoustic-connect-beta
Version:
BETA: React native plugin for Acoustic Connect
523 lines (522 loc) • 28.1 kB
JavaScript
;
// 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