@bubblsdk/react-native-sdk 3.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.
@@ -0,0 +1,643 @@
1
+ package tech.bubbl.reactnative
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
7
+ import com.facebook.react.bridge.ReactMethod
8
+ import com.facebook.react.bridge.ReadableArray
9
+ import com.facebook.react.bridge.ReadableMap
10
+ import com.facebook.react.bridge.WritableArray
11
+ import com.facebook.react.bridge.WritableMap
12
+ import com.facebook.react.modules.core.DeviceEventManagerModule
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.Job
16
+ import kotlinx.coroutines.SupervisorJob
17
+ import kotlinx.coroutines.cancel
18
+ import kotlinx.coroutines.flow.collectLatest
19
+ import kotlinx.coroutines.launch
20
+ import kotlinx.coroutines.withContext
21
+ import tech.bubbl.sdk.BubblBootResult
22
+ import tech.bubbl.sdk.BubblConfig
23
+ import tech.bubbl.sdk.BubblConfiguration
24
+ import tech.bubbl.sdk.BubblDiagnostics
25
+ import tech.bubbl.sdk.BubblEnvironment
26
+ import tech.bubbl.sdk.BubblEvent
27
+ import tech.bubbl.sdk.BubblFlushResult
28
+ import tech.bubbl.sdk.BubblGeofenceCircle
29
+ import tech.bubbl.sdk.BubblGeofencePolygon
30
+ import tech.bubbl.sdk.BubblGeofenceSnapshot
31
+ import tech.bubbl.sdk.BubblGeofenceTransition
32
+ import tech.bubbl.sdk.BubblGeofenceTransitionType
33
+ import tech.bubbl.sdk.BubblLocation
34
+ import tech.bubbl.sdk.BubblLogLevel
35
+ import tech.bubbl.sdk.BubblNotificationCta
36
+ import tech.bubbl.sdk.BubblNotificationDisplayResult
37
+ import tech.bubbl.sdk.BubblNotificationMedia
38
+ import tech.bubbl.sdk.BubblNotificationPayload
39
+ import tech.bubbl.sdk.BubblNotificationRenderingMode
40
+ import tech.bubbl.sdk.BubblNotificationSource
41
+ import tech.bubbl.sdk.BubblNotificationSurvey
42
+ import tech.bubbl.sdk.BubblNotificationTap
43
+ import tech.bubbl.sdk.BubblSdk
44
+ import tech.bubbl.sdk.BubblSurveyAnswer
45
+ import tech.bubbl.sdk.BubblSurveyChoice
46
+ import tech.bubbl.sdk.BubblSurveyQuestion
47
+ import tech.bubbl.sdk.BubblSurveyResponse
48
+ import tech.bubbl.sdk.BubblTrackEvent
49
+
50
+ class BubblSdkModule(
51
+ private val reactContext: ReactApplicationContext
52
+ ) : ReactContextBaseJavaModule(reactContext) {
53
+ private var scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
54
+ private var eventJob: Job? = null
55
+ private var listenerCount = 0
56
+
57
+ init {
58
+ BubblSdk.install(reactContext)
59
+ }
60
+
61
+ override fun getName(): String = NAME
62
+
63
+ override fun invalidate() {
64
+ eventJob?.cancel()
65
+ scope.cancel()
66
+ scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
67
+ super.invalidate()
68
+ }
69
+
70
+ @ReactMethod
71
+ fun addListener(eventName: String) {
72
+ listenerCount += 1
73
+ if (eventName == EVENT_NAME && eventJob == null) {
74
+ eventJob = scope.launch {
75
+ BubblSdk.events.collectLatest { event ->
76
+ emitEvent(event.toWritableMap())
77
+ }
78
+ }
79
+ BubblSdk.drainPendingNotificationTaps().forEach { tap ->
80
+ emitEvent(tap.toWritableMap())
81
+ }
82
+ }
83
+ }
84
+
85
+ @ReactMethod
86
+ fun removeListeners(count: Int) {
87
+ listenerCount = (listenerCount - count).coerceAtLeast(0)
88
+ if (listenerCount == 0) {
89
+ eventJob?.cancel()
90
+ eventJob = null
91
+ }
92
+ }
93
+
94
+ @ReactMethod
95
+ fun boot(config: ReadableMap, promise: Promise) = resolve(promise) {
96
+ BubblSdk.boot(config.toConfig()).toWritableMap()
97
+ }
98
+
99
+ @ReactMethod
100
+ fun shutdown(promise: Promise) = resolve(promise) {
101
+ BubblSdk.shutdown()
102
+ null
103
+ }
104
+
105
+ @ReactMethod
106
+ fun startLocationTracking(promise: Promise) = resolve(promise) {
107
+ BubblSdk.startLocationTracking(reactContext)
108
+ null
109
+ }
110
+
111
+ @ReactMethod
112
+ fun stopLocationTracking(promise: Promise) = resolve(promise) {
113
+ BubblSdk.stopLocationTracking(reactContext)
114
+ null
115
+ }
116
+
117
+ @ReactMethod
118
+ fun refresh(promise: Promise) = resolve(promise) {
119
+ BubblSdk.refresh()
120
+ null
121
+ }
122
+
123
+ @ReactMethod
124
+ fun refreshGeofence(latitude: Double, longitude: Double, promise: Promise) = resolve(promise) {
125
+ BubblSdk.refreshGeofence(BubblLocation(latitude, longitude))
126
+ null
127
+ }
128
+
129
+ @ReactMethod
130
+ fun handleLocationUpdate(latitude: Double, longitude: Double, promise: Promise) = resolve(promise) {
131
+ BubblSdk.handleLocationUpdate(BubblLocation(latitude, longitude))
132
+ null
133
+ }
134
+
135
+ @ReactMethod
136
+ fun refreshPush(promise: Promise) = resolve(promise) {
137
+ BubblSdk.refreshPush()
138
+ null
139
+ }
140
+
141
+ @ReactMethod
142
+ fun getConfiguration(promise: Promise) = resolve(promise) {
143
+ BubblSdk.getConfiguration()?.toWritableMap()
144
+ }
145
+
146
+ @ReactMethod
147
+ fun getPrivacyText(promise: Promise) = resolve(promise) {
148
+ BubblSdk.getPrivacyText()
149
+ }
150
+
151
+ @ReactMethod
152
+ fun updateSegments(tags: ReadableArray, promise: Promise) = resolve(promise) {
153
+ BubblSdk.updateSegments(tags.stringList())
154
+ null
155
+ }
156
+
157
+ @ReactMethod
158
+ fun setCorrelationId(value: String, promise: Promise) = resolve(promise) {
159
+ BubblSdk.setCorrelationId(value)
160
+ null
161
+ }
162
+
163
+ @ReactMethod
164
+ fun clearCorrelationId(promise: Promise) = resolve(promise) {
165
+ BubblSdk.clearCorrelationId()
166
+ null
167
+ }
168
+
169
+ @ReactMethod
170
+ fun setDefaultNotificationModalEnabled(enabled: Boolean, promise: Promise) = resolve(promise) {
171
+ BubblSdk.setDefaultNotificationModalEnabled(enabled)
172
+ null
173
+ }
174
+
175
+ @ReactMethod
176
+ fun registerPushToken(token: String, promise: Promise) = resolve(promise) {
177
+ BubblSdk.registerPushToken(token)
178
+ null
179
+ }
180
+
181
+ @ReactMethod
182
+ fun handleFirebasePayload(payload: ReadableMap, promise: Promise) = resolve(promise) {
183
+ BubblSdk.handleFirebasePayload(payload.stringMap())?.toWritableMap()
184
+ }
185
+
186
+ @ReactMethod
187
+ fun showNotification(payload: ReadableMap, promise: Promise) = resolve(promise) {
188
+ BubblSdk.showNotification(payload.toNotificationPayload()).toWritableMap()
189
+ }
190
+
191
+ @ReactMethod
192
+ fun handleNotificationPayload(payload: ReadableMap, promise: Promise) = resolve(promise) {
193
+ BubblSdk.handleNotificationPayload(payload.toNotificationPayload()).toWritableMap()
194
+ }
195
+
196
+ @ReactMethod
197
+ fun handleNotificationOpen(payload: ReadableMap, action: String?, promise: Promise) = resolve(promise) {
198
+ BubblSdk.handleNotificationOpen(payload.toNotificationPayload(), action)
199
+ null
200
+ }
201
+
202
+ @ReactMethod
203
+ fun openNotificationModal(payload: ReadableMap, action: String?, promise: Promise) = resolve(promise) {
204
+ BubblSdk.openNotificationModal(reactContext, payload.toNotificationPayload(), action)
205
+ }
206
+
207
+ @ReactMethod
208
+ fun handleNotificationCta(payload: ReadableMap, action: String?, promise: Promise) = resolve(promise) {
209
+ BubblSdk.handleNotificationCta(payload.toNotificationPayload(), action)
210
+ null
211
+ }
212
+
213
+ @ReactMethod
214
+ fun handleNotificationMediaViewed(payload: ReadableMap, promise: Promise) = resolve(promise) {
215
+ BubblSdk.handleNotificationMediaViewed(payload.toNotificationPayload())
216
+ null
217
+ }
218
+
219
+ @ReactMethod
220
+ fun handleNotificationSurveyRequested(payload: ReadableMap, promise: Promise) = resolve(promise) {
221
+ BubblSdk.handleNotificationSurveyRequested(payload.toNotificationPayload())
222
+ null
223
+ }
224
+
225
+ @ReactMethod
226
+ fun track(event: ReadableMap, promise: Promise) = resolve(promise) {
227
+ BubblSdk.track(event.toTrackEvent())
228
+ null
229
+ }
230
+
231
+ @ReactMethod
232
+ fun submitSurveyResponse(response: ReadableMap, promise: Promise) = resolve(promise) {
233
+ BubblSdk.submitSurveyResponse(response.toSurveyResponse())
234
+ null
235
+ }
236
+
237
+ @ReactMethod
238
+ fun flush(promise: Promise) = resolve(promise) {
239
+ BubblSdk.flush().toWritableMap()
240
+ }
241
+
242
+ @ReactMethod
243
+ fun diagnostics(promise: Promise) = resolve(promise) {
244
+ BubblSdk.diagnostics().copy(platform = "react-native").toWritableMap()
245
+ }
246
+
247
+ private fun resolve(promise: Promise, block: suspend () -> Any?) {
248
+ scope.launch {
249
+ try {
250
+ val value = withContext(Dispatchers.IO) { block() }
251
+ promise.resolve(value)
252
+ } catch (error: IllegalArgumentException) {
253
+ promise.reject("invalid_argument", error.message, error)
254
+ } catch (error: Throwable) {
255
+ promise.reject("bubbl_error", error.message ?: error.toString(), error)
256
+ }
257
+ }
258
+ }
259
+
260
+ private fun emitEvent(body: WritableMap) {
261
+ if (!reactContext.hasActiveReactInstance()) return
262
+
263
+ reactContext
264
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
265
+ .emit(EVENT_NAME, body)
266
+ }
267
+
268
+ private fun ReadableMap.toConfig(): BubblConfig =
269
+ BubblConfig(
270
+ apiKey = requiredString("apiKey"),
271
+ environment = enumValue(string("environment"), BubblEnvironment.Staging) {
272
+ BubblEnvironment.valueOf(it.replaceFirstChar(Char::uppercaseChar))
273
+ },
274
+ runtimeBaseUrl = string("runtimeBaseUrl"),
275
+ transmissionBaseUrl = string("transmissionBaseUrl"),
276
+ ingestBaseUrl = string("ingestBaseUrl"),
277
+ segments = stringList("segments"),
278
+ correlationId = string("correlationId"),
279
+ defaultDistanceMeters = int("defaultDistanceMeters", 10),
280
+ refreshIntervalSeconds = int("refreshIntervalSeconds", 300),
281
+ enablePushHandling = bool("enablePushHandling", true),
282
+ enableLocationTracking = bool("enableLocationTracking", false),
283
+ notificationRenderingMode = enumValue(string("notificationRenderingMode"), BubblNotificationRenderingMode.SdkDefault) {
284
+ when (it) {
285
+ "hostRendered" -> BubblNotificationRenderingMode.HostRendered
286
+ "eventOnly" -> BubblNotificationRenderingMode.EventOnly
287
+ else -> BubblNotificationRenderingMode.SdkDefault
288
+ }
289
+ },
290
+ enableDefaultNotificationModal = bool("enableDefaultNotificationModal", true),
291
+ enableDefaultSurveyUi = bool("enableDefaultSurveyUi", true),
292
+ logLevel = enumValue(string("logLevel"), BubblLogLevel.Warn) {
293
+ BubblLogLevel.valueOf(it.replaceFirstChar(Char::uppercaseChar))
294
+ }
295
+ )
296
+
297
+ private fun ReadableMap.toTrackEvent(): BubblTrackEvent =
298
+ BubblTrackEvent(
299
+ type = requiredString("type"),
300
+ activity = requiredString("activity"),
301
+ locationId = string("locationId"),
302
+ curatedNotificationId = string("curatedNotificationId"),
303
+ latitude = nullableDouble("latitude"),
304
+ longitude = nullableDouble("longitude")
305
+ )
306
+
307
+ private fun ReadableMap.toSurveyResponse(): BubblSurveyResponse =
308
+ BubblSurveyResponse(
309
+ curatedNotificationId = requiredString("curatedNotificationId"),
310
+ locationId = string("locationId"),
311
+ answers = mapList("answers").map { it.toSurveyAnswer() }
312
+ )
313
+
314
+ private fun ReadableMap.toSurveyAnswer(): BubblSurveyAnswer =
315
+ BubblSurveyAnswer(
316
+ questionId = requiredString("questionId"),
317
+ type = requiredString("type"),
318
+ value = string("value"),
319
+ choiceIds = stringList("choiceIds")
320
+ )
321
+
322
+ private fun ReadableMap.toNotificationPayload(): BubblNotificationPayload =
323
+ BubblNotificationPayload(
324
+ id = requiredString("id"),
325
+ title = requiredString("title"),
326
+ body = requiredString("body"),
327
+ source = enumValue(string("source"), BubblNotificationSource.Manual) {
328
+ when (it) {
329
+ "firebase" -> BubblNotificationSource.Firebase
330
+ "apns" -> BubblNotificationSource.Apns
331
+ "runtime" -> BubblNotificationSource.Runtime
332
+ "geofence" -> BubblNotificationSource.Geofence
333
+ else -> BubblNotificationSource.Manual
334
+ }
335
+ },
336
+ locationId = string("locationId"),
337
+ curatedNotificationId = string("curatedNotificationId"),
338
+ correlationId = string("correlationId"),
339
+ media = map("media")?.toNotificationMedia(),
340
+ cta = map("cta")?.toNotificationCta(),
341
+ survey = map("survey")?.toNotificationSurvey(),
342
+ raw = map("raw")?.stringMap().orEmpty()
343
+ )
344
+
345
+ private fun ReadableMap.toNotificationMedia(): BubblNotificationMedia =
346
+ BubblNotificationMedia(url = requiredString("url"), type = string("type"), altText = string("altText"))
347
+
348
+ private fun ReadableMap.toNotificationCta(): BubblNotificationCta =
349
+ BubblNotificationCta(label = requiredString("label"), url = string("url"), action = string("action"))
350
+
351
+ private fun ReadableMap.toNotificationSurvey(): BubblNotificationSurvey =
352
+ BubblNotificationSurvey(questions = mapList("questions").map { it.toSurveyQuestion() })
353
+
354
+ private fun ReadableMap.toSurveyQuestion(): BubblSurveyQuestion =
355
+ BubblSurveyQuestion(
356
+ id = requiredString("id"),
357
+ title = requiredString("title"),
358
+ type = requiredString("type"),
359
+ choices = mapList("choices").map { it.toSurveyChoice() }
360
+ )
361
+
362
+ private fun ReadableMap.toSurveyChoice(): BubblSurveyChoice =
363
+ BubblSurveyChoice(id = requiredString("id"), label = requiredString("label"))
364
+
365
+ private fun BubblBootResult.toWritableMap(): WritableMap = Arguments.createMap().apply {
366
+ putBoolean("ready", ready)
367
+ putBoolean("fromCache", fromCache)
368
+ putBoolean("deviceRegistered", deviceRegistered)
369
+ putArray("requiresPermission", requiresPermission.toWritableStringArray())
370
+ putArray("warnings", warnings.toWritableStringArray())
371
+ }
372
+
373
+ private fun BubblConfiguration.toWritableMap(): WritableMap = Arguments.createMap().apply {
374
+ putInt("notificationsCount", notificationsCount)
375
+ putInt("daysCount", daysCount)
376
+ putInt("batteryCount", batteryCount)
377
+ putString("privacyText", privacyText)
378
+ }
379
+
380
+ private fun BubblDiagnostics.toWritableMap(): WritableMap = Arguments.createMap().apply {
381
+ putString("sdkVersion", sdkVersion)
382
+ putString("platform", platform)
383
+ putBoolean("booted", booted)
384
+ putInt("pendingIngestCount", pendingIngestCount)
385
+ putNullableString("pushTokenSuffix", pushTokenSuffix)
386
+ }
387
+
388
+ private fun BubblFlushResult.toWritableMap(): WritableMap = Arguments.createMap().apply {
389
+ putInt("pendingCount", pendingCount)
390
+ }
391
+
392
+ private fun BubblNotificationDisplayResult.toWritableMap(): WritableMap = Arguments.createMap().apply {
393
+ putBoolean("displayed", displayed)
394
+ putNullableString("reason", reason)
395
+ }
396
+
397
+ private fun BubblLocation.toWritableMap(): WritableMap = Arguments.createMap().apply {
398
+ putDouble("latitude", latitude)
399
+ putDouble("longitude", longitude)
400
+ }
401
+
402
+ private fun BubblGeofenceTransition.toWritableMap(): WritableMap = Arguments.createMap().apply {
403
+ putString(
404
+ "type",
405
+ when (type) {
406
+ BubblGeofenceTransitionType.Enter -> "enter"
407
+ BubblGeofenceTransitionType.Exit -> "exit"
408
+ }
409
+ )
410
+ putNullableString("campaignId", campaignId)
411
+ putNullableString("locationId", locationId)
412
+ putMap("location", location.toWritableMap())
413
+ }
414
+
415
+ private fun BubblNotificationPayload.toWritableMap(): WritableMap = Arguments.createMap().apply {
416
+ putString("id", id)
417
+ putString("title", title)
418
+ putString("body", body)
419
+ putString(
420
+ "source",
421
+ when (source) {
422
+ BubblNotificationSource.Firebase -> "firebase"
423
+ BubblNotificationSource.Apns -> "apns"
424
+ BubblNotificationSource.Runtime -> "runtime"
425
+ BubblNotificationSource.Geofence -> "geofence"
426
+ BubblNotificationSource.Manual -> "manual"
427
+ }
428
+ )
429
+ putNullableString("locationId", locationId)
430
+ putNullableString("curatedNotificationId", curatedNotificationId)
431
+ putNullableString("correlationId", correlationId)
432
+ putNullableMap("media", media?.toWritableMap())
433
+ putNullableMap("cta", cta?.toWritableMap())
434
+ putNullableMap("survey", survey?.toWritableMap())
435
+ putMap("raw", raw.toWritableMap())
436
+ }
437
+
438
+ private fun BubblNotificationMedia.toWritableMap(): WritableMap = Arguments.createMap().apply {
439
+ putString("url", url)
440
+ putNullableString("type", type)
441
+ putNullableString("altText", altText)
442
+ }
443
+
444
+ private fun BubblNotificationCta.toWritableMap(): WritableMap = Arguments.createMap().apply {
445
+ putString("label", label)
446
+ putNullableString("url", url)
447
+ putNullableString("action", action)
448
+ }
449
+
450
+ private fun BubblNotificationSurvey.toWritableMap(): WritableMap = Arguments.createMap().apply {
451
+ putArray("questions", questions.map { it.toWritableMap() }.toWritableMapArray())
452
+ }
453
+
454
+ private fun BubblSurveyQuestion.toWritableMap(): WritableMap = Arguments.createMap().apply {
455
+ putString("id", id)
456
+ putString("title", title)
457
+ putString("type", type)
458
+ putArray("choices", choices.map { it.toWritableMap() }.toWritableMapArray())
459
+ }
460
+
461
+ private fun BubblSurveyChoice.toWritableMap(): WritableMap = Arguments.createMap().apply {
462
+ putString("id", id)
463
+ putString("label", label)
464
+ }
465
+
466
+ private fun BubblEvent.toWritableMap(): WritableMap = when (this) {
467
+ BubblEvent.Ready -> Arguments.createMap().apply { putString("type", "ready") }
468
+ is BubblEvent.Diagnostic -> Arguments.createMap().apply {
469
+ putString("type", "diagnostic")
470
+ putMap("diagnostics", diagnostics.copy(platform = "react-native").toWritableMap())
471
+ }
472
+ is BubblEvent.NotificationReceived -> eventPayload("notificationReceived", payload)
473
+ is BubblEvent.NotificationDisplayed -> eventPayload("notificationDisplayed", payload)
474
+ is BubblEvent.NotificationTapped -> eventPayload("notificationTapped", payload, action)
475
+ is BubblEvent.NotificationCtaTapped -> eventPayload("notificationCtaTapped", payload, action)
476
+ is BubblEvent.NotificationMediaViewed -> eventPayload("notificationMediaViewed", payload)
477
+ is BubblEvent.NotificationSurveyRequested -> eventPayload("notificationSurveyRequested", payload)
478
+ is BubblEvent.LocationUpdated -> Arguments.createMap().apply {
479
+ putString("type", "locationUpdated")
480
+ putMap("location", location.toWritableMap())
481
+ }
482
+ is BubblEvent.GeofenceSnapshot -> Arguments.createMap().apply {
483
+ putString("type", "geofenceSnapshot")
484
+ putMap("snapshot", snapshot.toWritableMap())
485
+ }
486
+ is BubblEvent.GeofenceEntered -> Arguments.createMap().apply {
487
+ putString("type", "geofenceEntered")
488
+ putMap("transition", transition.toWritableMap())
489
+ }
490
+ is BubblEvent.GeofenceExited -> Arguments.createMap().apply {
491
+ putString("type", "geofenceExited")
492
+ putMap("transition", transition.toWritableMap())
493
+ }
494
+ is BubblEvent.Error -> Arguments.createMap().apply {
495
+ putString("type", "error")
496
+ putString("code", code)
497
+ putString("message", message)
498
+ }
499
+ }
500
+
501
+ private fun BubblNotificationTap.toWritableMap(): WritableMap =
502
+ eventPayload("notificationTapped", payload, action)
503
+
504
+ private fun eventPayload(
505
+ type: String,
506
+ payload: BubblNotificationPayload,
507
+ action: String? = null
508
+ ): WritableMap = Arguments.createMap().apply {
509
+ putString("type", type)
510
+ putMap("payload", payload.toWritableMap())
511
+ if (action == null) {
512
+ putNull("action")
513
+ } else {
514
+ putString("action", action)
515
+ }
516
+ }
517
+
518
+ private fun BubblGeofenceSnapshot.toWritableMap(): WritableMap = Arguments.createMap().apply {
519
+ val statsMap = Arguments.createMap().apply {
520
+ putInt("campaignsTotal", stats.campaignsTotal)
521
+ putInt("polygonsTotal", stats.polygonsTotal)
522
+ }
523
+ putMap("stats", statsMap)
524
+ putArray("polygons", polygons.map { it.toWritableMap() }.toWritableMapArray())
525
+ putArray("circles", circles.map { it.toWritableMap() }.toWritableMapArray())
526
+ }
527
+
528
+ private fun BubblGeofencePolygon.toWritableMap(): WritableMap = Arguments.createMap().apply {
529
+ putNullableId("campaignId", campaignId)
530
+ putNullableString("campaignName", campaignName)
531
+ putNullableId("locationId", locationId)
532
+ putArray(
533
+ "vertices",
534
+ vertices.map { vertex ->
535
+ Arguments.createMap().apply {
536
+ putDouble("latitude", vertex.latitude)
537
+ putDouble("longitude", vertex.longitude)
538
+ }
539
+ }.toWritableMapArray()
540
+ )
541
+ }
542
+
543
+ private fun BubblGeofenceCircle.toWritableMap(): WritableMap = Arguments.createMap().apply {
544
+ putNullableId("campaignId", campaignId)
545
+ putNullableString("campaignName", campaignName)
546
+ putNullableId("locationId", locationId)
547
+ putMap(
548
+ "center",
549
+ Arguments.createMap().apply {
550
+ putDouble("latitude", center.latitude)
551
+ putDouble("longitude", center.longitude)
552
+ }
553
+ )
554
+ putDouble("radius", radiusMeters)
555
+ }
556
+
557
+ private fun ReadableMap.requiredString(key: String): String =
558
+ string(key)?.takeIf { it.isNotBlank() } ?: throw IllegalArgumentException("$key is required")
559
+
560
+ private fun ReadableMap.string(key: String): String? =
561
+ if (hasKey(key) && !isNull(key)) getString(key) else null
562
+
563
+ private fun ReadableMap.bool(key: String, default: Boolean): Boolean =
564
+ if (hasKey(key) && !isNull(key)) getBoolean(key) else default
565
+
566
+ private fun ReadableMap.int(key: String, default: Int): Int =
567
+ if (hasKey(key) && !isNull(key)) getDouble(key).toInt() else default
568
+
569
+ private fun ReadableMap.nullableDouble(key: String): Double? =
570
+ if (hasKey(key) && !isNull(key)) getDouble(key) else null
571
+
572
+ private fun ReadableMap.map(key: String): ReadableMap? =
573
+ if (hasKey(key) && !isNull(key)) getMap(key) else null
574
+
575
+ private fun ReadableMap.stringList(key: String): List<String> =
576
+ if (hasKey(key) && !isNull(key)) getArray(key)?.stringList().orEmpty() else emptyList()
577
+
578
+ private fun ReadableMap.mapList(key: String): List<ReadableMap> =
579
+ if (hasKey(key) && !isNull(key)) getArray(key)?.mapList().orEmpty() else emptyList()
580
+
581
+ private fun ReadableMap.stringMap(): Map<String, String> {
582
+ val output = mutableMapOf<String, String>()
583
+ val iterator = keySetIterator()
584
+ while (iterator.hasNextKey()) {
585
+ val key = iterator.nextKey()
586
+ if (!isNull(key)) {
587
+ output[key] = when (getType(key).name) {
588
+ "Boolean" -> getBoolean(key).toString()
589
+ "Number" -> getDouble(key).toString()
590
+ "String" -> getString(key).orEmpty()
591
+ else -> getDynamic(key).toString()
592
+ }
593
+ }
594
+ }
595
+ return output
596
+ }
597
+
598
+ private fun ReadableArray.stringList(): List<String> =
599
+ (0 until size()).mapNotNull { index ->
600
+ if (isNull(index)) null else getString(index)
601
+ }
602
+
603
+ private fun ReadableArray.mapList(): List<ReadableMap> =
604
+ (0 until size()).mapNotNull { index ->
605
+ if (isNull(index)) null else getMap(index)
606
+ }
607
+
608
+ private fun List<String>.toWritableStringArray(): WritableArray = Arguments.createArray().also { array ->
609
+ forEach { array.pushString(it) }
610
+ }
611
+
612
+ private fun List<WritableMap>.toWritableMapArray(): WritableArray = Arguments.createArray().also { array ->
613
+ forEach { array.pushMap(it) }
614
+ }
615
+
616
+ private fun Map<String, String>.toWritableMap(): WritableMap = Arguments.createMap().apply {
617
+ forEach { (key, value) -> putString(key, value) }
618
+ }
619
+
620
+ private fun WritableMap.putNullableString(key: String, value: String?) {
621
+ if (value == null) putNull(key) else putString(key, value)
622
+ }
623
+
624
+ private fun WritableMap.putNullableMap(key: String, value: WritableMap?) {
625
+ if (value == null) putNull(key) else putMap(key, value)
626
+ }
627
+
628
+ private fun WritableMap.putNullableId(key: String, value: String?) {
629
+ when {
630
+ value == null -> putNull(key)
631
+ value.toIntOrNull() != null -> putInt(key, value.toInt())
632
+ else -> putString(key, value)
633
+ }
634
+ }
635
+
636
+ private fun <T> enumValue(value: String?, default: T, block: (String) -> T): T =
637
+ value?.let { runCatching { block(it) }.getOrNull() } ?: default
638
+
639
+ companion object {
640
+ const val NAME = "BubblSdk"
641
+ private const val EVENT_NAME = "BubblSdkEvent"
642
+ }
643
+ }
@@ -0,0 +1,32 @@
1
+ package tech.bubbl.reactnative
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import tech.bubbl.sdk.BubblNotificationTapPresentation
6
+ import tech.bubbl.sdk.BubblSdk
7
+
8
+ object BubblSdkNotificationIntents {
9
+ @JvmStatic
10
+ fun open(activity: Activity, intent: Intent?): Boolean =
11
+ BubblSdk.openNotificationIntent(activity, intent)
12
+
13
+ @JvmStatic
14
+ fun openDefaultModal(activity: Activity, intent: Intent?): Boolean =
15
+ BubblSdk.openNotificationIntent(activity, intent, BubblNotificationTapPresentation.DefaultModal)
16
+
17
+ @JvmStatic
18
+ fun openHostModal(activity: Activity, intent: Intent?): Boolean =
19
+ BubblSdk.openNotificationIntent(activity, intent, BubblNotificationTapPresentation.HostModal)
20
+
21
+ @JvmStatic
22
+ fun open(activity: Activity, intent: Intent?, useDefaultModal: Boolean): Boolean =
23
+ BubblSdk.openNotificationIntent(
24
+ activity,
25
+ intent,
26
+ if (useDefaultModal) {
27
+ BubblNotificationTapPresentation.DefaultModal
28
+ } else {
29
+ BubblNotificationTapPresentation.HostModal
30
+ }
31
+ )
32
+ }
@@ -0,0 +1,14 @@
1
+ package tech.bubbl.reactnative
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class BubblSdkPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
10
+ listOf(BubblSdkModule(reactContext))
11
+
12
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
13
+ emptyList()
14
+ }