react-native-acoustic-connect-beta
Version:
BETA: React native plugin for Acoustic Connect
108 lines (97 loc) • 4.39 kB
text/typescript
// 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.
import { withXcodeProject, type ConfigPlugin } from '@expo/config-plugins'
import type { ExpoConfig } from '@expo/config-types'
import * as fs from 'fs'
import * as path from 'path'
import type { ConnectPluginProps } from './withConnectNSE'
// Derive the xcode project type from the callback (the `xcode` package ships no
// types of its own) — same approach as withConnectNSE.
type XcodeProject = Parameters<
Parameters<typeof withXcodeProject>[1]
>[0]['modResults']
const PLACEHOLDER_TEAM = 'YOUR_TEAM_ID'
/**
* Resolves the iOS signing team (Apple Team ID) for the host + push extensions.
*
* Priority:
* 1. `iosDevelopmentTeam` plugin prop in app.json (explicit override)
* 2. `Connect.iOSDevelopmentTeam` in `<projectRoot>/ConnectConfig.json`
*
* Returns `undefined` when neither source provides a real value (the placeholder
* `YOUR_TEAM_ID` counts as unset). The mod is then a no-op — never a regression.
*/
export function resolveDevelopmentTeam(
projectRoot: string,
props: ConnectPluginProps
): string | undefined {
const fromProp = props.iosDevelopmentTeam?.trim()
if (fromProp && fromProp !== PLACEHOLDER_TEAM) return fromProp
const configPath = path.join(projectRoot, 'ConnectConfig.json')
if (fs.existsSync(configPath)) {
try {
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8')) as {
Connect?: { iOSDevelopmentTeam?: unknown }
}
const team = parsed.Connect?.iOSDevelopmentTeam
if (typeof team === 'string' && team.trim() && team.trim() !== PLACEHOLDER_TEAM)
return team.trim()
} catch {
// Malformed JSON is already surfaced by resolveAppGroupIdentifier (which
// runs earlier in the NSE mod) — don't double-report here.
}
}
return undefined
}
/**
* Expo Config Plugin mod that stamps `DEVELOPMENT_TEAM` onto every target's
* build configuration in the generated Xcode project (host + ConnectNSE +
* ConnectNCE), so all three sign consistently.
*
* Why this matters: the NSE mod injects the `aps-environment` entitlement, but
* a CLI build (`expo run:ios` / `xcodebuild`) with no signing team falls back
* to ad-hoc signing (`CODE_SIGN_IDENTITY = -`), which DROPS that entitlement —
* so the OS issues no APNs token and push silently fails (CA-144135 §6). Setting
* the team closes that loop on the prebuild path the SDK owns. Provisioning
* itself still needs the developer's Apple ID in Xcode and
* `-allowProvisioningUpdates` on the build (documented in the README).
*
* No-op when no team is configured (no regression for consumers who sign in the
* Xcode GUI or via EAS, which manages the whole chain itself).
*
* Must run AFTER withConnectNSE / withConnectNCE so the extension targets exist.
*/
export const withConnectIosSigning: ConfigPlugin<ConnectPluginProps> = (
config,
props = {}
) => {
const projectRoot = config._internal?.projectRoot
// withConnectNSE already throws an actionable error when projectRoot is
// missing; here we simply skip so we never throw twice for the same cause.
if (!projectRoot) return config
const team = resolveDevelopmentTeam(projectRoot, props)
if (!team) return config
return withXcodeProject(config, (c) => {
const xcodeProject: XcodeProject = c.modResults
const configurations = xcodeProject.pbxXCBuildConfigurationSection() as Record<
string,
{ buildSettings?: Record<string, string | undefined> }
>
// Set the team on every build configuration that has a buildSettings block
// (host + both extensions). Project-level configs get it too, which is
// harmless and ensures consistency across all targets.
for (const key of Object.keys(configurations)) {
const entry = configurations[key]
if (entry && typeof entry === 'object' && entry.buildSettings) {
entry.buildSettings.DEVELOPMENT_TEAM = team
}
}
return c
}) as ExpoConfig
}
export default withConnectIosSigning