@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.
Files changed (176) hide show
  1. package/BubblReactNativeSdk.podspec +29 -9
  2. package/CHANGELOG.md +52 -1
  3. package/LICENSE +202 -1
  4. package/README.md +172 -83
  5. package/android/build.gradle +33 -30
  6. package/android/src/main/AndroidManifest.xml +1 -0
  7. package/android/src/main/java/tech/bubbl/reactnative/BubblJson.kt +135 -0
  8. package/android/src/main/java/tech/bubbl/reactnative/BubblSdkModule.kt +277 -0
  9. package/android/src/main/java/tech/bubbl/reactnative/BubblSdkPackage.kt +26 -0
  10. package/app.plugin.js +2 -0
  11. package/ios/BubblPush.swift +44 -0
  12. package/ios/BubblSdk.h +7 -0
  13. package/ios/BubblSdk.mm +252 -0
  14. package/ios/BubblSdkImpl.swift +335 -0
  15. package/ios-extension/BubblReactNativeNotificationService.podspec +44 -0
  16. package/ios-extension/Sources/BubblCore/API/ApiResponse.swift +90 -0
  17. package/ios-extension/Sources/BubblCore/API/CredentialStore.swift +116 -0
  18. package/ios-extension/Sources/BubblCore/API/DeviceApiClient.swift +239 -0
  19. package/ios-extension/Sources/BubblCore/API/ErrorActions.swift +68 -0
  20. package/ios-extension/Sources/BubblCore/API/Http.swift +39 -0
  21. package/ios-extension/Sources/BubblCore/API/ServerClock.swift +69 -0
  22. package/ios-extension/Sources/BubblCore/BubblVersion.swift +5 -0
  23. package/ios-extension/Sources/BubblCore/Device/ConfigSync.swift +216 -0
  24. package/ios-extension/Sources/BubblCore/Device/DeviceSync.swift +160 -0
  25. package/ios-extension/Sources/BubblCore/Device/ProvisioningProfile.swift +24 -0
  26. package/ios-extension/Sources/BubblCore/Engine/EngineCore.swift +523 -0
  27. package/ios-extension/Sources/BubblCore/Engine/Geofencing.swift +170 -0
  28. package/ios-extension/Sources/BubblCore/Engine/Permissions.swift +90 -0
  29. package/ios-extension/Sources/BubblCore/Engine/Privacy.swift +64 -0
  30. package/ios-extension/Sources/BubblCore/Engine/PrivacyControls.swift +184 -0
  31. package/ios-extension/Sources/BubblCore/Engine/Pushes.swift +61 -0
  32. package/ios-extension/Sources/BubblCore/Engine/Sandbox.swift +149 -0
  33. package/ios-extension/Sources/BubblCore/Events/EventQueue.swift +207 -0
  34. package/ios-extension/Sources/BubblCore/Events/EventStore.swift +159 -0
  35. package/ios-extension/Sources/BubblCore/Geofence/Geofence.swift +160 -0
  36. package/ios-extension/Sources/BubblCore/Geofence/GeofenceEngine.swift +488 -0
  37. package/ios-extension/Sources/BubblCore/Geofence/GeofenceState.swift +103 -0
  38. package/ios-extension/Sources/BubblCore/Geofence/RegionBudget.swift +62 -0
  39. package/ios-extension/Sources/BubblCore/Notification/BubblNotification.swift +137 -0
  40. package/ios-extension/Sources/BubblCore/Notification/NotificationEvents.swift +160 -0
  41. package/ios-extension/Sources/BubblCore/Notification/Push.swift +134 -0
  42. package/ios-extension/Sources/BubblCore/Notification/SurveyForm.swift +185 -0
  43. package/ios-extension/Sources/BubblCore/RequestSigner.swift +56 -0
  44. package/ios-extension/Sources/BubblCore/Support/AsyncMutex.swift +39 -0
  45. package/ios-extension/Sources/BubblCore/Support/BubblLog.swift +52 -0
  46. package/ios-extension/Sources/BubblCore/Support/JSON.swift +31 -0
  47. package/ios-extension/Sources/BubblCore/Support/JSONValue.swift +94 -0
  48. package/ios-extension/Sources/BubblCore/Support/Locking.swift +11 -0
  49. package/ios-extension/Sources/BubblCore/Support/ValueStore.swift +109 -0
  50. package/ios-extension/Sources/BubblNotificationService/BubblNotificationService.swift +124 -0
  51. package/ios-sdk/BubblCore/API/ApiResponse.swift +90 -0
  52. package/ios-sdk/BubblCore/API/CredentialStore.swift +116 -0
  53. package/ios-sdk/BubblCore/API/DeviceApiClient.swift +239 -0
  54. package/ios-sdk/BubblCore/API/ErrorActions.swift +68 -0
  55. package/ios-sdk/BubblCore/API/Http.swift +39 -0
  56. package/ios-sdk/BubblCore/API/ServerClock.swift +69 -0
  57. package/ios-sdk/BubblCore/BubblVersion.swift +5 -0
  58. package/ios-sdk/BubblCore/Device/ConfigSync.swift +216 -0
  59. package/ios-sdk/BubblCore/Device/DeviceSync.swift +160 -0
  60. package/ios-sdk/BubblCore/Device/ProvisioningProfile.swift +24 -0
  61. package/ios-sdk/BubblCore/Engine/EngineCore.swift +523 -0
  62. package/ios-sdk/BubblCore/Engine/Geofencing.swift +170 -0
  63. package/ios-sdk/BubblCore/Engine/Permissions.swift +90 -0
  64. package/ios-sdk/BubblCore/Engine/Privacy.swift +64 -0
  65. package/ios-sdk/BubblCore/Engine/PrivacyControls.swift +184 -0
  66. package/ios-sdk/BubblCore/Engine/Pushes.swift +61 -0
  67. package/ios-sdk/BubblCore/Engine/Sandbox.swift +149 -0
  68. package/ios-sdk/BubblCore/Events/EventQueue.swift +207 -0
  69. package/ios-sdk/BubblCore/Events/EventStore.swift +159 -0
  70. package/ios-sdk/BubblCore/Geofence/Geofence.swift +160 -0
  71. package/ios-sdk/BubblCore/Geofence/GeofenceEngine.swift +488 -0
  72. package/ios-sdk/BubblCore/Geofence/GeofenceState.swift +103 -0
  73. package/ios-sdk/BubblCore/Geofence/RegionBudget.swift +62 -0
  74. package/ios-sdk/BubblCore/Notification/BubblNotification.swift +137 -0
  75. package/ios-sdk/BubblCore/Notification/NotificationEvents.swift +160 -0
  76. package/ios-sdk/BubblCore/Notification/Push.swift +134 -0
  77. package/ios-sdk/BubblCore/Notification/SurveyForm.swift +185 -0
  78. package/ios-sdk/BubblCore/RequestSigner.swift +56 -0
  79. package/ios-sdk/BubblCore/Support/AsyncMutex.swift +39 -0
  80. package/ios-sdk/BubblCore/Support/BubblLog.swift +52 -0
  81. package/ios-sdk/BubblCore/Support/JSON.swift +31 -0
  82. package/ios-sdk/BubblCore/Support/JSONValue.swift +94 -0
  83. package/ios-sdk/BubblCore/Support/Locking.swift +11 -0
  84. package/ios-sdk/BubblCore/Support/ValueStore.swift +109 -0
  85. package/ios-sdk/BubblLaunch/BubblLaunch.m +45 -0
  86. package/ios-sdk/BubblLaunch/include/BubblLaunch.h +5 -0
  87. package/ios-sdk/BubblSDK/Bubbl.swift +308 -0
  88. package/ios-sdk/BubblSDK/BubblBackend.swift +118 -0
  89. package/ios-sdk/BubblSDK/BubblCredential.swift +30 -0
  90. package/ios-sdk/BubblSDK/BubblMessage.swift +151 -0
  91. package/ios-sdk/BubblSDK/BubblOptions.swift +39 -0
  92. package/ios-sdk/BubblSDK/BubblPermissions.swift +79 -0
  93. package/ios-sdk/BubblSDK/BubblTestDeviceResult.swift +10 -0
  94. package/ios-sdk/BubblSDK/Engine/BackgroundRefresh.swift +92 -0
  95. package/ios-sdk/BubblSDK/Engine/EngineHost+Geofences.swift +177 -0
  96. package/ios-sdk/BubblSDK/Engine/EngineHost+Privacy.swift +65 -0
  97. package/ios-sdk/BubblSDK/Engine/EngineHost+Push.swift +154 -0
  98. package/ios-sdk/BubblSDK/Engine/EngineHost.swift +356 -0
  99. package/ios-sdk/BubblSDK/Engine/SupportedBubbl.swift +320 -0
  100. package/ios-sdk/BubblSDK/Engine/SystemLog.swift +27 -0
  101. package/ios-sdk/BubblSDK/Engine/WorkRunner.swift +107 -0
  102. package/ios-sdk/BubblSDK/Notification/Look.swift +78 -0
  103. package/ios-sdk/BubblSDK/Notification/NotificationMedia.swift +329 -0
  104. package/ios-sdk/BubblSDK/Notification/NotificationModel.swift +172 -0
  105. package/ios-sdk/BubblSDK/Notification/NotificationScreen.swift +428 -0
  106. package/ios-sdk/BubblSDK/Notification/ScreenPresenter.swift +148 -0
  107. package/ios-sdk/BubblSDK/Permissions/PermissionFlow.swift +169 -0
  108. package/ios-sdk/BubblSDK/Permissions/PrivacyView.swift +110 -0
  109. package/ios-sdk/BubblSDK/Platform/AppleDevicePlatform.swift +95 -0
  110. package/ios-sdk/BubblSDK/Platform/KeychainCredentialStore.swift +114 -0
  111. package/ios-sdk/BubblSDK/Platform/LocationService.swift +418 -0
  112. package/ios-sdk/BubblSDK/Platform/PushIntegration.swift +288 -0
  113. package/ios-sdk/BubblSDK/Platform/URLSessionHttpClient.swift +69 -0
  114. package/ios-sdk/BubblSDK/PrivacyInfo.xcprivacy +76 -0
  115. package/jest/index.js +75 -0
  116. package/lib/commonjs/BubblModule.js +14 -0
  117. package/lib/commonjs/BubblModule.js.map +1 -0
  118. package/lib/commonjs/BubblModule.web.js +13 -0
  119. package/lib/commonjs/BubblModule.web.js.map +1 -0
  120. package/lib/commonjs/NativeBubblSdk.js +20 -0
  121. package/lib/commonjs/NativeBubblSdk.js.map +1 -0
  122. package/lib/commonjs/index.js +336 -0
  123. package/lib/commonjs/index.js.map +1 -0
  124. package/lib/commonjs/package.json +1 -0
  125. package/lib/commonjs/types.js +2 -0
  126. package/lib/commonjs/types.js.map +1 -0
  127. package/lib/module/BubblModule.js +5 -0
  128. package/lib/module/BubblModule.js.map +1 -0
  129. package/lib/module/BubblModule.web.js +9 -0
  130. package/lib/module/BubblModule.web.js.map +1 -0
  131. package/lib/module/NativeBubblSdk.js +18 -0
  132. package/lib/module/NativeBubblSdk.js.map +1 -0
  133. package/lib/module/index.js +331 -0
  134. package/lib/module/index.js.map +1 -0
  135. package/lib/module/package.json +1 -0
  136. package/lib/module/types.js +2 -0
  137. package/lib/module/types.js.map +1 -0
  138. package/lib/typescript/commonjs/package.json +1 -0
  139. package/lib/typescript/commonjs/src/BubblModule.d.ts +3 -0
  140. package/lib/typescript/commonjs/src/BubblModule.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/src/BubblModule.web.d.ts +8 -0
  142. package/lib/typescript/commonjs/src/BubblModule.web.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/src/NativeBubblSdk.d.ts +59 -0
  144. package/lib/typescript/commonjs/src/NativeBubblSdk.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/src/index.d.ts +111 -0
  146. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/src/types.d.ts +192 -0
  148. package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
  149. package/lib/typescript/module/package.json +1 -0
  150. package/lib/typescript/module/src/BubblModule.d.ts +3 -0
  151. package/lib/typescript/module/src/BubblModule.d.ts.map +1 -0
  152. package/lib/typescript/module/src/BubblModule.web.d.ts +8 -0
  153. package/lib/typescript/module/src/BubblModule.web.d.ts.map +1 -0
  154. package/lib/typescript/module/src/NativeBubblSdk.d.ts +59 -0
  155. package/lib/typescript/module/src/NativeBubblSdk.d.ts.map +1 -0
  156. package/lib/typescript/module/src/index.d.ts +111 -0
  157. package/lib/typescript/module/src/index.d.ts.map +1 -0
  158. package/lib/typescript/module/src/types.d.ts +192 -0
  159. package/lib/typescript/module/src/types.d.ts.map +1 -0
  160. package/package.json +105 -24
  161. package/plugin/build/imageNotifications.d.ts +35 -0
  162. package/plugin/build/imageNotifications.js +198 -0
  163. package/plugin/build/index.d.ts +84 -0
  164. package/plugin/build/index.js +176 -0
  165. package/src/BubblModule.ts +2 -0
  166. package/src/BubblModule.web.ts +9 -0
  167. package/src/NativeBubblSdk.ts +67 -0
  168. package/src/index.ts +338 -282
  169. package/src/types.ts +182 -0
  170. package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkModule.kt +0 -680
  171. package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkNotificationIntents.kt +0 -32
  172. package/android/src/main/kotlin/tech/bubbl/reactnative/BubblSdkPackage.kt +0 -14
  173. package/ios/BubblSdk.swift +0 -802
  174. package/ios/BubblSdkBridge.m +0 -86
  175. package/react-native.config.js +0 -11
  176. package/src/specs/NativeBubblSdk.ts +0 -54
