react-native-acoustic-connect-beta 18.0.36 → 18.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/android/src/main/assets/ConnectBasicConfig.properties +4 -4
  3. package/cli/doctor.mjs +294 -0
  4. package/cli/index.mjs +64 -0
  5. package/cli/ios/add_push_extensions.rb +192 -0
  6. package/cli/ios/connect_pods.rb +48 -0
  7. package/cli/ios/setup-push.mjs +196 -0
  8. package/cli/ios/templates/nce/ConnectNCE.entitlements +10 -0
  9. package/cli/ios/templates/nce/Info.plist +45 -0
  10. package/cli/ios/templates/nse/ConnectNSE.entitlements +10 -0
  11. package/cli/ios/templates/nse/Info.plist +33 -0
  12. package/cli/lib.mjs +240 -0
  13. package/ios/AcousticConnectRNConfig.json +7 -153
  14. package/ios/HybridAcousticConnectRN.swift +16 -0
  15. package/package.json +7 -3
  16. package/plugin/build/index.d.ts +16 -7
  17. package/plugin/build/index.d.ts.map +1 -1
  18. package/plugin/build/index.js +18 -7
  19. package/plugin/build/index.js.map +1 -1
  20. package/plugin/build/withConnectAndroidConfig.d.ts +20 -0
  21. package/plugin/build/withConnectAndroidConfig.d.ts.map +1 -0
  22. package/plugin/build/withConnectAndroidConfig.js +64 -0
  23. package/plugin/build/withConnectAndroidConfig.js.map +1 -0
  24. package/plugin/build/withConnectNCE.d.ts.map +1 -1
  25. package/plugin/build/withConnectNCE.js +20 -1
  26. package/plugin/build/withConnectNCE.js.map +1 -1
  27. package/plugin/build/withConnectNSE.d.ts +10 -2
  28. package/plugin/build/withConnectNSE.d.ts.map +1 -1
  29. package/plugin/build/withConnectNSE.js +13 -2
  30. package/plugin/build/withConnectNSE.js.map +1 -1
  31. package/plugin/src/index.ts +17 -7
  32. package/plugin/src/withConnectAndroidConfig.ts +70 -0
  33. package/plugin/src/withConnectNCE.ts +26 -1
  34. package/plugin/src/withConnectNSE.ts +13 -2
  35. package/scripts/postinstall.mjs +35 -0
  36. package/scripts/xmlparser.js +1 -1
  37. package/scripts/postInstallScripts.sh +0 -10
  38. package/scripts/verifyConnectSetup.js +0 -87
@@ -9,16 +9,25 @@
9
9
  import { type ConfigPlugin } from '@expo/config-plugins'
10
10
  import { withConnectNSE, type ConnectPluginProps } from './withConnectNSE'
11
11
  import { withConnectNCE } from './withConnectNCE'
12
+ import { withConnectAndroidConfig } from './withConnectAndroidConfig'
12
13
 
