@dashx/react-native 1.5.0 → 1.5.1

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 (85) hide show
  1. package/README.md +1 -1
  2. package/ios/DashXReactNative.mm +7 -7
  3. package/ios/DashXReactNative.swift +15 -11
  4. package/package.json +13 -11
  5. package/.claude/settings.local.json +0 -7
  6. package/.claude/worktrees/autocapture-conventions-review-78f665/.gitattributes +0 -1
  7. package/.claude/worktrees/autocapture-conventions-review-78f665/.github/workflows/ci.yml +0 -67
  8. package/.claude/worktrees/autocapture-conventions-review-78f665/.gitignore +0 -49
  9. package/.claude/worktrees/autocapture-conventions-review-78f665/.nvmrc +0 -1
  10. package/.claude/worktrees/autocapture-conventions-review-78f665/CHANGELOG.md +0 -144
  11. package/.claude/worktrees/autocapture-conventions-review-78f665/CONTRIBUTING.md +0 -190
  12. package/.claude/worktrees/autocapture-conventions-review-78f665/DashXReactNative.podspec +0 -38
  13. package/.claude/worktrees/autocapture-conventions-review-78f665/LICENSE +0 -21
  14. package/.claude/worktrees/autocapture-conventions-review-78f665/README.md +0 -97
  15. package/.claude/worktrees/autocapture-conventions-review-78f665/__tests__/fallback.test.js +0 -72
  16. package/.claude/worktrees/autocapture-conventions-review-78f665/__tests__/index.test.js +0 -702
  17. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gitignore +0 -10
  18. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
  19. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
  20. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
  21. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/gc.properties +0 -0
  22. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  23. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  24. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/vcs-1/gc.properties +0 -0
  25. package/.claude/worktrees/autocapture-conventions-review-78f665/android/build/reports/problems/problems-report.html +0 -663
  26. package/.claude/worktrees/autocapture-conventions-review-78f665/android/build.gradle +0 -121
  27. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  28. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  29. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle.properties +0 -1
  30. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradlew +0 -185
  31. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradlew.bat +0 -89
  32. package/.claude/worktrees/autocapture-conventions-review-78f665/android/settings.gradle +0 -27
  33. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/AndroidManifest.xml +0 -19
  34. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXNotificationClickedListener.kt +0 -113
  35. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeFirebaseMessagingService.kt +0 -72
  36. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeModuleImpl.kt +0 -445
  37. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/MessageReceivedEventHolder.kt +0 -14
  38. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/ReactNativeLibraryInfoProvider.kt +0 -8
  39. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/util/Utils.kt +0 -208
  40. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/resources/META-INF/services/com.dashx.android.DashXLibraryInfoProvider +0 -1
  41. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -85
  42. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -33
  43. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -106
  44. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -33
  45. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/test/java/com/dashx/rn/sdk/util/UtilsTest.kt +0 -305
  46. package/.claude/worktrees/autocapture-conventions-review-78f665/babel.config.js +0 -3
  47. package/.claude/worktrees/autocapture-conventions-review-78f665/index.d.ts +0 -321
  48. package/.claude/worktrees/autocapture-conventions-review-78f665/index.js +0 -300
  49. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXEventEmitter.swift +0 -15
  50. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXNotificationHandler.swift +0 -301
  51. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative-Bridging-Header.h +0 -3
  52. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.mm +0 -77
  53. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.swift +0 -244
  54. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcodeproj/project.pbxproj +0 -280
  55. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcworkspace/contents.xcworkspacedata +0 -7
  56. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  57. package/.claude/worktrees/autocapture-conventions-review-78f665/package.json +0 -123
  58. package/.claude/worktrees/autocapture-conventions-review-78f665/scripts/generate-bridge-shim.js +0 -259
  59. package/.claude/worktrees/autocapture-conventions-review-78f665/src/NativeDashXReactNative.ts +0 -42
  60. package/.claude/worktrees/autocapture-conventions-review-78f665/yarn.lock +0 -6244
  61. package/.gitattributes +0 -1
  62. package/.github/workflows/ci.yml +0 -67
  63. package/.gitignore +0 -49
  64. package/.nvmrc +0 -1
  65. package/CHANGELOG.md +0 -144
  66. package/CONTRIBUTING.md +0 -190
  67. package/__tests__/fallback.test.js +0 -72
  68. package/__tests__/index.test.js +0 -702
  69. package/android/.gitignore +0 -10
  70. package/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
  71. package/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
  72. package/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
  73. package/android/.gradle/8.14.3/gc.properties +0 -0
  74. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  75. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  76. package/android/.gradle/vcs-1/gc.properties +0 -0
  77. package/android/build/reports/problems/problems-report.html +0 -663
  78. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  79. package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  80. package/android/gradlew +0 -185
  81. package/android/gradlew.bat +0 -89
  82. package/android/src/test/java/com/dashx/rn/sdk/util/UtilsTest.kt +0 -305
  83. package/babel.config.js +0 -3
  84. package/scripts/generate-bridge-shim.js +0 -259
  85. package/yarn.lock +0 -6244
