@dashx/react-native 1.1.5 → 1.3.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.
@@ -0,0 +1,384 @@
1
+ package com.dashx.rn.sdk
2
+
3
+ import android.Manifest
4
+ import android.app.Activity
5
+ import android.app.NotificationChannel
6
+ import android.app.NotificationManager
7
+ import android.content.Context
8
+ import android.content.pm.PackageManager
9
+ import android.os.Build
10
+ import androidx.core.app.ActivityCompat
11
+ import androidx.core.content.ContextCompat
12
+ import com.dashx.rn.sdk.util.*
13
+ import com.dashx.android.DashX
14
+ import com.dashx.android.DashXLog
15
+ import com.dashx.android.DashXLog.LogLevel
16
+ import com.dashx.android.data.Asset
17
+ import com.facebook.react.bridge.*
18
+ import com.facebook.react.modules.core.PermissionAwareActivity
19
+ import com.facebook.react.modules.core.PermissionListener
20
+ import kotlinx.serialization.decodeFromString
21
+ import kotlinx.serialization.encodeToString
22
+ import kotlinx.serialization.json.Json
23
+ import kotlinx.serialization.json.JsonObject
24
+
25
+ class DashXReactNativeModuleImpl(private val reactContext: ReactApplicationContext) {
26
+ private val tag = DashXReactNativeModuleImpl::class.java.simpleName
27
+
28
+ fun initialize() {
29
+ MessageReceivedEventHolder.reactContext = reactContext
30
+ }
31
+
32
+ fun invalidate() {
33
+ MessageReceivedEventHolder.reactContext = null
34
+ }
35
+
36
+ fun setLogLevel(level: Double) {
37
+ // JS contract: 0 = off, 1 = errors, 2 = debug
38
+ // Native SDK: OFF = -1, ERROR = 0, INFO = 1, DEBUG = 2
39
+ val logLevel = when (level.toInt()) {
40
+ 0 -> LogLevel.OFF
41
+ 1 -> LogLevel.ERROR
42
+ 2 -> LogLevel.DEBUG
43
+ else -> LogLevel.OFF
44
+ }
45
+ DashXLog.setLogLevel(logLevel)
46
+ }
47
+
48
+ fun configure(options: ReadableMap) {
49
+ createDefaultNotificationChannel()
50
+ DashX.configure(
51
+ reactContext,
52
+ options.getString("publicKey")!!,
53
+ options.getStringIfPresent("baseURI"),
54
+ options.getStringIfPresent("targetEnvironment")
55
+ )
56
+ }
57
+
58
+ private fun createDefaultNotificationChannel() {
59
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
60
+ val notificationManager = reactContext.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return
61
+
62
+ val existing = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID)
63
+ if (existing != null) return
64
+
65
+ val channel = NotificationChannel(
66
+ DEFAULT_CHANNEL_ID,
67
+ DEFAULT_CHANNEL_NAME,
68
+ NotificationManager.IMPORTANCE_HIGH
69
+ ).apply {
70
+ enableVibration(true)
71
+ setShowBadge(true)
72
+ lockscreenVisibility = android.app.Notification.VISIBILITY_PUBLIC
73
+ }
74
+ notificationManager.createNotificationChannel(channel)
75
+ }
76
+
77
+ fun identify(options: ReadableMap?) {
78
+ val optionsHashMap = options?.toHashMap()
79
+ ?.filterValues { it != null }
80
+ ?.mapValues { it.value.toString() }
81
+ DashX.identify(optionsHashMap?.let { HashMap(it) })
82
+ }
83
+
84
+ fun setIdentity(uid: String?, token: String?) {
85
+ DashX.setIdentity(uid, token)
86
+ }
87
+
88
+ fun reset() {
89
+ DashX.reset()
90
+ }
91
+
92
+ fun track(event: String, data: ReadableMap?) {
93
+ val jsonData = try {
94
+ data?.toHashMap()
95
+ ?.filterValues { it != null }
96
+ ?.mapValues { it.value.toString() }
97
+ ?.let { HashMap(it) }
98
+ } catch (e: Exception) {
99
+ DashXLog.d(tag, "Failed to convert track data, sending event without data: ${e.message}")
100
+ null
101
+ }
102
+
103
+ DashX.track(event, jsonData)
104
+ }
105
+
106
+ fun screen(screenName: String, data: ReadableMap?) {
107
+ val screenData = data?.toHashMap()
108
+ ?.filterValues { it != null }
109
+ ?.mapValues { it.value.toString() }
110
+ ?.let { HashMap(it) }
111
+ ?: HashMap()
112
+ DashX.screen(screenName, screenData)
113
+ }
114
+
115
+ fun fetchRecord(urn: String, options: ReadableMap?, promise: Promise) {
116
+ val preview = options?.getBooleanIfPresent("preview")
117
+ val language = options?.getStringIfPresent("language")
118
+ val fields = try {
119
+ convertReadableArrayToKJsonList(options?.getArray("fields"))
120
+ } catch (e: Exception) {
121
+ DashXLog.d(tag, e.message)
122
+ promise.reject(E_UNSPECIFIED, "Error parsing fields: ${e.message}")
123
+ return
124
+ }
125
+ val include = try {
126
+ convertReadableArrayToKJsonList(options?.getArray("include"))
127
+ } catch (e: Exception) {
128
+ DashXLog.d(tag, e.message)
129
+ promise.reject(E_UNSPECIFIED, "Error parsing include: ${e.message}")
130
+ return
131
+ }
132
+ val exclude = try {
133
+ convertReadableArrayToKJsonList(options?.getArray("exclude"))
134
+ } catch (e: Exception) {
135
+ DashXLog.d(tag, e.message)
136
+ promise.reject(E_UNSPECIFIED, "Error parsing exclude: ${e.message}")
137
+ return
138
+ }
139
+
140
+ DashX.fetchRecord(
141
+ urn = urn,
142
+ preview = preview,
143
+ language = language,
144
+ fields = fields,
145
+ include = include,
146
+ exclude = exclude,
147
+ onError = {
148
+ promise.reject(E_UNSPECIFIED, it.message)
149
+ },
150
+ onSuccess = {
151
+ promise.resolve(convertJsonToMap(convertKJsonToJson(it)))
152
+ }
153
+ )
154
+ }
155
+
156
+ fun searchRecords(resource: String, options: ReadableMap?, promise: Promise) {
157
+ val jsonFilter = try {
158
+ convertMapToJson(options?.getMap("filter"))?.let { convertJsonToKJson(it) }
159
+ } catch (e: Exception) {
160
+ DashXLog.d(tag, e.message)
161
+ promise.reject(E_UNSPECIFIED, "Error parsing filter: ${e.message}")
162
+ return
163
+ }
164
+ val order = try {
165
+ convertReadableArrayToKJsonList(options?.getArray("order"))
166
+ } catch (e: Exception) {
167
+ DashXLog.d(tag, e.message)
168
+ promise.reject(E_UNSPECIFIED, "Error parsing order: ${e.message}")
169
+ return
170
+ }
171
+ val limit = options?.getIntIfPresent("limit")
172
+ val preview = options?.getBooleanIfPresent("preview")
173
+ val language = options?.getStringIfPresent("language")
174
+ val fields = try {
175
+ convertReadableArrayToKJsonList(options?.getArray("fields"))
176
+ } catch (e: Exception) {
177
+ DashXLog.d(tag, e.message)
178
+ promise.reject(E_UNSPECIFIED, "Error parsing fields: ${e.message}")
179
+ return
180
+ }
181
+ val include = try {
182
+ convertReadableArrayToKJsonList(options?.getArray("include"))
183
+ } catch (e: Exception) {
184
+ DashXLog.d(tag, e.message)
185
+ promise.reject(E_UNSPECIFIED, "Error parsing include: ${e.message}")
186
+ return
187
+ }
188
+ val exclude = try {
189
+ convertReadableArrayToKJsonList(options?.getArray("exclude"))
190
+ } catch (e: Exception) {
191
+ DashXLog.d(tag, e.message)
192
+ promise.reject(E_UNSPECIFIED, "Error parsing exclude: ${e.message}")
193
+ return
194
+ }
195
+
196
+ DashX.searchRecords(
197
+ resource = resource,
198
+ filter = jsonFilter,
199
+ order = order,
200
+ limit = limit,
201
+ preview = preview,
202
+ language = language,
203
+ fields = fields,
204
+ include = include,
205
+ exclude = exclude,
206
+ onError = {
207
+ promise.reject(E_UNSPECIFIED, it.message)
208
+ },
209
+ onSuccess = { records ->
210
+ val readableArray = Arguments.createArray()
211
+ records.forEach {
212
+ readableArray.pushMap(convertJsonToMap(convertKJsonToJson(it)))
213
+ }
214
+ promise.resolve(readableArray)
215
+ }
216
+ )
217
+ }
218
+
219
+ fun fetchStoredPreferences(promise: Promise) {
220
+ DashX.fetchStoredPreferences(
221
+ onError = {
222
+ promise.reject(E_UNSPECIFIED, it.message)
223
+ },
224
+ onSuccess = { content ->
225
+ promise.resolve(convertJsonToMap(convertKJsonToJson(Json.decodeFromString<JsonObject>(Json.encodeToString(content)))))
226
+ }
227
+ )
228
+ }
229
+
230
+ fun saveStoredPreferences(preferenceData: ReadableMap?, promise: Promise) {
231
+ val preferencesString = MapUtil.toJSONElement(preferenceData).toString()
232
+
233
+ DashX.saveStoredPreferences(
234
+ Json.decodeFromString<JsonObject>(preferencesString),
235
+ onError = {
236
+ promise.reject(E_UNSPECIFIED, it.message)
237
+ },
238
+ onSuccess = { content ->
239
+ promise.resolve(convertJsonToMap(convertKJsonToJson(Json.decodeFromString<JsonObject>(Json.encodeToString(content)))))
240
+ }
241
+ )
242
+ }
243
+
244
+ fun subscribe() {
245
+ DashX.subscribe()
246
+ }
247
+
248
+ fun unsubscribe() {
249
+ DashX.unsubscribe()
250
+ }
251
+
252
+ // --- iOS-only methods: no-op stubs on Android (JS guards prevent these from being called) ---
253
+
254
+ fun enableLifecycleTracking() {
255
+ // iOS-only; no-op on Android
256
+ }
257
+
258
+ fun enableAdTracking() {
259
+ // iOS-only; no-op on Android
260
+ }
261
+
262
+ fun processURL(url: String, source: String?, forwardToLinkHandler: Boolean) {
263
+ // iOS-only; no-op on Android
264
+ }
265
+
266
+ fun trackNotificationNavigation(action: ReadableMap?, notificationId: String?) {
267
+ // iOS-only; no-op on Android
268
+ }
269
+
270
+ fun uploadAsset(filePath: String, resource: String, attribute: String, promise: Promise) {
271
+ val file = java.io.File(filePath)
272
+ DashX.uploadAsset(
273
+ file = file,
274
+ resource = resource,
275
+ attribute = attribute,
276
+ onSuccess = { asset ->
277
+ try {
278
+ val jsonString = Json.encodeToString(asset)
279
+ val jsonObject = org.json.JSONObject(jsonString)
280
+ promise.resolve(convertJsonToMap(jsonObject))
281
+ } catch (e: Exception) {
282
+ promise.resolve(null)
283
+ }
284
+ },
285
+ onError = {
286
+ promise.reject(E_UNSPECIFIED, it.message)
287
+ }
288
+ )
289
+ }
290
+
291
+ fun fetchAsset(assetId: String, promise: Promise) {
292
+ DashX.fetchAsset(
293
+ assetId = assetId,
294
+ onSuccess = { asset ->
295
+ try {
296
+ val jsonString = Json.encodeToString(asset)
297
+ val jsonObject = org.json.JSONObject(jsonString)
298
+ promise.resolve(convertJsonToMap(jsonObject))
299
+ } catch (e: Exception) {
300
+ promise.resolve(null)
301
+ }
302
+ },
303
+ onError = {
304
+ promise.reject(E_UNSPECIFIED, it.message)
305
+ }
306
+ )
307
+ }
308
+
309
+ fun requestNotificationPermission(promise: Promise) {
310
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
311
+ promise.resolve(PERMISSION_AUTHORIZED)
312
+ return
313
+ }
314
+
315
+ val alreadyGranted = ContextCompat.checkSelfPermission(
316
+ reactContext,
317
+ Manifest.permission.POST_NOTIFICATIONS
318
+ ) == PackageManager.PERMISSION_GRANTED
319
+ if (alreadyGranted) {
320
+ promise.resolve(PERMISSION_AUTHORIZED)
321
+ return
322
+ }
323
+
324
+ val activity = reactContext.currentActivity as? PermissionAwareActivity
325
+ if (activity == null) {
326
+ promise.reject(E_UNSPECIFIED, "No current activity to request permission from")
327
+ return
328
+ }
329
+
330
+ reactContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
331
+ .edit()
332
+ .putBoolean(KEY_PERMISSION_REQUESTED, true)
333
+ .apply()
334
+
335
+ val listener = PermissionListener { requestCode, _, grantResults ->
336
+ if (requestCode != NOTIFICATION_PERMISSION_REQUEST_CODE) return@PermissionListener false
337
+ val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
338
+ promise.resolve(if (granted) PERMISSION_AUTHORIZED else PERMISSION_DENIED)
339
+ true
340
+ }
341
+
342
+ activity.requestPermissions(
343
+ arrayOf(Manifest.permission.POST_NOTIFICATIONS),
344
+ NOTIFICATION_PERMISSION_REQUEST_CODE,
345
+ listener
346
+ )
347
+ }
348
+
349
+ fun getNotificationPermissionStatus(promise: Promise) {
350
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
351
+ promise.resolve(PERMISSION_AUTHORIZED)
352
+ return
353
+ }
354
+ val granted = ContextCompat.checkSelfPermission(
355
+ reactContext,
356
+ Manifest.permission.POST_NOTIFICATIONS
357
+ ) == PackageManager.PERMISSION_GRANTED
358
+ if (granted) {
359
+ promise.resolve(PERMISSION_AUTHORIZED)
360
+ } else {
361
+ val wasRequested = reactContext
362
+ .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
363
+ .getBoolean(KEY_PERMISSION_REQUESTED, false)
364
+ promise.resolve(if (wasRequested) PERMISSION_DENIED else PERMISSION_NOT_DETERMINED)
365
+ }
366
+ }
367
+
368
+ companion object {
369
+ const val NAME = "DashXReactNative"
370
+ private const val E_UNSPECIFIED = "EUNSPECIFIED"
371
+ private const val DEFAULT_CHANNEL_ID = "default_dashx_notification_channel"
372
+ private const val DEFAULT_CHANNEL_NAME = "Notifications"
373
+
374
+ private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 3571
375
+
376
+ private const val PREFS_NAME = "dashx_rn_prefs"
377
+ private const val KEY_PERMISSION_REQUESTED = "notification_permission_requested"
378
+
379
+ // Mirrors iOS UNAuthorizationStatus values used by the JS layer.
380
+ private const val PERMISSION_NOT_DETERMINED = 0
381
+ private const val PERMISSION_DENIED = 1
382
+ private const val PERMISSION_AUTHORIZED = 2
383
+ }
384
+ }
@@ -0,0 +1,85 @@
1
+ package com.dashx.rn.sdk
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.bridge.ReadableMap
6
+
7
+ class DashXReactNativeModule(reactContext: ReactApplicationContext) :
8
+ NativeDashXReactNativeSpec(reactContext) {
9
+
10
+ private val impl = DashXReactNativeModuleImpl(reactContext)
11
+
12
+ override fun getName(): String = DashXReactNativeModuleImpl.NAME
13
+
14
+ override fun initialize() {
15
+ super.initialize()
16
+ impl.initialize()
17
+ }
18
+
19
+ override fun invalidate() {
20
+ impl.invalidate()
21
+ super.invalidate()
22
+ }
23
+
24
+ override fun configure(options: ReadableMap) = impl.configure(options)
25
+
26
+ override fun identify(options: ReadableMap?) = impl.identify(options)
27
+
28
+ override fun setIdentity(uid: String?, token: String?) = impl.setIdentity(uid, token)
29
+
30
+ override fun reset() = impl.reset()
31
+
32
+ override fun track(event: String, data: ReadableMap?) = impl.track(event, data)
33
+
34
+ override fun screen(screenName: String, data: ReadableMap?) = impl.screen(screenName, data)
35
+
36
+ override fun fetchRecord(urn: String, options: ReadableMap?, promise: Promise) =
37
+ impl.fetchRecord(urn, options, promise)
38
+
39
+ override fun searchRecords(resource: String, options: ReadableMap?, promise: Promise) =
40
+ impl.searchRecords(resource, options, promise)
41
+
42
+ override fun fetchStoredPreferences(promise: Promise) = impl.fetchStoredPreferences(promise)
43
+
44
+ override fun saveStoredPreferences(preferenceData: ReadableMap?, promise: Promise) =
45
+ impl.saveStoredPreferences(preferenceData, promise)
46
+
47
+ override fun subscribe() = impl.subscribe()
48
+
49
+ override fun unsubscribe() = impl.unsubscribe()
50
+
51
+ override fun setLogLevel(level: Double) = impl.setLogLevel(level)
52
+
53
+ override fun enableLifecycleTracking() = impl.enableLifecycleTracking()
54
+
55
+ override fun enableAdTracking() = impl.enableAdTracking()
56
+
57
+ override fun processURL(url: String, source: String?, forwardToLinkHandler: Boolean) =
58
+ impl.processURL(url, source, forwardToLinkHandler)
59
+
60
+ override fun trackNotificationNavigation(action: ReadableMap?, notificationId: String?) =
61
+ impl.trackNotificationNavigation(action, notificationId)
62
+
63
+ override fun uploadAsset(
64
+ filePath: String,
65
+ resource: String,
66
+ attribute: String,
67
+ promise: Promise
68
+ ) = impl.uploadAsset(filePath, resource, attribute, promise)
69
+
70
+ override fun fetchAsset(assetId: String, promise: Promise) = impl.fetchAsset(assetId, promise)
71
+
72
+ override fun requestNotificationPermission(promise: Promise) =
73
+ impl.requestNotificationPermission(promise)
74
+
75
+ override fun getNotificationPermissionStatus(promise: Promise) =
76
+ impl.getNotificationPermissionStatus(promise)
77
+
78
+ override fun addListener(eventName: String) {
79
+ // Required for RN built-in Event Emitter Calls
80
+ }
81
+
82
+ override fun removeListeners(count: Double) {
83
+ // Required for RN built-in Event Emitter Calls
84
+ }
85
+ }
@@ -0,0 +1,33 @@
1
+ package com.dashx.rn.sdk
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
+
9
+ class DashXReactNativePackage : BaseReactPackage() {
10
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
11
+ return if (name == DashXReactNativeModuleImpl.NAME) {
12
+ DashXReactNativeModule(reactContext)
13
+ } else {
14
+ null
15
+ }
16
+ }
17
+
18
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
19
+ return ReactModuleInfoProvider {
20
+ mapOf(
21
+ DashXReactNativeModuleImpl.NAME to ReactModuleInfo(
22
+ DashXReactNativeModuleImpl.NAME,
23
+ DashXReactNativeModuleImpl.NAME,
24
+ false, // canOverrideExistingModule
25
+ false, // needsEagerInit
26
+ false, // hasConstants
27
+ false, // isCxxModule
28
+ true // isTurboModule
29
+ )
30
+ )
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,106 @@
1
+ package com.dashx.rn.sdk
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
6
+ import com.facebook.react.bridge.ReactMethod
7
+ import com.facebook.react.bridge.ReadableMap
8
+
9
+ class DashXReactNativeModule(reactContext: ReactApplicationContext) :
10
+ ReactContextBaseJavaModule(reactContext) {
11
+
12
+ private val impl = DashXReactNativeModuleImpl(reactContext)
13
+
14
+ override fun getName(): String = DashXReactNativeModuleImpl.NAME
15
+
16
+ override fun initialize() {
17
+ super.initialize()
18
+ impl.initialize()
19
+ }
20
+
21
+ override fun invalidate() {
22
+ impl.invalidate()
23
+ super.invalidate()
24
+ }
25
+
26
+ @ReactMethod
27
+ fun configure(options: ReadableMap) = impl.configure(options)
28
+
29
+ @ReactMethod
30
+ fun identify(options: ReadableMap?) = impl.identify(options)
31
+
32
+ @ReactMethod
33
+ fun setIdentity(uid: String?, token: String?) = impl.setIdentity(uid, token)
34
+
35
+ @ReactMethod
36
+ fun reset() = impl.reset()
37
+
38
+ @ReactMethod
39
+ fun track(event: String, data: ReadableMap?) = impl.track(event, data)
40
+
41
+ @ReactMethod
42
+ fun screen(screenName: String, data: ReadableMap?) = impl.screen(screenName, data)
43
+
44
+ @ReactMethod
45
+ fun fetchRecord(urn: String, options: ReadableMap?, promise: Promise) =
46
+ impl.fetchRecord(urn, options, promise)
47
+
48
+ @ReactMethod
49
+ fun searchRecords(resource: String, options: ReadableMap?, promise: Promise) =
50
+ impl.searchRecords(resource, options, promise)
51
+
52
+ @ReactMethod
53
+ fun fetchStoredPreferences(promise: Promise) = impl.fetchStoredPreferences(promise)
54
+
55
+ @ReactMethod
56
+ fun saveStoredPreferences(preferenceData: ReadableMap?, promise: Promise) =
57
+ impl.saveStoredPreferences(preferenceData, promise)
58
+
59
+ @ReactMethod
60
+ fun subscribe() = impl.subscribe()
61
+
62
+ @ReactMethod
63
+ fun unsubscribe() = impl.unsubscribe()
64
+
65
+ @ReactMethod
66
+ fun setLogLevel(level: Double) = impl.setLogLevel(level)
67
+
68
+ @ReactMethod
69
+ fun enableLifecycleTracking() = impl.enableLifecycleTracking()
70
+
71
+ @ReactMethod
72
+ fun enableAdTracking() = impl.enableAdTracking()
73
+
74
+ @ReactMethod
75
+ fun processURL(url: String, source: String?, forwardToLinkHandler: Boolean) =
76
+ impl.processURL(url, source, forwardToLinkHandler)
77
+
78
+ @ReactMethod
79
+ fun trackNotificationNavigation(action: ReadableMap?, notificationId: String?) =
80
+ impl.trackNotificationNavigation(action, notificationId)
81
+
82
+ @ReactMethod
83
+ fun uploadAsset(filePath: String, resource: String, attribute: String, promise: Promise) =
84
+ impl.uploadAsset(filePath, resource, attribute, promise)
85
+
86
+ @ReactMethod
87
+ fun fetchAsset(assetId: String, promise: Promise) = impl.fetchAsset(assetId, promise)
88
+
89
+ @ReactMethod
90
+ fun requestNotificationPermission(promise: Promise) =
91
+ impl.requestNotificationPermission(promise)
92
+
93
+ @ReactMethod
94
+ fun getNotificationPermissionStatus(promise: Promise) =
95
+ impl.getNotificationPermissionStatus(promise)
96
+
97
+ @ReactMethod
98
+ fun addListener(eventName: String) {
99
+ // Required for RN built-in Event Emitter Calls
100
+ }
101
+
102
+ @ReactMethod
103
+ fun removeListeners(count: Double) {
104
+ // Required for RN built-in Event Emitter Calls
105
+ }
106
+ }
@@ -0,0 +1,33 @@
1
+ package com.dashx.rn.sdk
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
+
9
+ class DashXReactNativePackage : BaseReactPackage() {
10
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
11
+ return if (name == DashXReactNativeModuleImpl.NAME) {
12
+ DashXReactNativeModule(reactContext)
13
+ } else {
14
+ null
15
+ }
16
+ }
17
+
18
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
19
+ return ReactModuleInfoProvider {
20
+ mapOf(
21
+ DashXReactNativeModuleImpl.NAME to ReactModuleInfo(
22
+ DashXReactNativeModuleImpl.NAME,
23
+ DashXReactNativeModuleImpl.NAME,
24
+ false, // canOverrideExistingModule
25
+ false, // needsEagerInit
26
+ false, // hasConstants
27
+ false, // isCxxModule
28
+ false // isTurboModule
29
+ )
30
+ )
31
+ }
32
+ }
33
+ }
package/index.d.ts CHANGED
@@ -7,6 +7,8 @@ import { EmitterSubscription } from 'react-native';
7
7
  export enum DashXErrorCode {
8
8
  /** An unspecified error occurred. */
9
9
  EUNSPECIFIED = 'EUNSPECIFIED',
10
+ /** The operation is not supported on this platform. */
11
+ EUNSUPPORTED = 'EUNSUPPORTED',
10
12
  }