13
14
  /**
14
15
  * Expo Config Plugin for react-native-acoustic-connect-beta.
15
16
  *
16
- * Provisions the ConnectNSE Notification Service Extension and the ConnectNCE
17
- * Notification Content Extension targets during `expo prebuild`. Designed as a
18
- * chainable composition: additional mods are appended by adding to the mods
19
- * array. Order matters withConnectNSE runs first so it seeds the host
20
- * entitlements and the PBXTargetDependency/PBXContainerItemProxy sections that
21
- * withConnectNCE reuses.
17
+ * iOS: provisions the ConnectNSE Notification Service Extension and the
18
+ * ConnectNCE Notification Content Extension targets during `expo prebuild`.
19
+ *
20
+ * Android: wires the SDK's config.gradle into the generated app build so
21
+ * ConnectConfig.json values (collector URL, app key) reach the native assets
22
+ * at build time (withConnectAndroidConfig). FCM push needs no mod here — Expo
23
+ * wires the google-services Gradle plugin natively when the consumer sets
24
+ * `android.googleServicesFile` in app.json.
25
+ *
26
+ * Designed as a chainable composition: additional mods are appended by adding
27
+ * to the mods array. Order matters for the iOS pair — withConnectNSE runs
28
+ * first so it seeds the host entitlements and the
29
+ * PBXTargetDependency/PBXContainerItemProxy sections that withConnectNCE
30
+ * reuses. The Android mod is independent of that ordering.
22
31
  *
23
32
  * Consumer app.json usage:
24
33
  * // With explicit App Group override:
@@ -40,11 +49,12 @@ const withAcousticConnect: ConfigPlugin<ConnectPluginProps | void> = (
40
49
  const mods: Array<ConfigPlugin<ConnectPluginProps>> = [
41
50
  withConnectNSE,
42
51
  withConnectNCE,
52
+ withConnectAndroidConfig,
43
53
  ]
44
54
 
45
55
  return mods.reduce((accConfig, mod) => mod(accConfig, resolvedProps), config)
46
56
  }
47
57
 
48
58
  export default withAcousticConnect
49
- export { withConnectNSE, withConnectNCE }
59
+ export { withConnectNSE, withConnectNCE, withConnectAndroidConfig }
50
60
  export type { ConnectPluginProps }
@@ -0,0 +1,70 @@
1
+ // Copyright (C) 2026 Acoustic, L.P. All rights reserved.
2
+ //
3
+ // NOTICE: This file contains material that is confidential and proprietary to
4
+ // Acoustic, L.P. and/or other developers. No license is granted under any
5
+ // intellectual or industrial property rights of Acoustic, L.P. except as may
6
+ // be provided in an agreement with Acoustic, L.P. Any unauthorized copying or
7
+ // distribution of content from this file is prohibited.
8
+
9
+ import { withAppBuildGradle, type ConfigPlugin } from '@expo/config-plugins'
10
+ import type { ConnectPluginProps } from './withConnectNSE'
11
+
12
+ // The Gradle snippet that runs the SDK's config.gradle. config.gradle reads
13
+ // the consumer app's ConnectConfig.json (`$project.rootDir/../ConnectConfig.json`)
14
+ // and writes AppKey / PostMessageUrl / KillSwitchUrl (and the Tealeaf + EOCore
15
+ // configs) into the SDK module's `src/main/assets/*` at Gradle configuration
16
+ // time — so the collector the app reports to is driven by ConnectConfig.json.
17
+ //
18
+ // This mirrors the line `scripts/gradleParser.js` appends for the bare-workflow
19
+ // sample (Examples/bare-workflow/android/app/build.gradle). The Expo Android
20
+ // project is gitignored and regenerated by `expo prebuild`, so the line cannot
21
+ // be committed there — this mod re-injects it on every prebuild instead.
22
+ //
23
+ // The ':react-native-acoustic-connect-beta' Gradle project is provided by
24
+ // React Native autolinking and resolves in the Expo app the same way it does
25
+ // in bare-workflow.
26
+ export const CONFIG_GRADLE_APPLY =
27
+ `apply from: project(':react-native-acoustic-connect-beta')` +
28
+ `.projectDir.getPath() + "/config.gradle"`
29
+
30
+ /**
31
+ * Append the config.gradle `apply from:` line to an app `build.gradle` body.
32
+ *
33
+ * Idempotent: a no-op when the line is already present, so repeated prebuilds
34
+ * (and a prebuild over an already-patched, non-`--clean` project) don't stack
35
+ * duplicate applies.
36
+ */
37
+ export function appendConfigGradleApply(contents: string): string {
38
+ if (contents.includes('/config.gradle')) {
39
+ return contents
40
+ }
41
+ return `${contents}\n\n${CONFIG_GRADLE_APPLY}\n`
42
+ }
43
+
44
+ /**
45
+ * Expo config mod: wire the SDK's config.gradle into the generated Android
46
+ * app build so ConnectConfig.json values reach the native assets at build
47
+ * time. Without it, the Android SDK ships its committed default collector
48
+ * config and the app reports to the wrong endpoint (iOS is unaffected — it
49
+ * uses AcousticConnectRNConfig.json via a separate flow).
50
+ */
51
+ export const withConnectAndroidConfig: ConfigPlugin<ConnectPluginProps> = (
52
+ config
53
+ ) =>
54
+ withAppBuildGradle(config, (cfg) => {
55
+ // The RN/Expo template app build.gradle is Groovy. If a future template
56
+ // switches to the Kotlin DSL the append heuristic no longer applies —
57
+ // warn and skip rather than corrupt the file.
58
+ if (cfg.modResults.language !== 'groovy') {
59
+ console.warn(
60
+ `[react-native-acoustic-connect-beta] Skipping Android config ` +
61
+ `propagation: app/build.gradle is '${cfg.modResults.language}', ` +
62
+ `expected 'groovy'. Add the following line manually:\n` +
63
+ ` ${CONFIG_GRADLE_APPLY}`
64
+ )
65
+ return cfg
66
+ }
67
+
68
+ cfg.modResults.contents = appendConfigGradleApply(cfg.modResults.contents)
69
+ return cfg
70
+ })
@@ -264,13 +264,38 @@ export function withNCEXcodeProject(
264
264
  nceTarget.uuid
265
265
  )
266
266
 
