@bubblsdk/react-native-sdk 4.1.7 → 5.0.0-alpha.2
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 +29 -9
- package/CHANGELOG.md +52 -1
- package/LICENSE +202 -1
- package/README.md +172 -83
- package/android/build.gradle +33 -30
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/tech/bubbl/reactnative/BubblJson.kt +135 -0
- package/android/src/main/java/tech/bubbl/reactnative/BubblSdkModule.kt +277 -0
- package/android/src/main/java/tech/bubbl/reactnative/BubblSdkPackage.kt +26 -0
- package/app.plugin.js +2 -0
- package/ios/BubblPush.swift +44 -0
- package/ios/BubblSdk.h +7 -0
- package/ios/BubblSdk.mm +252 -0
- package/ios/BubblSdkImpl.swift +335 -0
- package/ios-extension/BubblReactNativeNotificationService.podspec +44 -0
- package/ios-extension/Sources/BubblCore/API/ApiResponse.swift +90 -0
- package/ios-extension/Sources/BubblCore/API/CredentialStore.swift +116 -0
- package/ios-extension/Sources/BubblCore/API/DeviceApiClient.swift +239 -0
- package/ios-extension/Sources/BubblCore/API/ErrorActions.swift +68 -0
- package/ios-extension/Sources/BubblCore/API/Http.swift +39 -0
- package/ios-extension/Sources/BubblCore/API/ServerClock.swift +69 -0
- package/ios-extension/Sources/BubblCore/BubblVersion.swift +5 -0
- package/ios-extension/Sources/BubblCore/Device/ConfigSync.swift +216 -0
- package/ios-extension/Sources/BubblCore/Device/DeviceSync.swift +160 -0
- package/ios-extension/Sources/BubblCore/Device/ProvisioningProfile.swift +24 -0
- package/ios-extension/Sources/BubblCore/Engine/EngineCore.swift +523 -0
- package/ios-extension/Sources/BubblCore/Engine/Geofencing.swift +170 -0
- package/ios-extension/Sources/BubblCore/Engine/Permissions.swift +90 -0
- package/ios-extension/Sources/BubblCore/Engine/Privacy.swift +64 -0
- package/ios-extension/Sources/BubblCore/Engine/PrivacyControls.swift +184 -0
- package/ios-extension/Sources/BubblCore/Engine/Pushes.swift +61 -0
- package/ios-extension/Sources/BubblCore/Engine/Sandbox.swift +149 -0
- package/ios-extension/Sources/BubblCore/Events/EventQueue.swift +207 -0
- package/ios-extension/Sources/BubblCore/Events/EventStore.swift +159 -0
- package/ios-extension/Sources/BubblCore/Geofence/Geofence.swift +160 -0
- package/ios-extension/Sources/BubblCore/Geofence/GeofenceEngine.swift +488 -0
- package/ios-extension/Sources/BubblCore/Geofence/GeofenceState.swift +103 -0
- package/ios-extension/Sources/BubblCore/Geofence/RegionBudget.swift +62 -0
- package/ios-extension/Sources/BubblCore/Notification/BubblNotification.swift +137 -0
- package/ios-extension/Sources/BubblCore/Notification/NotificationEvents.swift +160 -0
- package/ios-extension/Sources/BubblCore/Notification/Push.swift +134 -0
- package/ios-extension/Sources/BubblCore/Notification/SurveyForm.swift +185 -0
- package/ios-extension/Sources/BubblCore/RequestSigner.swift +56 -0
- package/ios-extension/Sources/BubblCore/Support/AsyncMutex.swift +39 -0
- package/ios-extension/Sources/BubblCore/Support/BubblLog.swift +52 -0
- package/ios-extension/Sources/BubblCore/Support/JSON.swift +31 -0
- package/ios-extension/Sources/BubblCore/Support/JSONValue.swift +94 -0
- package/ios-extension/Sources/BubblCore/Support/Locking.swift +11 -0
- package/ios-extension/Sources/BubblCore/Support/ValueStore.swift +109 -0
- package/ios-extension/Sources/BubblNotificationService/BubblNotificationService.swift +124 -0
- package/ios-sdk/BubblCore/API/ApiResponse.swift +90 -0
- package/ios-sdk/BubblCore/API/CredentialStore.swift +116 -0
- package/ios-sdk/BubblCore/API/DeviceApiClient.swift +239 -0
- package/ios-sdk/BubblCore/API/ErrorActions.swift +68 -0
- package/ios-sdk/BubblCore/API/Http.swift +39 -0
- package/ios-sdk/BubblCore/API/ServerClock.swift +69 -0
- package/ios-sdk/BubblCore/BubblVersion.swift +5 -0
- package/ios-sdk/BubblCore/Device/ConfigSync.swift +216 -0
- package/ios-sdk/BubblCore/Device/DeviceSync.swift +160 -0
- package/ios-sdk/BubblCore/Device/ProvisioningProfile.swift +24 -0
- package/ios-sdk/BubblCore/Engine/EngineCore.swift +523 -0
- package/ios-sdk/BubblCore/Engine/Geofencing.swift +170 -0
- package/ios-sdk/BubblCore/Engine/Permissions.swift +90 -0
- package/ios-sdk/BubblCore/Engine/Privacy.swift +64 -0
- package/ios-sdk/BubblCore/Engine/PrivacyControls.swift +184 -0
- package/ios-sdk/BubblCore/Engine/Pushes.swift +61 -0
- package/ios-sdk/BubblCore/Engine/Sandbox.swift +149 -0
- package/ios-sdk/BubblCore/Events/EventQueue.swift +207 -0
- package/ios-sdk/BubblCore/Events/EventStore.swift +159 -0
- package/ios-sdk/BubblCore/Geofence/Geofence.swift +160 -0
- package/ios-sdk/BubblCore/Geofence/GeofenceEngine.swift +488 -0
- package/ios-sdk/BubblCore/Geofence/GeofenceState.swift +103 -0
- package/ios-sdk/BubblCore/Geofence/RegionBudget.swift +62 -0
- package/ios-sdk/BubblCore/Notification/BubblNotification.swift +137 -0
- package/ios-sdk/BubblCore/Notification/NotificationEvents.swift +160 -0
- package/ios-sdk/BubblCore/Notification/Push.swift +134 -0
- package/ios-sdk/BubblCore/Notification/SurveyForm.swift +185 -0
- package/ios-sdk/BubblCore/RequestSigner.swift +56 -0
- package/ios-sdk/BubblCore/Support/AsyncMutex.swift +39 -0
- package/ios-sdk/BubblCore/Support/BubblLog.swift +52 -0
- package/ios-sdk/BubblCore/Support/JSON.swift +31 -0
- package/ios-sdk/BubblCore/Support/JSONValue.swift +94 -0
- package/ios-sdk/BubblCore/Support/Locking.swift +11 -0
- package/ios-sdk/BubblCore/Support/ValueStore.swift +109 -0
- package/ios-sdk/BubblLaunch/BubblLaunch.m +45 -0
- package/ios-sdk/BubblLaunch/include/BubblLaunch.h +5 -0
- package/ios-sdk/BubblSDK/Bubbl.swift +308 -0
- package/ios-sdk/BubblSDK/BubblBackend.swift +118 -0
- package/ios-sdk/BubblSDK/BubblCredential.swift +30 -0
- package/ios-sdk/BubblSDK/BubblMessage.swift +151 -0
- package/ios-sdk/BubblSDK/BubblOptions.swift +39 -0
- package/ios-sdk/BubblSDK/BubblPermissions.swift +79 -0
- package/ios-sdk/BubblSDK/BubblTestDeviceResult.swift +10 -0
- package/ios-sdk/BubblSDK/Engine/BackgroundRefresh.swift +92 -0
- package/ios-sdk/BubblSDK/Engine/EngineHost+Geofences.swift +177 -0
- package/ios-sdk/BubblSDK/Engine/EngineHost+Privacy.swift +65 -0
- package/ios-sdk/BubblSDK/Engine/EngineHost+Push.swift +154 -0
- package/ios-sdk/BubblSDK/Engine/EngineHost.swift +356 -0
- package/ios-sdk/BubblSDK/Engine/SupportedBubbl.swift +320 -0
- package/ios-sdk/BubblSDK/Engine/SystemLog.swift +27 -0
- package/ios-sdk/BubblSDK/Engine/WorkRunner.swift +107 -0
- package/ios-sdk/BubblSDK/Notification/Look.swift +78 -0
- package/ios-sdk/BubblSDK/Notification/NotificationMedia.swift +329 -0
- package/ios-sdk/BubblSDK/Notification/NotificationModel.swift +172 -0
- package/ios-sdk/BubblSDK/Notification/NotificationScreen.swift +428 -0
- package/ios-sdk/BubblSDK/Notification/ScreenPresenter.swift +148 -0
- package/ios-sdk/BubblSDK/Permissions/PermissionFlow.swift +169 -0
- package/ios-sdk/BubblSDK/Permissions/PrivacyView.swift +110 -0
- package/ios-sdk/BubblSDK/Platform/AppleDevicePlatform.swift +95 -0
- package/ios-sdk/BubblSDK/Platform/KeychainCredentialStore.swift +114 -0
- package/ios-sdk/BubblSDK/Platform/LocationService.swift +418 -0
- package/ios-sdk/BubblSDK/Platform/PushIntegration.swift +288 -0
- package/ios-sdk/BubblSDK/Platform/URLSessionHttpClient.swift +69 -0
- package/ios-sdk/BubblSDK/PrivacyInfo.xcprivacy +76 -0
- package/jest/index.js +75 -0
- package/lib/commonjs/BubblModule.js +14 -0
- package/lib/commonjs/BubblModule.js.map +1 -0
- package/lib/commonjs/BubblModule.web.js +13 -0
- package/lib/commonjs/BubblModule.web.js.map +1 -0
- package/lib/commonjs/NativeBubblSdk.js +20 -0
- package/lib/commonjs/NativeBubblSdk.js.map +1 -0
- package/lib/commonjs/index.js +336 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/BubblModule.js +5 -0
- package/lib/module/BubblModule.js.map +1 -0
- package/lib/module/BubblModule.web.js +9 -0
- package/lib/module/BubblModule.web.js.map +1 -0
- package/lib/module/NativeBubblSdk.js +18 -0
- package/lib/module/NativeBubblSdk.js.map +1 -0
- package/lib/module/index.js +331 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/BubblModule.d.ts +3 -0
- package/lib/typescript/commonjs/src/BubblModule.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/BubblModule.web.d.ts +8 -0
- package/lib/typescript/commonjs/src/BubblModule.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/NativeBubblSdk.d.ts +59 -0
- package/lib/typescript/commonjs/src/NativeBubblSdk.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +111 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types.d.ts +192 -0
- package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/BubblModule.d.ts +3 -0
- package/lib/typescript/module/src/BubblModule.d.ts.map +1 -0
- package/lib/typescript/module/src/BubblModule.web.d.ts +8 -0
- package/lib/typescript/module/src/BubblModule.web.d.ts.map +1 -0
- package/lib/typescript/module/src/NativeBubblSdk.d.ts +59 -0
- package/lib/typescript/module/src/NativeBubblSdk.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +111 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/lib/typescript/module/src/types.d.ts +192 -0
- package/lib/typescript/module/src/types.d.ts.map +1 -0
- package/package.json +105 -24
- package/plugin/build/imageNotifications.d.ts +35 -0
- package/plugin/build/imageNotifications.js +198 -0
- package/plugin/build/index.d.ts +84 -0
- package/plugin/build/index.js +176 -0
- package/src/BubblModule.ts +2 -0
- package/src/BubblModule.web.ts +9 -0
- package/src/NativeBubblSdk.ts +67 -0
- package/src/index.ts +338 -282
- package/src/types.ts +182 -0
- package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkModule.kt +0 -680
- package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkNotificationIntents.kt +0 -32
- package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkPackage.kt +0 -14
- package/ios/BubblSdk.swift +0 -802
- package/ios/BubblSdkBridge.m +0 -86
- package/react-native.config.js +0 -11
- package/src/specs/NativeBubblSdk.ts +0 -54
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#if os(iOS)
|
|
2
|
+
import Foundation
|
|
3
|
+
import ObjectiveC
|
|
4
|
+
import UIKit
|
|
5
|
+
import UserNotifications
|
|
6
|
+
#if !COCOAPODS
|
|
7
|
+
import BubblCore
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
/// Bubbl's part in the app's push handling, with no app code (as Firebase does it):
|
|
11
|
+
/// - the device token: the app delegate's application(_:didRegisterForRemoteNotifications…) is
|
|
12
|
+
/// extended (given one, if the app has none) to hand Bubbl the token as well;
|
|
13
|
+
/// - the notification center's delegate: whichever the app sets (its own, Firebase's,
|
|
14
|
+
/// flutter_local_notifications'…) is extended so Bubbl answers for Bubbl's pushes and passes
|
|
15
|
+
/// everything else on unchanged; when there's none by the end of launch, Bubbl sets its own.
|
|
16
|
+
///
|
|
17
|
+
/// BubblAutoIntegrationEnabled = NO in Info.plist turns all of it off: the app then calls
|
|
18
|
+
/// Bubbl.setPushToken, Bubbl.willPresent and Bubbl.didReceive from its own code.
|
|
19
|
+
@available(iOS 17, *)
|
|
20
|
+
enum PushIntegration {
|
|
21
|
+
private static let lock = NSLock()
|
|
22
|
+
nonisolated(unsafe) private static var asked = false
|
|
23
|
+
@MainActor private static var installed = false
|
|
24
|
+
|
|
25
|
+
/// Off with BubblAutoIntegrationEnabled = NO (a Boolean, or "NO" / "false").
|
|
26
|
+
static var enabled: Bool {
|
|
27
|
+
switch Bundle.main.object(forInfoDictionaryKey: "BubblAutoIntegrationEnabled") {
|
|
28
|
+
case let flag as Bool: return flag
|
|
29
|
+
case let text as String: return !["no", "false", "0"].contains(text.lowercased())
|
|
30
|
+
default: return true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Installs once launch has finished: apps and their plugins set their notification delegate
|
|
35
|
+
/// during launch only when there's none yet, so Bubbl's own must not be there first. Taps that
|
|
36
|
+
/// launched the app are delivered after launch, so they still reach it. At the end of launch
|
|
37
|
+
/// (`launchFinished`: Bubbl starting itself) it installs now; started after launch (a
|
|
38
|
+
/// wrapper's JavaScript or Dart), at the next turn of the main loop.
|
|
39
|
+
static func installWhenLaunched(launchFinished: Bool = false) {
|
|
40
|
+
guard enabled else {
|
|
41
|
+
BubblLog.info("Bubbl's push integration is off (BubblAutoIntegrationEnabled): the app hands Bubbl the token and its pushes")
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
let first: Bool = lock.sync {
|
|
45
|
+
defer { asked = true }
|
|
46
|
+
return !asked
|
|
47
|
+
}
|
|
48
|
+
guard first else { return }
|
|
49
|
+
|
|
50
|
+
if launchFinished, Thread.isMainThread {
|
|
51
|
+
MainActor.assumeIsolated { install() }
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
_ = NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, object: nil, queue: .main) { _ in
|
|
55
|
+
MainActor.assumeIsolated { install() }
|
|
56
|
+
}
|
|
57
|
+
DispatchQueue.main.async {
|
|
58
|
+
MainActor.assumeIsolated { install() }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@MainActor
|
|
63
|
+
private static func install() {
|
|
64
|
+
guard !installed else { return }
|
|
65
|
+
installed = true
|
|
66
|
+
AppDelegateHooks.install()
|
|
67
|
+
NotificationDelegateHooks.install()
|
|
68
|
+
registerIfAllowed()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Asks iOS for the device token once the user allows notifications: a token for a device that
|
|
72
|
+
/// can't show them would only make pushes that go nowhere look delivered. (Asking for the
|
|
73
|
+
/// permission through Bubbl registers as well.)
|
|
74
|
+
/// (Not main-actor code: iOS answers on a queue of its own, and a main-actor closure run there
|
|
75
|
+
/// is a Swift runtime trap.)
|
|
76
|
+
static func registerIfAllowed() {
|
|
77
|
+
Task {
|
|
78
|
+
let status = await PermissionFlow.notificationAuthorization()
|
|
79
|
+
guard status == .authorized || status == .provisional || status == .ephemeral else { return }
|
|
80
|
+
await MainActor.run { UIApplication.shared.registerForRemoteNotifications() }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: - The app delegate's token callbacks
|
|
86
|
+
|
|
87
|
+
@available(iOS 17, *)
|
|
88
|
+
enum AppDelegateHooks {
|
|
89
|
+
private typealias DidRegister = @convention(c) (AnyObject, Selector, UIApplication, NSData) -> Void
|
|
90
|
+
private typealias DidFail = @convention(c) (AnyObject, Selector, UIApplication, NSError) -> Void
|
|
91
|
+
|
|
92
|
+
@MainActor
|
|
93
|
+
static func install() {
|
|
94
|
+
guard let delegate = UIApplication.shared.delegate, let cls = object_getClass(delegate) else {
|
|
95
|
+
BubblLog.warning("The app has no delegate, so Bubbl can't get the device token: call Bubbl.setPushToken from the app")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let didRegister = #selector(UIApplicationDelegate.application(_:didRegisterForRemoteNotificationsWithDeviceToken:))
|
|
100
|
+
Swizzle.extend(cls, didRegister, types: "v@:@@") { original in
|
|
101
|
+
let block: @convention(block) (AnyObject, UIApplication, NSData) -> Void = { this, application, token in
|
|
102
|
+
EngineHost.shared.pushTokenReceived(token as Data)
|
|
103
|
+
if let original {
|
|
104
|
+
unsafeBitCast(original, to: DidRegister.self)(this, didRegister, application, token)
|
|
105
|
+
} else if let target = Swizzle.forwardee(this, didRegister) {
|
|
106
|
+
// SwiftUI's app delegate hands the app's UIApplicationDelegateAdaptor its
|
|
107
|
+
// messages; now that Bubbl gave it the method, pass it on the same way.
|
|
108
|
+
_ = target.perform(didRegister, with: application, with: token)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return block
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let didFail = #selector(UIApplicationDelegate.application(_:didFailToRegisterForRemoteNotificationsWithError:))
|
|
115
|
+
Swizzle.extend(cls, didFail, types: "v@:@@") { original in
|
|
116
|
+
let block: @convention(block) (AnyObject, UIApplication, NSError) -> Void = { this, application, error in
|
|
117
|
+
BubblLog.warning("iOS couldn't give the app a device token (\(error.domain) \(error.code)): pushes can't reach this device")
|
|
118
|
+
if let original {
|
|
119
|
+
unsafeBitCast(original, to: DidFail.self)(this, didFail, application, error)
|
|
120
|
+
} else if let target = Swizzle.forwardee(this, didFail) {
|
|
121
|
+
_ = target.perform(didFail, with: application, with: error)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return block
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: - The notification center's delegate
|
|
130
|
+
|
|
131
|
+
@available(iOS 17, *)
|
|
132
|
+
enum NotificationDelegateHooks {
|
|
133
|
+
private typealias SetDelegate = @convention(c) (AnyObject, Selector, AnyObject?) -> Void
|
|
134
|
+
private typealias WillPresent = @convention(c) (AnyObject, Selector, UNUserNotificationCenter, UNNotification, @escaping @convention(block) (UNNotificationPresentationOptions) -> Void) -> Void
|
|
135
|
+
private typealias DidReceive = @convention(c) (AnyObject, Selector, UNUserNotificationCenter, UNNotificationResponse, @escaping @convention(block) () -> Void) -> Void
|
|
136
|
+
|
|
137
|
+
private static let lock = NSLock()
|
|
138
|
+
nonisolated(unsafe) private static var hooked: Set<ObjectIdentifier> = []
|
|
139
|
+
/// Bubbl's own delegate when the app has none, kept alive here (the center holds it weakly).
|
|
140
|
+
nonisolated(unsafe) private static var own: BubblNotificationCenterDelegate?
|
|
141
|
+
|
|
142
|
+
@MainActor
|
|
143
|
+
static func install() {
|
|
144
|
+
hookSetDelegate()
|
|
145
|
+
let center = UNUserNotificationCenter.current()
|
|
146
|
+
if let delegate = center.delegate {
|
|
147
|
+
hook(object_getClass(delegate))
|
|
148
|
+
} else {
|
|
149
|
+
let delegate = BubblNotificationCenterDelegate()
|
|
150
|
+
lock.sync { own = delegate }
|
|
151
|
+
center.delegate = delegate
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Bubbl answers for a push arriving in front when it's Bubbl's; false when it isn't (then the
|
|
156
|
+
/// app's own handling decides).
|
|
157
|
+
static func willPresent(_ notification: UNNotification, _ completion: @escaping (UNNotificationPresentationOptions) -> Void) -> Bool {
|
|
158
|
+
let data = EngineHost.pushData(notification.request.content.userInfo)
|
|
159
|
+
guard PushMessage.isBubbl(data) else { return false }
|
|
160
|
+
arrived(data, completion)
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Bubbl answers for a tap on its push's notification; false when it isn't Bubbl's.
|
|
165
|
+
static func didReceive(_ response: UNNotificationResponse, _ completion: @escaping () -> Void) -> Bool {
|
|
166
|
+
let data = EngineHost.pushData(response.notification.request.content.userInfo)
|
|
167
|
+
guard PushMessage.isBubbl(data) else { return false }
|
|
168
|
+
opened(data, tapped: response.actionIdentifier == UNNotificationDefaultActionIdentifier, completion)
|
|
169
|
+
return true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// iOS's completion handlers are called on the main thread, whatever thread the delegate was
|
|
173
|
+
// called on and wherever the work in between ran: UIKit's for a notification response updates
|
|
174
|
+
// the app's snapshot and state restoration, and asserts it's on the main thread (a tap on a
|
|
175
|
+
// campaign push crashed TestFlight build 6 at launch when it wasn't).
|
|
176
|
+
|
|
177
|
+
/// A Bubbl push arriving in front: how to present it, handed to `completion` on the main thread.
|
|
178
|
+
static func arrived(_ data: [String: JSONValue], _ completion: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
179
|
+
let done = Handed(completion)
|
|
180
|
+
Task { @MainActor in
|
|
181
|
+
let options = await EngineHost.shared.presentation(forArriving: data)
|
|
182
|
+
done.value(options)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// A Bubbl push's notification answered (`tapped`: opened), then `completion` on the main thread.
|
|
187
|
+
static func opened(_ data: [String: JSONValue], tapped: Bool, _ completion: @escaping () -> Void) {
|
|
188
|
+
let done = Handed(completion)
|
|
189
|
+
Task { @MainActor in
|
|
190
|
+
if tapped { await EngineHost.shared.pushOpened(data) }
|
|
191
|
+
done.value()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// A delegate the app sets later (after Bubbl's, or replacing another) is extended too.
|
|
196
|
+
private static func hookSetDelegate() {
|
|
197
|
+
let selector = #selector(setter: UNUserNotificationCenter.delegate)
|
|
198
|
+
guard let method = class_getInstanceMethod(UNUserNotificationCenter.self, selector) else { return }
|
|
199
|
+
let original = unsafeBitCast(method_getImplementation(method), to: SetDelegate.self)
|
|
200
|
+
let block: @convention(block) (AnyObject, AnyObject?) -> Void = { center, delegate in
|
|
201
|
+
original(center, selector, delegate)
|
|
202
|
+
if let delegate, !(delegate is BubblNotificationCenterDelegate) { hook(object_getClass(delegate)) }
|
|
203
|
+
}
|
|
204
|
+
method_setImplementation(method, imp_implementationWithBlock(block))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private static func hook(_ cls: AnyClass?) {
|
|
208
|
+
guard let cls, lock.sync({ hooked.insert(ObjectIdentifier(cls)).inserted }) else { return }
|
|
209
|
+
|
|
210
|
+
let willPresentSelector = #selector(UNUserNotificationCenterDelegate.userNotificationCenter(_:willPresent:withCompletionHandler:))
|
|
211
|
+
Swizzle.extend(cls, willPresentSelector, types: "v@:@@@?") { original in
|
|
212
|
+
let block: @convention(block) (AnyObject, UNUserNotificationCenter, UNNotification, @escaping @convention(block) (UNNotificationPresentationOptions) -> Void) -> Void = { this, center, notification, completion in
|
|
213
|
+
if willPresent(notification, completion) { return }
|
|
214
|
+
if let original {
|
|
215
|
+
unsafeBitCast(original, to: WillPresent.self)(this, willPresentSelector, center, notification, completion)
|
|
216
|
+
} else {
|
|
217
|
+
// The app's delegate doesn't handle pushes in front: iOS's default, not shown.
|
|
218
|
+
completion([])
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return block
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let didReceiveSelector = #selector(UNUserNotificationCenterDelegate.userNotificationCenter(_:didReceive:withCompletionHandler:))
|
|
225
|
+
Swizzle.extend(cls, didReceiveSelector, types: "v@:@@@?") { original in
|
|
226
|
+
let block: @convention(block) (AnyObject, UNUserNotificationCenter, UNNotificationResponse, @escaping @convention(block) () -> Void) -> Void = { this, center, response, completion in
|
|
227
|
+
if didReceive(response, completion) { return }
|
|
228
|
+
if let original {
|
|
229
|
+
unsafeBitCast(original, to: DidReceive.self)(this, didReceiveSelector, center, response, completion)
|
|
230
|
+
} else {
|
|
231
|
+
completion()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return block
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// The notification center's delegate when the app has none: Bubbl's pushes are Bubbl's to
|
|
240
|
+
/// handle; others are left as iOS would (not shown in front, a tap just opens the app).
|
|
241
|
+
/// (The async forms: they match the protocol however its completion handlers are annotated.)
|
|
242
|
+
/// On the main actor, so iOS's completion handlers (which Swift calls when these return) run on
|
|
243
|
+
/// the main thread: nonisolated, they returned on a background thread (see `opened`).
|
|
244
|
+
@available(iOS 17, *)
|
|
245
|
+
@MainActor
|
|
246
|
+
final class BubblNotificationCenterDelegate: NSObject, @preconcurrency UNUserNotificationCenterDelegate {
|
|
247
|
+
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
|
248
|
+
let data = EngineHost.pushData(notification.request.content.userInfo)
|
|
249
|
+
guard PushMessage.isBubbl(data) else { return [] }
|
|
250
|
+
return await EngineHost.shared.presentation(forArriving: data)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
|
|
254
|
+
let data = EngineHost.pushData(response.notification.request.content.userInfo)
|
|
255
|
+
guard PushMessage.isBubbl(data), response.actionIdentifier == UNNotificationDefaultActionIdentifier else { return }
|
|
256
|
+
await EngineHost.shared.pushOpened(data)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// MARK: - Extending a class's method
|
|
261
|
+
|
|
262
|
+
@available(iOS 17, *)
|
|
263
|
+
enum Swizzle {
|
|
264
|
+
/// Gives `cls` its own `selector`, implemented by the block `make` returns for the original
|
|
265
|
+
/// implementation: the class's own or a superclass's, nil when it had none. A superclass's
|
|
266
|
+
/// implementation is left as it was, so other subclasses aren't touched.
|
|
267
|
+
static func extend(_ cls: AnyClass, _ selector: Selector, types: String, _ make: (IMP?) -> Any) {
|
|
268
|
+
let inherited = class_getInstanceMethod(cls, selector)
|
|
269
|
+
let replacement = imp_implementationWithBlock(make(inherited.map(method_getImplementation)))
|
|
270
|
+
let added = if let inherited, let encoding = method_getTypeEncoding(inherited) {
|
|
271
|
+
class_addMethod(cls, selector, replacement, encoding)
|
|
272
|
+
} else {
|
|
273
|
+
class_addMethod(cls, selector, replacement, types)
|
|
274
|
+
}
|
|
275
|
+
// The class defines it itself: replace it there (the original is that one).
|
|
276
|
+
if !added, let own = class_getInstanceMethod(cls, selector) { method_setImplementation(own, replacement) }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Where an object sends a message it doesn't implement itself (SwiftUI's app delegate sends
|
|
280
|
+
/// the app's UIApplicationDelegateAdaptor its messages this way), if anywhere else.
|
|
281
|
+
static func forwardee(_ object: AnyObject, _ selector: Selector) -> NSObject? {
|
|
282
|
+
guard let target = (object as? NSObject)?.forwardingTarget(for: selector) as? NSObject,
|
|
283
|
+
target !== object, target.responds(to: selector)
|
|
284
|
+
else { return nil }
|
|
285
|
+
return target
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
#endif
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
#if canImport(FoundationNetworking)
|
|
3
|
+
import FoundationNetworking
|
|
4
|
+
#endif
|
|
5
|
+
#if !COCOAPODS
|
|
6
|
+
import BubblCore
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
/// The engine's way to the network: an ephemeral URLSession of its own.
|
|
10
|
+
/// - No URL cache: a 304 for the engine's own If-None-Match reaches it as a 304, not as a
|
|
11
|
+
/// cached 200.
|
|
12
|
+
/// - No redirects followed: the request is signed for its own URL, and a redirect would carry
|
|
13
|
+
/// the signing headers to another host.
|
|
14
|
+
/// - Explicit timeouts, so work never hangs past what a background task allows.
|
|
15
|
+
@available(iOS 17, *)
|
|
16
|
+
final class URLSessionHttpClient: HttpClient, @unchecked Sendable {
|
|
17
|
+
private let session: URLSession
|
|
18
|
+
|
|
19
|
+
/// - Parameter protocolClasses: for tests, URLProtocols that answer instead of the network.
|
|
20
|
+
init(requestTimeout: TimeInterval = 20, resourceTimeout: TimeInterval = 40, protocolClasses: [AnyClass]? = nil) {
|
|
21
|
+
let configuration = URLSessionConfiguration.ephemeral
|
|
22
|
+
if let protocolClasses { configuration.protocolClasses = protocolClasses }
|
|
23
|
+
configuration.urlCache = nil
|
|
24
|
+
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
|
25
|
+
configuration.httpCookieStorage = nil
|
|
26
|
+
configuration.httpShouldSetCookies = false
|
|
27
|
+
configuration.timeoutIntervalForRequest = requestTimeout
|
|
28
|
+
configuration.timeoutIntervalForResource = resourceTimeout
|
|
29
|
+
configuration.waitsForConnectivity = false
|
|
30
|
+
session = URLSession(configuration: configuration, delegate: RedirectRefuser(), delegateQueue: nil)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
deinit {
|
|
34
|
+
session.finishTasksAndInvalidate()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func execute(_ request: HttpRequest) async throws -> HttpResponse {
|
|
38
|
+
guard let url = URL(string: request.url) else { throw URLError(.badURL) }
|
|
39
|
+
var urlRequest = URLRequest(url: url)
|
|
40
|
+
urlRequest.httpMethod = request.method
|
|
41
|
+
request.headers.forEach { urlRequest.setValue($0.value, forHTTPHeaderField: $0.key) }
|
|
42
|
+
urlRequest.httpBody = request.body.map { Data($0.utf8) }
|
|
43
|
+
|
|
44
|
+
let data: Data
|
|
45
|
+
let response: URLResponse
|
|
46
|
+
do {
|
|
47
|
+
(data, response) = try await session.data(for: urlRequest)
|
|
48
|
+
} catch let error as URLError where error.code == .appTransportSecurityRequiresSecureConnection {
|
|
49
|
+
BubblLog.warning("An http:// baseUrl needs NSAllowsLocalNetworking under NSAppTransportSecurity in the app's Info.plist")
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
guard let http = response as? HTTPURLResponse else { throw URLError(.badServerResponse) }
|
|
53
|
+
|
|
54
|
+
var headers: [String: [String]] = [:]
|
|
55
|
+
for (name, value) in http.allHeaderFields {
|
|
56
|
+
if let name = name as? String { headers[name] = [String(describing: value)] }
|
|
57
|
+
}
|
|
58
|
+
return HttpResponse(status: http.statusCode, body: String(decoding: data, as: UTF8.self), headers: headers)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Returns a redirect as the response itself (a 3xx the engine treats like any other status)
|
|
63
|
+
/// instead of following it.
|
|
64
|
+
@available(iOS 17, *)
|
|
65
|
+
private final class RedirectRefuser: NSObject, URLSessionTaskDelegate, @unchecked Sendable {
|
|
66
|
+
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) async -> URLRequest? {
|
|
67
|
+
nil
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!--
|
|
4
|
+
Bubbl's privacy manifest, which Xcode folds into the app's privacy report. Bubbl doesn't track
|
|
5
|
+
(no data is combined with other companies' for advertising, no tracking domains) and uses none of
|
|
6
|
+
Apple's "required reason" APIs. What it collects, for the app's workspace only, is keyed to an
|
|
7
|
+
install id it makes itself, never to a person: the app declares more if it links Bubbl's data to
|
|
8
|
+
its own accounts.
|
|
9
|
+
-->
|
|
10
|
+
<plist version="1.0">
|
|
11
|
+
<dict>
|
|
12
|
+
<key>NSPrivacyTracking</key>
|
|
13
|
+
<false/>
|
|
14
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
15
|
+
<array/>
|
|
16
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
17
|
+
<array/>
|
|
18
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
19
|
+
<array>
|
|
20
|
+
<!-- Where the device is when it enters or leaves one of the workspace's places. -->
|
|
21
|
+
<dict>
|
|
22
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
23
|
+
<string>NSPrivacyCollectedDataTypePreciseLocation</string>
|
|
24
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
25
|
+
<false/>
|
|
26
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
27
|
+
<false/>
|
|
28
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
29
|
+
<array>
|
|
30
|
+
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
|
31
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
32
|
+
</array>
|
|
33
|
+
</dict>
|
|
34
|
+
<!-- The install id Bubbl makes, and the device's push token. -->
|
|
35
|
+
<dict>
|
|
36
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
37
|
+
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
|
38
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
39
|
+
<false/>
|
|
40
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
41
|
+
<false/>
|
|
42
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
43
|
+
<array>
|
|
44
|
+
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
|
45
|
+
</array>
|
|
46
|
+
</dict>
|
|
47
|
+
<!-- Notifications shown, opened and acted on; app opens; the app's own events. -->
|
|
48
|
+
<dict>
|
|
49
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
50
|
+
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
|
51
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
52
|
+
<false/>
|
|
53
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
54
|
+
<false/>
|
|
55
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
56
|
+
<array>
|
|
57
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
58
|
+
</array>
|
|
59
|
+
</dict>
|
|
60
|
+
<!-- Survey answers. -->
|
|
61
|
+
<dict>
|
|
62
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
63
|
+
<string>NSPrivacyCollectedDataTypeOtherUserContent</string>
|
|
64
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
65
|
+
<false/>
|
|
66
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
67
|
+
<false/>
|
|
68
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
69
|
+
<array>
|
|
70
|
+
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
|
71
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
72
|
+
</array>
|
|
73
|
+
</dict>
|
|
74
|
+
</array>
|
|
75
|
+
</dict>
|
|
76
|
+
</plist>
|
package/jest/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bubbl for an app's Jest tests, where there's no native module:
|
|
3
|
+
*
|
|
4
|
+
* jest.mock('@bubblsdk/react-native-sdk', () => require('@bubblsdk/react-native-sdk/jest'));
|
|
5
|
+
*
|
|
6
|
+
* Every function is a jest.fn doing what Bubbl does before start (nothing; permissions null,
|
|
7
|
+
* submitSurvey false), so tests can assert the calls. isBubblMessage is the real one.
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fn = (implementation) => jest.fn(implementation);
|
|
12
|
+
const resolved = (value) => fn(() => Promise.resolve(value));
|
|
13
|
+
|
|
14
|
+
const Bubbl = {
|
|
15
|
+
isSupported: true,
|
|
16
|
+
start: fn(),
|
|
17
|
+
startWithCredential: fn(),
|
|
18
|
+
stop: fn(),
|
|
19
|
+
setConsent: fn(),
|
|
20
|
+
optOut: fn(),
|
|
21
|
+
deleteMyData: fn(),
|
|
22
|
+
setLocationEnabled: fn(),
|
|
23
|
+
permissions: {
|
|
24
|
+
status: resolved(null),
|
|
25
|
+
requestNotifications: resolved(null),
|
|
26
|
+
requestLocation: resolved(null),
|
|
27
|
+
openSettings: fn(),
|
|
28
|
+
},
|
|
29
|
+
setSegments: fn(),
|
|
30
|
+
track: fn(),
|
|
31
|
+
addEventListener: fn(() => ({ remove: fn() })),
|
|
32
|
+
setNotificationListener: fn(),
|
|
33
|
+
present: fn(),
|
|
34
|
+
reportDisplayed: fn(),
|
|
35
|
+
reportOpened: fn(),
|
|
36
|
+
reportCtaClicked: fn(),
|
|
37
|
+
reportDismissed: fn(),
|
|
38
|
+
reportMediaViewed: fn(),
|
|
39
|
+
reportMediaCompleted: fn(),
|
|
40
|
+
reportSurveyStarted: fn(),
|
|
41
|
+
submitSurvey: resolved(false),
|
|
42
|
+
openCta: fn(),
|
|
43
|
+
isBubblMessage: fn((data) => data != null && data.bubbl === '1'),
|
|
44
|
+
registerTestDevice: resolved({ approved: true }),
|
|
45
|
+
diagnostics: resolved({
|
|
46
|
+
started: false,
|
|
47
|
+
sdkVersion: require('../package.json').version,
|
|
48
|
+
supported: true,
|
|
49
|
+
sdkSupported: true,
|
|
50
|
+
registered: false,
|
|
51
|
+
installId: null,
|
|
52
|
+
active: false,
|
|
53
|
+
pausedUntil: null,
|
|
54
|
+
consentRequired: false,
|
|
55
|
+
consent: null,
|
|
56
|
+
locationEnabled: true,
|
|
57
|
+
permissions: null,
|
|
58
|
+
hasPushToken: false,
|
|
59
|
+
geofences: 0,
|
|
60
|
+
queuedEvents: 0,
|
|
61
|
+
lastError: null,
|
|
62
|
+
credentialRejected: false,
|
|
63
|
+
environment: null,
|
|
64
|
+
testDevice: null,
|
|
65
|
+
backgroundLocation: false,
|
|
66
|
+
transitionsDroppedWhileLocked: 0,
|
|
67
|
+
backgroundRestricted: null,
|
|
68
|
+
batteryOptimized: null,
|
|
69
|
+
backgroundRefreshAvailable: null,
|
|
70
|
+
lowPowerMode: null,
|
|
71
|
+
osWatchesGeofences: null,
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
module.exports = { __esModule: true, Bubbl, default: Bubbl };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "default", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _NativeBubblSdk.default;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _NativeBubblSdk = _interopRequireDefault(require("./NativeBubblSdk.js"));
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
//# sourceMappingURL=BubblModule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_NativeBubblSdk","_interopRequireDefault","require","e","__esModule","default"],"sourceRoot":"../../src","sources":["BubblModule.ts"],"mappings":";;;;;;;;;;;AACA,IAAAA,eAAA,GAAAC,sBAAA,CAAAC,OAAA;AAA2C,SAAAD,uBAAAE,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA","ignoreList":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* No native module on the web, and react-native-web has no TurboModuleRegistry to ask for one, so
|
|
9
|
+
* NativeBubblSdk.ts isn't loaded at all: Bubbl's calls do nothing there.
|
|
10
|
+
*/
|
|
11
|
+
const BubblModule = null;
|
|
12
|
+
var _default = exports.default = BubblModule;
|
|
13
|
+
//# sourceMappingURL=BubblModule.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["BubblModule","_default","exports","default"],"sourceRoot":"../../src","sources":["BubblModule.web.ts"],"mappings":";;;;;;AAEA;AACA;AACA;AACA;AACA,MAAMA,WAAwB,GAAG,IAAI;AAAC,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAEvBH,WAAW","ignoreList":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _reactNative = require("react-native");
|
|
8
|
+
/**
|
|
9
|
+
* The native module under the facade (src/index.ts): the Bubbl SDK on each platform, called with
|
|
10
|
+
* JSON strings rather than mapped objects (the same convention as the Flutter wrapper), so the
|
|
11
|
+
* bridge stays small and the native types stay the source of truth.
|
|
12
|
+
*
|
|
13
|
+
* - Messages cross as their contract Notification JSON (BubblMessage.json) going in, and as the
|
|
14
|
+
* public message object (fields worked out natively, plus json) coming out.
|
|
15
|
+
* - Events and notifications for the app's listener arrive as JSON on one event, "BubblSdkEvent".
|
|
16
|
+
*
|
|
17
|
+
* Codegen reads this file (package.json codegenConfig); keep it to the types codegen knows.
|
|
18
|
+
*/
|
|
19
|
+
var _default = exports.default = _reactNative.TurboModuleRegistry.get('BubblSdk');
|
|
20
|
+
//# sourceMappingURL=NativeBubblSdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeBubblSdk.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GA+DeC,gCAAmB,CAACC,GAAG,CAAO,UAAU,CAAC","ignoreList":[]}
|