@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.
- package/BubblReactNativeSdk.podspec +17 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +1 -0
- package/README.md +97 -0
- package/android/build.gradle +46 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkModule.kt +643 -0
- package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkNotificationIntents.kt +32 -0
- package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkPackage.kt +14 -0
- package/ios/BubblSdk.swift +677 -0
- package/ios/BubblSdkBridge.m +83 -0
- package/package.json +48 -0
- package/react-native.config.js +11 -0
- package/src/index.ts +292 -0
- package/src/specs/NativeBubblSdk.ts +52 -0
|
@@ -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
|
+
}
|