267
- // Add Frameworks build phase (empty CocoaPods populates it)
267
+ // Add the Frameworks build phase, then explicitly link the two
268
+ // user-notifications system frameworks into the NCE target.
269
+ //
270
+ // The NCE principal class conforms to `UNNotificationContentExtension`,
271
+ // whose extension-point context class
272
+ // (`_UNNotificationContentExtensionVendorContext`) is vended by
273
+ // UserNotificationsUI.framework, and whose `didReceive(_:)` consumes
274
+ // `UNNotification` / `UNNotificationContent` from UserNotifications.framework.
275
+ // Without these explicit links the extension binary never loads them, so on
276
+ // a PHYSICAL DEVICE iOS logs "Unable to find NSExtensionContextClass … did
277
+ // you link the framework that declares the extension point?" and never calls
278
+ // `didReceive(_:)`. The custom expanded view (rich-media `expandedImage`)
279
+ // then silently never renders, while the NSE-produced thumbnail still shows.
280
+ // The iOS Simulator resolves these classes implicitly, masking the omission —
281
+ // so this only surfaces on device. Both are Apple SYSTEM frameworks, so
282
+ // CocoaPods does NOT add them; they must be linked here, mirroring the
283
+ // canonical Examples/bare-workflow ConnectNCE target (which also lists the
284
+ // auto-linked Foundation/UIKit — omitted here as Swift links them implicitly).
268
285
  xcodeProject.addBuildPhase(
269
286
  [],
270
287
  'PBXFrameworksBuildPhase',
271
288
  'Frameworks',
272
289
  nceTarget.uuid
273
290
  )
291
+ xcodeProject.addFramework(
292
+ 'System/Library/Frameworks/UserNotifications.framework',
293
+ { target: nceTarget.uuid }
294
+ )
295
+ xcodeProject.addFramework(
296
+ 'System/Library/Frameworks/UserNotificationsUI.framework',
297
+ { target: nceTarget.uuid }
298
+ )
274
299
 
275
300
  // Make the host app target depend on ConnectNCE. CocoaPods resolves an
276
301
  // extension's host target through PBXTargetDependency
