@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,677 @@
1
+ import Foundation
2
+ import React
3
+ import BubblSDK
4
+
5
+ #if canImport(CoreLocation)
6
+ import CoreLocation
7
+ #endif
8
+
9
+ @objc(BubblSdk)
10
+ final class BubblSdk: RCTEventEmitter {
11
+ private let sdk = BubblClient.shared
12
+ private var eventTask: Task<Void, Never>?
13
+
14
+ #if canImport(CoreLocation)
15
+ @MainActor private lazy var locationMonitor = BubblLocationMonitor(sdk: sdk)
16
+ #endif
17
+
18
+ override static func requiresMainQueueSetup() -> Bool {
19
+ false
20
+ }
21
+
22
+ override func supportedEvents() -> [String]! {
23
+ [Self.eventName]
24
+ }
25
+
26
+ override func startObserving() {
27
+ eventTask?.cancel()
28
+ eventTask = Task { [sdk] in
29
+ let stream = await sdk.events
30
+ for await event in stream {
31
+ await MainActor.run {
32
+ self.sendEvent(withName: Self.eventName, body: event.reactMap())
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ override func stopObserving() {
39
+ eventTask?.cancel()
40
+ eventTask = nil
41
+ }
42
+
43
+ @objc(boot:resolver:rejecter:)
44
+ func boot(
45
+ _ config: NSDictionary,
46
+ resolve: @escaping RCTPromiseResolveBlock,
47
+ reject: @escaping RCTPromiseRejectBlock
48
+ ) {
49
+ resolveAsync(resolve, reject) {
50
+ try await self.sdk.boot(try config.swiftMap.bubblConfig()).reactMap()
51
+ }
52
+ }
53
+
54
+ @objc(shutdown:rejecter:)
55
+ func shutdown(
56
+ _ resolve: @escaping RCTPromiseResolveBlock,
57
+ reject: @escaping RCTPromiseRejectBlock
58
+ ) {
59
+ resolveAsync(resolve, reject) {
60
+ await self.sdk.shutdown()
61
+ return nil
62
+ }
63
+ }
64
+
65
+ @objc(startLocationTracking:rejecter:)
66
+ func startLocationTracking(
67
+ _ resolve: @escaping RCTPromiseResolveBlock,
68
+ reject: @escaping RCTPromiseRejectBlock
69
+ ) {
70
+ Task {
71
+ #if canImport(CoreLocation)
72
+ await MainActor.run { self.locationMonitor.start() }
73
+ #endif
74
+ resolve(NSNull())
75
+ }
76
+ }
77
+
78
+ @objc(stopLocationTracking:rejecter:)
79
+ func stopLocationTracking(
80
+ _ resolve: @escaping RCTPromiseResolveBlock,
81
+ reject: @escaping RCTPromiseRejectBlock
82
+ ) {
83
+ Task {
84
+ #if canImport(CoreLocation)
85
+ await MainActor.run { self.locationMonitor.stop() }
86
+ #endif
87
+ resolve(NSNull())
88
+ }
89
+ }
90
+
91
+ @objc(refresh:rejecter:)
92
+ func refresh(
93
+ _ resolve: @escaping RCTPromiseResolveBlock,
94
+ reject: @escaping RCTPromiseRejectBlock
95
+ ) {
96
+ resolveAsync(resolve, reject) {
97
+ try await self.sdk.refresh()
98
+ return nil
99
+ }
100
+ }
101
+
102
+ @objc(refreshGeofence:longitude:resolver:rejecter:)
103
+ func refreshGeofence(
104
+ _ latitude: Double,
105
+ longitude: Double,
106
+ resolve: @escaping RCTPromiseResolveBlock,
107
+ reject: @escaping RCTPromiseRejectBlock
108
+ ) {
109
+ resolveAsync(resolve, reject) {
110
+ try await self.sdk.refreshGeofence(BubblLocation(latitude: latitude, longitude: longitude))
111
+ return nil
112
+ }
113
+ }
114
+
115
+ @objc(handleLocationUpdate:longitude:resolver:rejecter:)
116
+ func handleLocationUpdate(
117
+ _ latitude: Double,
118
+ longitude: Double,
119
+ resolve: @escaping RCTPromiseResolveBlock,
120
+ reject: @escaping RCTPromiseRejectBlock
121
+ ) {
122
+ resolveAsync(resolve, reject) {
123
+ try await self.sdk.handleLocationUpdate(BubblLocation(latitude: latitude, longitude: longitude))
124
+ return nil
125
+ }
126
+ }
127
+
128
+ @objc(refreshPush:rejecter:)
129
+ func refreshPush(
130
+ _ resolve: @escaping RCTPromiseResolveBlock,
131
+ reject: @escaping RCTPromiseRejectBlock
132
+ ) {
133
+ resolveAsync(resolve, reject) {
134
+ try await self.sdk.refreshPush()
135
+ return nil
136
+ }
137
+ }
138
+
139
+ @objc(getConfiguration:rejecter:)
140
+ func getConfiguration(
141
+ _ resolve: @escaping RCTPromiseResolveBlock,
142
+ reject: @escaping RCTPromiseRejectBlock
143
+ ) {
144
+ resolveAsync(resolve, reject) {
145
+ await self.sdk.getConfiguration()?.reactMap()
146
+ }
147
+ }
148
+
149
+ @objc(getPrivacyText:rejecter:)
150
+ func getPrivacyText(
151
+ _ resolve: @escaping RCTPromiseResolveBlock,
152
+ reject: @escaping RCTPromiseRejectBlock
153
+ ) {
154
+ resolveAsync(resolve, reject) {
155
+ await self.sdk.getPrivacyText()
156
+ }
157
+ }
158
+
159
+ @objc(updateSegments:resolver:rejecter:)
160
+ func updateSegments(
161
+ _ tags: NSArray,
162
+ resolve: @escaping RCTPromiseResolveBlock,
163
+ reject: @escaping RCTPromiseRejectBlock
164
+ ) {
165
+ resolveAsync(resolve, reject) {
166
+ try await self.sdk.updateSegments(tags.compactMap { $0 as? String })
167
+ return nil
168
+ }
169
+ }
170
+
171
+ @objc(setCorrelationId:resolver:rejecter:)
172
+ func setCorrelationId(
173
+ _ value: NSString,
174
+ resolve: @escaping RCTPromiseResolveBlock,
175
+ reject: @escaping RCTPromiseRejectBlock
176
+ ) {
177
+ resolveAsync(resolve, reject) {
178
+ try await self.sdk.setCorrelationId(value as String)
179
+ return nil
180
+ }
181
+ }
182
+
183
+ @objc(clearCorrelationId:rejecter:)
184
+ func clearCorrelationId(
185
+ _ resolve: @escaping RCTPromiseResolveBlock,
186
+ reject: @escaping RCTPromiseRejectBlock
187
+ ) {
188
+ resolveAsync(resolve, reject) {
189
+ try await self.sdk.clearCorrelationId()
190
+ return nil
191
+ }
192
+ }
193
+
194
+ @objc(setDefaultNotificationModalEnabled:resolver:rejecter:)
195
+ func setDefaultNotificationModalEnabled(
196
+ _ enabled: Bool,
197
+ resolve: @escaping RCTPromiseResolveBlock,
198
+ reject: @escaping RCTPromiseRejectBlock
199
+ ) {
200
+ resolveAsync(resolve, reject) {
201
+ try await self.sdk.setDefaultNotificationModalEnabled(enabled)
202
+ return nil
203
+ }
204
+ }
205
+
206
+ @objc(registerPushToken:resolver:rejecter:)
207
+ func registerPushToken(
208
+ _ token: NSString,
209
+ resolve: @escaping RCTPromiseResolveBlock,
210
+ reject: @escaping RCTPromiseRejectBlock
211
+ ) {
212
+ resolveAsync(resolve, reject) {
213
+ try await self.sdk.registerPushToken(token as String)
214
+ return nil
215
+ }
216
+ }
217
+
218
+ @objc(handleFirebasePayload:resolver:rejecter:)
219
+ func handleFirebasePayload(
220
+ _ payload: NSDictionary,
221
+ resolve: @escaping RCTPromiseResolveBlock,
222
+ reject: @escaping RCTPromiseRejectBlock
223
+ ) {
224
+ resolveAsync(resolve, reject) {
225
+ try await self.sdk.handleFirebasePayload(payload.swiftMap.mapValues { String(describing: $0) })?.reactMap()
226
+ }
227
+ }
228
+
229
+ @objc(showNotification:resolver:rejecter:)
230
+ func showNotification(
231
+ _ payload: NSDictionary,
232
+ resolve: @escaping RCTPromiseResolveBlock,
233
+ reject: @escaping RCTPromiseRejectBlock
234
+ ) {
235
+ resolveAsync(resolve, reject) {
236
+ try await self.sdk.showNotification(try payload.swiftMap.notificationPayload()).reactMap()
237
+ }
238
+ }
239
+
240
+ @objc(handleNotificationPayload:resolver:rejecter:)
241
+ func handleNotificationPayload(
242
+ _ payload: NSDictionary,
243
+ resolve: @escaping RCTPromiseResolveBlock,
244
+ reject: @escaping RCTPromiseRejectBlock
245
+ ) {
246
+ resolveAsync(resolve, reject) {
247
+ try await self.sdk.handleNotificationPayload(try payload.swiftMap.notificationPayload()).reactMap()
248
+ }
249
+ }
250
+
251
+ @objc(handleNotificationOpen:action:resolver:rejecter:)
252
+ func handleNotificationOpen(
253
+ _ payload: NSDictionary,
254
+ action: NSString?,
255
+ resolve: @escaping RCTPromiseResolveBlock,
256
+ reject: @escaping RCTPromiseRejectBlock
257
+ ) {
258
+ resolveAsync(resolve, reject) {
259
+ try await self.sdk.handleNotificationOpen(try payload.swiftMap.notificationPayload(), action: action as String?)
260
+ return nil
261
+ }
262
+ }
263
+
264
+ @objc(openNotificationModal:action:resolver:rejecter:)
265
+ func openNotificationModal(
266
+ _ payload: NSDictionary,
267
+ action: NSString?,
268
+ resolve: @escaping RCTPromiseResolveBlock,
269
+ reject: @escaping RCTPromiseRejectBlock
270
+ ) {
271
+ resolveAsync(resolve, reject) {
272
+ try await self.sdk.openNotificationModal(try payload.swiftMap.notificationPayload(), action: action as String?)
273
+ }
274
+ }
275
+
276
+ @objc(handleNotificationCta:action:resolver:rejecter:)
277
+ func handleNotificationCta(
278
+ _ payload: NSDictionary,
279
+ action: NSString?,
280
+ resolve: @escaping RCTPromiseResolveBlock,
281
+ reject: @escaping RCTPromiseRejectBlock
282
+ ) {
283
+ resolveAsync(resolve, reject) {
284
+ try await self.sdk.handleNotificationCTA(try payload.swiftMap.notificationPayload(), action: action as String?)
285
+ return nil
286
+ }
287
+ }
288
+
289
+ @objc(handleNotificationMediaViewed:resolver:rejecter:)
290
+ func handleNotificationMediaViewed(
291
+ _ payload: NSDictionary,
292
+ resolve: @escaping RCTPromiseResolveBlock,
293
+ reject: @escaping RCTPromiseRejectBlock
294
+ ) {
295
+ resolveAsync(resolve, reject) {
296
+ try await self.sdk.handleNotificationMediaViewed(try payload.swiftMap.notificationPayload())
297
+ return nil
298
+ }
299
+ }
300
+
301
+ @objc(handleNotificationSurveyRequested:resolver:rejecter:)
302
+ func handleNotificationSurveyRequested(
303
+ _ payload: NSDictionary,
304
+ resolve: @escaping RCTPromiseResolveBlock,
305
+ reject: @escaping RCTPromiseRejectBlock
306
+ ) {
307
+ resolveAsync(resolve, reject) {
308
+ try await self.sdk.handleNotificationSurveyRequested(try payload.swiftMap.notificationPayload())
309
+ return nil
310
+ }
311
+ }
312
+
313
+ @objc(track:resolver:rejecter:)
314
+ func track(
315
+ _ event: NSDictionary,
316
+ resolve: @escaping RCTPromiseResolveBlock,
317
+ reject: @escaping RCTPromiseRejectBlock
318
+ ) {
319
+ resolveAsync(resolve, reject) {
320
+ try await self.sdk.track(try event.swiftMap.trackEvent())
321
+ return nil
322
+ }
323
+ }
324
+
325
+ @objc(submitSurveyResponse:resolver:rejecter:)
326
+ func submitSurveyResponse(
327
+ _ response: NSDictionary,
328
+ resolve: @escaping RCTPromiseResolveBlock,
329
+ reject: @escaping RCTPromiseRejectBlock
330
+ ) {
331
+ resolveAsync(resolve, reject) {
332
+ try await self.sdk.submitSurveyResponse(try response.swiftMap.surveyResponse())
333
+ return nil
334
+ }
335
+ }
336
+
337
+ @objc(flush:rejecter:)
338
+ func flush(
339
+ _ resolve: @escaping RCTPromiseResolveBlock,
340
+ reject: @escaping RCTPromiseRejectBlock
341
+ ) {
342
+ resolveAsync(resolve, reject) {
343
+ await self.sdk.flush().reactMap()
344
+ }
345
+ }
346
+
347
+ @objc(diagnostics:rejecter:)
348
+ func diagnostics(
349
+ _ resolve: @escaping RCTPromiseResolveBlock,
350
+ reject: @escaping RCTPromiseRejectBlock
351
+ ) {
352
+ resolveAsync(resolve, reject) {
353
+ var diagnostics = await self.sdk.diagnostics()
354
+ diagnostics.platform = "react-native"
355
+ return diagnostics.reactMap()
356
+ }
357
+ }
358
+
359
+ private func resolveAsync(
360
+ _ resolve: @escaping RCTPromiseResolveBlock,
361
+ _ reject: @escaping RCTPromiseRejectBlock,
362
+ _ body: @escaping () async throws -> Any?
363
+ ) {
364
+ Task {
365
+ do {
366
+ resolve(try await body() ?? NSNull())
367
+ } catch let error as BubblReactNativeError {
368
+ reject(error.code, error.message, error)
369
+ } catch {
370
+ reject("bubbl_error", String(describing: error), error)
371
+ }
372
+ }
373
+ }
374
+
375
+ private static let eventName = "BubblSdkEvent"
376
+ }
377
+
378
+ private struct BubblReactNativeError: Error {
379
+ let code: String
380
+ let message: String
381
+ }
382
+
383
+ private func reactValue(_ value: Any?) -> Any {
384
+ value ?? NSNull()
385
+ }
386
+
387
+ private extension NSDictionary {
388
+ var swiftMap: [String: Any] {
389
+ var output: [String: Any] = [:]
390
+ for (key, value) in self {
391
+ guard let key = key as? String else { continue }
392
+ output[key] = value
393
+ }
394
+ return output
395
+ }
396
+ }
397
+
398
+ private extension Dictionary where Key == String, Value == Any {
399
+ func requiredString(_ key: String) throws -> String {
400
+ guard let value = self[key] as? String, !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
401
+ throw BubblReactNativeError(code: "invalid_argument", message: "\(key) is required.")
402
+ }
403
+ return value
404
+ }
405
+
406
+ func string(_ key: String) -> String? {
407
+ self[key] as? String
408
+ }
409
+
410
+ func bool(_ key: String, default defaultValue: Bool) -> Bool {
411
+ self[key] as? Bool ?? defaultValue
412
+ }
413
+
414
+ func int(_ key: String, default defaultValue: Int) -> Int {
415
+ (self[key] as? NSNumber)?.intValue ?? defaultValue
416
+ }
417
+
418
+ func double(_ key: String) -> Double {
419
+ (self[key] as? NSNumber)?.doubleValue ?? 0
420
+ }
421
+
422
+ func optionalDouble(_ key: String) -> Double? {
423
+ (self[key] as? NSNumber)?.doubleValue
424
+ }
425
+
426
+ func stringArray(_ key: String) -> [String] {
427
+ (self[key] as? [Any] ?? []).map { String(describing: $0) }
428
+ }
429
+
430
+ func map(_ key: String) -> [String: Any]? {
431
+ self[key] as? [String: Any]
432
+ }
433
+
434
+ func mapArray(_ key: String) -> [[String: Any]] {
435
+ self[key] as? [[String: Any]] ?? []
436
+ }
437
+
438
+ func bubblConfig() throws -> BubblConfig {
439
+ BubblConfig(
440
+ apiKey: try requiredString("apiKey"),
441
+ environment: BubblEnvironment(rawValue: string("environment") ?? "staging") ?? .staging,
442
+ runtimeBaseUrl: string("runtimeBaseUrl").flatMap(URL.init(string:)),
443
+ transmissionBaseUrl: string("transmissionBaseUrl").flatMap(URL.init(string:)),
444
+ ingestBaseUrl: string("ingestBaseUrl").flatMap(URL.init(string:)),
445
+ segments: stringArray("segments"),
446
+ correlationId: string("correlationId"),
447
+ defaultDistanceMeters: int("defaultDistanceMeters", default: 10),
448
+ refreshIntervalSeconds: int("refreshIntervalSeconds", default: 300),
449
+ enablePushHandling: bool("enablePushHandling", default: true),
450
+ enableLocationTracking: bool("enableLocationTracking", default: false),
451
+ notificationRenderingMode: BubblNotificationRenderingMode(rawValue: string("notificationRenderingMode") ?? "sdkDefault") ?? .sdkDefault,
452
+ enableDefaultNotificationModal: bool("enableDefaultNotificationModal", default: true),
453
+ enableDefaultSurveyUi: bool("enableDefaultSurveyUi", default: true),
454
+ logLevel: BubblLogLevel(rawValue: string("logLevel") ?? "warn") ?? .warn
455
+ )
456
+ }
457
+
458
+ func trackEvent() throws -> BubblTrackEvent {
459
+ BubblTrackEvent(
460
+ type: try requiredString("type"),
461
+ activity: try requiredString("activity"),
462
+ locationId: string("locationId"),
463
+ curatedNotificationId: string("curatedNotificationId"),
464
+ latitude: optionalDouble("latitude"),
465
+ longitude: optionalDouble("longitude")
466
+ )
467
+ }
468
+
469
+ func surveyResponse() throws -> BubblSurveyResponse {
470
+ BubblSurveyResponse(
471
+ curatedNotificationId: try requiredString("curatedNotificationId"),
472
+ locationId: string("locationId"),
473
+ answers: try mapArray("answers").map { try $0.surveyAnswer() }
474
+ )
475
+ }
476
+
477
+ func surveyAnswer() throws -> BubblSurveyAnswer {
478
+ BubblSurveyAnswer(
479
+ questionId: try requiredString("questionId"),
480
+ type: try requiredString("type"),
481
+ value: string("value"),
482
+ choiceIds: stringArray("choiceIds")
483
+ )
484
+ }
485
+
486
+ func notificationPayload() throws -> BubblNotificationPayload {
487
+ BubblNotificationPayload(
488
+ id: try requiredString("id"),
489
+ title: try requiredString("title"),
490
+ body: try requiredString("body"),
491
+ source: BubblNotificationSource(rawValue: string("source") ?? "manual") ?? .manual,
492
+ locationId: string("locationId"),
493
+ curatedNotificationId: string("curatedNotificationId"),
494
+ correlationId: string("correlationId"),
495
+ media: try map("media")?.notificationMedia(),
496
+ cta: try map("cta")?.notificationCTA(),
497
+ survey: try map("survey")?.notificationSurvey(),
498
+ raw: map("raw")?.mapValues { String(describing: $0) } ?? [:]
499
+ )
500
+ }
501
+
502
+ func notificationMedia() throws -> BubblNotificationMedia {
503
+ guard let url = URL(string: try requiredString("url")) else {
504
+ throw BubblReactNativeError(code: "invalid_argument", message: "media.url is invalid.")
505
+ }
506
+ return BubblNotificationMedia(url: url, type: string("type"), altText: string("altText"))
507
+ }
508
+
509
+ func notificationCTA() throws -> BubblNotificationCTA {
510
+ BubblNotificationCTA(
511
+ label: try requiredString("label"),
512
+ url: string("url").flatMap(URL.init(string:)),
513
+ action: string("action")
514
+ )
515
+ }
516
+
517
+ func notificationSurvey() throws -> BubblNotificationSurvey {
518
+ BubblNotificationSurvey(questions: try mapArray("questions").map { try $0.surveyQuestion() })
519
+ }
520
+
521
+ func surveyQuestion() throws -> BubblSurveyQuestion {
522
+ BubblSurveyQuestion(
523
+ id: try requiredString("id"),
524
+ title: try requiredString("title"),
525
+ type: try requiredString("type"),
526
+ choices: try mapArray("choices").map { try $0.surveyChoice() }
527
+ )
528
+ }
529
+
530
+ func surveyChoice() throws -> BubblSurveyChoice {
531
+ BubblSurveyChoice(id: try requiredString("id"), label: try requiredString("label"))
532
+ }
533
+ }
534
+
535
+ private extension BubblBootResult {
536
+ func reactMap() -> [String: Any] {
537
+ [
538
+ "ready": ready,
539
+ "fromCache": fromCache,
540
+ "deviceRegistered": deviceRegistered,
541
+ "requiresPermission": requiresPermission,
542
+ "warnings": warnings
543
+ ]
544
+ }
545
+ }
546
+
547
+ private extension BubblConfiguration {
548
+ func reactMap() -> [String: Any] {
549
+ [
550
+ "notificationsCount": notificationsCount,
551
+ "daysCount": daysCount,
552
+ "batteryCount": batteryCount,
553
+ "privacyText": privacyText
554
+ ]
555
+ }
556
+ }
557
+
558
+ private extension BubblDiagnostics {
559
+ func reactMap() -> [String: Any] {
560
+ [
561
+ "sdkVersion": sdkVersion,
562
+ "platform": platform,
563
+ "booted": booted,
564
+ "pendingIngestCount": pendingIngestCount,
565
+ "pushTokenSuffix": reactValue(pushTokenSuffix)
566
+ ]
567
+ }
568
+ }
569
+
570
+ private extension BubblFlushResult {
571
+ func reactMap() -> [String: Any] {
572
+ ["pendingCount": pendingCount]
573
+ }
574
+ }
575
+
576
+ private extension BubblNotificationDisplayResult {
577
+ func reactMap() -> [String: Any] {
578
+ ["displayed": displayed, "reason": reactValue(reason)]
579
+ }
580
+ }
581
+
582
+ private extension BubblLocation {
583
+ func reactMap() -> [String: Any] {
584
+ ["latitude": latitude, "longitude": longitude]
585
+ }
586
+ }
587
+
588
+ private extension BubblGeofenceTransition {
589
+ func reactMap() -> [String: Any] {
590
+ [
591
+ "type": type.rawValue,
592
+ "campaignId": reactValue(campaignId),
593
+ "locationId": reactValue(locationId),
594
+ "location": location.reactMap()
595
+ ]
596
+ }
597
+ }
598
+
599
+ private extension BubblNotificationPayload {
600
+ func reactMap() -> [String: Any] {
601
+ [
602
+ "id": id,
603
+ "title": title,
604
+ "body": body,
605
+ "source": source.rawValue,
606
+ "locationId": reactValue(locationId),
607
+ "curatedNotificationId": reactValue(curatedNotificationId),
608
+ "correlationId": reactValue(correlationId),
609
+ "media": reactValue(media?.reactMap()),
610
+ "cta": reactValue(cta?.reactMap()),
611
+ "survey": reactValue(survey?.reactMap()),
612
+ "raw": raw
613
+ ]
614
+ }
615
+ }
616
+
617
+ private extension BubblNotificationMedia {
618
+ func reactMap() -> [String: Any] {
619
+ ["url": url.absoluteString, "type": reactValue(type), "altText": reactValue(altText)]
620
+ }
621
+ }
622
+
623
+ private extension BubblNotificationCTA {
624
+ func reactMap() -> [String: Any] {
625
+ ["label": label, "url": reactValue(url?.absoluteString), "action": reactValue(action)]
626
+ }
627
+ }
628
+
629
+ private extension BubblNotificationSurvey {
630
+ func reactMap() -> [String: Any] {
631
+ ["questions": questions.map { $0.reactMap() }]
632
+ }
633
+ }
634
+
635
+ private extension BubblSurveyQuestion {
636
+ func reactMap() -> [String: Any] {
637
+ ["id": id, "title": title, "type": type, "choices": choices.map { $0.reactMap() }]
638
+ }
639
+ }
640
+
641
+ private extension BubblSurveyChoice {
642
+ func reactMap() -> [String: Any] {
643
+ ["id": id, "label": label]
644
+ }
645
+ }
646
+
647
+ private extension BubblEvent {
648
+ func reactMap() -> [String: Any] {
649
+ switch self {
650
+ case .ready:
651
+ return ["type": "ready"]
652
+ case .diagnostic(var diagnostics):
653
+ diagnostics.platform = "react-native"
654
+ return ["type": "diagnostic", "diagnostics": diagnostics.reactMap()]
655
+ case .notificationReceived(let payload):
656
+ return ["type": "notificationReceived", "payload": payload.reactMap()]
657
+ case .notificationDisplayed(let payload):
658
+ return ["type": "notificationDisplayed", "payload": payload.reactMap()]
659
+ case .notificationTapped(let payload, let action):
660
+ return ["type": "notificationTapped", "payload": payload.reactMap(), "action": reactValue(action)]
661
+ case .notificationCtaTapped(let payload, let action):
662
+ return ["type": "notificationCtaTapped", "payload": payload.reactMap(), "action": reactValue(action)]
663
+ case .notificationMediaViewed(let payload):
664
+ return ["type": "notificationMediaViewed", "payload": payload.reactMap()]
665
+ case .notificationSurveyRequested(let payload):
666
+ return ["type": "notificationSurveyRequested", "payload": payload.reactMap()]
667
+ case .locationUpdated(let location):
668
+ return ["type": "locationUpdated", "location": location.reactMap()]
669
+ case .geofenceEntered(let transition):
670
+ return ["type": "geofenceEntered", "transition": transition.reactMap()]
671
+ case .geofenceExited(let transition):
672
+ return ["type": "geofenceExited", "transition": transition.reactMap()]
673
+ case .error(let code, let message):
674
+ return ["type": "error", "code": code, "message": message]
675
+ }
676
+ }
677
+ }