@@ -1,445 +0,0 @@
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.Intent
9
- import android.content.pm.PackageManager
10
- import android.os.Build
11
- import android.provider.Settings
12
- import androidx.core.app.ActivityCompat
13
- import androidx.core.content.ContextCompat
14
- import com.dashx.rn.sdk.util.*
15
- import com.dashx.android.DashX
16
- import com.dashx.android.DashXActivityLifecycleCallbacks
17
- import com.dashx.android.DashXLog
18
- import com.dashx.android.DashXLog.LogLevel
19
- import com.dashx.android.data.Asset
20
- import com.facebook.react.bridge.*
21
- import com.facebook.react.modules.core.PermissionAwareActivity
22
- import com.facebook.react.modules.core.PermissionListener
23
- import kotlinx.serialization.json.Json
24
- import kotlinx.serialization.json.JsonObject
25
- import kotlinx.serialization.json.buildJsonObject
26
- import kotlinx.serialization.json.encodeToJsonElement
27
- import kotlinx.serialization.json.jsonObject
28
-
29
- class DashXReactNativeModuleImpl(private val reactContext: ReactApplicationContext) {
30
- private val tag = DashXReactNativeModuleImpl::class.java.simpleName
31
-
32
- // Bridges SDK notification-click callbacks to the JS
33
- // `DashX.onNotificationClicked` listener. Registered in `initialize`,
34
- // removed in `invalidate` so we don't leak across RN reloads.
35
- private val notificationClickedListener = DashXNotificationClickedListener(reactContext)
36
-
37
- fun initialize() {
38
- MessageReceivedEventHolder.reactContext = reactContext
39
- DashX.registerNotificationListener(notificationClickedListener)
40
- }
41
-
42
- fun invalidate() {
43
- MessageReceivedEventHolder.reactContext = null
44
- DashX.unregisterNotificationListener(notificationClickedListener)
45
- }
46
-
47
- fun setLogLevel(level: Double) {
48
- // JS contract: 0 = off, 1 = errors, 2 = debug
49
- // Native SDK: OFF = -1, ERROR = 0, INFO = 1, DEBUG = 2
50
- val logLevel = when (level.toInt()) {
51
- 0 -> LogLevel.OFF
52
- 1 -> LogLevel.ERROR
53
- 2 -> LogLevel.DEBUG
54
- else -> LogLevel.OFF
55
- }
56
- DashXLog.setLogLevel(logLevel)
57
- }
58
-
59
- fun configure(options: ReadableMap) {
60
- createDefaultNotificationChannel()
61
- DashX.configure(
62
- reactContext,
63
- options.getString("publicKey")!!,
64
- options.getStringIfPresent("baseURI"),
65
- options.getStringIfPresent("targetEnvironment")
66
- )
67
- }
68
-
69
- private fun createDefaultNotificationChannel() {
70
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
71
- val notificationManager = reactContext.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return
72
-
73
- val existing = notificationManager.getNotificationChannel(DEFAULT_CHANNEL_ID)
74
- if (existing != null) return
75
-
76
- val channel = NotificationChannel(
77
- DEFAULT_CHANNEL_ID,
78
- DEFAULT_CHANNEL_NAME,
79
- NotificationManager.IMPORTANCE_HIGH
80
- ).apply {
81
- enableVibration(true)
82
- setShowBadge(true)
83
- lockscreenVisibility = android.app.Notification.VISIBILITY_PUBLIC
84
- }
85
- notificationManager.createNotificationChannel(channel)
86
- }
87
-
88
- fun identify(options: ReadableMap?) {
89
- val optionsHashMap = options?.toHashMap()
90
- ?.filterValues { it != null }
91
- ?.mapValues { it.value.toString() }
92
- DashX.identify(optionsHashMap?.let { HashMap(it) })
93
- }
94
-
95
- fun setIdentity(uid: String?, token: String?) {
96
- DashX.setIdentity(uid, token)
97
- }
98
-
99
- fun reset() {
100
- DashX.reset()
101
- }
102
-
103
- fun track(event: String, data: ReadableMap?) {
104
- val jsonData = try {
105
- data?.toHashMap()
106
- ?.filterValues { it != null }
107
- ?.mapValues { it.value.toString() }
108
- ?.let { HashMap(it) }
109
- } catch (e: Exception) {
110
- DashXLog.d(tag, "Failed to convert track data, sending event without data: ${e.message}")
111
- null
112
- }
113
-
114
- DashX.track(event, jsonData)
115
- }
116
-
117
- fun screen(screenName: String, data: ReadableMap?) {
118
- val screenData = data?.toHashMap()
119
- ?.filterValues { it != null }
120
- ?.mapValues { it.value.toString() }
121
- ?.let { HashMap(it) }
122
- ?: HashMap()
123
- DashX.screen(screenName, screenData)
124
- }
125
-
126
- fun fetchRecord(urn: String, options: ReadableMap?, promise: Promise) {
127
- val preview = options?.getBooleanIfPresent("preview")
128
- val language = options?.getStringIfPresent("language")
129
- val fields = try {
130
- convertReadableArrayToKJsonList(options?.getArray("fields"))
131
- } catch (e: Exception) {
132
- DashXLog.d(tag, e.message)
133
- promise.reject(E_UNSPECIFIED, "Error parsing fields: ${e.message}")
134
- return
135
- }
136
- val include = try {
137
- convertReadableArrayToKJsonList(options?.getArray("include"))
138
- } catch (e: Exception) {
139
- DashXLog.d(tag, e.message)
140
- promise.reject(E_UNSPECIFIED, "Error parsing include: ${e.message}")
141
- return
142
- }
143
- val exclude = try {
144
- convertReadableArrayToKJsonList(options?.getArray("exclude"))
145
- } catch (e: Exception) {
146
- DashXLog.d(tag, e.message)
147
- promise.reject(E_UNSPECIFIED, "Error parsing exclude: ${e.message}")
148
- return
149
- }
150
-
151
- DashX.fetchRecord(
152
- urn = urn,
153
- preview = preview,
154
- language = language,
155
- fields = fields,
156
- include = include,
157
- exclude = exclude,
158
- onError = {
159
- promise.reject(E_UNSPECIFIED, it.message)
160
- },
161
- onSuccess = {
162
- promise.resolve(convertJsonToMap(it))
163
- }
164
- )
165
- }
166
-
167
- fun searchRecords(resource: String, options: ReadableMap?, promise: Promise) {
168
- val jsonFilter = try {
169
- convertMapToJson(options?.getMap("filter"))
170
- } catch (e: Exception) {
171
- DashXLog.d(tag, e.message)
172
- promise.reject(E_UNSPECIFIED, "Error parsing filter: ${e.message}")
173
- return
174
- }
175
- val order = try {
176
- convertReadableArrayToKJsonList(options?.getArray("order"))
177
- } catch (e: Exception) {
178
- DashXLog.d(tag, e.message)
179
- promise.reject(E_UNSPECIFIED, "Error parsing order: ${e.message}")
180
- return
181
- }
182
- val limit = options?.getIntIfPresent("limit")
183
- val preview = options?.getBooleanIfPresent("preview")
184
- val language = options?.getStringIfPresent("language")
185
- val fields = try {
186
- convertReadableArrayToKJsonList(options?.getArray("fields"))
187
- } catch (e: Exception) {
188
- DashXLog.d(tag, e.message)
189
- promise.reject(E_UNSPECIFIED, "Error parsing fields: ${e.message}")
190
- return
191
- }
192
- val include = try {
193
- convertReadableArrayToKJsonList(options?.getArray("include"))
194
- } catch (e: Exception) {
195
- DashXLog.d(tag, e.message)
196
- promise.reject(E_UNSPECIFIED, "Error parsing include: ${e.message}")
197
- return
198
- }
199
- val exclude = try {
200
- convertReadableArrayToKJsonList(options?.getArray("exclude"))
201
- } catch (e: Exception) {
202
- DashXLog.d(tag, e.message)
203
- promise.reject(E_UNSPECIFIED, "Error parsing exclude: ${e.message}")
204
- return
205
- }
206
-
207
- DashX.searchRecords(
208
- resource = resource,
209
- filter = jsonFilter,
210
- order = order,
211
- limit = limit,
212
- preview = preview,
213
- language = language,
214
- fields = fields,
215
- include = include,
216
- exclude = exclude,
217
- onError = {
218
- promise.reject(E_UNSPECIFIED, it.message)
219
- },
220
- onSuccess = { records ->
221
- val readableArray = Arguments.createArray()
222
- records.forEach {
223
- readableArray.pushMap(convertJsonToMap(it))
224
- }
225
- promise.resolve(readableArray)
226
- }
227
- )
228
- }
229
-
230
- fun fetchStoredPreferences(promise: Promise) {
231
- DashX.fetchStoredPreferences(
232
- onError = {
233
- promise.reject(E_UNSPECIFIED, it.message)
234
- },
235
- // The SDK hands back a typed wrapper whose `preferenceData`
236
- // field is the raw kotlinx `JsonObject`. Unwrap and flatten so
237
- // the JS side receives the prefs directly — matches how the iOS
238
- // bridge resolves `DashXClient.fetchStoredPreferences`.
239
- onSuccess = { content ->
240
- promise.resolve(convertJsonToMap(content.preferenceData))
241
- }
242
- )
243
- }
244
-
245
- fun saveStoredPreferences(preferenceData: ReadableMap?, promise: Promise) {
246
- // A null/empty payload maps to an empty JSON object — matches the
247
- // SDK's "I received preferences but they're empty" contract and
248
- // replaces a pre-migration NPE path that crashed when the caller
249
- // passed `null`.
250
- val preferencesJson = convertMapToJson(preferenceData) ?: buildJsonObject { }
251
-
252
- DashX.saveStoredPreferences(
253
- preferencesJson,
254
- onError = {
255
- promise.reject(E_UNSPECIFIED, it.message)
256
- },
257
- // Mutation response is just `{ success: Boolean }` — resolve the
258
- // Promise with that Boolean, matching what the iOS bridge does.
259
- onSuccess = { content ->
260
- promise.resolve(content.success)
261
- }
262
- )
263
- }
264
-
265
- fun subscribe() {
266
- DashX.subscribe()
267
- }
268
-
269
- fun unsubscribe(promise: Promise) {
270
- DashX.unsubscribe(
271
- onSuccess = { didUnsubscribe ->
272
- val result = Arguments.createMap().apply {
273
- putBoolean("success", didUnsubscribe)
274
- }
275
- promise.resolve(result)
276
- },
277
- onError = { promise.reject(E_UNSPECIFIED, it.message) }
278
- )
279
- }
280
-
281
- fun enableLifecycleTracking() {
282
- // The SDK's tracking flags are unsynchronized statics read on the main thread.
283
- val application = reactContext.applicationContext
284
- UiThreadUtil.runOnUiThread {
285
- DashXActivityLifecycleCallbacks.enableActivityLifecycleTracking(application)
286
- }
287
- }
288
-
289
- // --- iOS-only methods: no-op stubs on Android (JS guards prevent these from being called) ---
290
-
291
- fun enableAdTracking() {
292
- // iOS-only; no-op on Android
293
- }
294
-
295
- fun processURL(url: String, source: String?, forwardToLinkHandler: Boolean) {
296
- // iOS-only; no-op on Android
297
- }
298
-
299
- fun trackNotificationNavigation(action: ReadableMap?, notificationId: String?) {
300
- // iOS-only; no-op on Android
301
- }
302
-
303
- fun uploadAsset(filePath: String, resource: String, attribute: String, promise: Promise) {
304
- val file = java.io.File(filePath)
305
- DashX.uploadAsset(
306
- file = file,
307
- resource = resource,
308
- attribute = attribute,
309
- onSuccess = { asset ->
310
- try {
311
- // `Asset` is `@Serializable` — go straight to a
312
- // `JsonElement` tree without the string + parser hop.
313
- promise.resolve(convertJsonToMap(Json.encodeToJsonElement(asset).jsonObject))
314
- } catch (e: Exception) {
315
- promise.resolve(null)
316
- }
317
- },
318
- onError = {
319
- promise.reject(E_UNSPECIFIED, it.message)
320
- }
321
- )
322
- }
323
-
324
- fun fetchAsset(assetId: String, promise: Promise) {
325
- DashX.fetchAsset(
326
- assetId = assetId,
327
- onSuccess = { asset ->
328
- try {
329
- promise.resolve(convertJsonToMap(Json.encodeToJsonElement(asset).jsonObject))
330
- } catch (e: Exception) {
331
- promise.resolve(null)
332
- }
333
- },
334
- onError = {
335
- promise.reject(E_UNSPECIFIED, it.message)
336
- }
337
- )
338
- }
339
-
340
- fun requestNotificationPermission(fallbackToSettings: Boolean, promise: Promise) {
341
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
342
- promise.resolve(PERMISSION_AUTHORIZED)
343
- return
344
- }
345
-
346
- val alreadyGranted = ContextCompat.checkSelfPermission(
347
- reactContext,
348
- Manifest.permission.POST_NOTIFICATIONS
349
- ) == PackageManager.PERMISSION_GRANTED
350
- if (alreadyGranted) {
351
- promise.resolve(PERMISSION_AUTHORIZED)
352
- return
353
- }
354
-
355
- val activity = reactContext.currentActivity
356
- if (activity == null) {
357
- promise.reject(E_UNSPECIFIED, "No current activity to request permission from")
358
- return
359
- }
360
-
361
- // "Permanently denied" detection on Android 13+: we asked at least
362
- // once before AND the system says we shouldn't show a rationale.
363
- // In that state `requestPermissions()` auto-resolves DENIED without
364
- // any UI, so re-prompting is futile — Settings is the only path.
365
- val prefs = reactContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
366
- val previouslyRequested = prefs.getBoolean(KEY_PERMISSION_REQUESTED, false)
367
- val permanentlyDenied = previouslyRequested && !ActivityCompat.shouldShowRequestPermissionRationale(
368
- activity,
369
- Manifest.permission.POST_NOTIFICATIONS
370
- )
371
-
372
- if (fallbackToSettings && permanentlyDenied) {
373
- openAppNotificationSettings()
374
- promise.resolve(PERMISSION_DENIED)
375
- return
376
- }
377
-
378
- val permAwareActivity = activity as? PermissionAwareActivity
379
- if (permAwareActivity == null) {
380
- promise.reject(E_UNSPECIFIED, "Activity does not support permission requests")
381
- return
382
- }
383
-
384
- prefs.edit()
385
- .putBoolean(KEY_PERMISSION_REQUESTED, true)
386
- .apply()
387
-
388
- val listener = PermissionListener { requestCode, _, grantResults ->
389
- if (requestCode != NOTIFICATION_PERMISSION_REQUEST_CODE) return@PermissionListener false
390
- val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
391
- promise.resolve(if (granted) PERMISSION_AUTHORIZED else PERMISSION_DENIED)
392
- true
393
- }
394
-
395
- permAwareActivity.requestPermissions(
396
- arrayOf(Manifest.permission.POST_NOTIFICATIONS),
397
- NOTIFICATION_PERMISSION_REQUEST_CODE,
398
- listener
399
- )
400
- }
401
-
402
- private fun openAppNotificationSettings() {
403
- val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
404
- putExtra(Settings.EXTRA_APP_PACKAGE, reactContext.packageName)
405
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
406
- }
407
- reactContext.startActivity(intent)
408
- }
409
-
410
- fun getNotificationPermissionStatus(promise: Promise) {
411
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
412
- promise.resolve(PERMISSION_AUTHORIZED)
413
- return
414
- }
415
- val granted = ContextCompat.checkSelfPermission(
416
- reactContext,
417
- Manifest.permission.POST_NOTIFICATIONS
418
- ) == PackageManager.PERMISSION_GRANTED
419
- if (granted) {
420
- promise.resolve(PERMISSION_AUTHORIZED)
421
- } else {
422
- val wasRequested = reactContext
423
- .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
424
- .getBoolean(KEY_PERMISSION_REQUESTED, false)
425
- promise.resolve(if (wasRequested) PERMISSION_DENIED else PERMISSION_NOT_DETERMINED)
426
- }
427
- }
428
-
429
- companion object {
430
- const val NAME = "DashXReactNative"
431
- private const val E_UNSPECIFIED = "EUNSPECIFIED"
432
- private const val DEFAULT_CHANNEL_ID = "default_dashx_notification_channel"
433
- private const val DEFAULT_CHANNEL_NAME = "Notifications"
434
-
435
- private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 3571
436
-
437
- private const val PREFS_NAME = "dashx_rn_prefs"
438
- private const val KEY_PERMISSION_REQUESTED = "notification_permission_requested"
439
-
440
- // Mirrors iOS UNAuthorizationStatus values used by the JS layer.
441
- private const val PERMISSION_NOT_DETERMINED = 0
442
- private const val PERMISSION_DENIED = 1
443
- private const val PERMISSION_AUTHORIZED = 2
444
- }
445
- }
@@ -1,14 +0,0 @@
1
- package com.dashx.rn.sdk
2
-
3
- import com.facebook.react.bridge.ReactApplicationContext
4
-
5
- /**
6
- * Holds a reference to the React context so that [DashXReactNativeFirebaseMessagingService]
7
- * can emit "messageReceived" events to JS when FCM messages arrive.
8
- * Set by [DashXReactNativeModule], cleared when the module is invalidated.
9
- */
10
- object MessageReceivedEventHolder {
11
- @Volatile
12
- var reactContext: ReactApplicationContext? = null
13
- internal set
14
- }
@@ -1,8 +0,0 @@
1
- package com.dashx.rn.sdk
2
-
3
- import com.dashx.android.DashXLibraryInfoProvider
4
-
5
- class ReactNativeLibraryInfoProvider : DashXLibraryInfoProvider {
6
- override val name = "@dashx/react-native"
7
- override val version: String = BuildConfig.VERSION_NAME
8
- }
@@ -1,208 +0,0 @@
1
- @file:JvmName("Utils")
2
-
3
- package com.dashx.rn.sdk.util
4
-
5
- import com.facebook.react.bridge.*
6
- import kotlinx.serialization.json.JsonArray
7
- import kotlinx.serialization.json.JsonNull
8
- import kotlinx.serialization.json.JsonObject
9
- import kotlinx.serialization.json.JsonPrimitive
10
- import kotlinx.serialization.json.add
11
- import kotlinx.serialization.json.booleanOrNull
12
- import kotlinx.serialization.json.buildJsonArray
13
- import kotlinx.serialization.json.buildJsonObject
14
- import kotlinx.serialization.json.doubleOrNull
15
- import kotlinx.serialization.json.put
16
-
17
- // JSON conversion helpers between React Native's ReadableMap/ReadableArray/
18
- // WritableMap/WritableArray types and kotlinx.serialization's JsonObject/
19
- // JsonArray. Kept kotlinx-only on purpose — the DashX Android SDK uses
20
- // kotlinx-serialization end-to-end (see `mapScalar("JSON", "kotlinx...JsonObject")`
21
- // in dashx-android/dashx/build.gradle), so threading a single JSON library
22
- // through the bridge avoids string round-trips and the type-confusion class
23
- // of bugs that arose when Gson/org.json/kotlinx all coexisted.
24
-
25
- /**
26
- * Factory seam for Writable instances. Production defaults to the JNI-backed
27
- * `WritableNativeMap` / `WritableNativeArray`. JVM unit tests swap these to
28
- * `JavaOnlyMap` / `JavaOnlyArray` (pure-Java, no JNI) — the native writables
29
- * crash in a plain JUnit harness because their constructors call into
30
- * `initHybrid()`. `internal` visibility keeps the seam invisible to SDK
31
- * consumers.
32
- */
33
- internal object WritableFactory {
34
- var createMap: () -> WritableMap = { WritableNativeMap() }
35
- var createArray: () -> WritableArray = { WritableNativeArray() }
36
- }
37
-
38
- /**
39
- * Convert a [ReadableMap] (RN → native boundary) into a kotlinx [JsonObject].
40
- * Returns `null` when [readableMap] is null, so callers can distinguish
41
- * "absent" from "empty".
42
- *
43
- * Numbers are coerced to [Double] on purpose — RN has a single JS `Number`
44
- * type and the DashX backend's JSON scalars are agnostic to int/double.
45
- */
46
- fun convertMapToJson(readableMap: ReadableMap?): JsonObject? {
47
- val iterator = readableMap?.keySetIterator() ?: return null
48
- return buildJsonObject {
49
- while (iterator.hasNextKey()) {
50
- val key = iterator.nextKey()
51
- when (readableMap.getType(key)) {
52
- ReadableType.Null -> put(key, JsonNull)
53
- ReadableType.Boolean -> put(key, readableMap.getBoolean(key))
54
- ReadableType.Number -> put(key, readableMap.getDouble(key))
55
- ReadableType.String -> put(key, readableMap.getString(key))
56
- ReadableType.Map -> put(key, convertMapToJson(readableMap.getMap(key)) ?: JsonNull)
57
- ReadableType.Array -> put(key, convertArrayToJson(readableMap.getArray(key)) ?: JsonNull)
58
- }
59
- }
60
- }
61
- }
62
-
63
- /**
64
- * Convert a [ReadableArray] into a kotlinx [JsonArray]. Mirrors
65
- * [convertMapToJson] — null in, null out.
66
- */
67
- fun convertArrayToJson(readableArray: ReadableArray?): JsonArray? {
68
- if (readableArray == null) return null
69
- return buildJsonArray {
70
- for (i in 0 until readableArray.size()) {
71
- when (readableArray.getType(i)) {
72
- ReadableType.Null -> add(JsonNull)
73
- ReadableType.Boolean -> add(readableArray.getBoolean(i))
74
- ReadableType.Number -> add(readableArray.getDouble(i))
75
- ReadableType.String -> add(readableArray.getString(i))
76
- ReadableType.Map -> add(convertMapToJson(readableArray.getMap(i)) ?: JsonNull)
77
- ReadableType.Array -> add(convertArrayToJson(readableArray.getArray(i)) ?: JsonNull)
78
- }
79
- }
80
- }
81
- }
82
-
83
- /**
84
- * Convert a [ReadableArray] of maps into a list of [JsonObject]s. Used for
85
- * the `fields` / `include` / `exclude` / `order` option arrays on
86
- * `fetchRecord` / `searchRecords`. Non-map entries are silently dropped to
87
- * preserve the behaviour of the pre-migration helper.
88
- */
89
- fun convertReadableArrayToKJsonList(readableArray: ReadableArray?): List<JsonObject>? {
90
- if (readableArray == null || readableArray.size() == 0) return null
91
- return (0 until readableArray.size()).mapNotNull { i ->
92
- if (readableArray.getType(i) == ReadableType.Map) {
93
- readableArray.getMap(i)?.let { convertMapToJson(it) }
94
- } else {
95
- null
96
- }
97
- }.ifEmpty { null }
98
- }
99
-
100
- /**
101
- * Convert a kotlinx [JsonObject] (native → RN boundary) into a [WritableMap].
102
- * Returns `null` when [jsonObject] is null.
103
- *
104
- * Numeric primitives are coerced to [Double] (`putDouble`) to match JS's
105
- * single `Number` type. JSON nulls map to `putNull(key)`. Non-finite /
106
- * non-numeric primitives fall back to string form.
107
- */
108
- fun convertJsonToMap(jsonObject: JsonObject?): WritableMap? {
109
- jsonObject ?: return null
110
- val map: WritableMap = WritableFactory.createMap()
111
- for ((key, element) in jsonObject) {
112
- when (element) {
113
- is JsonNull -> map.putNull(key)
114
- is JsonPrimitive -> putPrimitive(map, key, element)
115
- is JsonObject -> map.putMap(key, convertJsonToMap(element))
116
- is JsonArray -> map.putArray(key, convertJsonToArray(element))
117
- }
118
- }
119
- return map
120
- }
121
-
122
- /**
123
- * Convert a kotlinx [JsonArray] into a [WritableArray]. Mirrors
124
- * [convertJsonToMap]. Null-safe.
125
- */
126
- fun convertJsonToArray(jsonArray: JsonArray?): WritableArray? {
127
- jsonArray ?: return null
128
- val array: WritableArray = WritableFactory.createArray()
129
- for (element in jsonArray) {
130
- when (element) {
131
- is JsonNull -> array.pushNull()
132
- is JsonPrimitive -> pushPrimitive(array, element)
133
- is JsonObject -> array.pushMap(convertJsonToMap(element))
134
- is JsonArray -> array.pushArray(convertJsonToArray(element))
135
- }
136
- }
137
- return array
138
- }
139
-
140
- // Shared primitive dispatch — keeps the two public functions free of repeated
141
- // isString / booleanOrNull / doubleOrNull ladders. [primitive] is guaranteed
142
- // not to be [JsonNull] by the caller (JsonNull is a subtype of JsonPrimitive).
143
- private fun putPrimitive(map: WritableMap, key: String, primitive: JsonPrimitive) {
144
- when {
145
- primitive.isString -> map.putString(key, primitive.content)
146
- primitive.booleanOrNull != null -> map.putBoolean(key, primitive.booleanOrNull!!)
147
- primitive.doubleOrNull != null -> map.putDouble(key, primitive.doubleOrNull!!)
148
- else -> map.putString(key, primitive.content)
149
- }
150
- }
151
-
152
- private fun pushPrimitive(array: WritableArray, primitive: JsonPrimitive) {
153
- when {
154
- primitive.isString -> array.pushString(primitive.content)
155
- primitive.booleanOrNull != null -> array.pushBoolean(primitive.booleanOrNull!!)
156
- primitive.doubleOrNull != null -> array.pushDouble(primitive.doubleOrNull!!)
157
- else -> array.pushString(primitive.content)
158
- }
159
- }
160
-
161
- /**
162
- * Generic `Map<*, *>` → `WritableMap` converter. Preserved from the original
163
- * [Utils] surface — no JSON libraries involved, but used by callers that
164
- * need to hand arbitrary Kotlin maps to the RN bridge.
165
- */
166
- @JvmOverloads
167
- @Throws(Exception::class)
168
- fun convertToWritableMap(
169
- map: Map<*, *>,
170
- blacklist: List<String> = emptyList<String>()
171
- ): WritableMap {
172
- val writableMap: WritableMap = WritableFactory.createMap()
173
- val iterator: Iterator<String> = map.keys.iterator() as Iterator<String>
174
- while (iterator.hasNext()) {
175
- val key = iterator.next()
176
-
177
- if (blacklist.contains(key)) {
178
- continue
179
- }
180
-
181
- when (val value = map[key]) {
182
- is Boolean -> writableMap.putBoolean(key, value)
183
- is Int -> writableMap.putInt(key, value)
184
- is Double -> writableMap.putDouble(key, value)
185
- is String -> writableMap.putString(key, value)
186
- else -> writableMap.putString(key, value.toString())
187
- }
188
- }
189
- return writableMap
190
- }
191
-
192
- // --- ReadableMap convenience accessors (unchanged from the previous Utils). ---
193
-
194
- fun ReadableMap.getMapIfPresent(key: String): ReadableMap? {
195
- return if (this.hasKey(key)) this.getMap(key) else null
196
- }
197
-
198
- fun ReadableMap.getIntIfPresent(key: String): Int? {
199
- return if (this.hasKey(key)) this.getInt(key) else null
200
- }
201
-
202
- fun ReadableMap.getStringIfPresent(key: String): String? {
203
- return if (this.hasKey(key)) this.getString(key) else null
204
- }
205
-
206
- fun ReadableMap.getBooleanIfPresent(key: String): Boolean? {
207
- return if (this.hasKey(key)) this.getBoolean(key) else null
208
- }