@@ -268,14 +268,25 @@ export function injectPodfileBlock(podfileContent: string): string {
268
268
  // ─── Entitlements mod ─────────────────────────────────────────────────────────
269
269
 
270
270
  /**
271
- * Adds the App Group to the HOST app's entitlements. Merges idempotently
272
- * — never clobbers an existing `com.apple.security.application-groups` array.
271
+ * Adds the HOST app's push entitlements. Merges idempotently:
272
+ * - `aps-environment` the APNs (Push Notifications) capability. Required for
273
+ * the SDK's automatic `registerForRemoteNotifications()` to receive a device
274
+ * token; without it iOS fails registration with "no valid aps-environment
275
+ * entitlement string found", so no token is ever sent to the collector.
276
+ * Mirrors the bare-workflow host entitlements. `development` targets the
277
+ * sandbox APNs used by dev/`expo run:ios`/EAS `development` builds;
278
+ * production/TestFlight builds need `production`.
279
+ * - `com.apple.security.application-groups` — shared App Group for the host app
280
+ * and the NSE/NCE extensions. Never clobbers an existing array.
273
281
  */
274
282
  export function withNSEEntitlements(
275
283
  config: ExpoConfig,
276
284
  appGroupIdentifier: string
277
285
  ): ExpoConfig {
278
286
  return withEntitlementsPlist(config, (c) => {
287
+ if (!c.modResults['aps-environment']) {
288
+ c.modResults['aps-environment'] = 'development'
289
+ }
279
290
  const existing: string[] =
280
291
  (c.modResults['com.apple.security.application-groups'] as
281
292
  | string[]
@@ -0,0 +1,35 @@
1
+ // Cross-platform postinstall (replaces the bash-only postInstallScripts.sh,
2
+ // which silently no-opped on Windows and violated the repo's cross-platform
3
+ // rule). Runs the same three helper scripts that integrate the SDK into a
4
+ // consumer project, from the package root, best-effort: a non-zero exit from
5
+ // any of them is logged but never fails `npm install`.
6
+
7
+ import {spawnSync} from 'node:child_process'
8
+ import path from 'node:path'
9
+ import {fileURLToPath} from 'node:url'
10
+
11
+ const scriptsDir = path.dirname(fileURLToPath(import.meta.url))
12
+ const packageRoot = path.dirname(scriptsDir)
13
+
14
+ // Order matches the original postInstallScripts.sh. Note: the file is
15
+ // `xmlparser.js` (lowercase) — the old shell script referenced `xmlParser.js`
16
+ // with a capital P, which failed on case-sensitive filesystems (Linux CI).
17
+ const steps = ['reviewConnectConfig.js', 'xmlparser.js', 'gradleParser.js']
18
+
19
+ console.log(
20
+ '**Acoustic Integration***********************************************************************',
21
+ )
22
+ for (const step of steps) {
23
+ console.log(`Running scripts/${step} ...`)
24
+ const result = spawnSync(process.execPath, [path.join('scripts', step)], {
25
+ cwd: packageRoot,
26
+ stdio: 'inherit',
27
+ })
28
+ if (result.status !== 0)
29
+ console.log(
30
+ `scripts/${step} exited with ${result.status ?? result.signal} — continuing (postinstall is best-effort).`,
31
+ )
32
+ }
33
+ console.log(
34
+ '*********************************************************************************************',
35
+ )
@@ -14,7 +14,7 @@ const xmlPaserPlugin = require("@prettier/plugin-xml");
14
14
  const { exit } = require('process');
15
15
  const { isValid } = require('./util');
16
16
 
17
- console.log("Run xmlParser.js");
17
+ console.log("Run xmlparser.js");
18
18
 
19
19
  const directoryPath = path.join(__dirname,"..","..","..")
20
20
  const filePath = `${directoryPath}/android/app/src/main/AndroidManifest.xml`;
@@ -1,10 +0,0 @@
1
- #!/bin/bash
2
- echo "**Acoustic Integration***********************************************************************"
3
- echo "Current directory:"
4
- pwd
5
- echo "Running Node scripts..."
6
- node ./scripts/reviewConnectConfig.js
7
- node ./scripts/xmlParser.js
8
- node ./scripts/gradleParser.js
9
- echo "*********************************************************************************************"
10
- echo "Running here scripts..."
@@ -1,87 +0,0 @@
1
- /********************************************************************************************
2
- * Copyright (C) 2024 Acoustic, L.P. All rights reserved.
3
- *
4
- * NOTICE: This file contains material that is confidential and proprietary to
5
- * Acoustic, L.P. and/or other developers. No license is granted under any intellectual or
6
- * industrial property rights of Acoustic, L.P. except as may be provided in an agreement with
7
- * Acoustic, L.P. Any unauthorized copying or distribution of content from this file is
8
- * prohibited.
9
- ********************************************************************************************/
10
-
11
- /**
12
- * Verify and Update React Native SDK integration to capture screen tracking.
13
- *
14
- * cd node_modules/react-native-acoustic-connect-beta folder
15
- *
16
- * yarn run verifyConnectSetup
17
- *
18
- */
19
- const fs = require('fs');
20
-
21
- //const filePath = "../../src/components/RootComponent.tsx"
22
- const filePath = "Example/nativebase-v3-kitchensink/src/components/RootComponent.tsx"
23
-
24
- function log(message) {
25
- console.log(message);
26
- }
27
-
28
- function isRootFileAvailable() {
29
- return fs.existsSync(filePath);
30
- }
31
-
32
- function isConnectComponentAvailable() {
33
- log("Looking for Connect tag...");
34
- return isComponentAvailable(/<Connect/);
35
- }
36
-
37
- function isNavigationContainerAvailable() {
38
- log("Looking for NavigationContainer tag...");
39
- return isComponentAvailable(/<NavigationContainer/);
40
- }
41
-
42
- function isComponentAvailable(componentTag) {
43
- try {
44
- let data = fs.readFileSync(filePath, 'utf8');
45
- const re = new RegExp(componentTag, "g");
46
- const found = re.test(data);
47
-
48
- return found;
49
- } catch (err) {
50
- console.error(err);
51
- }
52
- }
53
-
54
- if (isRootFileAvailable()) {
55
- if (isConnectComponentAvailable()) {
56
- log("Connect component found. You are ready to go!");
57
- } else if (isNavigationContainerAvailable()) {
58
- log("NavigationContainer component is available but Connect component is missing.\n I'll add Connect around NavigationContainer component.");
59
- AddConnectComponent();
60
- } else {
61
- log("Missing both Connect and NavigationContainer component.\n Please refer to SDK integration without React Navigation.");
62
- };
63
- } else {
64
- console.error("App.js file not found in root app folder.");
65
- }
66
-
67
- // Update and add Connect around NavigationContainer
68
- function AddConnectComponent() {
69
- const connectImport = `import { Connect } from 'react-native-acoustic-connect-beta';`
70
-
71
- const startNavigationContainer = /<NavigationContainer/
72
- const startConnectComponent = `<Connect><NavigationContainer`
73
-
74
- const endNavigationContainer = /<\/NavigationContainer>/
75
- const endConnectComponent = `</NavigationContainer></Connect>`
76
-
77
- try {
78
- let data = fs.readFileSync(filePath, 'utf8');
79
- data = data
80
- .replace(startNavigationContainer, startConnectComponent)
81
- .replace(endNavigationContainer, endConnectComponent);
82
- fs.writeFileSync(filePath, ConnectImport + '\n' + data);
83
- } catch (err) {
84
- log("Something went wrong.\n Please refer to SDK integration with React Navigation.");
85
- console.error(err);
86
- }
87
- }