@@ -0,0 +1,160 @@
1
+ import Foundation
2
+
3
+ package enum SyncResult: Sendable, Equatable {
4
+ /// The server already has all of it.
5
+ case upToDate
6
+ /// Sent; `fields` are the ones that had changed.
7
+ case sent(fields: Set<String>)
8
+ case failed(ApiFailure)
9
+ }
10
+
11
+ /// Keeps the server's copy of the device (PUT /device) in step with the device itself: the OS and
12
+ /// app version, locale, the push token, permissions, consent. Only fields that changed since the
13
+ /// server last acknowledged them are sent (the contract leaves anything left out unchanged), so an
14
+ /// app open where nothing changed costs no request at all.
15
+ package final class DeviceSync: Sendable {
16
+ private let api: DeviceApiClient
17
+ /// What the server last acknowledged, as sent.
18
+ private let acknowledged: any ValueStore<[String: JSONValue]>
19
+ private let lock = AsyncMutex()
20
+
21
+ package init(api: DeviceApiClient, acknowledged: any ValueStore<[String: JSONValue]>) {
22
+ self.api = api
23
+ self.acknowledged = acknowledged
24
+ }
25
+
26
+ /// Send whatever in `attributes` the server doesn't have yet; everything when `force`.
27
+ package func sync(_ attributes: [String: JSONValue], force: Bool = false) async -> SyncResult {
28
+ await lock.withLock { await self.syncLocked(attributes, force: force) }
29
+ }
30
+
31
+ private func syncLocked(_ attributes: [String: JSONValue], force: Bool) async -> SyncResult {
32
+ let known: [String: JSONValue]?
33
+ do {
34
+ known = force ? nil : try acknowledged.load()
35
+ } catch {
36
+ return .failed(.backoff)
37
+ }
38
+ // JSONValue compares objects by content, so key order is never a change.
39
+ let changes = attributes.filter { key, value in known?[key] != value || known?.keys.contains(key) != true }
40
+ if changes.isEmpty { return .upToDate }
41
+
42
+ let response: ApiResponse
43
+ do {
44
+ let body = String(decoding: try JSONEncoder().encode(changes), as: UTF8.self)
45
+ response = try await api.request("PUT", "api/v1/device", body: body)
46
+ } catch {
47
+ return .failed(.backoff)
48
+ }
49
+ guard response.isSuccessful else { return .failed(ApiFailure.of(response)) }
50
+
51
+ var merged = known ?? [:]
52
+ merged.merge(changes) { _, new in new }
53
+ try? acknowledged.save(merged)
54
+ return .sent(fields: Set(changes.keys))
55
+ }
56
+
57
+ /// POST /installs has just sent `attributes` in full: the server has exactly those now.
58
+ package func registered(_ attributes: [String: JSONValue]) {
59
+ try? acknowledged.save(attributes)
60
+ }
61
+
62
+ /// Whether the server has ever acknowledged this device.
63
+ package var known: Bool { (try? acknowledged.load()) != nil }
64
+
65
+ /// The server lost track of the device (or it was erased): send everything next time.
66
+ package func forget() {
67
+ try? acknowledged.delete()
68
+ }
69
+
70
+ /// The server dropped `fields` (a push token Apple called dead): send them again next time,
71
+ /// and only them.
72
+ package func forget(_ fields: Set<String>) async {
73
+ await lock.withLock {
74
+ guard var known = try? self.acknowledged.load() else { return }
75
+ for field in fields { known.removeValue(forKey: field) }
76
+ try? self.acknowledged.save(known)
77
+ }
78
+ }
79
+ }
80
+
81
+ /// The device's segments as the app last set them, and whether the server has them yet
82
+ /// (PUT /device/segments replaces them whole). Registration sends them too.
83
+ package final class Segments: Sendable {
84
+ package static let maxSegments = 100
85
+ package static let maxLength = 255
86
+
87
+ package struct Saved: Codable, Sendable, Equatable {
88
+ package var segments: [String]
89
+ package var pending: Bool
90
+ }
91
+
92
+ private let store: any ValueStore<Saved>
93
+ private let lock = NSLock()
94
+ private let cache = Cached<Saved>()
95
+
96
+ package init(store: any ValueStore<Saved>) {
97
+ self.store = store
98
+ }
99
+
100
+ /// The segments the app set; nil if it never has.
101
+ package var current: [String]? { lock.sync { (try? load())??.segments } }
102
+
103
+ package var pending: Bool { lock.sync { (try? load())??.pending == true } }
104
+
105
+ /// Forget them (deleteMyData): the device starts again with none.
106
+ package func forget() throws {
107
+ try lock.sync {
108
+ try store.delete()
109
+ cache.set(nil)
110
+ }
111
+ }
112
+
113
+ private func load() throws -> Saved? {
114
+ try cache.value { try store.load() }
115
+ }
116
+
117
+ private func save(_ saved: Saved) throws {
118
+ try store.save(saved)
119
+ cache.set(saved)
120
+ }
121
+
122
+ /// Keeps `segments` (trimmed, de-duplicated, at most 100 of 255 characters); false if unchanged.
123
+ @discardableResult
124
+ package func set(_ segments: [String]) throws -> Bool {
125
+ var seen = Set<String>()
126
+ let clean = segments
127
+ .map { String($0.trimmingCharacters(in: .whitespacesAndNewlines).prefix(Self.maxLength)) }
128
+ .filter { !$0.isEmpty && seen.insert($0).inserted }
129
+ .prefix(Self.maxSegments)
130
+ return try lock.sync {
131
+ let saved = try load()
132
+ if saved?.segments == Array(clean), saved?.pending == false { return false }
133
+ try save(Saved(segments: Array(clean), pending: true))
134
+ return true
135
+ }
136
+ }
137
+
138
+ /// The server has `segments` now (unless the app changed them again meanwhile).
139
+ package func sent(_ segments: [String]) {
140
+ lock.sync {
141
+ guard (try? load())??.segments == segments else { return }
142
+ try? save(Saved(segments: segments, pending: false))
143
+ }
144
+ }
145
+
146
+ /// PUT /device/segments with the pending segments, if any.
147
+ package func push(_ api: DeviceApiClient) async -> SyncResult {
148
+ guard pending, let segments = current else { return .upToDate }
149
+ let response: ApiResponse
150
+ do {
151
+ let body = String(decoding: try JSONEncoder().encode(["segments": segments]), as: UTF8.self)
152
+ response = try await api.request("PUT", "api/v1/device/segments", body: body)
153
+ } catch {
154
+ return .failed(.backoff)
155
+ }
156
+ guard response.isSuccessful else { return .failed(ApiFailure.of(response)) }
157
+ sent(segments)
158
+ return .sent(fields: ["segments"])
159
+ }
160
+ }
@@ -0,0 +1,24 @@
1
+ import Foundation
2
+
3
+ /// What a build's provisioning profile (embedded.mobileprovision) says about push: which APNs
4
+ /// environment its device tokens belong to. A development-signed build's tokens are sandbox
5
+ /// tokens; ad hoc, TestFlight and App Store builds' are production ones. Sent with the token
6
+ /// (PUT /device apns_environment), since a token sent to the wrong environment is refused
7
+ /// (BadDeviceToken).
8
+ package enum ProvisioningProfile {
9
+ /// "sandbox" or "production" from the profile's aps-environment entitlement; nil when the data
10
+ /// isn't a profile or the app has no push entitlement.
11
+ ///
12
+ /// A profile is a CMS-signed file with its property list in plain text inside, so the plist is
13
+ /// cut out between "<?xml" and "</plist>" rather than verifying the signature (iOS did that
14
+ /// when it installed the app).
15
+ package static func apnsEnvironment(_ profile: Data) -> String? {
16
+ guard let start = profile.range(of: Data("<?xml".utf8)),
17
+ let end = profile.range(of: Data("</plist>".utf8), in: start.lowerBound..<profile.endIndex),
18
+ let plist = try? PropertyListSerialization.propertyList(from: profile.subdata(in: start.lowerBound..<end.upperBound), format: nil),
19
+ let entitlements = (plist as? [String: Any])?["Entitlements"] as? [String: Any],
20
+ let environment = entitlements["aps-environment"] as? String
21
+ else { return nil }
22
+ return environment == "development" ? "sandbox" : "production"
23
+ }
24
+ }