@amplitude/plugin-engagement-react-native 0.1.1-alpha.10

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 (44) hide show
  1. package/LICENSE +20 -0
  2. package/PluginEngagementReactNative.podspec +22 -0
  3. package/README.md +95 -0
  4. package/android/build.gradle +89 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/amplitude/pluginengagementreactnative/PluginEngagementReactNativeModule.kt +183 -0
  8. package/android/src/main/java/com/amplitude/pluginengagementreactnative/PluginEngagementReactNativePackage.kt +33 -0
  9. package/ios/AmplitudeEngagementAdapter.swift +121 -0
  10. package/ios/PluginEngagementReactNative.h +5 -0
  11. package/ios/PluginEngagementReactNative.mm +87 -0
  12. package/lib/module/AmplitudeEngagement.js +42 -0
  13. package/lib/module/AmplitudeEngagement.js.map +1 -0
  14. package/lib/module/AmplitudeEngagementPlugin.js +29 -0
  15. package/lib/module/AmplitudeEngagementPlugin.js.map +1 -0
  16. package/lib/module/NativePluginEngagementReactNative.js +5 -0
  17. package/lib/module/NativePluginEngagementReactNative.js.map +1 -0
  18. package/lib/module/index.js +64 -0
  19. package/lib/module/index.js.map +1 -0
  20. package/lib/module/logger.js +53 -0
  21. package/lib/module/logger.js.map +1 -0
  22. package/lib/module/package.json +1 -0
  23. package/lib/module/types.js +2 -0
  24. package/lib/module/types.js.map +1 -0
  25. package/lib/typescript/package.json +1 -0
  26. package/lib/typescript/src/AmplitudeEngagement.d.ts +17 -0
  27. package/lib/typescript/src/AmplitudeEngagement.d.ts.map +1 -0
  28. package/lib/typescript/src/AmplitudeEngagementPlugin.d.ts +10 -0
  29. package/lib/typescript/src/AmplitudeEngagementPlugin.d.ts.map +1 -0
  30. package/lib/typescript/src/NativePluginEngagementReactNative.d.ts +18 -0
  31. package/lib/typescript/src/NativePluginEngagementReactNative.d.ts.map +1 -0
  32. package/lib/typescript/src/index.d.ts +13 -0
  33. package/lib/typescript/src/index.d.ts.map +1 -0
  34. package/lib/typescript/src/logger.d.ts +28 -0
  35. package/lib/typescript/src/logger.d.ts.map +1 -0
  36. package/lib/typescript/src/types.d.ts +7 -0
  37. package/lib/typescript/src/types.d.ts.map +1 -0
  38. package/package.json +168 -0
  39. package/src/AmplitudeEngagement.ts +62 -0
  40. package/src/AmplitudeEngagementPlugin.ts +47 -0
  41. package/src/NativePluginEngagementReactNative.ts +28 -0
  42. package/src/index.tsx +78 -0
  43. package/src/logger.ts +72 -0
  44. package/src/types.ts +7 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Amplitude, Inc.
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "PluginEngagementReactNative"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://amplitude.com.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
17
+ s.private_header_files = "ios/**/*.h"
18
+ #s.dependency "AmplitudeEngagementSwift"
19
+ s.dependency "AmplitudeEngagementSwift"
20
+
21
+ install_modules_dependencies(s)
22
+ end
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @amplitude/plugin-engagement-react-native
2
+
3
+ Amplitude Engagement plugin for React Native
4
+
5
+ ## Installation
6
+
7
+ To install:
8
+ - Add `@amplitude/analytics-react-native` and `@amplitude/plugin-engagement-react-native`
9
+ - Also, add `@react-native-async-storage/async-storage`
10
+ - Why? This is used by the `engagement` native module, but the CLI only auto links native modules which you directly depend on in your app’s `package.json`
11
+
12
+
13
+ ```sh
14
+ npm install @amplitude/analytics-react-native
15
+ npm install @amplitude/plugin-engagement-react-native
16
+ npm install @react-native-async-storage/async-storage
17
+ ```
18
+
19
+ Add the source for the `AmplitudeEngagementSwift` pod to your Podfile:
20
+ ```rb
21
+ target '<your appname>' do
22
+ ...
23
+
24
+ pod "AmplitudeEngagementSwift", :git => "https://github.com/amplitude/Amplitude-Engagement-Swift"
25
+
26
+ ...
27
+ end
28
+ ```
29
+
30
+ Re-run `pod install` in the `ios` directory:
31
+ ```sh
32
+ $ cd ios
33
+ $ bundle exec pod install
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Setup code
39
+
40
+ Setup by adding the plugin and calling “init” as usual:
41
+
42
+ ```
43
+ // index.js
44
+
45
+ import {Linking} from 'react-native';
46
+
47
+ import { init, add } from '@amplitude/analytics-react-native';
48
+ import { getPlugin, handleURL } from '@amplitude/plugin-engagement-react-native';
49
+
50
+ init('<<< YOUR API KEY HERE >>>');
51
+ add(getPlugin());
52
+
53
+ Linking.getInitialURL().then(async (url) => {
54
+ if (url) {
55
+ const didHandleURL = await handleURL(url);
56
+ if (didHandleURL) { return; }
57
+
58
+ // Handle a non-Amplitude SDK URL
59
+ }
60
+ });
61
+
62
+ Linking.addEventListener('url', async ({ url }) => {
63
+ const didHandleURL = await handleURL(url);
64
+ if (didHandleURL) { return; }
65
+
66
+ // Handle a non-Amplitude SDK URL
67
+ });
68
+ ```
69
+
70
+ ### Linking setup
71
+ If you don't already have it set up, please follow this guide to enable deep-linking support in your React Native app in addition to adding the above Linking code:
72
+ https://reactnative.dev/docs/linking#enabling-deep-links
73
+
74
+ Then, following the Alpha Onboarding guide to setup the "scheme" for the deep links in your Android and iOS projects.
75
+
76
+ ### Boot
77
+
78
+ Finally, “boot” with the user’s ID:
79
+
80
+ ```
81
+ import {
82
+ boot
83
+ } from '@amplitude/plugin-engagement-react-native';
84
+
85
+ export default function App() {
86
+ useEffect(() => {
87
+ boot('rn-test-user-1', 'test-device-1');
88
+ }, []);
89
+
90
+ ```
91
+
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,89 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['PluginEngagementReactNative_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ apply plugin: "com.facebook.react"
23
+
24
+ def getExtOrIntegerDefault(name) {
25
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["PluginEngagementReactNative_" + name]).toInteger()
26
+ }
27
+
28
+ android {
29
+ namespace "com.amplitude.pluginengagementreactnative"
30
+
31
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
32
+
33
+ defaultConfig {
34
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
36
+ }
37
+
38
+ buildFeatures {
39
+ buildConfig true
40
+ }
41
+
42
+ buildTypes {
43
+ release {
44
+ minifyEnabled false
45
+ }
46
+ }
47
+
48
+ lintOptions {
49
+ disable "GradleCompatible"
50
+ }
51
+
52
+ compileOptions {
53
+ sourceCompatibility JavaVersion.VERSION_1_8
54
+ targetCompatibility JavaVersion.VERSION_1_8
55
+ }
56
+
57
+ sourceSets {
58
+ main {
59
+ java.srcDirs += [
60
+ "generated/java",
61
+ "generated/jni"
62
+ ]
63
+ }
64
+ }
65
+ }
66
+
67
+ repositories {
68
+ mavenCentral()
69
+ google()
70
+ }
71
+
72
+ def kotlin_version = getExtOrDefault("kotlinVersion")
73
+
74
+ dependencies {
75
+ implementation "com.facebook.react:react-android"
76
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
+
78
+ // Amplitude Engagement SDK
79
+ implementation "com.amplitude:amplitude-engagement-android:1.0.14"
80
+
81
+ // Amplitude Analytics SDK (required dependency)
82
+ implementation "com.amplitude:analytics-android:1.+"
83
+ }
84
+
85
+ react {
86
+ jsRootDir = file("../src/")
87
+ libraryName = "PluginEngagementReactNative"
88
+ codegenJavaPackageName = "com.amplitude.pluginengagementreactnative"
89
+ }
@@ -0,0 +1,5 @@
1
+ PluginEngagementReactNative_kotlinVersion=2.0.21
2
+ PluginEngagementReactNative_minSdkVersion=24
3
+ PluginEngagementReactNative_targetSdkVersion=34
4
+ PluginEngagementReactNative_compileSdkVersion=35
5
+ PluginEngagementReactNative_ndkVersion=27.1.12297006
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,183 @@
1
+ package com.amplitude.pluginengagementreactnative
2
+
3
+ import android.app.Activity
4
+ import android.app.Application
5
+ import android.content.Intent
6
+ import android.os.Bundle
7
+ import android.util.Log
8
+ import androidx.core.net.toUri
9
+ import com.amplitude.android.engagement.AmplitudeEngagement
10
+ import com.amplitude.android.engagement.__ReactNative__AESDK
11
+ import com.amplitude.android.engagement.AmplitudeInitOptions
12
+ import com.amplitude.android.engagement.ui.theme.ThemeMode
13
+ import com.amplitude.core.events.BaseEvent
14
+ import com.facebook.react.bridge.ActivityEventListener
15
+ import com.facebook.react.bridge.Arguments
16
+ import com.facebook.react.bridge.Callback
17
+ import com.facebook.react.bridge.LifecycleEventListener
18
+ import com.facebook.react.bridge.ReactApplicationContext
19
+ import com.facebook.react.bridge.ReadableMap
20
+ import com.facebook.react.bridge.WritableArray
21
+ import com.facebook.react.module.annotations.ReactModule
22
+ import kotlinx.coroutines.Dispatchers
23
+ import kotlinx.coroutines.runBlocking
24
+ import java.util.concurrent.ConcurrentHashMap
25
+
26
+ @ReactModule(name = PluginEngagementReactNativeModule.NAME)
27
+ class PluginEngagementReactNativeModule(val reactContext: ReactApplicationContext) :
28
+ NativePluginEngagementReactNativeSpec(reactContext) {
29
+
30
+ private data class InstanceInfo(val apiKey: String, val instance: AmplitudeEngagement)
31
+
32
+ private var instances = ConcurrentHashMap<Double, InstanceInfo>()
33
+ private var _id: Double = 0.0
34
+
35
+ override fun getName(): String {
36
+ return NAME
37
+ }
38
+
39
+ override fun newInstance(apiKey: String): Double {
40
+ val existingId = instances.entries.firstOrNull { it.value.apiKey == apiKey }?.key
41
+ if (existingId != null) {
42
+ return existingId
43
+ }
44
+
45
+ return synchronized(this) {
46
+ runBlocking(Dispatchers.Main) {
47
+ val amplitudeEngagement = __ReactNative__AESDK(reactContext, apiKey, AmplitudeInitOptions())
48
+
49
+ // The React Native environment seems to have a different activity management lifecycle;
50
+ // so we need to register our own listener to get the current activity.
51
+ amplitudeEngagement.setCurrentActivity(reactContext.currentActivity)
52
+ val lifecycleEventListener: LifecycleEventListener = object : LifecycleEventListener {
53
+ override fun onHostResume() {
54
+ amplitudeEngagement.setCurrentActivity(reactContext.currentActivity)
55
+ }
56
+
57
+ override fun onHostPause() {
58
+ }
59
+
60
+ override fun onHostDestroy() {
61
+ }
62
+ }
63
+
64
+ reactContext.addLifecycleEventListener(lifecycleEventListener)
65
+
66
+ _id++
67
+ val id = _id
68
+ instances[id] = InstanceInfo(
69
+ apiKey,
70
+ amplitudeEngagement
71
+ )
72
+
73
+ id
74
+ }
75
+ }
76
+ }
77
+
78
+ override fun boot(id: Double, userId: String, deviceId: String) {
79
+ val instance = instances[id]?.instance ?: return
80
+
81
+ runBlocking(Dispatchers.Main) {
82
+ Log.d("PluginEngagementReactNativeModule", "boot: $userId, $deviceId")
83
+ instance.boot(userId, deviceId)
84
+ }
85
+ }
86
+
87
+ override fun setThemeMode(id: Double, themeMode: String?) {
88
+ val instance = instances[id]?.instance ?: return
89
+
90
+ runBlocking(Dispatchers.Main) {
91
+ if (themeMode != null) {
92
+ instance.setThemeMode(ThemeMode.valueOf(themeMode))
93
+ }
94
+ }
95
+ }
96
+
97
+ override fun reset(id: Double, key: String, stepIndex: Double) {
98
+ val instance = instances[id]?.instance ?: return
99
+ runBlocking(Dispatchers.Main) {
100
+ instance.reset(key, stepIndex.toInt())
101
+ }
102
+ }
103
+
104
+ override fun list(id: Double): WritableArray {
105
+ val writableArray = Arguments.createArray()
106
+ val instance = instances[id]?.instance ?: return writableArray
107
+ runBlocking(Dispatchers.Main) {
108
+ val guidesAndSurveysList = instance.list()
109
+ guidesAndSurveysList.forEach { guide ->
110
+ Log.d("PluginEngagementReactNativeModule", "list: $guide")
111
+ val map = Arguments.createMap();
112
+ map.putInt("id", guide.id);
113
+ map.putString("title", guide.title);
114
+ map.putString("status", guide.status);
115
+ map.putInt("step", guide.step);
116
+ writableArray.pushMap(map);
117
+ }
118
+ }
119
+ return writableArray
120
+ }
121
+
122
+ override fun show(id: Double, key: String, stepIndex: Double) {
123
+ val instance = instances[id]?.instance ?: return
124
+ runBlocking(Dispatchers.Main) {
125
+ instance.show(key, stepIndex.toInt())
126
+ }
127
+ }
128
+
129
+ override fun screen(id: Double, screenName: String) {
130
+ val instance = instances[id]?.instance ?: return
131
+ runBlocking(Dispatchers.Main) {
132
+ instance.screen(screenName)
133
+ }
134
+ }
135
+
136
+ override fun closeAll(id: Double) {
137
+ val instance = instances[id]?.instance ?: return
138
+ runBlocking(Dispatchers.Main) {
139
+ instance.closeAll()
140
+ }
141
+ }
142
+
143
+ override fun forwardEvent(id: Double, event: ReadableMap) {
144
+ val instance = instances[id]?.instance ?: return
145
+ val baseEvent = BaseEvent()
146
+ baseEvent.eventType = event.getString("event_type") ?: return
147
+ baseEvent.eventId = event.getDouble("event_id").toLong()
148
+ baseEvent.platform = event.getString("platform")
149
+ baseEvent.osName = event.getString("os_name")
150
+ baseEvent.osVersion = event.getString("os_version")
151
+ baseEvent.eventProperties = event.getMap("event_properties")?.toHashMap()
152
+ baseEvent.groupProperties = event.getMap("group_properties")?.toHashMap()
153
+ baseEvent.groups = event.getMap("groups")?.toHashMap()
154
+ baseEvent.userProperties = event.getMap("user_properties")?.toHashMap()
155
+ Log.d("PluginEngagementReactNativeModule", "forwardEvent: $baseEvent")
156
+ runBlocking(Dispatchers.Main) {
157
+ instance.forwardEvent(baseEvent)
158
+ }
159
+ }
160
+
161
+ override fun addCallback(id: Double, key: String, func: Callback) {
162
+ val instance = instances[id]?.instance ?: return
163
+ runBlocking(Dispatchers.Main) {
164
+ instance.addCallback(key) { func.invoke() }
165
+ }
166
+ }
167
+
168
+ override fun handleURL(id: Double, url: String): Boolean {
169
+ val instance = instances[id]?.instance ?: return false
170
+
171
+ val uri = url.toUri()
172
+ val intent = Intent(Intent.ACTION_VIEW, uri)
173
+
174
+ return runBlocking(Dispatchers.Main) {
175
+ instance.handlePreviewLinkIntent(intent)
176
+ }
177
+ }
178
+
179
+
180
+ companion object {
181
+ const val NAME = "PluginEngagementReactNative"
182
+ }
183
+ }
@@ -0,0 +1,33 @@
1
+ package com.amplitude.pluginengagementreactnative
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class PluginEngagementReactNativePackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == PluginEngagementReactNativeModule.NAME) {
13
+ PluginEngagementReactNativeModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
+ return ReactModuleInfoProvider {
21
+ val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
+ moduleInfos[PluginEngagementReactNativeModule.NAME] = ReactModuleInfo(
23
+ PluginEngagementReactNativeModule.NAME,
24
+ PluginEngagementReactNativeModule.NAME,
25
+ false, // canOverrideExistingModule
26
+ false, // needsEagerInit
27
+ false, // isCxxModule
28
+ true // isTurboModule
29
+ )
30
+ moduleInfos
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,121 @@
1
+ //
2
+ // AmplitudeEngagementAdapter.swift
3
+ // PluginEngagementReactNative
4
+ //
5
+ // Created by Jared Luxenberg on 5/19/25.
6
+ //
7
+
8
+ import Foundation
9
+ import AmplitudeEngagementSwift
10
+
11
+
12
+ extension Encodable {
13
+ /// Converting object to postable dictionary
14
+ func toDictionary(_ encoder: JSONEncoder = JSONEncoder()) throws -> [String: Any] {
15
+ let data = try encoder.encode(self)
16
+ let object = try JSONSerialization.jsonObject(with: data)
17
+ if let json = object as? [String: Any] { return json }
18
+
19
+ let context = DecodingError.Context(codingPath: [], debugDescription: "Deserialized object is not a dictionary")
20
+ throw DecodingError.typeMismatch(type(of: object), context)
21
+ }
22
+ }
23
+
24
+ // id -> (api_key, amplitude engagement instance)
25
+ var instances: [Int: (String, AmplitudeEngagement)] = [:]
26
+ var _id = 0
27
+
28
+ /** `@objc` attribute exposes Swift methods to the Objective-C runtime**/
29
+ @objc public class AmplitudeEngagementAdapter: NSObject {
30
+ @objc public func newInstance(_ apiKey: String) -> Int {
31
+ if let existingId = instances.first(where: { $0.value.0 == apiKey })?.key {
32
+ return existingId
33
+ }
34
+
35
+ return DispatchQueue.main.sync {
36
+ _id += 1;
37
+ let id = _id + 1;
38
+ instances[id] = (apiKey, __ReactNative__AESDK(apiKey, AmplitudeInitOptions(logLevel: AmplitudeLogLevel.verbose)))
39
+ return id
40
+ }
41
+ }
42
+
43
+
44
+ @objc public func boot(_ instanceId: Int, _ user_id: String, device_id: String? = nil) {
45
+ DispatchQueue.main.sync {
46
+ guard let (_, instance) = instances[instanceId] else { return }
47
+
48
+ return instance.boot(user_id, device_id: device_id)
49
+ }
50
+ }
51
+
52
+ @objc public func setThemeMode(_ instanceId: Int, themeMode: String) {
53
+ DispatchQueue.main.sync {
54
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
55
+ guard let themeModeParsed = ThemeMode(rawValue: themeMode) else { return }
56
+
57
+ return instance.setThemeMode(themeMode: themeModeParsed)
58
+ }
59
+ }
60
+
61
+ @objc public func reset(_ instanceId: Int, key: String, stepIndex: Int = 0) {
62
+ DispatchQueue.main.sync {
63
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
64
+
65
+ return instance.reset(key: key, stepIndex: stepIndex)
66
+ }
67
+ }
68
+
69
+ @objc public func list(_ instanceId: Int) -> [[String: Any]] {
70
+ DispatchQueue.main.sync {
71
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
72
+
73
+ return instance.list().compactMap { x in try? x.toDictionary() }
74
+ }
75
+ }
76
+
77
+ @objc public func handleUrl(_ instanceId: Int, _ url: String) -> Bool {
78
+ DispatchQueue.main.sync {
79
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
80
+ guard let url = URL(string: url) else { return false; }
81
+
82
+ return instance.handleUrl(url)
83
+ }
84
+ }
85
+
86
+ @objc public func show(_ instanceId: Int, key: String, stepIndex: Int = 0) {
87
+ DispatchQueue.main.sync {
88
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
89
+ return instance.show(key: key, stepIndex: stepIndex)
90
+ }
91
+ }
92
+
93
+ @objc public func screen(_ instanceId: Int, _ screenName: String) {
94
+ DispatchQueue.main.sync {
95
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
96
+ return instance.screen(screenName)
97
+ }
98
+ }
99
+
100
+ @objc public func closeAll(_ instanceId: Int) {
101
+ DispatchQueue.main.sync {
102
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
103
+ return instance.closeAll()
104
+ }
105
+ }
106
+
107
+ @objc public func forwardEvent(_ instanceId: Int, _ event: [String: Any]) {
108
+ DispatchQueue.main.sync {
109
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
110
+ return instance.forwardEvent(event)
111
+ }
112
+ }
113
+
114
+ @objc(addCallback:::) public func addCallback(_ instanceId: Int, _ key: String, _ function: @escaping () -> Void) {
115
+ DispatchQueue.main.sync {
116
+ guard let (_, instance) = instances[instanceId] else { fatalError("🚨 Runtime exception: no instance found for id “\(instanceId)”") }
117
+ return instance.addCallback(key, function)
118
+ }
119
+ }
120
+
121
+ }
@@ -0,0 +1,5 @@
1
+ #import <PluginEngagementReactNativeSpec/PluginEngagementReactNativeSpec.h>
2
+
3
+ @interface PluginEngagementReactNative : NSObject <NativePluginEngagementReactNativeSpec>
4
+
5
+ @end
@@ -0,0 +1,87 @@
1
+ #import "PluginEngagementReactNative.h"
2
+ #import "PluginEngagementReactNative-Swift.h"
3
+
4
+ //@interface RCT_EXTERN_MODULE(AmplitudeEngagementAdapter, NSObject)
5
+
6
+ @implementation PluginEngagementReactNative {
7
+ AmplitudeEngagementAdapter *adapter;
8
+ }
9
+ //RCT_EXPORT_MODULE()
10
+
11
+
12
+ - (id) init {
13
+ if (self = [super init]) {
14
+ adapter = [AmplitudeEngagementAdapter new];
15
+ }
16
+ return self;
17
+ }
18
+
19
+ // TODO: binding for AmplitudeEngagement here
20
+
21
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
22
+ (const facebook::react::ObjCTurboModule::InitParams &)params
23
+ {
24
+ return std::make_shared<facebook::react::NativePluginEngagementReactNativeSpecJSI>(params);
25
+ }
26
+
27
+ + (NSString *)moduleName {
28
+ return @"PluginEngagementReactNative";
29
+ }
30
+
31
+ - (void)boot:(double)id user_id:(nonnull NSString *)user_id device_id:(nonnull NSString *)device_id {
32
+ [adapter boot:id :user_id device_id:device_id];
33
+ }
34
+
35
+ - (nonnull NSArray<NSDictionary *> *)list:(double)id {
36
+ return [adapter list:id];
37
+ }
38
+
39
+ - (NSNumber *)newInstance:(nonnull NSString *)apiKey {
40
+ return @([adapter newInstance:apiKey]);
41
+ }
42
+
43
+ - (NSNumber*)handleURL:(double)id url:(NSString *)url {
44
+ BOOL success = [adapter handleUrl:id :url];
45
+ return @(success);
46
+ }
47
+
48
+ - (void)addCallback:(double)id key:(nonnull NSString *)key func:(nonnull RCTResponseSenderBlock)func {
49
+ RCTResponseSenderBlock jsCallback = func;
50
+ void (^callback)(void) = ^{
51
+ jsCallback(@[]); // or pass whatever args you need
52
+ };
53
+
54
+ [adapter addCallback:id :key :callback];
55
+ }
56
+
57
+
58
+ - (void)closeAll:(double)id {
59
+ [adapter closeAll:id];
60
+ }
61
+
62
+
63
+ - (void)forwardEvent:(double)id event:(nonnull NSDictionary *)event {
64
+ [adapter forwardEvent:id :event];
65
+ }
66
+
67
+
68
+ - (void)reset:(double)id key:(nonnull NSString *)key stepIndex:(double)stepIndex {
69
+ [adapter reset:id key:key stepIndex:stepIndex];
70
+ }
71
+
72
+
73
+ - (void)screen:(double)id screenName:(nonnull NSString *)screenName {
74
+ [adapter screen:id :screenName];
75
+ }
76
+
77
+
78
+ - (void)setThemeMode:(double)id themeMode:(nonnull NSString *)themeMode {
79
+ [adapter setThemeMode:id themeMode:themeMode];
80
+ }
81
+
82
+
83
+ - (void)show:(double)id key:(nonnull NSString *)key stepIndex:(double)stepIndex {
84
+ [adapter show:id key:key stepIndex:stepIndex];
85
+ }
86
+
87
+ @end