zixflow-reactnative 1.1.2 → 1.1.4
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/README.md +2224 -322
- package/android/build.gradle +18 -1
- package/android/consumer-rules.pro +3 -2
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/zixflow/reactnative/sdk/NativeZixflowModule.kt +69 -24
- package/android/src/main/java/com/zixflow/reactnative/sdk/ZixflowReactNativePackage.kt +25 -1
- package/android/src/main/java/com/zixflow/reactnative/sdk/constant/Keys.kt +19 -3
- package/android/src/main/java/com/zixflow/reactnative/sdk/geofence/NativeGeofenceModule.kt +65 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/liveactivities/NativeLiveActivitiesModule.kt +310 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/logging/NativeZixflowLoggingModule.kt +2 -2
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginapp/NativeMessagingInAppModule.kt +149 -10
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginapp/ReactInboxEventListener.kt +99 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/NotificationInboxBellViewManager.kt +38 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/NotificationInboxViewManager.kt +30 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactComposeHostView.kt +96 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactNotificationInboxBellView.kt +48 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/messaginginbox/ReactNotificationInboxView.kt +17 -0
- package/android/src/main/java/com/zixflow/reactnative/sdk/messagingpush/NativeMessagingPushModule.kt +11 -1
- package/android/src/main/res/values/zixflow_config.xml +1 -1
- package/android/zixflow-core.gradle +17 -7
- package/docs/cocoapods-deployment-target-normalization.md +139 -0
- package/ios/cocoapods_deployment_target.rb +432 -0
- package/ios/wrappers/NativeZixflow.mm +37 -0
- package/ios/wrappers/NativeZixflow.swift +135 -1
- package/ios/wrappers/ZixflowReactNativeDeepLinkRequestStore.swift +123 -0
- package/ios/wrappers/ZixflowReactNativeDeepLinkRouter.swift +312 -0
- package/ios/wrappers/geofence/NativeGeofence.mm +46 -0
- package/ios/wrappers/geofence/NativeGeofence.swift +27 -0
- package/ios/wrappers/inapp/NativeMessagingInApp.mm +22 -0
- package/ios/wrappers/inapp/NativeMessagingInApp.swift +90 -0
- package/ios/wrappers/inbox/InboxHostContainment.swift +40 -0
- package/ios/wrappers/inbox/RCTNotificationInboxBellNative.h +12 -0
- package/ios/wrappers/inbox/RCTNotificationInboxBellNative.mm +98 -0
- package/ios/wrappers/inbox/RCTNotificationInboxViewNative.h +12 -0
- package/ios/wrappers/inbox/RCTNotificationInboxViewNative.mm +78 -0
- package/ios/wrappers/inbox/ReactInboxEventListener.swift +76 -0
- package/ios/wrappers/inbox/ReactNotificationInboxBellView.h +22 -0
- package/ios/wrappers/inbox/ReactNotificationInboxBellView.swift +109 -0
- package/ios/wrappers/inbox/ReactNotificationInboxView.h +22 -0
- package/ios/wrappers/inbox/ReactNotificationInboxView.swift +69 -0
- package/ios/wrappers/liveactivities/NativeLiveActivities.mm +83 -0
- package/ios/wrappers/liveactivities/NativeLiveActivities.swift +390 -0
- package/ios/wrappers/location/NativeLocation.swift +7 -5
- package/ios/wrappers/utils/ZixflowConfigUtils.swift +5 -9
- package/ios/wrappers/utils/ZixflowConstants.swift +4 -0
- package/ios/wrappers/zixflow_reactnative.h +6 -0
- package/lib/commonjs/components/NotificationInboxBellView.js +49 -0
- package/lib/commonjs/components/NotificationInboxBellView.js.map +1 -0
- package/lib/commonjs/components/NotificationInboxView.js +36 -0
- package/lib/commonjs/components/NotificationInboxView.js.map +1 -0
- package/lib/commonjs/components/index.js +14 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/index.js +81 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/notification-inbox.js +1 -0
- package/lib/commonjs/notification-inbox.js.map +1 -1
- package/lib/commonjs/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
- package/lib/commonjs/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
- package/lib/commonjs/specs/modules/NativeZixflow.js.map +1 -1
- package/lib/commonjs/specs/modules/NativeZixflowGeofence.js +15 -0
- package/lib/commonjs/specs/modules/NativeZixflowGeofence.js.map +1 -0
- package/lib/commonjs/specs/modules/NativeZixflowLiveActivities.js +23 -0
- package/lib/commonjs/specs/modules/NativeZixflowLiveActivities.js.map +1 -0
- package/lib/commonjs/specs/modules/NativeZixflowMessagingInApp.js +3 -1
- package/lib/commonjs/specs/modules/NativeZixflowMessagingInApp.js.map +1 -1
- package/lib/commonjs/specs/modules/NativeZixflowMessagingPush.js +3 -1
- package/lib/commonjs/specs/modules/NativeZixflowMessagingPush.js.map +1 -1
- package/lib/commonjs/types/data-pipelines.js +15 -15
- package/lib/commonjs/types/data-pipelines.js.map +1 -1
- package/lib/commonjs/types/in-app.js +24 -1
- package/lib/commonjs/types/in-app.js.map +1 -1
- package/lib/commonjs/types/inbox.js +79 -0
- package/lib/commonjs/types/inbox.js.map +1 -0
- package/lib/commonjs/types/index.js +22 -0
- package/lib/commonjs/types/index.js.map +1 -1
- package/lib/commonjs/types/live-activities.js +85 -0
- package/lib/commonjs/types/live-activities.js.map +1 -0
- package/lib/commonjs/utils/param-validation.js +77 -2
- package/lib/commonjs/utils/param-validation.js.map +1 -1
- package/lib/commonjs/zixflow-cdp.js +101 -11
- package/lib/commonjs/zixflow-cdp.js.map +1 -1
- package/lib/commonjs/zixflow-geofence.js +55 -0
- package/lib/commonjs/zixflow-geofence.js.map +1 -0
- package/lib/commonjs/zixflow-inapp.js +120 -0
- package/lib/commonjs/zixflow-inapp.js.map +1 -1
- package/lib/commonjs/zixflow-liveactivities.js +85 -0
- package/lib/commonjs/zixflow-liveactivities.js.map +1 -0
- package/lib/commonjs/zixflow.js.map +1 -1
- package/lib/module/components/NotificationInboxBellView.js +44 -0
- package/lib/module/components/NotificationInboxBellView.js.map +1 -0
- package/lib/module/components/NotificationInboxView.js +31 -0
- package/lib/module/components/NotificationInboxView.js.map +1 -0
- package/lib/module/components/index.js +5 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/index.js +8 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/notification-inbox.js +1 -1
- package/lib/module/notification-inbox.js.map +1 -1
- package/lib/module/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
- package/lib/module/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
- package/lib/module/specs/modules/NativeZixflow.js.map +1 -1
- package/lib/module/specs/modules/NativeZixflowGeofence.js +13 -0
- package/lib/module/specs/modules/NativeZixflowGeofence.js.map +1 -0
- package/lib/module/specs/modules/NativeZixflowLiveActivities.js +21 -0
- package/lib/module/specs/modules/NativeZixflowLiveActivities.js.map +1 -0
- package/lib/module/specs/modules/NativeZixflowMessagingInApp.js +3 -1
- package/lib/module/specs/modules/NativeZixflowMessagingInApp.js.map +1 -1
- package/lib/module/specs/modules/NativeZixflowMessagingPush.js +3 -1
- package/lib/module/specs/modules/NativeZixflowMessagingPush.js.map +1 -1
- package/lib/module/types/data-pipelines.js +15 -15
- package/lib/module/types/data-pipelines.js.map +1 -1
- package/lib/module/types/in-app.js +24 -0
- package/lib/module/types/in-app.js.map +1 -1
- package/lib/module/types/inbox.js +75 -0
- package/lib/module/types/inbox.js.map +1 -0
- package/lib/module/types/index.js +2 -0
- package/lib/module/types/index.js.map +1 -1
- package/lib/module/types/live-activities.js +88 -0
- package/lib/module/types/live-activities.js.map +1 -0
- package/lib/module/utils/param-validation.js +77 -2
- package/lib/module/utils/param-validation.js.map +1 -1
- package/lib/module/zixflow-cdp.js +101 -11
- package/lib/module/zixflow-cdp.js.map +1 -1
- package/lib/module/zixflow-geofence.js +50 -0
- package/lib/module/zixflow-geofence.js.map +1 -0
- package/lib/module/zixflow-inapp.js +99 -1
- package/lib/module/zixflow-inapp.js.map +1 -1
- package/lib/module/zixflow-liveactivities.js +80 -0
- package/lib/module/zixflow-liveactivities.js.map +1 -0
- package/lib/module/zixflow.js +0 -6
- package/lib/module/zixflow.js.map +1 -1
- package/lib/typescript/commonjs/components/NotificationInboxBellView.d.ts +39 -0
- package/lib/typescript/commonjs/components/NotificationInboxBellView.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/NotificationInboxView.d.ts +25 -0
- package/lib/typescript/commonjs/components/NotificationInboxView.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/index.d.ts +4 -0
- package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +8 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/notification-inbox.d.ts +5 -0
- package/lib/typescript/commonjs/notification-inbox.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/components/NotificationInboxBellNativeComponent.d.ts +13 -0
- package/lib/typescript/commonjs/specs/components/NotificationInboxBellNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/components/NotificationInboxViewNativeComponent.d.ts +12 -0
- package/lib/typescript/commonjs/specs/components/NotificationInboxViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/modules/NativeZixflow.d.ts +6 -1
- package/lib/typescript/commonjs/specs/modules/NativeZixflow.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/modules/NativeZixflowGeofence.d.ts +13 -0
- package/lib/typescript/commonjs/specs/modules/NativeZixflowGeofence.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/modules/NativeZixflowLiveActivities.d.ts +31 -0
- package/lib/typescript/commonjs/specs/modules/NativeZixflowLiveActivities.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingInApp.d.ts +5 -1
- package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingInApp.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingPush.d.ts +1 -1
- package/lib/typescript/commonjs/specs/modules/NativeZixflowMessagingPush.d.ts.map +1 -1
- package/lib/typescript/commonjs/types/data-pipelines.d.ts +54 -18
- package/lib/typescript/commonjs/types/data-pipelines.d.ts.map +1 -1
- package/lib/typescript/commonjs/types/in-app.d.ts +22 -0
- package/lib/typescript/commonjs/types/in-app.d.ts.map +1 -1
- package/lib/typescript/commonjs/types/inbox.d.ts +97 -0
- package/lib/typescript/commonjs/types/inbox.d.ts.map +1 -0
- package/lib/typescript/commonjs/types/index.d.ts +2 -0
- package/lib/typescript/commonjs/types/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/types/live-activities.d.ts +161 -0
- package/lib/typescript/commonjs/types/live-activities.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/param-validation.d.ts +7 -0
- package/lib/typescript/commonjs/utils/param-validation.d.ts.map +1 -1
- package/lib/typescript/commonjs/zixflow-cdp.d.ts +38 -0
- package/lib/typescript/commonjs/zixflow-cdp.d.ts.map +1 -1
- package/lib/typescript/commonjs/zixflow-geofence.d.ts +36 -0
- package/lib/typescript/commonjs/zixflow-geofence.d.ts.map +1 -0
- package/lib/typescript/commonjs/zixflow-inapp.d.ts +36 -2
- package/lib/typescript/commonjs/zixflow-inapp.d.ts.map +1 -1
- package/lib/typescript/commonjs/zixflow-liveactivities.d.ts +66 -0
- package/lib/typescript/commonjs/zixflow-liveactivities.d.ts.map +1 -0
- package/lib/typescript/commonjs/zixflow.d.ts +0 -5
- package/lib/typescript/commonjs/zixflow.d.ts.map +1 -1
- package/lib/typescript/module/components/NotificationInboxBellView.d.ts +39 -0
- package/lib/typescript/module/components/NotificationInboxBellView.d.ts.map +1 -0
- package/lib/typescript/module/components/NotificationInboxView.d.ts +25 -0
- package/lib/typescript/module/components/NotificationInboxView.d.ts.map +1 -0
- package/lib/typescript/module/components/index.d.ts +4 -0
- package/lib/typescript/module/components/index.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +8 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/notification-inbox.d.ts +5 -0
- package/lib/typescript/module/notification-inbox.d.ts.map +1 -1
- package/lib/typescript/module/specs/components/NotificationInboxBellNativeComponent.d.ts +13 -0
- package/lib/typescript/module/specs/components/NotificationInboxBellNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/specs/components/NotificationInboxViewNativeComponent.d.ts +12 -0
- package/lib/typescript/module/specs/components/NotificationInboxViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/specs/modules/NativeZixflow.d.ts +6 -1
- package/lib/typescript/module/specs/modules/NativeZixflow.d.ts.map +1 -1
- package/lib/typescript/module/specs/modules/NativeZixflowGeofence.d.ts +13 -0
- package/lib/typescript/module/specs/modules/NativeZixflowGeofence.d.ts.map +1 -0
- package/lib/typescript/module/specs/modules/NativeZixflowLiveActivities.d.ts +31 -0
- package/lib/typescript/module/specs/modules/NativeZixflowLiveActivities.d.ts.map +1 -0
- package/lib/typescript/module/specs/modules/NativeZixflowMessagingInApp.d.ts +5 -1
- package/lib/typescript/module/specs/modules/NativeZixflowMessagingInApp.d.ts.map +1 -1
- package/lib/typescript/module/specs/modules/NativeZixflowMessagingPush.d.ts +1 -1
- package/lib/typescript/module/specs/modules/NativeZixflowMessagingPush.d.ts.map +1 -1
- package/lib/typescript/module/types/data-pipelines.d.ts +54 -18
- package/lib/typescript/module/types/data-pipelines.d.ts.map +1 -1
- package/lib/typescript/module/types/in-app.d.ts +22 -0
- package/lib/typescript/module/types/in-app.d.ts.map +1 -1
- package/lib/typescript/module/types/inbox.d.ts +97 -0
- package/lib/typescript/module/types/inbox.d.ts.map +1 -0
- package/lib/typescript/module/types/index.d.ts +2 -0
- package/lib/typescript/module/types/index.d.ts.map +1 -1
- package/lib/typescript/module/types/live-activities.d.ts +161 -0
- package/lib/typescript/module/types/live-activities.d.ts.map +1 -0
- package/lib/typescript/module/utils/param-validation.d.ts +7 -0
- package/lib/typescript/module/utils/param-validation.d.ts.map +1 -1
- package/lib/typescript/module/zixflow-cdp.d.ts +38 -0
- package/lib/typescript/module/zixflow-cdp.d.ts.map +1 -1
- package/lib/typescript/module/zixflow-geofence.d.ts +36 -0
- package/lib/typescript/module/zixflow-geofence.d.ts.map +1 -0
- package/lib/typescript/module/zixflow-inapp.d.ts +36 -2
- package/lib/typescript/module/zixflow-inapp.d.ts.map +1 -1
- package/lib/typescript/module/zixflow-liveactivities.d.ts +66 -0
- package/lib/typescript/module/zixflow-liveactivities.d.ts.map +1 -0
- package/lib/typescript/module/zixflow.d.ts +0 -5
- package/lib/typescript/module/zixflow.d.ts.map +1 -1
- package/package.json +14 -5
- package/src/components/NotificationInboxBellView.tsx +55 -0
- package/src/components/NotificationInboxView.tsx +30 -0
- package/src/components/index.ts +6 -0
- package/src/index.ts +8 -3
- package/src/notification-inbox.ts +1 -1
- package/src/specs/components/NotificationInboxBellNativeComponent.ts +24 -0
- package/src/specs/components/NotificationInboxViewNativeComponent.ts +22 -0
- package/src/specs/modules/NativeZixflow.ts +9 -1
- package/src/specs/modules/NativeZixflowGeofence.ts +14 -0
- package/src/specs/modules/NativeZixflowLiveActivities.ts +39 -0
- package/src/specs/modules/NativeZixflowMessagingInApp.ts +12 -1
- package/src/specs/modules/NativeZixflowMessagingPush.ts +3 -1
- package/src/types/data-pipelines.ts +55 -19
- package/src/types/in-app.ts +23 -0
- package/src/types/inbox.ts +110 -0
- package/src/types/index.ts +2 -0
- package/src/types/live-activities.ts +170 -0
- package/src/utils/param-validation.ts +104 -1
- package/src/zixflow-cdp.ts +119 -13
- package/src/zixflow-geofence.ts +56 -0
- package/src/zixflow-inapp.ts +125 -2
- package/src/zixflow-liveactivities.ts +86 -0
- package/src/zixflow.ts +0 -6
- package/zixflow-reactnative-richpush.podspec +6 -3
- package/zixflow-reactnative.podspec +28 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
struct ZixflowReactNativeDeepLinkRequestStore {
|
|
4
|
+
enum Acceptance: Equatable {
|
|
5
|
+
case buffered(UUID)
|
|
6
|
+
case linking(URL)
|
|
7
|
+
case handler(UUID, URL)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
enum Resolution: Equatable {
|
|
11
|
+
case handled
|
|
12
|
+
case fallback(URL)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private enum DeliveryMode: Equatable {
|
|
16
|
+
case unavailable
|
|
17
|
+
case linking
|
|
18
|
+
case handler
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private enum RequestState: Equatable {
|
|
22
|
+
case buffered
|
|
23
|
+
case awaitingAcknowledgement
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private struct Request {
|
|
27
|
+
let id: UUID
|
|
28
|
+
let url: URL
|
|
29
|
+
var state: RequestState
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private var isLinkingReady = false
|
|
33
|
+
private var requiresHandler = false
|
|
34
|
+
private var hasHandler = false
|
|
35
|
+
private var requests: [Request] = []
|
|
36
|
+
|
|
37
|
+
private var deliveryMode: DeliveryMode {
|
|
38
|
+
if hasHandler {
|
|
39
|
+
return .handler
|
|
40
|
+
}
|
|
41
|
+
if !requiresHandler, isLinkingReady {
|
|
42
|
+
return .linking
|
|
43
|
+
}
|
|
44
|
+
return .unavailable
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var requiresAcknowledgedHandler: Bool {
|
|
48
|
+
requiresHandler
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var canDeliverWithLinking: Bool {
|
|
52
|
+
isLinkingReady
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
mutating func requireAcknowledgedHandler() {
|
|
56
|
+
requiresHandler = true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
mutating func accept(_ url: URL) -> Acceptance {
|
|
60
|
+
switch deliveryMode {
|
|
61
|
+
case .unavailable:
|
|
62
|
+
let request = Request(id: UUID(), url: url, state: .buffered)
|
|
63
|
+
requests.append(request)
|
|
64
|
+
return .buffered(request.id)
|
|
65
|
+
case .linking:
|
|
66
|
+
return .linking(url)
|
|
67
|
+
case .handler:
|
|
68
|
+
let request = Request(id: UUID(), url: url, state: .awaitingAcknowledgement)
|
|
69
|
+
requests.append(request)
|
|
70
|
+
return .handler(request.id, request.url)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
mutating func useLinking() -> [URL] {
|
|
75
|
+
isLinkingReady = true
|
|
76
|
+
guard deliveryMode == .linking else { return [] }
|
|
77
|
+
|
|
78
|
+
let urls = requests.compactMap { request in
|
|
79
|
+
request.state == .buffered ? request.url : nil
|
|
80
|
+
}
|
|
81
|
+
requests.removeAll { $0.state == .buffered }
|
|
82
|
+
return urls
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
mutating func useHandler() -> [(UUID, URL)] {
|
|
86
|
+
hasHandler = true
|
|
87
|
+
var deliveries: [(UUID, URL)] = []
|
|
88
|
+
for index in requests.indices where requests[index].state == .buffered {
|
|
89
|
+
requests[index].state = .awaitingAcknowledgement
|
|
90
|
+
deliveries.append((requests[index].id, requests[index].url))
|
|
91
|
+
}
|
|
92
|
+
return deliveries
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
mutating func removeHandler() {
|
|
96
|
+
hasHandler = false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
mutating func acknowledge(_ id: UUID, handled: Bool) -> Resolution? {
|
|
100
|
+
guard let index = requests.firstIndex(where: {
|
|
101
|
+
$0.id == id && $0.state == .awaitingAcknowledgement
|
|
102
|
+
}) else { return nil }
|
|
103
|
+
|
|
104
|
+
let url = requests.remove(at: index).url
|
|
105
|
+
return handled ? .handled : .fallback(url)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
mutating func expireReadiness(_ id: UUID) -> URL? {
|
|
109
|
+
expire(id, expectedState: .buffered)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
mutating func expireAcknowledgement(_ id: UUID) -> URL? {
|
|
113
|
+
expire(id, expectedState: .awaitingAcknowledgement)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private mutating func expire(_ id: UUID, expectedState: RequestState) -> URL? {
|
|
117
|
+
guard let index = requests.firstIndex(where: {
|
|
118
|
+
$0.id == id && $0.state == expectedState
|
|
119
|
+
}) else { return nil }
|
|
120
|
+
|
|
121
|
+
return requests.remove(at: index).url
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
@_spi(Internal) import ZixflowInternalCommon
|
|
2
|
+
import Foundation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
enum ZixflowReactNativeDeepLinkRouter {
|
|
6
|
+
private static let readinessTimeout: TimeInterval = 10
|
|
7
|
+
private static let acknowledgementTimeout: TimeInterval = 10
|
|
8
|
+
private static let sceneManifestKey = "UIApplicationSceneManifest"
|
|
9
|
+
private static let sceneConfigurationsKey = "UISceneConfigurations"
|
|
10
|
+
private static let sceneOpenURLContextsSelector = NSSelectorFromString("scene:openURLContexts:")
|
|
11
|
+
// React Native 0.83.6 and 0.88.0-nightly-20260823-0c7f63a4e use this
|
|
12
|
+
// notification name and payload for Linking URL events.
|
|
13
|
+
private static let openURLNotification = Notification.Name("RCTOpenURLNotification")
|
|
14
|
+
private static let stateLock = NSLock()
|
|
15
|
+
private static var requestStore = ZixflowReactNativeDeepLinkRequestStore()
|
|
16
|
+
private static var handlerEmitter: ((_ id: String, _ url: String) -> Void)?
|
|
17
|
+
private static var handlerToken: UUID?
|
|
18
|
+
|
|
19
|
+
static var requiresAcknowledgedHandler: Bool {
|
|
20
|
+
stateLock.lock()
|
|
21
|
+
defer { stateLock.unlock() }
|
|
22
|
+
return requestStore.requiresAcknowledgedHandler
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private static var hasSceneManifest: Bool {
|
|
26
|
+
guard let manifest = Bundle.main.object(forInfoDictionaryKey: sceneManifestKey) as? [String: Any],
|
|
27
|
+
let configurations = manifest[sceneConfigurationsKey] as? [String: Any]
|
|
28
|
+
else { return false }
|
|
29
|
+
|
|
30
|
+
return !configurations.isEmpty
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Mirrors `RCTIsSceneDelegateApp()` without linking to the internal symbol, and also requires
|
|
34
|
+
/// React Native's scene-Linking entry point. A scene manifest alone does not make older React
|
|
35
|
+
/// Native versions scene-aware.
|
|
36
|
+
static var isSceneLifecycleEnabled: Bool {
|
|
37
|
+
guard hasSceneManifest,
|
|
38
|
+
let linkingManager = NSClassFromString("RCTLinkingManager")
|
|
39
|
+
else { return false }
|
|
40
|
+
|
|
41
|
+
return linkingManager.responds(to: sceneOpenURLContextsSelector)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Expo owns scene-to-Linking forwarding even when its React Native version does not expose
|
|
45
|
+
/// the scene selector itself.
|
|
46
|
+
static var isExpoSceneLifecycleEnabled: Bool {
|
|
47
|
+
hasSceneManifest && NSClassFromString("EXExpoAppSceneDelegate") != nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static func install() {
|
|
51
|
+
guard isSceneLifecycleEnabled else { return }
|
|
52
|
+
installCallback(requiringAcknowledgedHandler: false)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static func installAcknowledgedHandler() {
|
|
56
|
+
guard isSceneLifecycleEnabled else {
|
|
57
|
+
DIGraphShared.shared.logger.error(
|
|
58
|
+
"Zixflow could not install acknowledged deep-link routing because the host " +
|
|
59
|
+
"does not expose a supported React Native scene lifecycle"
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
installCallback(requiringAcknowledgedHandler: true)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Expo owns scene-to-Linking forwarding even on React Native versions that do not expose the
|
|
67
|
+
/// scene selector themselves. Prefer the generic React Native capability when available, then
|
|
68
|
+
/// fall back to Expo's scene delegate capability.
|
|
69
|
+
static func installForExpoSceneLifecycle() {
|
|
70
|
+
guard isSceneLifecycleEnabled || isExpoSceneLifecycleEnabled else {
|
|
71
|
+
DIGraphShared.shared.logger.error(
|
|
72
|
+
"Zixflow could not install Expo scene deep-link routing because the host " +
|
|
73
|
+
"does not expose a supported React Native or Expo scene lifecycle"
|
|
74
|
+
)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
installCallback(requiringAcknowledgedHandler: false)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static func installCallback(requiringAcknowledgedHandler: Bool) {
|
|
81
|
+
stateLock.lock()
|
|
82
|
+
if requiringAcknowledgedHandler {
|
|
83
|
+
requestStore.requireAcknowledgedHandler()
|
|
84
|
+
}
|
|
85
|
+
stateLock.unlock()
|
|
86
|
+
DIGraphShared.shared.deepLinkUtil.setDeepLinkCallback { url in
|
|
87
|
+
accept(url)
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static func accept(_ url: URL) {
|
|
93
|
+
stateLock.lock()
|
|
94
|
+
let acceptance = requestStore.accept(url)
|
|
95
|
+
stateLock.unlock()
|
|
96
|
+
|
|
97
|
+
switch acceptance {
|
|
98
|
+
case let .buffered(id):
|
|
99
|
+
DIGraphShared.shared.logger.info(
|
|
100
|
+
"Zixflow buffered an SDK deep link until a React Native route is ready"
|
|
101
|
+
)
|
|
102
|
+
scheduleReadinessExpiration(for: id)
|
|
103
|
+
case let .linking(url):
|
|
104
|
+
route(url)
|
|
105
|
+
case let .handler(id, url):
|
|
106
|
+
publishToHandler(id: id, url: url)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static func markReactNativeReady() {
|
|
111
|
+
let markReady = {
|
|
112
|
+
stateLock.lock()
|
|
113
|
+
let urls = requestStore.useLinking()
|
|
114
|
+
stateLock.unlock()
|
|
115
|
+
|
|
116
|
+
urls.forEach(route)
|
|
117
|
+
}
|
|
118
|
+
if Thread.isMainThread {
|
|
119
|
+
markReady()
|
|
120
|
+
} else {
|
|
121
|
+
DispatchQueue.main.async(execute: markReady)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static func registerHandler(
|
|
126
|
+
_ emitter: @escaping (_ id: String, _ url: String) -> Void
|
|
127
|
+
) -> UUID {
|
|
128
|
+
let token = UUID()
|
|
129
|
+
stateLock.lock()
|
|
130
|
+
let hasAcknowledgedHandlerConfiguration = requestStore.requiresAcknowledgedHandler
|
|
131
|
+
handlerToken = token
|
|
132
|
+
handlerEmitter = emitter
|
|
133
|
+
let deliveries = requestStore.useHandler()
|
|
134
|
+
stateLock.unlock()
|
|
135
|
+
|
|
136
|
+
if !hasAcknowledgedHandlerConfiguration {
|
|
137
|
+
DIGraphShared.shared.logger.error(
|
|
138
|
+
"Zixflow registered an acknowledged deep-link handler without configuring " +
|
|
139
|
+
"acknowledged scene routing before React Native started"
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
for (id, url) in deliveries {
|
|
143
|
+
publishToHandler(id: id, url: url)
|
|
144
|
+
}
|
|
145
|
+
return token
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static func unregisterHandler(_ token: UUID) {
|
|
149
|
+
stateLock.lock()
|
|
150
|
+
guard handlerToken == token else {
|
|
151
|
+
stateLock.unlock()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
handlerToken = nil
|
|
155
|
+
handlerEmitter = nil
|
|
156
|
+
requestStore.removeHandler()
|
|
157
|
+
stateLock.unlock()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static func acknowledge(_ id: String, handled: Bool) {
|
|
161
|
+
guard let requestId = UUID(uuidString: id) else {
|
|
162
|
+
DIGraphShared.shared.logger.error(
|
|
163
|
+
"Zixflow ignored an invalid React Native deep-link acknowledgement"
|
|
164
|
+
)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
stateLock.lock()
|
|
169
|
+
let resolution = requestStore.acknowledge(requestId, handled: handled)
|
|
170
|
+
stateLock.unlock()
|
|
171
|
+
|
|
172
|
+
switch resolution {
|
|
173
|
+
case .handled:
|
|
174
|
+
DIGraphShared.shared.logger.info(
|
|
175
|
+
"React Native acknowledged the Zixflow deep link"
|
|
176
|
+
)
|
|
177
|
+
case let .fallback(url):
|
|
178
|
+
DIGraphShared.shared.logger.info(
|
|
179
|
+
"React Native declined the Zixflow deep link; using the native fallback"
|
|
180
|
+
)
|
|
181
|
+
fallback(url)
|
|
182
|
+
case nil:
|
|
183
|
+
DIGraphShared.shared.logger.info(
|
|
184
|
+
"Zixflow ignored a late or unknown React Native deep-link acknowledgement; " +
|
|
185
|
+
"the native fallback may have already routed the destination"
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static func route(_ url: URL) {
|
|
191
|
+
let publish = {
|
|
192
|
+
// React Native's AppDelegate URL entry point rejects scene-based hosts. Linking listens
|
|
193
|
+
// for this notification in both lifecycle modes, so publish the URL without choosing a scene.
|
|
194
|
+
DIGraphShared.shared.logger.info(
|
|
195
|
+
"Zixflow published an SDK deep link to React Native Linking"
|
|
196
|
+
)
|
|
197
|
+
NotificationCenter.default.post(
|
|
198
|
+
name: openURLNotification,
|
|
199
|
+
object: nil,
|
|
200
|
+
userInfo: ["url": url.absoluteString]
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
if Thread.isMainThread {
|
|
204
|
+
publish()
|
|
205
|
+
} else {
|
|
206
|
+
DispatchQueue.main.async(execute: publish)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private static func publishToHandler(id: UUID, url: URL) {
|
|
211
|
+
let publish = {
|
|
212
|
+
stateLock.lock()
|
|
213
|
+
let emitter = handlerEmitter
|
|
214
|
+
stateLock.unlock()
|
|
215
|
+
|
|
216
|
+
emitter?(id.uuidString, url.absoluteString)
|
|
217
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + acknowledgementTimeout) {
|
|
218
|
+
expireAcknowledgement(id)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if Thread.isMainThread {
|
|
222
|
+
publish()
|
|
223
|
+
} else {
|
|
224
|
+
DispatchQueue.main.async(execute: publish)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private static func scheduleReadinessExpiration(for id: UUID) {
|
|
229
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + readinessTimeout) {
|
|
230
|
+
expireReadiness(id)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private static func expireReadiness(_ id: UUID) {
|
|
235
|
+
stateLock.lock()
|
|
236
|
+
let url = requestStore.expireReadiness(id)
|
|
237
|
+
stateLock.unlock()
|
|
238
|
+
|
|
239
|
+
guard let url else { return }
|
|
240
|
+
|
|
241
|
+
DIGraphShared.shared.logger.error(
|
|
242
|
+
"Zixflow is using the native fallback because React Native deep-link routing " +
|
|
243
|
+
"did not become ready in time"
|
|
244
|
+
)
|
|
245
|
+
fallback(url)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private static func expireAcknowledgement(_ id: UUID) {
|
|
249
|
+
stateLock.lock()
|
|
250
|
+
let url = requestStore.expireAcknowledgement(id)
|
|
251
|
+
stateLock.unlock()
|
|
252
|
+
|
|
253
|
+
guard let url else { return }
|
|
254
|
+
|
|
255
|
+
DIGraphShared.shared.logger.error(
|
|
256
|
+
"Zixflow is using the native fallback because the React Native deep-link " +
|
|
257
|
+
"handler did not acknowledge the URL in time"
|
|
258
|
+
)
|
|
259
|
+
fallback(url)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private static func fallback(_ url: URL) {
|
|
263
|
+
let open = {
|
|
264
|
+
let uiKit = DIGraphShared.shared.uIKitWrapper
|
|
265
|
+
if uiKit.continueNSUserActivity(webpageURL: url) {
|
|
266
|
+
DIGraphShared.shared.logger.info(
|
|
267
|
+
"Zixflow deep link was handled by the host AppDelegate fallback"
|
|
268
|
+
)
|
|
269
|
+
} else if isAppOwnedCustomScheme(url) {
|
|
270
|
+
stateLock.lock()
|
|
271
|
+
let linkingIsReady = requestStore.canDeliverWithLinking
|
|
272
|
+
stateLock.unlock()
|
|
273
|
+
route(url)
|
|
274
|
+
if linkingIsReady {
|
|
275
|
+
DIGraphShared.shared.logger.info(
|
|
276
|
+
"Zixflow forwarded the host app's custom-scheme deep link to React " +
|
|
277
|
+
"Native Linking"
|
|
278
|
+
)
|
|
279
|
+
} else {
|
|
280
|
+
DIGraphShared.shared.logger.error(
|
|
281
|
+
"Zixflow published the host app's custom-scheme deep link before " +
|
|
282
|
+
"React Native Linking was marked ready; the destination may not be handled"
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
uiKit.open(url: url)
|
|
287
|
+
DIGraphShared.shared.logger.info(
|
|
288
|
+
"Zixflow opened the deep link through the system fallback"
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if Thread.isMainThread {
|
|
293
|
+
open()
|
|
294
|
+
} else {
|
|
295
|
+
DispatchQueue.main.async(execute: open)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private static func isAppOwnedCustomScheme(_ url: URL) -> Bool {
|
|
300
|
+
guard let scheme = url.scheme,
|
|
301
|
+
scheme.caseInsensitiveCompare("http") != .orderedSame,
|
|
302
|
+
scheme.caseInsensitiveCompare("https") != .orderedSame,
|
|
303
|
+
let urlTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes")
|
|
304
|
+
as? [[String: Any]]
|
|
305
|
+
else { return false }
|
|
306
|
+
|
|
307
|
+
return urlTypes.contains { urlType in
|
|
308
|
+
guard let schemes = urlType["CFBundleURLSchemes"] as? [String] else { return false }
|
|
309
|
+
return schemes.contains { $0.caseInsensitiveCompare(scheme) == .orderedSame }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <RNZixflowSpec/RNZixflowSpec.h>
|
|
3
|
+
|
|
4
|
+
// Objective-C wrapper for new architecture TurboModule implementation
|
|
5
|
+
@interface RCTNativeZixflowGeofence : NSObject <NativeZixflowGeofenceSpec>
|
|
6
|
+
// Bridge to Swift implementation for cross-language compatibility
|
|
7
|
+
@property(nonatomic, strong) id<NativeZixflowGeofenceSpec> swiftBridge;
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
@implementation RCTNativeZixflowGeofence
|
|
11
|
+
|
|
12
|
+
RCT_EXPORT_MODULE()
|
|
13
|
+
|
|
14
|
+
// Create TurboModule instance for new architecture JSI integration
|
|
15
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
16
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
17
|
+
return std::make_shared<facebook::react::NativeZixflowGeofenceSpecJSI>(params);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
- (instancetype)init {
|
|
21
|
+
if (self = [super init]) {
|
|
22
|
+
// Use runtime class lookup - NativeGeofence class only exists when the ZixflowLocationGeofence subspec is installed
|
|
23
|
+
Class swiftClass = NSClassFromString(@"NativeZixflowGeofence");
|
|
24
|
+
if (swiftClass) {
|
|
25
|
+
_swiftBridge = [[swiftClass alloc] init];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return self;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Module initialization can happen on background thread
|
|
32
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
33
|
+
return NO;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)refreshFromCurrentLocation {
|
|
37
|
+
if (!_swiftBridge) return;
|
|
38
|
+
[_swiftBridge refreshFromCurrentLocation];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Export class factory function for React Native component registration
|
|
42
|
+
Class<RCTBridgeModule> NativeZixflowGeofenceCls(void) {
|
|
43
|
+
return RCTNativeZixflowGeofence.class;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#if ZIXFLOW_GEOFENCE_ENABLED
|
|
2
|
+
import ZixflowDataPipelines
|
|
3
|
+
import ZixflowLocationGeofence
|
|
4
|
+
|
|
5
|
+
/// Registers the optional geofence module with the native iOS SDK and exposes its app-facing
|
|
6
|
+
/// methods. The reference to `GeofenceModule` is isolated here so it is only compiled when the
|
|
7
|
+
/// geofence subspec is installed. Geofence depends on the location module, which the caller
|
|
8
|
+
/// registers alongside it.
|
|
9
|
+
@objc(NativeZixflowGeofence)
|
|
10
|
+
public class NativeGeofence: NSObject {
|
|
11
|
+
|
|
12
|
+
/// Returns a `GeofenceModule` when the app opts into geofence via the `geofence` config,
|
|
13
|
+
/// applying the optional `locationMode` (defaults to `.automatic`).
|
|
14
|
+
static func module(from config: [String: Any]) -> GeofenceModule? {
|
|
15
|
+
guard let geofenceConfig = config["geofence"] as? [String: Any] else { return nil }
|
|
16
|
+
let locationModeValue = geofenceConfig["locationMode"] as? String
|
|
17
|
+
let locationMode: GeofenceLocationMode =
|
|
18
|
+
locationModeValue?.uppercased() == "MANUAL" ? .manual : .automatic
|
|
19
|
+
return GeofenceModule(config: GeofenceModuleConfig(locationMode: locationMode))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@objc
|
|
23
|
+
func refreshFromCurrentLocation() {
|
|
24
|
+
Zixflow.geofence.refreshFromCurrentLocation()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
#endif
|
|
@@ -66,11 +66,29 @@ RCT_EXPORT_MODULE(NativeZixflowMessagingInApp)
|
|
|
66
66
|
[_swiftBridge dismissMessage];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Signature matches the Codegen protocol, which declares the argument nonnull. ObjC does not
|
|
70
|
+
// enforce that at runtime, so a JavaScript null still arrives here as nil and is forwarded; the
|
|
71
|
+
// Swift side takes an optional and reports it rather than trapping.
|
|
72
|
+
- (void)setColorScheme:(NSString *)colorScheme {
|
|
73
|
+
[self assertBridgeAvailable:@"during setColorScheme"];
|
|
74
|
+
[_swiftBridge setColorScheme:colorScheme];
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
- (void)setupInboxListener {
|
|
70
78
|
[self assertBridgeAvailable:@"during setupInboxListener"];
|
|
71
79
|
[_swiftBridge setupInboxListener];
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
- (void)registerInboxEventListener {
|
|
83
|
+
[self assertBridgeAvailable:@"during registerInboxEventListener"];
|
|
84
|
+
[_swiftBridge registerInboxEventListener];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)unregisterInboxEventListener {
|
|
88
|
+
[self assertBridgeAvailable:@"during unregisterInboxEventListener"];
|
|
89
|
+
[_swiftBridge unregisterInboxEventListener];
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
- (void)getMessages:(NSString *)topic
|
|
75
93
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
76
94
|
reject:(RCTPromiseRejectBlock)reject {
|
|
@@ -110,6 +128,10 @@ RCT_EXPORT_MODULE(NativeZixflowMessagingInApp)
|
|
|
110
128
|
[super emitOnInAppEventReceived:value];
|
|
111
129
|
}
|
|
112
130
|
|
|
131
|
+
- (void)emitOnInboxEventReceived:(NSDictionary *)value {
|
|
132
|
+
[super emitOnInboxEventReceived:value];
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
- (void)emitSubscribeToMessagesChanged:(NSDictionary *)value {
|
|
114
136
|
[super emitSubscribeToMessagesChanged:value];
|
|
115
137
|
}
|
|
@@ -50,6 +50,48 @@ public class NativeMessagingInApp: NSObject {
|
|
|
50
50
|
// Clear in-app event listener to prevent leaks
|
|
51
51
|
clearInAppEventListener()
|
|
52
52
|
clearInboxChangeListener()
|
|
53
|
+
clearInboxEventListener()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// MARK: - Inbox Event Listener
|
|
57
|
+
|
|
58
|
+
/// Registers the native inbox event forwarder with the SDK and wires it to the ObjC event
|
|
59
|
+
/// emitter. Called by React Native when a JS inbox event listener is registered.
|
|
60
|
+
@objc(registerInboxEventListener)
|
|
61
|
+
public func registerInboxEventListener() {
|
|
62
|
+
ReactInboxEventListener.shared.setEventEmitter { [weak self] data in
|
|
63
|
+
guard let self else { return }
|
|
64
|
+
// Filter out nil values to convert [String: Any?] to [String: Any]
|
|
65
|
+
let body = data.compactMapValues { $0 }
|
|
66
|
+
self.sendInboxEvent(body: body)
|
|
67
|
+
}
|
|
68
|
+
MessagingInApp.shared.setInboxEventListener(ReactInboxEventListener.shared)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Unregisters the native inbox event forwarder from the SDK, restoring the SDK's default action
|
|
72
|
+
/// handling. Called by React Native when the JS inbox event listener is removed.
|
|
73
|
+
@objc(unregisterInboxEventListener)
|
|
74
|
+
public func unregisterInboxEventListener() {
|
|
75
|
+
MessagingInApp.shared.setInboxEventListener(nil)
|
|
76
|
+
ReactInboxEventListener.shared.clearEventEmitter()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func clearInboxEventListener() {
|
|
80
|
+
MessagingInApp.shared.setInboxEventListener(nil)
|
|
81
|
+
ReactInboxEventListener.shared.clearEventEmitter()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Send inbox event to React Native layer using ObjC event emitter
|
|
85
|
+
private func sendInboxEvent(body: [String: Any]) {
|
|
86
|
+
guard let emitter = objcEventEmitter else {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let selector = Selector(("emitOnInboxEventReceived:"))
|
|
91
|
+
guard emitter.responds(to: selector) else {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
_ = emitter.perform(selector, with: body as NSDictionary)
|
|
53
95
|
}
|
|
54
96
|
|
|
55
97
|
/// Dismisses any currently displayed in-app message
|
|
@@ -58,6 +100,54 @@ public class NativeMessagingInApp: NSObject {
|
|
|
58
100
|
MessagingInApp.shared.dismissMessage()
|
|
59
101
|
}
|
|
60
102
|
|
|
103
|
+
/// Overrides the color scheme used to render in-app messages.
|
|
104
|
+
///
|
|
105
|
+
/// Receives the raw value of the JavaScript `ZixflowColorScheme`, since the enum itself cannot
|
|
106
|
+
/// cross Codegen. An unrecognized value leaves the current scheme alone instead of resetting
|
|
107
|
+
/// it to `.auto`, so a typo cannot quietly undo a scheme the app set correctly earlier — the
|
|
108
|
+
/// Android bridge behaves the same way.
|
|
109
|
+
///
|
|
110
|
+
/// The parameter is optional even though Codegen declares it non-null: untyped JavaScript can
|
|
111
|
+
/// still pass `null` or `undefined`, which the bridge forwards as `nil`. Bridging that into a
|
|
112
|
+
/// non-optional `String` would trap before the value could be reported, and Android accepts a
|
|
113
|
+
/// nullable argument for the same reason, so a non-optional type here would also leave the two
|
|
114
|
+
/// platforms behaving differently on the same input.
|
|
115
|
+
@objc(setColorScheme:)
|
|
116
|
+
public func setColorScheme(_ colorScheme: String?) {
|
|
117
|
+
guard let resolved = Self.colorScheme(fromRawValue: colorScheme) else {
|
|
118
|
+
logger.error(
|
|
119
|
+
"Unrecognized in-app colorScheme '\(colorScheme ?? "nil")', expected one of auto, light, dark. Leaving the color scheme unchanged."
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
// Without this the call is dropped in silence: `setColorScheme` forwards through the
|
|
124
|
+
// module's `implementation?`, which is nil until the SDK is initialized, and the only
|
|
125
|
+
// trace is an `.info` line the default `.error` log level discards. Android logs this
|
|
126
|
+
// case at error, so reporting it here is what keeps the two platforms diagnosable in the
|
|
127
|
+
// same way. Logged rather than failed, again to match Android, which completes the call.
|
|
128
|
+
guard MessagingInApp.shared.hasBeenInitialized else {
|
|
129
|
+
logger.error(
|
|
130
|
+
"In-app messaging is not available, so the color scheme was not applied. Ensure Zixflow SDK is initialized with the inApp configuration."
|
|
131
|
+
)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
MessagingInApp.shared.setColorScheme(resolved)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Maps the wrapper's lowercase wire value onto the native `ColorScheme`.
|
|
138
|
+
///
|
|
139
|
+
/// Matched explicitly rather than handed to `MessagingInAppConfigBuilder`, which resolves
|
|
140
|
+
/// anything unrecognized to `.auto`; here an unrecognized value has to stay distinguishable
|
|
141
|
+
/// so it can be reported.
|
|
142
|
+
private static func colorScheme(fromRawValue rawValue: String?) -> ColorScheme? {
|
|
143
|
+
switch rawValue {
|
|
144
|
+
case "auto": return .auto
|
|
145
|
+
case "light": return .light
|
|
146
|
+
case "dark": return .dark
|
|
147
|
+
default: return nil
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
61
151
|
// MARK: - Inbox Methods
|
|
62
152
|
|
|
63
153
|
@objc(setupInboxListener)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Shared view-controller containment for the inbox `UIHostingController` hosts.
|
|
5
|
+
///
|
|
6
|
+
/// Adopting only a hosting controller's `view` leaves the controller outside the view-controller
|
|
7
|
+
/// hierarchy. UIKit then has no presentation context or trait/safe-area chain for it, which showed up
|
|
8
|
+
/// as a phantom top margin in the overlay's sheet (the sheet content resolved insets against the
|
|
9
|
+
/// wrong container) and means no lifecycle callbacks ever reach the hosted SwiftUI. Native usage does
|
|
10
|
+
/// not hit this because there the hosting controller *is* a real pushed view controller.
|
|
11
|
+
///
|
|
12
|
+
/// Containment has to be deferred until the Fabric view is actually in a window: React Native creates
|
|
13
|
+
/// component views before mounting them, so at `init` there is no parent controller to attach to.
|
|
14
|
+
enum InboxHostContainment {
|
|
15
|
+
/// Nearest view controller up the responder chain, or nil while the view is unmounted.
|
|
16
|
+
static func nearestViewController(from view: UIView?) -> UIViewController? {
|
|
17
|
+
var responder: UIResponder? = view
|
|
18
|
+
while let current = responder {
|
|
19
|
+
if let viewController = current as? UIViewController {
|
|
20
|
+
return viewController
|
|
21
|
+
}
|
|
22
|
+
responder = current.next
|
|
23
|
+
}
|
|
24
|
+
return nil
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Attaches `child` to the controller owning `view`. No-op when already attached or unmounted.
|
|
28
|
+
static func attach(_ child: UIViewController, hostedIn view: UIView?) {
|
|
29
|
+
guard child.parent == nil, let parent = nearestViewController(from: view) else { return }
|
|
30
|
+
parent.addChild(child)
|
|
31
|
+
child.didMove(toParent: parent)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Detaches `child` from its parent. No-op when it was never attached.
|
|
35
|
+
static func detach(_ child: UIViewController) {
|
|
36
|
+
guard child.parent != nil else { return }
|
|
37
|
+
child.willMove(toParent: nil)
|
|
38
|
+
child.removeFromParent()
|
|
39
|
+
}
|
|
40
|
+
}
|