@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,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
|
+
}
|