mergn-react-native 1.0.0
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.
- package/LICENSE +44 -0
- package/README.md +131 -0
- package/android/build.gradle +66 -0
- package/android/src/main/java/com/mergn/reactnative/MergnModule.java +152 -0
- package/android/src/main/java/com/mergn/reactnative/MergnPackage.java +31 -0
- package/app.plugin.js +2 -0
- package/docs/ANDROID.md +147 -0
- package/docs/IOS.md +156 -0
- package/docs/MIGRATION.md +365 -0
- package/ios/MergnModule.swift +149 -0
- package/ios/MergnModuleBridge.m +14 -0
- package/ios/MergnNotificationHandler.swift +160 -0
- package/ios/NotificationService/Info.plist +31 -0
- package/ios/NotificationService/NotificationService.swift +82 -0
- package/mergn-react-native.podspec +32 -0
- package/package.json +49 -0
- package/plugin/index.js +219 -0
- package/react-native.config.js +19 -0
- package/scripts/release-ios.sh +79 -0
- package/scripts/verify-package.js +58 -0
- package/sdk/MergnSDK.podspec +28 -0
- package/sdk/mergn_ios.xcframework/Info.plist +44 -0
- package/sdk/mergn_ios.xcframework/ios-arm64/mergn_ios.framework/Info.plist +0 -0
- package/sdk/mergn_ios.xcframework/ios-arm64/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios.abi.json +2517 -0
- package/sdk/mergn_ios.xcframework/ios-arm64/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios.private.swiftinterface +82 -0
- package/sdk/mergn_ios.xcframework/ios-arm64/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/sdk/mergn_ios.xcframework/ios-arm64/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios.swiftinterface +82 -0
- package/sdk/mergn_ios.xcframework/ios-arm64/mergn_ios.framework/mergn_ios +0 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Info.plist +0 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios-simulator.abi.json +2517 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +82 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/arm64-apple-ios-simulator.swiftinterface +82 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/x86_64-apple-ios-simulator.abi.json +2517 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +82 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/Modules/mergn_ios.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +82 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/_CodeSignature/CodeResources +101 -0
- package/sdk/mergn_ios.xcframework/ios-arm64_x86_64-simulator/mergn_ios.framework/mergn_ios +0 -0
- package/src/index.d.ts +37 -0
- package/src/index.js +34 -0
package/docs/IOS.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# iOS setup
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
npm install mergn-react-native
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{ "expo": { "plugins": ["mergn-react-native"] } }
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx expo prebuild --platform ios
|
|
13
|
+
cd ios && pod install && cd ..
|
|
14
|
+
npx expo run:ios
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What the package does for you
|
|
18
|
+
|
|
19
|
+
| | |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Native module registration | Autolinking (`mergn-react-native.podspec`) |
|
|
22
|
+
| MERGN iOS SDK | `mergn_ios.xcframework`, bundled — nothing to download |
|
|
23
|
+
| `ios.deploymentTarget` raised to 14.0 | Config plugin (only ever raised, never lowered) |
|
|
24
|
+
| `ios.useFrameworks: "static"` | Config plugin |
|
|
25
|
+
| `pod 'MergnSDK'` in your Podfile | Config plugin |
|
|
26
|
+
| Notification view / click tracking | Automatic (see below) |
|
|
27
|
+
|
|
28
|
+
You do **not** edit your `Podfile` or `AppDelegate`.
|
|
29
|
+
|
|
30
|
+
### Why those two build settings are needed
|
|
31
|
+
|
|
32
|
+
- **iOS 14.0** — the framework is built for iOS 14. Expo's template defaults to
|
|
33
|
+
13.4, which fails with *"they required a higher minimum deployment target"*.
|
|
34
|
+
- **`useFrameworks: "static"`** — the SDK ships a Swift module with no
|
|
35
|
+
Objective-C headers, so without `use_frameworks!` the build fails with
|
|
36
|
+
*"Unable to resolve module dependency: 'mergn_ios'"*. `static` is chosen
|
|
37
|
+
because Firebase's Swift pods also require it.
|
|
38
|
+
|
|
39
|
+
Both are applied automatically. If your app already targets a newer iOS, that is
|
|
40
|
+
left alone.
|
|
41
|
+
|
|
42
|
+
## Push notifications
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npx expo install @react-native-firebase/app @react-native-firebase/messaging
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
1. Register your app's **bundle ID** in the Firebase console and download
|
|
49
|
+
`GoogleService-Info.plist`.
|
|
50
|
+
2. Wire it up in `app.json`:
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"expo": {
|
|
54
|
+
"ios": {
|
|
55
|
+
"googleServicesFile": "./GoogleService-Info.plist",
|
|
56
|
+
"entitlements": { "aps-environment": "development" }
|
|
57
|
+
},
|
|
58
|
+
"plugins": [
|
|
59
|
+
"mergn-react-native",
|
|
60
|
+
"@react-native-firebase/app"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
3. Upload an **APNs key** (`.p8`) to Firebase → Project settings → Cloud
|
|
66
|
+
Messaging. This needs a paid Apple Developer account.
|
|
67
|
+
4. Pass the token to MERGN:
|
|
68
|
+
```js
|
|
69
|
+
const token = await messaging().getToken();
|
|
70
|
+
await Mergn.setFirebaseToken(token);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Without `GoogleService-Info.plist`, `messaging()` throws
|
|
74
|
+
`No Firebase App '[DEFAULT]' has been created`. Everything else in this package
|
|
75
|
+
keeps working — only FCM is affected.
|
|
76
|
+
|
|
77
|
+
> APNs cannot be used on the iOS Simulator. Real push delivery requires a
|
|
78
|
+
> physical device. To test *handling* without APNs, inject a payload locally:
|
|
79
|
+
> ```sh
|
|
80
|
+
> xcrun simctl push booted <your.bundle.id> payload.json
|
|
81
|
+
> ```
|
|
82
|
+
> The payload needs `aps` at the top level (raw APNs shape). An FCM v1 request —
|
|
83
|
+
> the JSON you POST to Firebase, with a top-level `message` — will be rejected
|
|
84
|
+
> with *"Notification payload is missing the aps key"*, because only Firebase's
|
|
85
|
+
> servers unwrap that envelope.
|
|
86
|
+
|
|
87
|
+
### Notification view / click tracking
|
|
88
|
+
|
|
89
|
+
Automatic — no JS API and no AppDelegate changes.
|
|
90
|
+
|
|
91
|
+
MERGN's iOS SDK expects the host app to call `notificationViewed` and
|
|
92
|
+
`notificationTapped` from the `UNUserNotificationCenter` delegate. A React Native
|
|
93
|
+
app cannot simply assign that delegate: `expo-notifications` and
|
|
94
|
+
`@react-native-firebase` both claim it, and the last one to load wins.
|
|
95
|
+
|
|
96
|
+
So this package **wraps** whichever delegate your app ends up with, forwards the
|
|
97
|
+
two events to MERGN, then calls the original implementation. Your own
|
|
98
|
+
notification handling is untouched, and tracking is additive.
|
|
99
|
+
|
|
100
|
+
It also refreshes the SDK's current view controller whenever the app comes to the
|
|
101
|
+
foreground, which is what in-app popups need.
|
|
102
|
+
|
|
103
|
+
## In-app popups
|
|
104
|
+
|
|
105
|
+
Triggered by MERGN campaigns in response to events:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
await Mergn.performEvent("Collection Viewed", { title: "Beauty" });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The package keeps `SDKManager.shared.setCurrentViewController(...)` up to date,
|
|
112
|
+
so popups have somewhere to present. If none appears, confirm a campaign is
|
|
113
|
+
configured for that event name in your MERGN dashboard.
|
|
114
|
+
|
|
115
|
+
## Rich push images (optional)
|
|
116
|
+
|
|
117
|
+
iOS renders a notification image only if your app contains a **Notification
|
|
118
|
+
Service Extension** — a separate binary that must be a target of *your* app, so
|
|
119
|
+
no library can supply it. Without one, a push carrying `mutable-content: 1` and
|
|
120
|
+
an `image` URL still arrives, just as text.
|
|
121
|
+
|
|
122
|
+
To add it:
|
|
123
|
+
|
|
124
|
+
1. **Xcode → File → New → Target → Notification Service Extension**, name it
|
|
125
|
+
`NotificationService`.
|
|
126
|
+
2. Replace the generated `NotificationService.swift` with the reference
|
|
127
|
+
implementation:
|
|
128
|
+
`node_modules/mergn-react-native/ios/NotificationService/NotificationService.swift`
|
|
129
|
+
3. Set the extension's **iOS Deployment Target to match your app** — Xcode
|
|
130
|
+
defaults it to the latest SDK, and an extension must not require a newer OS
|
|
131
|
+
than its host.
|
|
132
|
+
|
|
133
|
+
Because `expo prebuild --clean` regenerates the Xcode project, commit your `ios/`
|
|
134
|
+
directory (as you would `android/`) or re-add the target after a clean prebuild.
|
|
135
|
+
|
|
136
|
+
Android needs none of this — the MERGN SDK handles images itself there.
|
|
137
|
+
|
|
138
|
+
## Troubleshooting
|
|
139
|
+
|
|
140
|
+
**`Unable to resolve module dependency: 'mergn_ios'`** — `use_frameworks!` is not
|
|
141
|
+
active. Ensure `mergn-react-native` is in your `app.json` plugins, then
|
|
142
|
+
`npx expo prebuild --platform ios` and `pod install` again.
|
|
143
|
+
|
|
144
|
+
**`they required a higher minimum deployment target`** — same cause; the plugin
|
|
145
|
+
sets iOS 14.0 for you.
|
|
146
|
+
|
|
147
|
+
**`Unable to find compatibility version string for object version 70`** —
|
|
148
|
+
Xcode 16/26 wrote a project format your CocoaPods cannot read. The plugin pins it
|
|
149
|
+
back on prebuild; alternatively update CocoaPods.
|
|
150
|
+
|
|
151
|
+
**`Cycle inside <YourApp>`** — `@react-native-firebase`'s script phase declares
|
|
152
|
+
your built `Info.plist` as an input, which cycles once an app extension is
|
|
153
|
+
embedded. The plugin clears that input; re-run prebuild.
|
|
154
|
+
|
|
155
|
+
**`MergnModule.<method> is unavailable`** — native module not loaded. Expo Go
|
|
156
|
+
cannot load native code; use `npx expo run:ios` or an EAS build.
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# Migrating from a manual MERGN integration
|
|
2
|
+
|
|
3
|
+
If you wired MERGN in by hand — copying `MergnModule.java` into your app,
|
|
4
|
+
registering `MergnPackage()` in `MainApplication`, and dropping the `.aar` into
|
|
5
|
+
`android/app/libs/` — you **must remove those pieces** before installing this
|
|
6
|
+
package.
|
|
7
|
+
|
|
8
|
+
Read this page top to bottom before you start. The removal is not optional and
|
|
9
|
+
it is not all loud: one of the four steps fails your build immediately, and two
|
|
10
|
+
of them fail silently while the app still looks healthy.
|
|
11
|
+
|
|
12
|
+
- [Is removing the `.aar` enough?](#is-removing-the-aar-enough)
|
|
13
|
+
- [Android: removing the old integration, step by step](#android-removing-the-old-integration-step-by-step)
|
|
14
|
+
- [Android: installing the new package](#android-installing-the-new-package)
|
|
15
|
+
- [iOS: what to remove](#ios-what-to-remove)
|
|
16
|
+
- [JavaScript: update imports](#javascript-update-imports)
|
|
17
|
+
- [Verifying the migration worked](#verifying-the-migration-worked)
|
|
18
|
+
- [Rollback](#rollback)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Is removing the `.aar` enough?
|
|
23
|
+
|
|
24
|
+
**No.** Removing the `.aar` fixes the *build failure*. Two further problems
|
|
25
|
+
remain, and neither announces itself:
|
|
26
|
+
|
|
27
|
+
| Step skipped | What happens | Do you notice? |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `.aar` left in `libs/` | `Duplicate class com.mergn.insights.*` — build fails | **Yes, immediately** |
|
|
30
|
+
| `MergnModule.java` / `MergnPackage.java` left in your app | Two native modules **both named `MergnModule`**. React Native registers one and warns; which one wins is not guaranteed | **No** — builds and runs |
|
|
31
|
+
| `packages.add(MergnPackage())` left in `MainApplication` | `MergnPackage` resolves to your *old* class, so this package's module is bypassed entirely | **No** |
|
|
32
|
+
|
|
33
|
+
Both old and new modules return the identical name from `getName()`:
|
|
34
|
+
|
|
35
|
+
```java
|
|
36
|
+
@Override
|
|
37
|
+
public String getName() {
|
|
38
|
+
return "MergnModule"; // identical in com.android.test.MergnModule
|
|
39
|
+
} // and com.mergn.reactnative.MergnModule
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`NativeModules.MergnModule` is resolved by that string, so with both classes
|
|
43
|
+
present your JS call may reach either one. The old one references SDK classes
|
|
44
|
+
that no longer come from an `.aar`, so you get a working-looking app with calls
|
|
45
|
+
landing in the wrong place.
|
|
46
|
+
|
|
47
|
+
Do all four steps below.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Android: removing the old integration, step by step
|
|
52
|
+
|
|
53
|
+
### Step 1 — Find what you actually have
|
|
54
|
+
|
|
55
|
+
Run this from your project root. It prints every trace of the old integration:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
echo "--- copied native sources ---"
|
|
59
|
+
find android/app/src/main/java -name 'Mergn*.java' -o -name 'Mergn*.kt'
|
|
60
|
+
|
|
61
|
+
echo "--- bundled SDK binaries ---"
|
|
62
|
+
find android -name '*.aar' | grep -i mergn
|
|
63
|
+
|
|
64
|
+
echo "--- gradle references ---"
|
|
65
|
+
grep -rn -i 'mergn\|libs/.*\.aar\|fileTree' android/app/build.gradle
|
|
66
|
+
|
|
67
|
+
echo "--- MainApplication registration ---"
|
|
68
|
+
grep -rn -i 'mergn' android/app/src/main/java --include='MainApplication.*'
|
|
69
|
+
|
|
70
|
+
echo "--- manifest service ---"
|
|
71
|
+
grep -n -i 'mergn' android/app/src/main/AndroidManifest.xml
|
|
72
|
+
|
|
73
|
+
echo "--- JS imports ---"
|
|
74
|
+
grep -rn 'MergnModule' --include='*.js' --include='*.jsx' --include='*.ts' --include='*.tsx' . \
|
|
75
|
+
| grep -v node_modules
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Everything it lists gets deleted. Keep the output — you will use it to confirm
|
|
79
|
+
you got all of it.
|
|
80
|
+
|
|
81
|
+
> **If `android/` is not committed to git** (a pure Expo CNG project where
|
|
82
|
+
> `npx expo prebuild` generates it), your changes live in `app.json`, a config
|
|
83
|
+
> plugin, or a `patch-package` patch instead. In that case delete the plugin or
|
|
84
|
+
> patch that injected MERGN, and run `npx expo prebuild --clean`. Steps 2–5 then
|
|
85
|
+
> apply to whatever your plugin was writing.
|
|
86
|
+
|
|
87
|
+
### Step 2 — Delete the copied native sources
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
rm -f android/app/src/main/java/<your/package/path>/MergnModule.java
|
|
91
|
+
rm -f android/app/src/main/java/<your/package/path>/MergnPackage.java
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For example, if your package is `com.android.test`:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
rm -f android/app/src/main/java/com/android/test/MergnModule.java
|
|
98
|
+
rm -f android/app/src/main/java/com/android/test/MergnPackage.java
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If you translated them to Kotlin, delete the `.kt` files instead. If you also
|
|
102
|
+
added a custom `FirebaseMessagingService` subclass purely for MERGN, delete that
|
|
103
|
+
too — the SDK ships its own.
|
|
104
|
+
|
|
105
|
+
**This step is silent if you skip it.** Do not skip it.
|
|
106
|
+
|
|
107
|
+
### Step 3 — Un-register the package in `MainApplication`
|
|
108
|
+
|
|
109
|
+
`android/app/src/main/java/<your/package/path>/MainApplication.kt`:
|
|
110
|
+
|
|
111
|
+
```diff
|
|
112
|
+
override fun getPackages(): List<ReactPackage> =
|
|
113
|
+
PackageList(this).packages.apply {
|
|
114
|
+
- add(MergnPackage())
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or, if it's still Java:
|
|
119
|
+
|
|
120
|
+
```diff
|
|
121
|
+
@Override
|
|
122
|
+
protected List<ReactPackage> getPackages() {
|
|
123
|
+
List<ReactPackage> packages = new PackageList(this).getPackages();
|
|
124
|
+
- packages.add(new MergnPackage());
|
|
125
|
+
return packages;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Also delete the now-unused import if you had an explicit one:
|
|
130
|
+
|
|
131
|
+
```diff
|
|
132
|
+
- import com.android.test.MergnPackage;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Anything you added to `onCreate` for MERGN comes out as well:
|
|
136
|
+
|
|
137
|
+
```diff
|
|
138
|
+
override fun onCreate() {
|
|
139
|
+
super.onCreate()
|
|
140
|
+
- MergnSDK.initialize(this)
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This package initializes the SDK for you on the first `registerApi` call.
|
|
145
|
+
|
|
146
|
+
**This step is also silent if you skip it.**
|
|
147
|
+
|
|
148
|
+
### Step 4 — Remove the bundled `.aar`
|
|
149
|
+
|
|
150
|
+
In `android/app/build.gradle`:
|
|
151
|
+
|
|
152
|
+
```diff
|
|
153
|
+
dependencies {
|
|
154
|
+
- implementation files('libs/MERGN_SDK_KOTLIN_REACT_NATIVE_5.2.4.aar')
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Some integrations used a `fileTree` instead — remove the MERGN part of it, or
|
|
159
|
+
the whole line if MERGN was the only thing in `libs/`:
|
|
160
|
+
|
|
161
|
+
```diff
|
|
162
|
+
- implementation fileTree(dir: 'libs', include: ['*.aar'])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Then delete the binaries:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
rm -f android/app/libs/*mergn*.aar android/app/libs/*MERGN*.aar
|
|
169
|
+
rmdir android/app/libs 2>/dev/null # only succeeds if now empty
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If your old integration also declared the SDK's dependencies by hand (Room,
|
|
173
|
+
WorkManager, coroutines, gson…) *solely for MERGN*, remove those too — this
|
|
174
|
+
package declares them itself. Leave anything your own code uses.
|
|
175
|
+
|
|
176
|
+
**Skipping this step fails the build:**
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
Duplicate class com.mergn.insights.classes.EventManager found in modules
|
|
180
|
+
jetified-MERGN_SDK_KOTLIN_REACT_NATIVE_5.2.4.aar and
|
|
181
|
+
jetified-Mergn_sdk_android-5.2.4.aar
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Step 5 — Remove the hand-added manifest service
|
|
185
|
+
|
|
186
|
+
The config plugin declares MERGN's messaging service, so a hand-written
|
|
187
|
+
duplicate for the same class is a manifest-merger error. In
|
|
188
|
+
`android/app/src/main/AndroidManifest.xml`:
|
|
189
|
+
|
|
190
|
+
```diff
|
|
191
|
+
- <service
|
|
192
|
+
- android:name="com.mergn.insights.firebaseservices.FireBaseMessagingService"
|
|
193
|
+
- android:exported="false">
|
|
194
|
+
- <intent-filter>
|
|
195
|
+
- <action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
196
|
+
- </intent-filter>
|
|
197
|
+
- </service>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Leave `INTERNET`, `POST_NOTIFICATIONS` and `VIBRATE` alone if they're there —
|
|
201
|
+
the plugin adds them idempotently, and duplicate `<uses-permission>` entries
|
|
202
|
+
merge cleanly.
|
|
203
|
+
|
|
204
|
+
### Step 6 — Confirm the old integration is gone
|
|
205
|
+
|
|
206
|
+
Re-run the Step 1 audit. Every section should now be empty except your JS
|
|
207
|
+
imports, which Step 8 handles:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
find android/app/src/main/java -name 'Mergn*' ; \
|
|
211
|
+
find android -iname '*mergn*.aar' ; \
|
|
212
|
+
grep -rn -i mergn android/app/build.gradle android/app/src/main/AndroidManifest.xml ; \
|
|
213
|
+
grep -rn -i mergn android/app/src/main/java --include='MainApplication.*' ; \
|
|
214
|
+
echo "audit complete — no output above this line means clean"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Android: installing the new package
|
|
220
|
+
|
|
221
|
+
### Step 7 — Install
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
npm install mergn-react-native
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Add the plugin to `app.json`:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"expo": {
|
|
232
|
+
"plugins": ["mergn-react-native"]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
That is the entire native setup. Autolinking registers the module; the config
|
|
238
|
+
plugin adds the manifest service and permissions; the package's own
|
|
239
|
+
`build.gradle` pulls the SDK from JitPack and declares its transitive
|
|
240
|
+
dependencies. You do **not** touch `MainApplication`, `settings.gradle`, or
|
|
241
|
+
`app/build.gradle` again.
|
|
242
|
+
|
|
243
|
+
See [ANDROID.md](./ANDROID.md) for Firebase/push setup and the Android 13+
|
|
244
|
+
runtime permission, which are unchanged from your old integration.
|
|
245
|
+
|
|
246
|
+
### Step 8 — Update your JavaScript
|
|
247
|
+
|
|
248
|
+
```diff
|
|
249
|
+
- import MergnModule from "../MergnModule";
|
|
250
|
+
+ import Mergn from "mergn-react-native";
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Method names, arguments and return values are identical**, so the call sites
|
|
254
|
+
do not change:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
await Mergn.registerApi(key);
|
|
258
|
+
await Mergn.performEvent("Collection Viewed", { title: "Beauty" });
|
|
259
|
+
await Mergn.setAttribute("Email", "user@example.com");
|
|
260
|
+
await Mergn.login("user@example.com");
|
|
261
|
+
await Mergn.setFirebaseToken(token);
|
|
262
|
+
await Mergn.setAppContext(token);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The action strings on the bridge (`register_api`, `perform_event`,
|
|
266
|
+
`add_attribute`, `login_mergn`, `firebase_token_mergn`, `mergn_app_context`) are
|
|
267
|
+
byte-identical to the old module's, so anything you built on top still lines up.
|
|
268
|
+
|
|
269
|
+
Delete your local `MergnModule.js`.
|
|
270
|
+
|
|
271
|
+
### Step 9 — Rebuild from scratch
|
|
272
|
+
|
|
273
|
+
Stale native artefacts survive an ordinary rebuild and will hide your work:
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
rm -rf android/app/build android/build android/.gradle
|
|
277
|
+
npx expo prebuild --platform android --clean
|
|
278
|
+
npx expo run:android
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
> `prebuild --clean` regenerates `android/`. If you hand-edited anything else in
|
|
282
|
+
> there that is *not* committed as a config plugin, it is lost — commit or note
|
|
283
|
+
> those changes first.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## iOS: what to remove
|
|
288
|
+
|
|
289
|
+
If you had a hand-rolled iOS integration:
|
|
290
|
+
|
|
291
|
+
1. Delete any hand-added bridge sources from `ios/<YourApp>/`:
|
|
292
|
+
```sh
|
|
293
|
+
rm -f ios/<YourApp>/MergnModule.swift ios/<YourApp>/MergnModuleBridge.m
|
|
294
|
+
```
|
|
295
|
+
2. Remove a manual pod declaration from your `Podfile` — the config plugin
|
|
296
|
+
injects it:
|
|
297
|
+
```diff
|
|
298
|
+
- pod 'MergnSDK', :path => '...'
|
|
299
|
+
```
|
|
300
|
+
3. Delete a vendored framework from your project. Keeping it alongside the
|
|
301
|
+
package's copy produces duplicate-symbol link errors:
|
|
302
|
+
```sh
|
|
303
|
+
rm -rf ios/Frameworks/mergn_ios.xcframework
|
|
304
|
+
```
|
|
305
|
+
Also remove it from the target's *Link Binary With Libraries* and *Embed
|
|
306
|
+
Frameworks* phases in Xcode.
|
|
307
|
+
4. Rebuild clean:
|
|
308
|
+
```sh
|
|
309
|
+
rm -rf ios/Pods ios/Podfile.lock ios/build
|
|
310
|
+
npx expo prebuild --platform ios --clean
|
|
311
|
+
npx expo run:ios
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
See [IOS.md](./IOS.md) for the full iOS setup.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Verifying the migration worked
|
|
319
|
+
|
|
320
|
+
After `npx expo run:android`, watch the native log:
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
adb logcat -s MergnModule:D ReactNativeJS:V
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Call each method once from your app. You should see exactly one line per call:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
D MergnModule: Registered API key
|
|
330
|
+
D MergnModule: User Logged In
|
|
331
|
+
D MergnModule: Attribute Set: Email
|
|
332
|
+
D MergnModule: Event Sent: Collection Viewed
|
|
333
|
+
D MergnModule: Firebase Token Set
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Then confirm the *new* class is the one running:
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
adb logcat -d | grep -i 'MergnModule\|mergn.reactnative'
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Two warning signs:
|
|
343
|
+
|
|
344
|
+
- **`Native module MergnModule tried to override …`** in logcat — both modules
|
|
345
|
+
are still registered. Step 2 or Step 3 was missed.
|
|
346
|
+
- **`MergnModule.<method> is unavailable`** thrown in JS — the native module did
|
|
347
|
+
not load at all. You are either in Expo Go (which cannot load native code) or
|
|
348
|
+
the build predates the install; re-run Step 9.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Rollback
|
|
353
|
+
|
|
354
|
+
Nothing in the migration touches SDK storage. Same SDK version (Android 5.2.4,
|
|
355
|
+
iOS 19.0.0), same API key, same customer ids and attributes — identified users
|
|
356
|
+
stay identified across the upgrade, and back again.
|
|
357
|
+
|
|
358
|
+
To revert: `git revert` the migration commit, `npm uninstall
|
|
359
|
+
mergn-react-native`, then rebuild clean as in Step 9.
|
|
360
|
+
|
|
361
|
+
## If you cannot migrate yet
|
|
362
|
+
|
|
363
|
+
Stay on your manual integration — it keeps working. **Do not install this
|
|
364
|
+
package alongside it**; the duplicate-class failure has no workaround short of
|
|
365
|
+
removing one side.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import UIKit
|
|
4
|
+
import mergn_ios
|
|
5
|
+
|
|
6
|
+
// iOS counterpart of android/app/src/main/java/com/android/test/MergnModule.java.
|
|
7
|
+
// The JS side (MergnModule.js) funnels everything through one method:
|
|
8
|
+
// performAction(action, jsonData) -> Promise
|
|
9
|
+
// so the same action strings must be honoured here. Written in Swift because
|
|
10
|
+
// mergn_ios ships only a .swiftmodule (no Objective-C headers), so ObjC cannot
|
|
11
|
+
// import it.
|
|
12
|
+
@objc(MergnModule)
|
|
13
|
+
class MergnModule: NSObject {
|
|
14
|
+
|
|
15
|
+
private enum Action {
|
|
16
|
+
static let registerApi = "register_api"
|
|
17
|
+
static let performEvent = "perform_event"
|
|
18
|
+
static let setAttribute = "add_attribute"
|
|
19
|
+
static let login = "login_mergn"
|
|
20
|
+
static let firebaseToken = "firebase_token_mergn"
|
|
21
|
+
static let appContext = "mergn_app_context"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private let manager = EventManager.shared
|
|
25
|
+
|
|
26
|
+
// Matches the Android module: the SDK is a singleton, so re-registering the
|
|
27
|
+
// API key on every call would be wasted work. Tracked only to log it.
|
|
28
|
+
private static var didRegister = false
|
|
29
|
+
|
|
30
|
+
@objc static func requiresMainQueueSetup() -> Bool { true }
|
|
31
|
+
|
|
32
|
+
override init() {
|
|
33
|
+
super.init()
|
|
34
|
+
// Install notification tracking once the module is constructed. By this
|
|
35
|
+
// point expo-notifications / RNFB have claimed UNUserNotificationCenter's
|
|
36
|
+
// delegate, so the handler wraps theirs instead of replacing it.
|
|
37
|
+
// Deferred to main because it touches UIKit.
|
|
38
|
+
DispatchQueue.main.async {
|
|
39
|
+
MergnNotificationHandler.shared.install()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// The SDK renders in-app popups, so it needs a live view controller. Android
|
|
44
|
+
/// gets this from getCurrentActivity(); on iOS we hand the SDK the top-most
|
|
45
|
+
/// view controller before any call that might present UI.
|
|
46
|
+
private func refreshTopViewController() {
|
|
47
|
+
if let top = UIApplication.topViewController() {
|
|
48
|
+
SDKManager.shared.setCurrentViewController(top)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@objc(performAction:jsonData:resolver:rejecter:)
|
|
53
|
+
func performAction(_ action: String,
|
|
54
|
+
jsonData: String,
|
|
55
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
56
|
+
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
57
|
+
|
|
58
|
+
// Everything here can touch UIKit (popups, view controllers), so stay on main.
|
|
59
|
+
DispatchQueue.main.async {
|
|
60
|
+
let options: [String: Any]
|
|
61
|
+
do {
|
|
62
|
+
guard let data = jsonData.data(using: .utf8),
|
|
63
|
+
let parsed = try JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
64
|
+
else {
|
|
65
|
+
throw NSError(domain: "MergnModule", code: 1,
|
|
66
|
+
userInfo: [NSLocalizedDescriptionKey:
|
|
67
|
+
"jsonData is not a JSON object: \(jsonData)"])
|
|
68
|
+
}
|
|
69
|
+
options = parsed
|
|
70
|
+
} catch {
|
|
71
|
+
NSLog("[MergnModule] performAction failed to parse payload for \(action): \(error)")
|
|
72
|
+
reject("MERGN_ERROR", error.localizedDescription, error)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func requireString(_ key: String) throws -> String {
|
|
77
|
+
guard let value = options[key] as? String else {
|
|
78
|
+
throw NSError(domain: "MergnModule", code: 2,
|
|
79
|
+
userInfo: [NSLocalizedDescriptionKey:
|
|
80
|
+
"Missing required string '\(key)' for action '\(action)'"])
|
|
81
|
+
}
|
|
82
|
+
return value
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
do {
|
|
86
|
+
switch action {
|
|
87
|
+
|
|
88
|
+
case Action.registerApi:
|
|
89
|
+
let apiKey = try requireString("apiKey")
|
|
90
|
+
self.refreshTopViewController()
|
|
91
|
+
self.manager.registerAPI(clientApiKey: apiKey)
|
|
92
|
+
MergnModule.didRegister = true
|
|
93
|
+
NSLog("[MergnModule] Registered API key")
|
|
94
|
+
resolve("API Key Registered")
|
|
95
|
+
|
|
96
|
+
case Action.performEvent:
|
|
97
|
+
let eventName = try requireString("eventName")
|
|
98
|
+
// Android stringifies every property value; the iOS SDK takes
|
|
99
|
+
// [String: Any], so pass the JSON values through as-is.
|
|
100
|
+
let properties = options["eventPropertiesMap"] as? [String: Any] ?? [:]
|
|
101
|
+
self.refreshTopViewController()
|
|
102
|
+
self.manager.sendEvent(eventName: eventName, properties: properties)
|
|
103
|
+
NSLog("[MergnModule] Event Sent: \(eventName)")
|
|
104
|
+
resolve("Event Performed")
|
|
105
|
+
|
|
106
|
+
case Action.setAttribute:
|
|
107
|
+
let name = try requireString("attributeName")
|
|
108
|
+
let value = try requireString("attributeValue")
|
|
109
|
+
self.manager.sendAttribute(attributeName: name, attributeValue: value)
|
|
110
|
+
NSLog("[MergnModule] Attribute Set: \(name)")
|
|
111
|
+
resolve("Attribute Set")
|
|
112
|
+
|
|
113
|
+
case Action.login:
|
|
114
|
+
let identifier = try requireString("uniqueIdentifier")
|
|
115
|
+
// Android's single login() both persists the identifier and tells the
|
|
116
|
+
// backend. iOS splits these, so do both:
|
|
117
|
+
// saveCustomerId -> writes customerId to UserDefaults, so later
|
|
118
|
+
// sendEvent calls are attributed to this user
|
|
119
|
+
// postIdentification-> POST /customer/set-identity, merging the
|
|
120
|
+
// anonymous profile into the identified one
|
|
121
|
+
// postIdentification may well save the id itself, in which case the
|
|
122
|
+
// first call is a harmless duplicate write. Doing only the POST would
|
|
123
|
+
// risk unattributed events, so both are called deliberately.
|
|
124
|
+
_ = self.manager.saveCustomerId(customerId: identifier)
|
|
125
|
+
let afterSave = self.manager.getCustomerId()
|
|
126
|
+
self.manager.postIdentification(identity: identifier)
|
|
127
|
+
NSLog("[MergnModule] User Logged In: \(identifier) (customerId now '\(afterSave)')")
|
|
128
|
+
resolve("User Logged In")
|
|
129
|
+
|
|
130
|
+
case Action.firebaseToken, Action.appContext:
|
|
131
|
+
// Android routes both actions down the same path; keep that parity.
|
|
132
|
+
let token = try requireString("fcmTokenMergn")
|
|
133
|
+
_ = self.manager.saveFirebaseToken(token: token)
|
|
134
|
+
self.manager.firebaseToken(token: token)
|
|
135
|
+
let label = action == Action.appContext
|
|
136
|
+
? "Firebase Token in App Context Set" : "Firebase Token Set"
|
|
137
|
+
NSLog("[MergnModule] \(label)")
|
|
138
|
+
resolve(label)
|
|
139
|
+
|
|
140
|
+
default:
|
|
141
|
+
reject("UNSUPPORTED_ACTION", "Action \(action) is not supported.", nil)
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
NSLog("[MergnModule] performAction failed for action \(action): \(error)")
|
|
145
|
+
reject("MERGN_ERROR", error.localizedDescription, error)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
// Registers the Swift MergnModule with React Native. The implementation lives in
|
|
4
|
+
// MergnModule.swift (mergn_ios ships a .swiftmodule only, so the SDK cannot be
|
|
5
|
+
// imported from Objective-C); this interface is what makes
|
|
6
|
+
// NativeModules.MergnModule resolve on the JS side.
|
|
7
|
+
@interface RCT_EXTERN_MODULE(MergnModule, NSObject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(performAction:(NSString *)action
|
|
10
|
+
jsonData:(NSString *)jsonData
|
|
11
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
12
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
|
|
14
|
+
@end
|