11
13
 
12
14
  export interface ConfigureOptions {
@@ -72,6 +74,12 @@ export type NavigationAction =
72
74
  | { type: 'richLanding'; url: string }
73
75
  | { type: 'clickAction'; action: string };
74
76
 
77
+ export interface NotificationClickedEvent {
78
+ notification: NotificationMessage;
79
+ action: NavigationAction | null;
80
+ actionIdentifier: string;
81
+ }
82
+
75
83
  export interface ProcessURLOptions {
76
84
  source?: string;
77
85
  forwardToLinkHandler?: boolean;
@@ -90,9 +98,11 @@ export interface DashXInstance {
90
98
  identify(options: IdentifyOptions): void;
91
99
 
92
100
  /**
93
- * Set user identity using a UID and authentication token.
101
+ * Set user identity using a UID and authentication token. Both are
102
+ * nullable — passing `null` clears that field on the native SDK. Empty
103
+ * strings are forwarded as-is (no coercion to `null`).
94
104
  */
95
- setIdentity(uid: string, token: string): void;
105
+ setIdentity(uid: string | null, token?: string | null): void;
96
106
 
97
107
  /**
98
108
  * Clear the current user identity and reset SDK state.
@@ -222,6 +232,18 @@ export interface DashXInstance {
222
232
  * Returns a subscription that can be removed via `.remove()`.
223
233
  */
224
234
  onLinkReceived(callback: (url: string) => void): EmitterSubscription;
235
+
236
+ /**
237
+ * Register a listener for notification tap events. Fired when the user taps
238
+ * a DashX notification or one of its action buttons. The callback receives
239
+ * the full notification payload, the resolved `NavigationAction` (parsed
240
+ * from the payload; `null` if no navigation intent could be resolved), and
241
+ * the raw `actionIdentifier` from `UNNotificationResponse`.
242
+ * Returns a subscription that can be removed via `.remove()`.
243
+ */
244
+ onNotificationClicked(
245
+ callback: (event: NotificationClickedEvent) => void
246
+ ): EmitterSubscription;
225
247
  }
226
248
 
227
249
  declare const DashX: DashXInstance;