react-native-acoustic-connect
v19.0.11
Published
React native plugin for Acoustic Connect
Maintainers
Readme
react-native-acoustic-connect
React Native plugin for the Acoustic Connect SDK. Captures user interactions, screen replays, and analytics events on iOS and Android. Supports optional push notifications via the Connect backend.
For the full product overview see the Connect SDK overview on the developer portal.
Requirements
- React Native 0.82.x – 0.85.x with the new architecture
- React 19.1.1 or newer (or whatever your RN version pins)
react-native-nitro-modulesat the exact version this package pins inpeerDependencies(currently0.35.9) — your app must resolve exactly this version; see Nitro version pin- Node 20 or newer
- iOS deployment target ≥ 15.1, AcousticConnect / AcousticConnectDebug pod ≥ 2.0.5
- Android
minSdk≥ 26,compileSdk≥ 35,io.github.go-acoustic:connectin[11.0.11, 12.0.0) - Expo SDK 55+ is supported via the bundled Expo Config Plugin — development builds only (Expo Go is not supported); see Using with Expo SDK 55+.
Nitro version pin
This package pins react-native-nitro-modules to an exact version
(currently 0.35.9) in its peerDependencies — deliberately not a range.
Your app must resolve exactly that version.
Why (important): Nitro patch releases can contain breaking changes. The SDK
ships native bindings generated against one specific Nitro version, and Nitro's
generated-code ↔ runtime contract is not patch-safe. For example, a
0.35.4 → 0.35.9 patch bump changed Nitro's native registration and caused
this SDK's HybridObject to fail to load with a ClassNotFoundException, taking
the entire module offline at runtime. Because even a patch can break it, a
version range (^/~/>=…<…) cannot guarantee compatibility — so the pin is
exact.
If your app resolves a different Nitro version, the SDK may fail to start. You
can force past the peer check with --legacy-peer-deps / --force, but
then you own making it work — we cannot guard against breaking changes in
arbitrary future Nitro patch releases. When this SDK adopts a newer Nitro, it
ships in a new SDK release with the pin bumped; upgrade the SDK and Nitro
together.
Installation
npm install react-native-acoustic-connect react-native-nitro-modules
cd ios && pod installThe plugin reads a ConnectConfig.json from your project root at install time
(iOS via the podspec, Android via config.gradle) and bakes the values into the
native bundles. ConnectConfig.json is the single source of truth — there
is no runtime override path. A minimal config looks like:
{
"Connect": {
"AppKey": "your-app-key",
"PostMessageUrl": "https://collector.example.com/collectorPost",
"KillSwitchUrl": "https://collector.example.com/collector/switch/your-app-key",
"useRelease": false,
"PushEnabled": false,
"iOSPushMode": "automatic",
"iOSAppGroupIdentifier": null,
"AndroidNotificationIconResName": null
}
}Field summary (see API reference for full semantics):
| Field | Default | Purpose |
| --- | --- | --- |
| AppKey | (required) | Your Connect application key. |
| PostMessageUrl | (required) | Collector endpoint URL. |
| KillSwitchUrl | (optional) | Kill-switch endpoint URL. |
| useRelease | false | true selects the release AcousticConnect iOS pod over the debug variant. |
| iOSVersion | "" | Pin a specific iOS pod version; empty = newest in the supported range. |
| AndroidVersion | "" | Pin a specific Android Connect SDK version; empty = newest in the supported range. |
| PushEnabled | false | Master switch. On Android, also gates the connect-push-fcm artifact inclusion. |
| iOSPushMode | "automatic" | iOS-only: "automatic" (SDK owns APNs delegate) or "manual" (app owns it). Ignored when PushEnabled is false. |
| iOSAppGroupIdentifier | null | iOS App Group shared with the Notification Service / Notification Content extension. |
| AndroidNotificationIconResName | null | Drawable resource name (no extension) for the Android notification small icon. |
What npm install / pod install do for you
Both commands run bootstrap steps against your project, not just the package's own build:
npm installtriggers a postinstall step that (idempotently, best-effort):- scaffolds a starter
ConnectConfig.jsonat your project root if one isn't already there, - adds the Android permissions Connect needs
(
INTERNET,ACCESS_NETWORK_STATE,ACCESS_WIFI_STATE,ACCESS_FINE_LOCATION) toandroid/app/src/main/AndroidManifest.xml, - wires
android/app/build.gradleto apply the SDK'sconfig.gradle.
- scaffolds a starter
pod installmerges yourConnectConfig.jsoninto the iOS pod's generated config bundle at install time (see the podspec).
None of this touches files outside your project's android//ios/
directories, and re-running either command is safe. After install, run
npx acoustic-connect doctor to validate the result — see
Setup CLI below.
Setup CLI (acoustic-connect)
The package ships a small CLI, installed as acoustic-connect (invoke it with
npx acoustic-connect <command>), that validates and scaffolds the native
side of an integration. Both commands auto-detect bare RN vs. Expo and are
idempotent — safe to re-run, and they never overwrite a file that's already
there.
npx acoustic-connect doctor [dir] [--require-push]
Checks the things that otherwise fail late and confusingly at build time:
- Node version.
ConnectConfig.jsonvalues —AppKey,PostMessageUrl/KillSwitchUrl(must be realhttpsURLs, not the placeholder host),iOSAppGroupIdentifierformat,iOSDevelopmentTeamformat.- Android identifiers — the package/namespace is a Java-safe name, and it has
a matching client in
google-services.json(Expo also checks for a staleandroid/from an incrementalexpo prebuildafter anandroid.packagechange). - iOS entitlements — bare projects need
aps-environment+ an App Group entitlement (fromsetup-ios-push, below, or hand-authored). - (macOS only) whether a local Apple Development signing identity is installed, when push is on.
The push-related checks above are hard failures only when
Connect.PushEnabled is true (or --require-push is passed) — a non-push
integration only needs AppKey. doctor exits non-zero on any failure, so
it's usable as a CI gate (npx acoustic-connect doctor --require-push).
dir defaults to the current directory. If ConnectConfig.json is missing,
doctor scaffolds it first (from a project-local ConnectConfig.example.json
if you have one, else from the copy bundled with the package) before running
the checks above — so it's also a fine first command to run in a fresh
project.
npx acoustic-connect setup-ios-push [dir]
Bare-workflow only — the Expo Config Plugin does the equivalent automatically
on expo prebuild. Scaffolds the two iOS push extensions:
ConnectNSE(Notification Service Extension — rich-media attachments, delivery tracking)ConnectNCE(Notification Content Extension — expanded rich-media UI)
It writes each extension's Swift source, Info.plist, and entitlements from
the SDK's templates (substituting your App Group), adds the App Group +
aps-environment entitlement to the host app if it doesn't have one yet, and
wires both targets into your .xcodeproj via a bundled Ruby script — which
needs ruby and the xcodeproj gem (gem install xcodeproj; ships with
CocoaPods, so most Mac dev setups already have it).
Requires macOS and Connect.iOSAppGroupIdentifier already set in
ConnectConfig.json — run doctor first if it's missing.
Using with Expo SDK 55+
Expo SDK 55 and newer is supported via the bundled Expo Config Plugin.
Development builds only — Expo Go is not supported: Nitro Modules and
native push registration require native code that Expo Go cannot load. There
is no runtime Expo Go detection or fallback; use
expo-dev-client
or EAS Build.
Install
npx create-expo-app@latest my-app # Expo SDK 55+, new architecture enabled
cd my-app
npx expo install expo-dev-client expo-build-properties
npm install react-native-acoustic-connect react-native-nitro-modulesexpo-build-properties is needed to raise Android minSdkVersion to 26
(the Connect Android SDK floor — Expo templates default to a lower value):
{
"expo": {
"plugins": [
["expo-build-properties", { "android": { "minSdkVersion": 26 } }]
]
}
}Configure
Put
ConnectConfig.jsonat the project root — the same file documented in Installation. For push, setPushEnabled,iOSPushMode,iOSAppGroupIdentifier, andiOSDevelopmentTeam(your 10-char Apple Team ID) in theConnectblock. Runnpx acoustic-connect doctorto validate it — whenPushEnabledistrue, the doctor fails (exits non-zero) on any missing push input (collector URLs, App Group, signing team,google-services.json, app ids); when push is off it needs onlyAppKey.Add the plugin to
app.json:
{
"expo": {
"plugins": ["react-native-acoustic-connect"]
}
}The plugin reads the App Group from Connect.iOSAppGroupIdentifier and the
signing team from Connect.iOSDevelopmentTeam in ConnectConfig.json — the
same values the SDK reads at runtime, so the entitlement and the runtime config
agree by construction. To override either for the native project only (rarely
needed), pass plugin props; props take precedence over ConnectConfig.json:
{
"expo": {
"plugins": [
[
"react-native-acoustic-connect",
{
"iosAppGroupIdentifier": "group.com.example.myapp",
"iosDevelopmentTeam": "ABCDE12345"
}
]
]
}
}iosDevelopmentTeam is required for push: during expo prebuild the plugin
stamps DEVELOPMENT_TEAM onto the host app and both push extensions. Without a
team, a CLI build falls back to ad-hoc signing, which drops the aps-environment
entitlement — so iOS issues no APNs token and push silently fails.
Build
npx expo prebuild
npx expo run:ios -- --extra-params "-allowProvisioningUpdates" # or: eas build --profile development --platform ios
npx expo run:android # or: eas build --profile development --platform androidiOS push needs provisioning. Setting iOSDevelopmentTeam lets the plugin
stamp the team, but xcodebuild still has to fetch/create the Development
certificate and provisioning profiles (the host + both extension App IDs, with
the Push Notifications and App Groups capabilities). Pass
-allowProvisioningUpdates so it does that automatically — it requires your
Apple ID to be added once in Xcode → Settings → Accounts. eas build
manages the whole signing chain itself, so no flag is needed there; CI/headless
runs can use an App Store Connect API key
(-authenticationKeyPath/-authenticationKeyID/-authenticationKeyIssuerID with
-allowProvisioningUpdates). For the bare workflow, the equivalent is
react-native run-ios --extra-params "-allowProvisioningUpdates".
During expo prebuild the plugin automatically:
- adds a
ConnectNSENotification Service Extension target to the Xcode project (rich-media push attachments + delivery tracking), withNotificationService.swiftgenerated from the SDK template; - adds a
ConnectNCENotification Content Extension target (rich expansion UI — renders the attached media / expanded body when the user expands an Acoustic notification), withNotificationViewController.swiftgenerated from the SDK template; - adds the App Group entitlement to the host app and to both extensions, so all three processes share the same pending store;
- appends
ConnectNSEandConnectNCEtargets to the generatedPodfile, each linking the same Connect SDK pod as the app.
Re-running expo prebuild — with or without --clean — is idempotent: no
duplicate targets, entitlements, or Podfile entries.
The NCE renders notifications whose category matches
ACOUSTIC_RICH_NOTIFICATION or ACTIONABLE_NOTIFICATION (the categories the
Connect backend sets on rich-media pushes). No app-side configuration is
required — the category identifiers are baked into the generated target's
Info.plist.
EAS Build
The plugin works with EAS Build
out of the box — the NSE and NCE targets are provisioned by the same
expo prebuild step EAS runs on its build workers. iOS extension targets are
signed automatically when you use EAS-managed credentials (eas credentials),
which provisions the host app and both extension App IDs together.
eas build --profile development --platform ios
eas build --profile development --platform androidA working example — including a sample
eas.json — lives in
Examples/expo.
Quick start
1. Initialise the SDK
The SDK auto-initialises at module load time using the values from
ConnectConfig.json. For most apps there is no JS-side init code to write —
just import the package and you're done:
// index.js
import AcousticConnectRN from 'react-native-acoustic-connect'
// SDK is already initialising on the main actor / main looper. No further
// setup required.For consent-gated apps (GDPR, CCPA, COPPA), use the lifecycle pair:
import AcousticConnectRN from 'react-native-acoustic-connect'
// At app start, if you don't yet have user consent:
AcousticConnectRN.disable()
// Later, after the user opts in:
AcousticConnectRN.enable()enable() and disable() are both parameterless — all configuration comes
from ConnectConfig.json. They're idempotent at the native layer; calling
enable() on an already-running SDK is a no-op.
2. Add the <Connect> wrapper (declarative, in your tree)
Wrap your NavigationContainer in <Connect> to enable navigation tracking,
touch capture, and optional keyboard / dialog interception. The wrapper does
not own SDK lifecycle — that's been done in step 1 — so it can mount, unmount,
or remount freely without disrupting the session.
import { useNavigationContainerRef, NavigationContainer } from '@react-navigation/native'
import { Connect } from 'react-native-acoustic-connect'
export default function App() {
const navigationRef = useNavigationContainerRef()
return (
<Connect
navigationRef={navigationRef}
captureKeyboardEvents
captureDialogEvents
>
<NavigationContainer ref={navigationRef}>
{/* your screens */}
</NavigationContainer>
</Connect>
)
}<Connect> is optional. Apps that only need custom event logging (no automatic
screen / touch / keyboard tracking) can skip it entirely.
3. Log events (imperative, anywhere)
import AcousticConnectRN from 'react-native-acoustic-connect'
// Custom application event
AcousticConnectRN.logCustomEvent('checkout_started', { cartId: 'abc' }, 1)
// Force a logical screen name (e.g. for non-NavigationContainer screens)
AcousticConnectRN.setCurrentScreenName('CheckoutScreen')
// Manual exception capture
AcousticConnectRN.logExceptionEvent(
'Payment failed',
err.stack ?? '',
/* unhandled */ false
)Dialog tracking helpers (useDialogTracking, DialogListener,
withAcousticAutoDialog) instrument React Native Alert.alert(...) and custom
dialogs automatically. See the
developer portal
for details.
API reference
AcousticConnectRN.enable(): boolean
Re-enables the SDK after a prior disable(). Reads all configuration from
ConnectConfig.json. Returns true on accepted dispatch; false only when
the platform can't satisfy a precondition (e.g. Android without an
Application context yet). Idempotent — the native SDK short-circuits if it's
already running.
The SDK also auto-initialises at module load using the same configuration, so you typically don't need to call
enable()at all. The method exists to pair withdisable()for opt-out / consent flows.
AcousticConnectRN.disable(): boolean
Stops data capture, flushes pending messages, releases push state. Idempotent.
Push configuration (ConnectConfig.json)
| Field | Type | Default | Semantics |
| --- | --- | --- | --- |
| PushEnabled | boolean | false | Cross-platform master switch. On Android, drives connect-push-fcm artifact inclusion at build time. |
| iOSPushMode | "automatic" / "manual" | "automatic" | iOS-only. Ignored when PushEnabled is false. |
| iOSAppGroupIdentifier | string | null | null | iOS App Group shared with NSE / NCE for rich push payloads. Required when push is on. |
| iOSDevelopmentTeam | string | null | null | 10-char Apple Team ID used to sign the host + push extensions. Required for iOS push — without it the build drops aps-environment and the OS issues no APNs token. |
| AndroidNotificationIconResName | string | null | null | Drawable resource name for the Android notification small icon. |
When
PushEnabledistrue,npx acoustic-connect doctortreats the push-required inputs above (plus collector URLs,google-services.json, and the app ids) as hard failures and exits non-zero. When push is off it validates onlyAppKey— a non-push integration needs nothing more.
iOS push modes
"automatic"— the iOS Connect SDK manages APNs token registration internally. The host app only requests user permission viaUNUserNotificationCenter; token delivery and forwarding to the Connect backend are handled by the SDK."manual"— the host app owns APNs delegate callbacks (application(_:didRegisterForRemoteNotificationsWithDeviceToken:)) and forwards tokens explicitly viaConnectSDK.shared.push.didRegisterWithToken(token).
Android push
The iOSPushMode field is iOS-only; Android push is gated solely by
PushEnabled. On Android, FCM requires a FirebaseMessagingService subclass
that the host app declares in its AndroidManifest.xml, so push is always
app-driven. The host app is responsible for:
- shipping
google-services.jsoninandroid/app/, - implementing
FirebaseMessagingService.onNewToken(...), - forwarding the FCM token to the Connect SDK.
The Android push-forwarding API itself is wired under follow-up work — until
that lands, PushEnabled: true on Android only changes which artifact is on
the classpath; the host-side token forwarding API is not yet exposed.
<Connect> props
| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| children | ReactNode | yes | Your NavigationContainer (or any subtree). |
| navigationRef | RefObject | recommended | Ref from useNavigationContainerRef(). Enables screen-name tracking. |
| captureKeyboardEvents | boolean | yes | Capture iOS/Android keyboard show/hide events. |
| captureDialogEvents | boolean | no (default false) | Auto-track Alert.alert(...) calls. |
Other methods
The plugin exposes the full Connect SDK surface — custom events, signals,
exceptions, location, screen layout, click / text-change events, dialog events,
and config-item getters/setters. Signatures are stable across platforms; see
src/specs/react-native-acoustic-connect.nitro.ts for the typed Nitro spec and
the
developer portal
for end-to-end recipes.
Migration from earlier versions
See Migration-Guide.md for the steps to move from the
legacy NativeModules.AcousticConnectRN interface to the current ESM exports
and the <Connect> component.
Troubleshooting
npm install peer-dependency conflicts
This package pins react-native-nitro-modules to an exact version (see
Nitro version pin). If npm install errors on the Nitro
peer, your app is resolving a different Nitro version. Check it with:
npm ls react-native-nitro-modulesSupported fix: align your app to the exact version this package requires (and match React Native / React per the Requirements).
--legacy-peer-deps / --force will silence the error, but the pin is
intentional: a mismatched Nitro version can break the SDK at runtime (an
init-time ClassNotFoundException), and bypassing means you take on that
risk — we cannot guard against breaking changes in arbitrary Nitro patch
releases. Match the version rather than bypass it.
iOS — push session reaches the collector but no notifications arrive (no APNs token)
Fingerprint: analytics and pushRegistration still reach the collector
normally, but mobileToken stays null forever — and, notably, no
didFailToRegisterForRemoteNotificationsWithError (or any push-related
failure) shows up in logs either. It just silently never happens. That
absence of any error is what makes this look like a missing/broken app-side
callback or an SDK bug at first glance; it's almost always a signing
artifact instead. Run npx acoustic-connect doctor first — it detects this
exact condition (missing/invalid iOSDevelopmentTeam, no Development
certificate) up front, before you go digging through portal config.
Cause: a CLI build (expo run:ios / react-native run-ios / xcodebuild)
doesn't auto-pick a signing team the way the Xcode GUI does. With no team, it
signs ad-hoc and drops the aps-environment entitlement, so iOS issues no
APNs token — the app still launches and analytics reach the collector, but no
pushRegistration with a mobileToken is ever sent.
Setting Connect.iOSDevelopmentTeam in ConnectConfig.json by itself does
nothing. It's just a source value — it has no effect on the build until
expo prebuild / npx acoustic-connect setup-ios-push actually stamps
DEVELOPMENT_TEAM onto the host and the ConnectNSE and ConnectNCE
targets in the generated .pbxproj. Re-run that step any time
iOSDevelopmentTeam changes, and after every fresh clone — a teammate's
machine may already have the stamp baked in from a prior run; yours doesn't
until you run it too. This is the classic "works for me, not for them" trap:
every portal-side check (App ID push capability, provisioning profile,
entitlements file contents, App Group registration, .p8 key upload) can
look perfect and still not reflect what's actually embedded in the binary
that got installed on the device.
Fix:
- Set
Connect.iOSDevelopmentTeam(10-char Apple Team ID) inConnectConfig.json(or pass theiosDevelopmentTeamplugin prop). Re-runexpo prebuild/npx acoustic-connect setup-ios-pushso the team is stamped on the host + extensions. - Build with
-allowProvisioningUpdates(see Build) and your Apple ID added in Xcode → Settings → Accounts, so a Development certificate + profiles are provisioned.
Verify the fix actually landed — rerunning the steps above isn't proof by itself; confirm the build artifacts directly:
# 1. DEVELOPMENT_TEAM must be stamped on the host AND both push extensions
grep DEVELOPMENT_TEAM ios/*.xcodeproj/project.pbxproj
# 2. The *installed* binary must carry the entitlement — check the actual
# .app, not just the source .entitlements file in your repo
codesign -d --entitlements :- --xml /path/to/YourApp.app
# must show aps-environment in the outputIf DEVELOPMENT_TEAM is missing for any of the three targets, or codesign
doesn't show aps-environment, the stamp didn't take (or a stale build was
reused) — re-run setup-ios-push / prebuild and do a clean rebuild.
iOS — Expo Android build fails: No matching client found for package name …
Your app.json android.package isn't registered in the active
google-services.json (FCM matches by package). The config plugin and
acoustic-connect doctor now catch this up front. Register that exact package
in the same Firebase project and re-download google-services.json — and note
that changing android.package requires a clean prebuild
(npx expo prebuild --platform android --clean); an incremental prebuild keeps
the stale applicationId.
iOS — pod install fails with [Connect] requires AcousticConnect >= 2.0.5
You've pinned an older iOSVersion in ConnectConfig.json. Bump it to a
2.0.5+ release (or leave it empty for the newest available) and re-run
pod install.
Android — Gradle resolution fails on io.github.go-acoustic:connect
The strict constraint at [11.0.11, 12.0.0) is rejecting your pin. Bump
AndroidVersion in ConnectConfig.json to a release within that range (or
leave it empty for the newest 11.x available). 12.x is intentionally outside
the supported range pending compatibility validation — track that work
separately if you need it.
Gradle can't find node
Common when Gradle is launched outside an NVM-loaded shell. Either:
- Stop the daemon (
./gradlew --stop) and re-runnpm run androidfrom a shell wherewhich noderesolves, or - Symlink node onto a stable PATH:
ln -sf "$(which node)" /opt/homebrew/bin/node.
For more, see the Connect React Native integration guide and the sample app walk-through.
