@bubblsdk/react-native-sdk 4.1.7 → 5.0.0-alpha.1
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 +47 -0
- 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 +77 -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 +318 -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 +167 -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
package/android/build.gradle
CHANGED
|
@@ -1,46 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// The TurboModule over the Bubbl Android SDK (tech.bubbl.sdk:bubbl-sdk). The app's build supplies
|
|
2
|
+
// the Android Gradle and React Native plugins; codegen generates NativeBubblSdkSpec from
|
|
3
|
+
// ../src/NativeBubblSdk.ts.
|
|
4
|
+
apply plugin: "com.android.library"
|
|
5
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
6
|
+
apply plugin: "com.facebook.react"
|
|
7
|
+
|
|
8
|
+
def ext(String name, fallback) {
|
|
9
|
+
rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
|
|
4
10
|
}
|
|
5
11
|
|
|
6
|
-
group = "tech.bubbl.reactnative"
|
|
7
|
-
version = "4.1.7"
|
|
8
|
-
|
|
9
|
-
def isNewArchitectureEnabled() {
|
|
10
|
-
project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (isNewArchitectureEnabled()) {
|
|
14
|
-
apply plugin: "com.facebook.react"
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
def safeExtGet(prop, fallback) {
|
|
18
|
-
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
def reactNativeVersion = safeExtGet("reactNativeVersion", "0.76.9")
|
|
22
12
|
|
|
23
13
|
android {
|
|
24
|
-
namespace
|
|
25
|
-
compileSdk
|
|
14
|
+
namespace "tech.bubbl.reactnative"
|
|
15
|
+
compileSdk ext("compileSdkVersion", 35)
|
|
26
16
|
|
|
27
17
|
defaultConfig {
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
// Android 6 and up install it; Bubbl runs on 8.1 (27) and up, and does nothing below.
|
|
19
|
+
minSdk Math.max(ext("minSdkVersion", 23) as int, 23)
|
|
30
20
|
}
|
|
31
21
|
|
|
32
22
|
compileOptions {
|
|
33
|
-
sourceCompatibility
|
|
34
|
-
targetCompatibility
|
|
23
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
24
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
35
25
|
}
|
|
26
|
+
}
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
|
29
|
+
compilerOptions {
|
|
30
|
+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
|
39
31
|
}
|
|
40
32
|
}
|
|
41
33
|
|
|
34
|
+
react {
|
|
35
|
+
jsRootDir = file("../src")
|
|
36
|
+
libraryName = "BubblSdkSpec"
|
|
37
|
+
codegenJavaPackageName = "tech.bubbl.reactnative"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
repositories {
|
|
41
|
+
google()
|
|
42
|
+
mavenCentral()
|
|
43
|
+
}
|
|
44
|
+
|
|
42
45
|
dependencies {
|
|
43
|
-
implementation "com.facebook.react:react-android
|
|
44
|
-
|
|
45
|
-
implementation "
|
|
46
|
+
implementation "com.facebook.react:react-android"
|
|
47
|
+
// The Bubbl Android SDK, the same version as this package (stamped by scripts/sync-version.mjs).
|
|
48
|
+
implementation "tech.bubbl.sdk:bubbl-sdk:5.0.0-alpha.1"
|
|
46
49
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
package tech.bubbl.reactnative
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
import tech.bubbl.sdk.Bubbl
|
|
6
|
+
import tech.bubbl.sdk.BubblEvent
|
|
7
|
+
import tech.bubbl.sdk.BubblMessage
|
|
8
|
+
import tech.bubbl.sdk.BubblOptions
|
|
9
|
+
import tech.bubbl.sdk.BubblPermissionStatus
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The JSON that crosses the bridge, in the shape the JS facade's types (src/types.ts) and the
|
|
13
|
+
* Flutter wrapper share: the SDK's public names in lower camel case, messages as the public
|
|
14
|
+
* message object with the contract JSON alongside, and events as {"type": "…", …}.
|
|
15
|
+
*/
|
|
16
|
+
internal object BubblJson {
|
|
17
|
+
/** start()'s options: {"baseUrl", "requireConsent"?, "logLevel"?, "segments"?}. */
|
|
18
|
+
fun options(json: String): BubblOptions? = runCatching {
|
|
19
|
+
val o = JSONObject(json)
|
|
20
|
+
BubblOptions(
|
|
21
|
+
baseUrl = o.getString("baseUrl"),
|
|
22
|
+
requireConsent = o.optBoolean("requireConsent", false),
|
|
23
|
+
logLevel = o.optString("logLevel").takeIf { it.isNotEmpty() }
|
|
24
|
+
?.let { BubblOptions.LogLevel.valueOf(it.uppercase()) } ?: BubblOptions.LogLevel.WARNING,
|
|
25
|
+
segments = o.optJSONArray("segments")?.let { array -> List(array.length()) { array.getString(it) } },
|
|
26
|
+
)
|
|
27
|
+
}.getOrNull()
|
|
28
|
+
|
|
29
|
+
/** The public message: its fields as the SDK works them out, plus the contract JSON to send back. */
|
|
30
|
+
fun message(message: BubblMessage): JSONObject = JSONObject()
|
|
31
|
+
.put("id", message.id)
|
|
32
|
+
.put("headline", message.headline)
|
|
33
|
+
.put("body", message.body)
|
|
34
|
+
.put("isSurvey", message.isSurvey)
|
|
35
|
+
.put("isSandbox", message.isSandbox)
|
|
36
|
+
.put("mediaType", message.mediaType ?: JSONObject.NULL)
|
|
37
|
+
.put("mediaUrl", message.mediaUrl ?: JSONObject.NULL)
|
|
38
|
+
.put("mediaThumbnailUrl", message.mediaThumbnailUrl ?: JSONObject.NULL)
|
|
39
|
+
.put("ctaLabel", message.ctaLabel ?: JSONObject.NULL)
|
|
40
|
+
.put("ctaUrl", message.ctaUrl ?: JSONObject.NULL)
|
|
41
|
+
.put("questions", JSONArray(message.questions.map { question ->
|
|
42
|
+
JSONObject()
|
|
43
|
+
.put("id", question.id)
|
|
44
|
+
.put("text", question.text)
|
|
45
|
+
.put("type", lowerCamel(question.type.name))
|
|
46
|
+
.put("required", question.required)
|
|
47
|
+
.put("choices", JSONArray(question.choices.map { JSONObject().put("id", it.id).put("text", it.text) }))
|
|
48
|
+
}))
|
|
49
|
+
.put("json", message.json)
|
|
50
|
+
|
|
51
|
+
fun event(event: BubblEvent): String? {
|
|
52
|
+
val (type, detail) = when (event) {
|
|
53
|
+
is BubblEvent.NotificationReceived -> "notification.received" to event.message
|
|
54
|
+
is BubblEvent.NotificationDisplayed -> "notification.displayed" to event.message
|
|
55
|
+
is BubblEvent.NotificationOpened -> "notification.opened" to event.message
|
|
56
|
+
is BubblEvent.NotificationCtaClicked -> "notification.cta_clicked" to event.message
|
|
57
|
+
is BubblEvent.NotificationDismissed -> "notification.dismissed" to event.message
|
|
58
|
+
is BubblEvent.MediaViewed -> "media.viewed" to event.message
|
|
59
|
+
is BubblEvent.MediaCompleted -> "media.completed" to event.message
|
|
60
|
+
is BubblEvent.SurveyStarted -> "survey.started" to event.message
|
|
61
|
+
is BubblEvent.SurveySubmitted -> "survey.submitted" to event.message
|
|
62
|
+
is BubblEvent.GeofenceEntered -> return JSONObject().put("type", "geofence.entered").put("locationId", event.locationId).toString()
|
|
63
|
+
is BubblEvent.GeofenceExited -> return JSONObject().put("type", "geofence.exited").put("locationId", event.locationId).toString()
|
|
64
|
+
is BubblEvent.Error -> return JSONObject().put("type", "error").put("message", event.message).toString()
|
|
65
|
+
is BubblEvent.CredentialRejected -> return JSONObject().put("type", "credential.rejected").toString()
|
|
66
|
+
else -> return null // One from a newer SDK that JS doesn't know yet.
|
|
67
|
+
}
|
|
68
|
+
return JSONObject().put("type", type).put("message", message(detail)).toString()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A notification held back for the app's listener (never a public event). */
|
|
72
|
+
/** A notification for the app's listener; JS answers with claimNotification / finishNotification(token). */
|
|
73
|
+
fun handle(token: String, message: BubblMessage): String =
|
|
74
|
+
JSONObject().put("type", "notification.handle").put("token", token).put("message", message(message)).toString()
|
|
75
|
+
|
|
76
|
+
/** Bubbl.TestDeviceResult: {approved, message}. */
|
|
77
|
+
fun testDeviceResult(result: Bubbl.TestDeviceResult): String =
|
|
78
|
+
JSONObject().put("approved", result.approved).put("message", result.message ?: JSONObject.NULL).toString()
|
|
79
|
+
|
|
80
|
+
fun error(message: String): String = JSONObject().put("type", "error").put("message", message).toString()
|
|
81
|
+
|
|
82
|
+
fun permissions(status: BubblPermissionStatus): String = permissionsObject(status).toString()
|
|
83
|
+
|
|
84
|
+
private fun permissionsObject(status: BubblPermissionStatus): JSONObject = JSONObject()
|
|
85
|
+
.put("notifications", lowerCamel(status.notifications.name))
|
|
86
|
+
.put("location", lowerCamel(status.location.name))
|
|
87
|
+
.put("preciseLocation", status.preciseLocation)
|
|
88
|
+
|
|
89
|
+
fun diagnostics(d: Bubbl.Diagnostics): String = JSONObject()
|
|
90
|
+
.put("started", d.started)
|
|
91
|
+
.put("sdkVersion", d.sdkVersion)
|
|
92
|
+
.put("supported", d.supported)
|
|
93
|
+
.put("sdkSupported", d.sdkSupported)
|
|
94
|
+
.put("registered", d.registered)
|
|
95
|
+
.put("installId", d.installId ?: JSONObject.NULL)
|
|
96
|
+
.put("active", d.active)
|
|
97
|
+
.put("pausedUntil", d.pausedUntil ?: JSONObject.NULL)
|
|
98
|
+
.put("consentRequired", d.consentRequired)
|
|
99
|
+
.put("consent", d.consent ?: JSONObject.NULL)
|
|
100
|
+
.put("locationEnabled", d.locationEnabled)
|
|
101
|
+
.put("permissions", d.permissions?.let(::permissionsObject) ?: JSONObject.NULL)
|
|
102
|
+
.put("hasPushToken", d.hasPushToken)
|
|
103
|
+
.put("geofences", d.geofences)
|
|
104
|
+
.put("queuedEvents", d.queuedEvents)
|
|
105
|
+
.put("lastError", d.lastError ?: JSONObject.NULL)
|
|
106
|
+
.put("credentialRejected", d.credentialRejected)
|
|
107
|
+
.put("environment", d.environment ?: JSONObject.NULL)
|
|
108
|
+
.put("testDevice", d.testDevice?.let { JSONObject().put("status", it.status).put("code", it.code ?: JSONObject.NULL) } ?: JSONObject.NULL)
|
|
109
|
+
.put("backgroundLocation", d.backgroundLocation)
|
|
110
|
+
.put("transitionsDroppedWhileLocked", d.transitionsDroppedWhileLocked)
|
|
111
|
+
.put("backgroundRestricted", d.backgroundRestricted)
|
|
112
|
+
.put("batteryOptimized", d.batteryOptimized)
|
|
113
|
+
// iOS's own, null here (as in the Flutter wrapper, so both send the same fields).
|
|
114
|
+
.put("backgroundRefreshAvailable", JSONObject.NULL)
|
|
115
|
+
.put("lowPowerMode", JSONObject.NULL)
|
|
116
|
+
.put("osWatchesGeofences", JSONObject.NULL)
|
|
117
|
+
.toString()
|
|
118
|
+
|
|
119
|
+
/** A JSON object as the map track() and submitSurvey() take: null for JSON null, lists for arrays. */
|
|
120
|
+
fun map(json: String): Map<String, Any?>? = runCatching {
|
|
121
|
+
val o = JSONObject(json)
|
|
122
|
+
o.keys().asSequence().associateWith { value(o.get(it)) }
|
|
123
|
+
}.getOrNull()
|
|
124
|
+
|
|
125
|
+
private fun value(value: Any?): Any? = when (value) {
|
|
126
|
+
JSONObject.NULL, null -> null
|
|
127
|
+
is JSONArray -> List(value.length()) { value(value.get(it)) }
|
|
128
|
+
is JSONObject -> value.keys().asSequence().associateWith { value(value.get(it)) }
|
|
129
|
+
else -> value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** OPEN_ENDED → openEnded, WHEN_IN_USE → whenInUse. */
|
|
133
|
+
private fun lowerCamel(name: String): String =
|
|
134
|
+
name.lowercase().split('_').mapIndexed { i, part -> if (i == 0) part else part.replaceFirstChar(Char::uppercaseChar) }.joinToString("")
|
|
135
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
package tech.bubbl.reactnative
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import androidx.lifecycle.Lifecycle
|
|
7
|
+
import androidx.lifecycle.LifecycleOwner
|
|
8
|
+
import com.facebook.react.bridge.Promise
|
|
9
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
10
|
+
import com.facebook.react.bridge.ReadableArray
|
|
11
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
12
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
13
|
+
import tech.bubbl.sdk.Bubbl
|
|
14
|
+
import tech.bubbl.sdk.BubblCredential
|
|
15
|
+
import tech.bubbl.sdk.BubblEventListener
|
|
16
|
+
import tech.bubbl.sdk.BubblMessage
|
|
17
|
+
import tech.bubbl.sdk.BubblNotificationListener
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The TurboModule the JS facade (src/index.ts) calls: each function passes straight to
|
|
21
|
+
* tech.bubbl.sdk.Bubbl, with JSON across the bridge (BubblJson). The SDK does the work, and keeps
|
|
22
|
+
* working without JS: it starts itself from its saved settings when a geofence or a push wakes the
|
|
23
|
+
* app, and shows its own notifications.
|
|
24
|
+
*
|
|
25
|
+
* Events go to JS on one device event (EVENT). The app's notification listener is consulted only
|
|
26
|
+
* while JS is listening and an activity of the app is resumed: then the notification goes to JS as
|
|
27
|
+
* "notification.handle" and the facade decides (showing it with present() unless the listener says
|
|
28
|
+
* the app shows it). In the background Bubbl posts its system notification as usual, so nothing is
|
|
29
|
+
* lost for want of a screen to draw on.
|
|
30
|
+
*/
|
|
31
|
+
@ReactModule(name = BubblSdkModule.NAME)
|
|
32
|
+
class BubblSdkModule(private val context: ReactApplicationContext) : NativeBubblSdkSpec(context) {
|
|
33
|
+
/** JS subscriptions to EVENT (NativeEventEmitter's bookkeeping). */
|
|
34
|
+
@Volatile private var jsListeners = 0
|
|
35
|
+
|
|
36
|
+
@Volatile private var notificationListenerEnabled = false
|
|
37
|
+
|
|
38
|
+
private val events = BubblEventListener { event -> BubblJson.event(event)?.let(::emit) }
|
|
39
|
+
|
|
40
|
+
private val notificationListener = BubblNotificationListener { message -> offerToApp(message) }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Notifications handed to JS and not yet settled, by token: JS claims one when it arrives and
|
|
44
|
+
* finishes it once the app's listener has decided. Only touched on the main thread.
|
|
45
|
+
*/
|
|
46
|
+
private val handedOver = mutableMapOf<String, Handover>()
|
|
47
|
+
private var nextToken = 0L
|
|
48
|
+
private val main = Handler(Looper.getMainLooper())
|
|
49
|
+
|
|
50
|
+
private class Handover(val message: BubblMessage, val timeout: Runnable)
|
|
51
|
+
|
|
52
|
+
init {
|
|
53
|
+
Bubbl.addEventListener(events)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun getName() = NAME
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* JS is going away (a reload, the host destroyed). Whatever it was handed and hadn't settled,
|
|
60
|
+
* Bubbl shows. The app's listener is cleared only if this instance set it: another React host
|
|
61
|
+
* (a headless one, a second screen) going away leaves it alone.
|
|
62
|
+
*/
|
|
63
|
+
override fun invalidate() {
|
|
64
|
+
Bubbl.removeEventListener(events)
|
|
65
|
+
UiThreadUtil.runOnUiThread {
|
|
66
|
+
handedOver.values.forEach { main.removeCallbacks(it.timeout); Bubbl.present(it.message) }
|
|
67
|
+
handedOver.clear()
|
|
68
|
+
if (owner === this) {
|
|
69
|
+
owner = null
|
|
70
|
+
Bubbl.setNotificationListener(null)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
notificationListenerEnabled = false
|
|
74
|
+
super.invalidate()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Starting -----------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
override fun isSupported(): Boolean = Bubbl.isSupported
|
|
80
|
+
|
|
81
|
+
/** A mistake (bad options, an empty key, an http:// URL) is logged and reported, never thrown. */
|
|
82
|
+
override fun start(apiKey: String, optionsJson: String) {
|
|
83
|
+
val options = BubblJson.options(optionsJson)
|
|
84
|
+
?: return mistake("Bubbl.start: options need a baseUrl (and logLevel none, error, warning, info or debug)")
|
|
85
|
+
try {
|
|
86
|
+
Bubbl.start(context.applicationContext, apiKey, options)
|
|
87
|
+
} catch (e: IllegalArgumentException) {
|
|
88
|
+
mistake(e.message ?: "Bubbl.start: invalid arguments")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The secret goes to the SDK and nowhere else: it arrives as its own argument (never inside
|
|
94
|
+
* JSON), and what's logged for a mistake is the SDK's message, which leaves it out.
|
|
95
|
+
*/
|
|
96
|
+
override fun startWithCredential(keyId: String, secret: String, installId: String, optionsJson: String) {
|
|
97
|
+
val options = BubblJson.options(optionsJson)
|
|
98
|
+
?: return mistake("Bubbl.startWithCredential: options need a baseUrl (and logLevel none, error, warning, info or debug)")
|
|
99
|
+
try {
|
|
100
|
+
Bubbl.start(context.applicationContext, BubblCredential(keyId, secret, installId), options)
|
|
101
|
+
} catch (e: IllegalArgumentException) {
|
|
102
|
+
mistake(e.message ?: "Bubbl.startWithCredential: invalid arguments")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun stop() = Bubbl.stop()
|
|
107
|
+
|
|
108
|
+
// --- Consent and privacy ------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
override fun setConsent(granted: Boolean) = Bubbl.setConsent(granted)
|
|
111
|
+
|
|
112
|
+
override fun optOut() = Bubbl.optOut()
|
|
113
|
+
|
|
114
|
+
override fun deleteMyData() = Bubbl.deleteMyData()
|
|
115
|
+
|
|
116
|
+
override fun setLocationEnabled(enabled: Boolean) = Bubbl.setLocationEnabled(enabled)
|
|
117
|
+
|
|
118
|
+
// --- Permissions --------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
override fun permissionStatus(promise: Promise) {
|
|
121
|
+
promise.resolve(Bubbl.permissions.status()?.let(BubblJson::permissions))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override fun requestNotifications(promise: Promise) {
|
|
125
|
+
Bubbl.permissions.requestNotifications { status -> promise.resolve(status?.let(BubblJson::permissions)) }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun requestLocation(always: Boolean, promise: Promise) {
|
|
129
|
+
Bubbl.permissions.requestLocation(always) { status -> promise.resolve(status?.let(BubblJson::permissions)) }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun openSettings() = Bubbl.permissions.openSettings()
|
|
133
|
+
|
|
134
|
+
// --- Targeting and events -----------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
override fun setSegments(segments: ReadableArray) {
|
|
137
|
+
Bubbl.setSegments(List(segments.size()) { segments.getString(it).orEmpty() }.filter { it.isNotEmpty() })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
override fun track(name: String, propertiesJson: String) {
|
|
141
|
+
Bubbl.track(name, BubblJson.map(propertiesJson) ?: return mistake("Bubbl.track: properties must be a JSON object"))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- Notifications ------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
override fun setNotificationListenerEnabled(enabled: Boolean) {
|
|
147
|
+
notificationListenerEnabled = enabled
|
|
148
|
+
// The SDK consults its listener on the main thread; swap it there too.
|
|
149
|
+
UiThreadUtil.runOnUiThread {
|
|
150
|
+
if (enabled) {
|
|
151
|
+
owner = this
|
|
152
|
+
Bubbl.setNotificationListener(notificationListener)
|
|
153
|
+
} else if (owner === this) {
|
|
154
|
+
owner = null
|
|
155
|
+
Bubbl.setNotificationListener(null)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** JS has the notification (the timeout that would show it is off). */
|
|
161
|
+
override fun claimNotification(token: String) {
|
|
162
|
+
UiThreadUtil.runOnUiThread {
|
|
163
|
+
handedOver[token]?.let { main.removeCallbacks(it.timeout) }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The app's listener decided: Bubbl shows the notification unless the app does. */
|
|
168
|
+
override fun finishNotification(token: String, appShowsIt: Boolean) {
|
|
169
|
+
UiThreadUtil.runOnUiThread {
|
|
170
|
+
val handover = handedOver.remove(token) ?: return@runOnUiThread
|
|
171
|
+
main.removeCallbacks(handover.timeout)
|
|
172
|
+
if (!appShowsIt) Bubbl.present(handover.message)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
override fun present(messageJson: String) = withMessage(messageJson, "present", Bubbl::present)
|
|
177
|
+
|
|
178
|
+
override fun reportDisplayed(messageJson: String) = withMessage(messageJson, "reportDisplayed", Bubbl::reportDisplayed)
|
|
179
|
+
|
|
180
|
+
override fun reportOpened(messageJson: String) = withMessage(messageJson, "reportOpened", Bubbl::reportOpened)
|
|
181
|
+
|
|
182
|
+
override fun reportCtaClicked(messageJson: String) = withMessage(messageJson, "reportCtaClicked", Bubbl::reportCtaClicked)
|
|
183
|
+
|
|
184
|
+
override fun reportDismissed(messageJson: String) = withMessage(messageJson, "reportDismissed", Bubbl::reportDismissed)
|
|
185
|
+
|
|
186
|
+
override fun reportMediaViewed(messageJson: String) = withMessage(messageJson, "reportMediaViewed", Bubbl::reportMediaViewed)
|
|
187
|
+
|
|
188
|
+
override fun reportMediaCompleted(messageJson: String, durationSeconds: Double) =
|
|
189
|
+
withMessage(messageJson, "reportMediaCompleted") { Bubbl.reportMediaCompleted(it, durationSeconds) }
|
|
190
|
+
|
|
191
|
+
override fun reportSurveyStarted(messageJson: String) = withMessage(messageJson, "reportSurveyStarted", Bubbl::reportSurveyStarted)
|
|
192
|
+
|
|
193
|
+
override fun submitSurvey(messageJson: String, answersJson: String, promise: Promise) {
|
|
194
|
+
val message = BubblMessage.fromJson(messageJson)
|
|
195
|
+
val answers = BubblJson.map(answersJson)
|
|
196
|
+
when {
|
|
197
|
+
message == null -> mistake("Bubbl.submitSurvey: that isn't a Bubbl message's json").also { promise.resolve(false) }
|
|
198
|
+
answers == null -> mistake("Bubbl.submitSurvey: answers must be a JSON object").also { promise.resolve(false) }
|
|
199
|
+
else -> promise.resolve(Bubbl.submitSurvey(message, answers))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
override fun openCta(messageJson: String) = withMessage(messageJson, "openCta", Bubbl::openCta)
|
|
204
|
+
|
|
205
|
+
// --- Sandbox ------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
/** Approve this device as a Sandbox test device with the dashboard's code: {approved, message} as JSON. */
|
|
208
|
+
override fun registerTestDevice(code: String, promise: Promise) {
|
|
209
|
+
Bubbl.registerTestDevice(code) { promise.resolve(BubblJson.testDeviceResult(it)) }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- Diagnostics --------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
override fun diagnostics(promise: Promise) {
|
|
215
|
+
Bubbl.diagnostics { promise.resolve(BubblJson.diagnostics(it)) }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// --- Events to JS -------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
override fun addListener(eventName: String) {
|
|
221
|
+
jsListeners++
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
override fun removeListeners(count: Double) {
|
|
225
|
+
jsListeners = (jsListeners - count.toInt()).coerceAtLeast(0)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The SDK's notification listener: hand [message] to JS only while the app can show it (JS is
|
|
230
|
+
* listening and an activity is resumed); otherwise Bubbl shows it itself.
|
|
231
|
+
*/
|
|
232
|
+
private fun offerToApp(message: BubblMessage): Boolean {
|
|
233
|
+
val activity = context.currentActivity as? LifecycleOwner
|
|
234
|
+
val inForeground = activity?.lifecycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true
|
|
235
|
+
if (!notificationListenerEnabled || !inForeground || !canEmit()) {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
// Handed over, it's JS's to settle; if JS doesn't claim it in time (reloading, stuck),
|
|
239
|
+
// Bubbl shows it: a notification is never lost between the two.
|
|
240
|
+
val token = (++nextToken).toString()
|
|
241
|
+
val timeout = Runnable { handedOver.remove(token)?.let { Bubbl.present(it.message) } }
|
|
242
|
+
handedOver[token] = Handover(message, timeout)
|
|
243
|
+
main.postDelayed(timeout, CLAIM_TIMEOUT_MS)
|
|
244
|
+
emit(BubblJson.handle(token, message))
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private fun canEmit(): Boolean = jsListeners > 0 && context.hasActiveReactInstance()
|
|
249
|
+
|
|
250
|
+
private fun emit(json: String) {
|
|
251
|
+
if (canEmit()) {
|
|
252
|
+
context.emitDeviceEvent(EVENT, json)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private inline fun withMessage(json: String, call: String, block: (BubblMessage) -> Unit) {
|
|
257
|
+
block(BubblMessage.fromJson(json) ?: return mistake("Bubbl.$call: that isn't a Bubbl message's json"))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** A developer's mistake: in Logcat (the SDK's tag) and to JS as an error event. */
|
|
261
|
+
private fun mistake(text: String) {
|
|
262
|
+
Log.e(LOG_TAG, text)
|
|
263
|
+
emit(BubblJson.error(text))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
companion object {
|
|
267
|
+
const val NAME = "BubblSdk"
|
|
268
|
+
|
|
269
|
+
/** How long JS has to claim a notification handed to it before Bubbl shows it. */
|
|
270
|
+
const val CLAIM_TIMEOUT_MS = 5_000L
|
|
271
|
+
|
|
272
|
+
/** The instance whose JS set the app's notification listener (main thread). */
|
|
273
|
+
private var owner: BubblSdkModule? = null
|
|
274
|
+
const val EVENT = "BubblSdkEvent"
|
|
275
|
+
private const val LOG_TAG = "BubblSDK"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package tech.bubbl.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
/** Registers the Bubbl TurboModule (found by React Native's autolinking). */
|
|
10
|
+
class BubblSdkPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
|
12
|
+
if (name == BubblSdkModule.NAME) BubblSdkModule(reactContext) else null
|
|
13
|
+
|
|
14
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
15
|
+
mapOf(
|
|
16
|
+
BubblSdkModule.NAME to ReactModuleInfo(
|
|
17
|
+
BubblSdkModule.NAME,
|
|
18
|
+
BubblSdkModule::class.java.name,
|
|
19
|
+
false, // canOverrideExistingModule
|
|
20
|
+
false, // needsEagerInit
|
|
21
|
+
false, // isCxxModule
|
|
22
|
+
true, // isTurboModule
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UserNotifications
|
|
3
|
+
|
|
4
|
+
/// The iOS SDK's three push hand-overs for an app with `BubblAutoIntegrationEnabled` = NO in
|
|
5
|
+
/// Info.plist, callable from an Objective-C AppDelegate too (the SDK's `Bubbl` is Swift only).
|
|
6
|
+
/// From Swift, `import BubblReactNativeSdk` and call `Bubbl.setPushToken(_:)` and the rest
|
|
7
|
+
/// directly; from Objective-C:
|
|
8
|
+
///
|
|
9
|
+
/// #import <UserNotifications/UserNotifications.h>
|
|
10
|
+
/// #import <BubblReactNativeSdk/BubblReactNativeSdk-Swift.h>
|
|
11
|
+
///
|
|
12
|
+
/// [BubblPush setPushToken:deviceToken];
|
|
13
|
+
/// if ([BubblPush willPresent:notification completionHandler:completionHandler]) return;
|
|
14
|
+
/// if ([BubblPush didReceive:response completionHandler:completionHandler]) return;
|
|
15
|
+
@objc(BubblPush)
|
|
16
|
+
public final class BubblPush: NSObject {
|
|
17
|
+
/// From `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`. Before start too.
|
|
18
|
+
@objc public static func setPushToken(_ deviceToken: Data) {
|
|
19
|
+
Bubbl.setPushToken(deviceToken)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// From `userNotificationCenter(_:willPresent:…)`: true when the push is Bubbl's, and Bubbl
|
|
23
|
+
/// calls `completionHandler`; false when it isn't, and the app decides.
|
|
24
|
+
@objc public static func willPresent(
|
|
25
|
+
_ notification: UNNotification,
|
|
26
|
+
completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
27
|
+
) -> Bool {
|
|
28
|
+
Bubbl.willPresent(notification, completionHandler: completionHandler)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// From `userNotificationCenter(_:didReceive:…)`: true when the tapped notification is
|
|
32
|
+
/// Bubbl's, and Bubbl calls `completionHandler`; false when it isn't.
|
|
33
|
+
@objc public static func didReceive(
|
|
34
|
+
_ response: UNNotificationResponse,
|
|
35
|
+
completionHandler: @escaping () -> Void
|
|
36
|
+
) -> Bool {
|
|
37
|
+
Bubbl.didReceive(response, completionHandler: completionHandler)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Whether a push's payload is Bubbl's, so the app's own handling can leave it alone.
|
|
41
|
+
@objc public static func isBubblMessage(_ userInfo: [AnyHashable: Any]) -> Bool {
|
|
42
|
+
Bubbl.isBubblMessage(userInfo)
|
|
43
|
+
}
|
|
44
|
+
}
|
package/ios/BubblSdk.h
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#import <React/RCTEventEmitter.h>
|
|
2
|
+
#import <BubblSdkSpec/BubblSdkSpec.h>
|
|
3
|
+
|
|
4
|
+
/// The Bubbl TurboModule (NativeBubblSdk.ts's spec): an event emitter for BubblSdkEvent, passing
|
|
5
|
+
/// every call to BubblSdkImpl (Swift), which drives the iOS SDK.
|
|
6
|
+
@interface BubblSdk : RCTEventEmitter <NativeBubblSdkSpec>
|
|
7
|
+
@end
|