react-native-acoustic-connect-beta 18.0.39 → 18.0.40

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.
@@ -6,7 +6,13 @@
6
6
  // be provided in an agreement with Acoustic, L.P. Any unauthorized copying or
7
7
  // distribution of content from this file is prohibited.
8
8
 
9
- import { withAppBuildGradle, type ConfigPlugin } from '@expo/config-plugins'
9
+ import {
10
+ withAppBuildGradle,
11
+ withDangerousMod,
12
+ type ConfigPlugin,
13
+ } from '@expo/config-plugins'
14
+ import * as fs from 'fs'
15
+ import * as path from 'path'
10
16
  import type { ConnectPluginProps } from './withConnectNSE'
11
17
 
12
18
  // The Gradle snippet that runs the SDK's config.gradle. config.gradle reads
@@ -68,3 +74,83 @@ export const withConnectAndroidConfig: ConfigPlugin<ConnectPluginProps> = (
68
74
  cfg.modResults.contents = appendConfigGradleApply(cfg.modResults.contents)
69
75
  return cfg
70
76
  })
77
+
78
+ /** True when ConnectConfig.json at `projectRoot` has Connect.PushEnabled === true. */
79
+ function isPushEnabled(projectRoot: string): boolean {
80
+ const configPath = path.join(projectRoot, 'ConnectConfig.json')
81
+ if (!fs.existsSync(configPath)) return false
82
+ try {
83
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8')) as {
84
+ Connect?: { PushEnabled?: unknown }
85
+ }
86
+ return parsed.Connect?.PushEnabled === true
87
+ } catch {
88
+ return false
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Fail fast at prebuild when push is enabled but `android.package` is absent
94
+ * from the configured `google-services.json`. FCM matches its client by package
95
+ * name, so a mismatch makes Gradle fail much later at
96
+ * `:app:processDebugGoogleServices` with the opaque "No matching client found
97
+ * for package name …" — and only after a full prebuild + Gradle config. Surface
98
+ * it here, at the moment the native project is (re)generated, with the exact fix
99
+ * (CA-144135 §10b). `acoustic-connect doctor` performs the same check up front;
100
+ * this is the guard for a developer who runs `expo run:android` directly.
101
+ *
102
+ * Only the genuine MISMATCH throws. A missing package / googleServicesFile is
103
+ * left to `doctor` and the build itself (this mod doesn't duplicate those).
104
+ */
105
+ export const withConnectAndroidGoogleServicesMatch: ConfigPlugin<
106
+ ConnectPluginProps
107
+ > = (config) =>
108
+ withDangerousMod(config, [
109
+ 'android',
110
+ async (cfg) => {
111
+ const projectRoot = cfg._internal?.projectRoot
112
+ if (!projectRoot || !isPushEnabled(projectRoot)) return cfg
113
+
114
+ const androidPackage = cfg.android?.package
115
+ if (!androidPackage) return cfg
116
+
117
+ // Most Expo + FCM consumers omit android.googleServicesFile and rely on
118
+ // the default ./google-services.json at the project root. Mirror that
119
+ // default (and `acoustic-connect doctor`'s) so the match check isn't
120
+ // silently skipped for the common case.
121
+ const gsFile = cfg.android?.googleServicesFile ?? 'google-services.json'
122
+ const gsPath = path.isAbsolute(gsFile)
123
+ ? gsFile
124
+ : path.join(projectRoot, gsFile)
125
+ if (!fs.existsSync(gsPath)) return cfg // missing file is doctor's / the build's concern
126
+
127
+ let parsed: {
128
+ client?: Array<{
129
+ client_info?: { android_client_info?: { package_name?: string } }
130
+ }>
131
+ }
132
+ try {
133
+ parsed = JSON.parse(fs.readFileSync(gsPath, 'utf8'))
134
+ } catch {
135
+ return cfg // malformed google-services.json — doctor/build will report it
136
+ }
137
+
138
+ const packages = (parsed.client ?? [])
139
+ .map((c) => c.client_info?.android_client_info?.package_name)
140
+ .filter((p): p is string => typeof p === 'string')
141
+
142
+ if (packages.length > 0 && !packages.includes(androidPackage)) {
143
+ throw new Error(
144
+ `[react-native-acoustic-connect-beta] android.package "${androidPackage}" ` +
145
+ `has no matching client in ${gsFile} (it has: ${packages.join(', ')}).\n\n` +
146
+ `FCM matches its client by package name, so the Android build would ` +
147
+ `fail at :app:processDebugGoogleServices. Register "${androidPackage}" in ` +
148
+ `the same Firebase project and re-download google-services.json, or set ` +
149
+ `app.json android.package to one of the registered packages.\n` +
150
+ `Note: changing android.package requires a clean prebuild — ` +
151
+ `\`npx expo prebuild --platform android --clean\`.`
152
+ )
153
+ }
154
+ return cfg
155
+ },
156
+ ])
@@ -0,0 +1,107 @@
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 { withXcodeProject, type ConfigPlugin } from '@expo/config-plugins'
10
+ import type { ExpoConfig } from '@expo/config-types'
11
+ import * as fs from 'fs'
12
+ import * as path from 'path'
13
+
14
+ import type { ConnectPluginProps } from './withConnectNSE'
15
+
16
+ // Derive the xcode project type from the callback (the `xcode` package ships no
17
+ // types of its own) — same approach as withConnectNSE.
18
+ type XcodeProject = Parameters<
19
+ Parameters<typeof withXcodeProject>[1]
20
+ >[0]['modResults']
21
+
22
+ const PLACEHOLDER_TEAM = 'YOUR_TEAM_ID'
23
+
24
+ /**
25
+ * Resolves the iOS signing team (Apple Team ID) for the host + push extensions.
26
+ *
27
+ * Priority:
28
+ * 1. `iosDevelopmentTeam` plugin prop in app.json (explicit override)
29
+ * 2. `Connect.iOSDevelopmentTeam` in `<projectRoot>/ConnectConfig.json`
30
+ *
31
+ * Returns `undefined` when neither source provides a real value (the placeholder
32
+ * `YOUR_TEAM_ID` counts as unset). The mod is then a no-op — never a regression.
33
+ */
34
+ export function resolveDevelopmentTeam(
35
+ projectRoot: string,
36
+ props: ConnectPluginProps
37
+ ): string | undefined {
38
+ const fromProp = props.iosDevelopmentTeam?.trim()
39
+ if (fromProp && fromProp !== PLACEHOLDER_TEAM) return fromProp
40
+
41
+ const configPath = path.join(projectRoot, 'ConnectConfig.json')
42
+ if (fs.existsSync(configPath)) {
43
+ try {
44
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8')) as {
45
+ Connect?: { iOSDevelopmentTeam?: unknown }
46
+ }
47
+ const team = parsed.Connect?.iOSDevelopmentTeam
48
+ if (typeof team === 'string' && team.trim() && team.trim() !== PLACEHOLDER_TEAM)
49
+ return team.trim()
50
+ } catch {
51
+ // Malformed JSON is already surfaced by resolveAppGroupIdentifier (which
52
+ // runs earlier in the NSE mod) — don't double-report here.
53
+ }
54
+ }
55
+ return undefined
56
+ }
57
+
58
+ /**
59
+ * Expo Config Plugin mod that stamps `DEVELOPMENT_TEAM` onto every target's
60
+ * build configuration in the generated Xcode project (host + ConnectNSE +
61
+ * ConnectNCE), so all three sign consistently.
62
+ *
63
+ * Why this matters: the NSE mod injects the `aps-environment` entitlement, but
64
+ * a CLI build (`expo run:ios` / `xcodebuild`) with no signing team falls back
65
+ * to ad-hoc signing (`CODE_SIGN_IDENTITY = -`), which DROPS that entitlement —
66
+ * so the OS issues no APNs token and push silently fails (CA-144135 §6). Setting
67
+ * the team closes that loop on the prebuild path the SDK owns. Provisioning
68
+ * itself still needs the developer's Apple ID in Xcode and
69
+ * `-allowProvisioningUpdates` on the build (documented in the README).
70
+ *
71
+ * No-op when no team is configured (no regression for consumers who sign in the
72
+ * Xcode GUI or via EAS, which manages the whole chain itself).
73
+ *
74
+ * Must run AFTER withConnectNSE / withConnectNCE so the extension targets exist.
75
+ */
76
+ export const withConnectIosSigning: ConfigPlugin<ConnectPluginProps> = (
77
+ config,
78
+ props = {}
79
+ ) => {
80
+ const projectRoot = config._internal?.projectRoot
81
+ // withConnectNSE already throws an actionable error when projectRoot is
82
+ // missing; here we simply skip so we never throw twice for the same cause.
83
+ if (!projectRoot) return config
84
+
85
+ const team = resolveDevelopmentTeam(projectRoot, props)
86
+ if (!team) return config
87
+
88
+ return withXcodeProject(config, (c) => {
89
+ const xcodeProject: XcodeProject = c.modResults
90
+ const configurations = xcodeProject.pbxXCBuildConfigurationSection() as Record<
91
+ string,
92
+ { buildSettings?: Record<string, string | undefined> }
93
+ >
94
+ // Set the team on every build configuration that has a buildSettings block
95
+ // (host + both extensions). Project-level configs get it too, which is
96
+ // harmless and ensures consistency across all targets.
97
+ for (const key of Object.keys(configurations)) {
98
+ const entry = configurations[key]
99
+ if (entry && typeof entry === 'object' && entry.buildSettings) {
100
+ entry.buildSettings.DEVELOPMENT_TEAM = team
101
+ }
102
+ }
103
+ return c
104
+ }) as ExpoConfig
105
+ }
106
+
107
+ export default withConnectIosSigning
@@ -28,6 +28,13 @@ import * as path from 'path'
28
28
  export interface ConnectPluginProps {
29
29
  /** Explicit App Group override. Wins over ConnectConfig.json when set. */
30
30
  iosAppGroupIdentifier?: string
31
+ /**
32
+ * Apple Team ID (10-char) used to sign the host app and the push extensions.
33
+ * Wins over `Connect.iOSDevelopmentTeam` in ConnectConfig.json when set.
34
+ * Required for push: without a team, a CLI build drops the `aps-environment`
35
+ * entitlement to ad-hoc signing and the OS issues no APNs token.
36
+ */
37
+ iosDevelopmentTeam?: string
31
38
  }
32
39
 
33
40
  // ─── App Group resolution ─────────────────────────────────────────────────────