@dashx/react-native 1.1.4 → 1.2.0
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/.DS_Store +0 -0
- package/.claude/settings.local.json +18 -1
- package/.cursor/plans/deep_linking_implementation_plan_303c2ca9.plan.md +216 -0
- package/.gitignore +2 -2
- package/DashXReactNative.podspec +5 -26
- package/README.md +10 -478
- package/__tests__/fallback.test.js +72 -0
- package/__tests__/index.test.js +147 -4
- package/android/build.gradle +27 -1
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeModuleImpl.kt +384 -0
- package/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +85 -0
- package/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +33 -0
- package/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +106 -0
- package/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +33 -0
- package/index.d.ts +32 -1
- package/index.js +31 -8
- package/ios/DashXNotificationHandler.swift +222 -0
- package/ios/DashXReactNative-Bridging-Header.h +0 -2
- package/ios/DashXReactNative.mm +32 -9
- package/ios/DashXReactNative.swift +60 -18
- package/package.json +11 -2
- package/src/NativeDashXReactNative.ts +42 -0
- package/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -231
- package/android/src/main/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -18
- package/ios/DashXRCTAppDelegate.h +0 -3
- package/ios/DashXRCTAppDelegate.swift +0 -266
- package/ios/DashXReactNative.h +0 -1
package/.DS_Store
CHANGED
|
Binary file
|
|
@@ -8,7 +8,24 @@
|
|
|
8
8
|
"Bash(swift build:*)",
|
|
9
9
|
"Bash(xcodebuild -scheme DashX -destination 'generic/platform=iOS' build)",
|
|
10
10
|
"Bash(./gradlew :dashx:compileDebugKotlin)",
|
|
11
|
-
"Bash(./gradlew :dashx:compileDebugKotlin --info)"
|
|
11
|
+
"Bash(./gradlew :dashx:compileDebugKotlin --info)",
|
|
12
|
+
"Bash(gh pr:*)",
|
|
13
|
+
"WebFetch(domain:github.com)",
|
|
14
|
+
"WebFetch(domain:patch-diff.githubusercontent.com)",
|
|
15
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
16
|
+
"Bash(ls -la /Users/gulshan/Work/dashx-react-native/ios/DashXRCTAppDelegate*)",
|
|
17
|
+
"Bash(npx jest *)",
|
|
18
|
+
"Bash(ls *)",
|
|
19
|
+
"Bash(grep *)",
|
|
20
|
+
"Read(//private/tmp/**)",
|
|
21
|
+
"Bash(javap -p com/dashx/android/DashX.class)",
|
|
22
|
+
"Bash(javap -p 'com/dashx/android/DashX$Companion.class')",
|
|
23
|
+
"Bash(jar xf *)",
|
|
24
|
+
"Bash(javap 'com/dashx/android/DashX$Companion.class')",
|
|
25
|
+
"Bash(javap 'com/dashx/android/data/Asset.class')",
|
|
26
|
+
"Bash(javap 'com/dashx/android/data/AssetData.class')",
|
|
27
|
+
"Read(//Users/gulshan/Work/dashx-ios/**)",
|
|
28
|
+
"WebSearch"
|
|
12
29
|
]
|
|
13
30
|
}
|
|
14
31
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Deep Linking Implementation Plan
|
|
3
|
+
overview: Implement MoEngage-like deep linking features in dashx-ios and dashx-android SDKs, covering notification-driven deep links, universal/app links, rich landing pages, structured navigation actions, and host-app callbacks.
|
|
4
|
+
todos:
|
|
5
|
+
- id: phase-1-payload
|
|
6
|
+
content: "Phase 1: Extend payload models on both platforms (ActionButton URLs, screenName, screenData, NavigationAction type)"
|
|
7
|
+
status: pending
|
|
8
|
+
- id: phase-2-callbacks
|
|
9
|
+
content: "Phase 2: Notification click callback API -- DashXAppDelegate override (iOS) and DashXNotificationListener interface (Android)"
|
|
10
|
+
status: pending
|
|
11
|
+
- id: phase-3-action-buttons
|
|
12
|
+
content: "Phase 3: Per-action-button deep links on iOS (UNNotificationAction) and Android (NotificationCompat.Action)"
|
|
13
|
+
status: pending
|
|
14
|
+
- id: phase-4-rich-landing
|
|
15
|
+
content: "Phase 4: Rich Landing -- SFSafariViewController (iOS) and Chrome Custom Tabs (Android)"
|
|
16
|
+
status: completed
|
|
17
|
+
- id: phase-5-scene-delegate
|
|
18
|
+
content: "Phase 5: SceneDelegate deep link support -- openURLContexts and continue userActivity (iOS only)"
|
|
19
|
+
status: completed
|
|
20
|
+
- id: phase-6-analytics
|
|
21
|
+
content: "Phase 6: Deep link source tracking -- processURL / processDeepLink analytics events"
|
|
22
|
+
status: completed
|
|
23
|
+
- id: phase-7-docs
|
|
24
|
+
content: "Phase 7: Documentation and demo app updates for all deep linking features"
|
|
25
|
+
status: completed
|
|
26
|
+
isProject: false
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Deep Linking Implementation Plan
|
|
30
|
+
|
|
31
|
+
## Current State vs MoEngage: Feature Comparison
|
|
32
|
+
|
|
33
|
+
### What We Have Today
|
|
34
|
+
|
|
35
|
+
**dashx-ios** ([DashXAppDelegate.swift](dashx-ios/Sources/DashXFirebase/DashXAppDelegate.swift), [DashXClient.swift](dashx-ios/Sources/DashX/DashXClient.swift), [DashXData.swift](dashx-ios/Sources/DashX/DashXData.swift)):
|
|
36
|
+
- Notification `url` field in `DashXNotificationData` -- opens via `handleLink(url:)` callback
|
|
37
|
+
- Universal link forwarding via `DashXClient.handleUserActivity()` + `linkHandler` closure (AppDelegate only)
|
|
38
|
+
- `notificationClicked(message:, actionIdentifier:)` open method for subclass overrides
|
|
39
|
+
- `notificationDeliveredInForeground(message:)` callback
|
|
40
|
+
- Message tracking: delivered / clicked / dismissed
|
|
41
|
+
- `DashXSceneDelegate` exists but only handles ad tracking -- no deep link or URL handling
|
|
42
|
+
|
|
43
|
+
**dashx-android** ([NotificationProcessor.kt](dashx-android/dashx/src/main/java/com/dashx/sdk/NotificationProcessor.kt), [DashXFirebaseMessagingService.kt](dashx-android/dashx/src/main/java/com/dashx/sdk/DashXFirebaseMessagingService.kt)):
|
|
44
|
+
- Notification `url` field in `DashXPayload` -- opens via system `ACTION_VIEW` intent
|
|
45
|
+
- `click_action` field -- resolves as Activity class name or Intent action string
|
|
46
|
+
- `NotificationReceiver` (translucent Activity) delegates to `NotificationProcessor.handleClick()`
|
|
47
|
+
- Message tracking: DELIVERED / OPENED / DISMISSED
|
|
48
|
+
- No callback mechanism for the host app to intercept deep link navigation
|
|
49
|
+
|
|
50
|
+
### What MoEngage Has That We Don't
|
|
51
|
+
|
|
52
|
+
| Feature | MoEngage | DashX iOS | DashX Android |
|
|
53
|
+
|---------|----------|-----------|---------------|
|
|
54
|
+
| Custom URL scheme handling | Auto via swizzle or `processURL()` | Not handled | N/A (system intent) |
|
|
55
|
+
| SceneDelegate deep link support | `scene(_:openURLContexts:)` + `scene(_:continue:)` | Missing | N/A |
|
|
56
|
+
| Rich Landing (in-app browser) | SFSafariViewController / Chrome Custom Tabs | Missing | Missing |
|
|
57
|
+
| Navigate to Screen (structured) | `screenName` + `screenData` in payload | Missing | Missing |
|
|
58
|
+
| Per-action-button deep links | Each button has its own URL/action | Buttons have no URL | No action buttons at all |
|
|
59
|
+
| Notification click callback with control | `onNotificationClicked()` returns bool to control redirection | Partial (subclass override, no control flag) | Missing entirely |
|
|
60
|
+
| Notification received callback | `onNotificationReceived()` | Partial (foreground only) | Missing |
|
|
61
|
+
| Notification cleared callback | `onNotificationCleared()` | Has `dismissed` tracking | Has `DISMISSED` tracking |
|
|
62
|
+
| Deep link URL tracking/analytics | `processURL()` tracks source attribution | Missing | Missing |
|
|
63
|
+
| Customizable notification builder | `customizeNotification()` / `customizeNotificationBuilder()` | Not exposed | Not exposed |
|
|
64
|
+
| Self-handled notification | `onSelfHandledNotificationReceived()` | N/A | Missing |
|
|
65
|
+
| AppDelegate proxy/swizzling | Auto-intercepts deep link AppDelegate methods | Not implemented | N/A |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Implementation Plan
|
|
70
|
+
|
|
71
|
+
### Phase 1: Enhanced Payload Models (Both Platforms)
|
|
72
|
+
|
|
73
|
+
Extend the notification payload models to support structured navigation.
|
|
74
|
+
|
|
75
|
+
**iOS** -- Update `ActionButton` and `DashXNotificationData` in [DashXData.swift](dashx-ios/Sources/DashX/DashXData.swift):
|
|
76
|
+
- Add `url: String?` and `clickAction: String?` to `ActionButton`
|
|
77
|
+
- Add `screenName: String?` and `screenData: [String: String]?` to `DashXNotificationData`
|
|
78
|
+
- Add `clickAction: String?` (currently iOS has no `click_action` at all)
|
|
79
|
+
|
|
80
|
+
**Android** -- Update `DashXPayload` in [DashXFirebaseMessagingService.kt](dashx-android/dashx/src/main/java/com/dashx/sdk/DashXFirebaseMessagingService.kt):
|
|
81
|
+
- Add `screenName: String?` (`screen_name`) and `screenData: Map<String, String>?` (`screen_data`) to `DashXPayload`
|
|
82
|
+
- Create an `ActionButton` model (currently Android has no action button support)
|
|
83
|
+
- Add `actionButtons: List<ActionButton>?` (`action_buttons`) to `DashXPayload`
|
|
84
|
+
|
|
85
|
+
Create a shared `NavigationAction` model on both platforms:
|
|
86
|
+
|
|
87
|
+
```kotlin
|
|
88
|
+
// Android
|
|
89
|
+
sealed class NavigationAction {
|
|
90
|
+
data class DeepLink(val url: String) : NavigationAction()
|
|
91
|
+
data class Screen(val name: String, val data: Map<String, String>?) : NavigationAction()
|
|
92
|
+
data class RichLanding(val url: String) : NavigationAction()
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```swift
|
|
97
|
+
// iOS
|
|
98
|
+
public enum NavigationAction {
|
|
99
|
+
case deepLink(url: URL)
|
|
100
|
+
case screen(name: String, data: [String: String]?)
|
|
101
|
+
case richLanding(url: URL)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Phase 2: Notification Click Callback API (Both Platforms)
|
|
106
|
+
|
|
107
|
+
Provide a callback mechanism that gives the host app full control over deep link handling.
|
|
108
|
+
|
|
109
|
+
**iOS** -- In [DashXAppDelegate.swift](dashx-ios/Sources/DashXFirebase/DashXAppDelegate.swift):
|
|
110
|
+
- Add `open func onNotificationClicked(message:, action: NavigationAction?, actionIdentifier: String) -> Bool` -- return `true` to suppress SDK default handling
|
|
111
|
+
- Refactor `userNotificationCenter(_:didReceive:)` to resolve `NavigationAction` from the payload, call the callback, and only proceed with default handling if it returns `false`
|
|
112
|
+
- Deprecate the existing `notificationClicked(message:actionIdentifier:)` (keep for backward compat)
|
|
113
|
+
|
|
114
|
+
**Android** -- Create a new `DashXNotificationListener` interface in a new file `DashXNotificationListener.kt`:
|
|
115
|
+
```kotlin
|
|
116
|
+
interface DashXNotificationListener {
|
|
117
|
+
fun onNotificationReceived(payload: DashXPayload) {}
|
|
118
|
+
fun onNotificationClicked(payload: DashXPayload, action: NavigationAction?): Boolean = false
|
|
119
|
+
fun onNotificationDismissed(payload: DashXPayload) {}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
- Add `DashX.registerNotificationListener(listener)` and `DashX.unregisterNotificationListener()`
|
|
123
|
+
- Wire into `NotificationProcessor.handleClick()` and `DashXFirebaseMessagingService.onMessageReceived()`
|
|
124
|
+
- If `onNotificationClicked` returns `true`, skip the SDK's default URL/click_action handling
|
|
125
|
+
|
|
126
|
+
### Phase 3: Action Button Deep Links
|
|
127
|
+
|
|
128
|
+
**iOS** -- In [DashXAppDelegate.swift](dashx-ios/Sources/DashXFirebase/DashXAppDelegate.swift):
|
|
129
|
+
- When building `UNNotificationAction` items from `actionButtons`, store the per-button URL/action in `userInfo` (via `UNNotificationCategory`)
|
|
130
|
+
- In `didReceive response`, match `response.actionIdentifier` to the button's identifier and resolve its `NavigationAction`
|
|
131
|
+
|
|
132
|
+
**Android**:
|
|
133
|
+
- Parse `action_buttons` from `DashXPayload` and build `NotificationCompat.Action` items
|
|
134
|
+
- Attach per-button `PendingIntent` with the button's URL or action
|
|
135
|
+
- Handle button clicks in `NotificationProcessor` by resolving the specific button's `NavigationAction`
|
|
136
|
+
|
|
137
|
+
### Phase 4: Rich Landing (In-App Browser)
|
|
138
|
+
|
|
139
|
+
When a notification's action is `richLanding` (or a URL is explicitly marked for in-app display):
|
|
140
|
+
|
|
141
|
+
**iOS**:
|
|
142
|
+
- Add a helper in `DashXClient` or a new `DashXBrowser.swift` that presents `SFSafariViewController` for a given URL
|
|
143
|
+
- Wire it as the default handler when `NavigationAction.richLanding` is resolved and the callback doesn't suppress it
|
|
144
|
+
|
|
145
|
+
**Android**:
|
|
146
|
+
- Add `implementation 'androidx.browser:browser:1.x.x'` as a dependency
|
|
147
|
+
- Create `DashXBrowser.kt` that opens `CustomTabsIntent` for the URL
|
|
148
|
+
- Wire as default handler for `NavigationAction.RichLanding`
|
|
149
|
+
|
|
150
|
+
### Phase 5: SceneDelegate Deep Link Support (iOS Only)
|
|
151
|
+
|
|
152
|
+
Update [DashXSceneDelegate.swift](dashx-ios/Sources/DashX/DashXSceneDelegate.swift) to handle both custom URL schemes and universal links:
|
|
153
|
+
|
|
154
|
+
```swift
|
|
155
|
+
@available(iOS 13.0, *)
|
|
156
|
+
open func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
|
157
|
+
guard let url = URLContexts.first?.url else { return }
|
|
158
|
+
DashXClient.instance.linkHandler?(url)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@available(iOS 13.0, *)
|
|
162
|
+
open func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
|
|
163
|
+
DashXClient.instance.handleUserActivity(userActivity: userActivity)
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Phase 6: Deep Link Source Tracking / Analytics
|
|
168
|
+
|
|
169
|
+
Add a `trackDeepLink(url:, source:)` method on both platforms to record deep link opens as analytics events.
|
|
170
|
+
|
|
171
|
+
**iOS** -- In [DashXClient.swift](dashx-ios/Sources/DashX/DashXClient.swift):
|
|
172
|
+
- Add `public func processURL(_ url: URL, source: String? = nil)` that:
|
|
173
|
+
1. Fires a `dx_deep_link_opened` event with URL, source, timestamp
|
|
174
|
+
2. Invokes `linkHandler?(url)`
|
|
175
|
+
- Update `handleUserActivity` and the notification click path to go through `processURL`
|
|
176
|
+
|
|
177
|
+
**Android** -- In `DashX.kt`:
|
|
178
|
+
- Add `fun processDeepLink(uri: Uri, source: String? = null)` with equivalent behavior
|
|
179
|
+
- Update `NotificationProcessor.handleClick()` to call `processDeepLink` before opening the URL
|
|
180
|
+
|
|
181
|
+
### Phase 7: Documentation and Demo App Updates
|
|
182
|
+
|
|
183
|
+
- Update [dashx-ios/README.md](dashx-ios/README.md) and [dashx-android/README.md](dashx-android/README.md) with deep linking integration guides
|
|
184
|
+
- Update [dashx-demo-rn](dashx-demo-rn/), [dashx-demo-ios](dashx-demo-ios/), and [dashx-demo-android](dashx-demo-android/) to demonstrate:
|
|
185
|
+
- Handling deep links from push notifications
|
|
186
|
+
- Custom URL scheme setup
|
|
187
|
+
- Universal Links / App Links configuration
|
|
188
|
+
- Rich landing page behavior
|
|
189
|
+
- Navigate-to-screen with custom data
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Architecture: Notification Click Flow (After Implementation)
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
flowchart TD
|
|
197
|
+
NotifTap[User Taps Notification] --> ParsePayload[Parse DashX Payload]
|
|
198
|
+
ParsePayload --> ResolveAction[Resolve NavigationAction]
|
|
199
|
+
ResolveAction --> TrackClick[trackMessage: CLICKED/OPENED]
|
|
200
|
+
TrackClick --> CallbackCheck{Host App Callback}
|
|
201
|
+
CallbackCheck -->|returns true| Done[Host App Handles]
|
|
202
|
+
CallbackCheck -->|returns false| ActionType{Action Type?}
|
|
203
|
+
ActionType -->|DeepLink| OpenURL[Open URL via system / linkHandler]
|
|
204
|
+
ActionType -->|RichLanding| InAppBrowser[SFSafari / CustomTabs]
|
|
205
|
+
ActionType -->|Screen| ScreenNav[Invoke screenHandler callback]
|
|
206
|
+
ActionType -->|None| FallbackClick[Call legacy notificationClicked]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Key Design Decisions
|
|
212
|
+
|
|
213
|
+
- **Callback-first, not swizzle**: Unlike MoEngage's AppDelegate proxy approach, we keep our subclass/callback pattern. Swizzling is fragile and conflicts with other SDKs. The host app subclasses `DashXAppDelegate`/`DashXSceneDelegate` (iOS) or registers a `DashXNotificationListener` (Android).
|
|
214
|
+
- **NavigationAction as a first-class type**: Rather than passing raw strings, provide a sealed/enum type so host apps can pattern-match on the navigation intent.
|
|
215
|
+
- **Backward compatible**: The existing `handleLink(url:)`, `notificationClicked(message:actionIdentifier:)` (iOS), and `click_action`/`url` behavior (Android) continue to work. New APIs are additive.
|
|
216
|
+
- **Rich landing is opt-in**: URLs default to system handling (external browser / `ACTION_VIEW`). Rich landing requires explicit `rich_landing` flag in the payload or a configuration option.
|
package/.gitignore
CHANGED
package/DashXReactNative.podspec
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
-
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
|
5
4
|
|
|
6
5
|
Pod::Spec.new do |s|
|
|
7
6
|
s.name = "DashXReactNative"
|
|
@@ -17,33 +16,13 @@ Pod::Spec.new do |s|
|
|
|
17
16
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
17
|
s.requires_arc = true
|
|
19
18
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
install_modules_dependencies(s)
|
|
24
|
-
else
|
|
25
|
-
s.dependency "React-Core"
|
|
19
|
+
# React Native >= 0.71 provides install_modules_dependencies.
|
|
20
|
+
# Since our minimum supported RN version is 0.74, we can call it directly.
|
|
21
|
+
install_modules_dependencies(s)
|
|
26
22
|
|
|
27
|
-
# Don't install the dependencies when we run `pod install` in the old architecture.
|
|
28
|
-
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
29
|
-
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
30
|
-
s.pod_target_xcconfig = {
|
|
31
|
-
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
|
32
|
-
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
33
|
-
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
|
34
|
-
}
|
|
35
|
-
s.dependency "React-Codegen"
|
|
36
|
-
s.dependency "RCT-Folly"
|
|
37
|
-
s.dependency "RCTRequired"
|
|
38
|
-
s.dependency "RCTTypeSafety"
|
|
39
|
-
s.dependency "ReactCommon/turbomodule/core"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
s.dependency "React-RCTAppDelegate"
|
|
44
23
|
# DashX iOS SDK — consumers must provide the source in their Podfile:
|
|
45
|
-
# pod 'DashX', :git => 'https://github.com/dashxhq/dashx-ios.git', :tag => '1.1.
|
|
24
|
+
# pod 'DashX', :git => 'https://github.com/dashxhq/dashx-ios.git', :tag => '1.1.9'
|
|
46
25
|
# For local development:
|
|
47
26
|
# pod 'DashX', :path => '../dashx-ios'
|
|
48
|
-
s.dependency "DashX", "~> 1.1.
|
|
27
|
+
s.dependency "DashX", "~> 1.1.9"
|
|
49
28
|
end
|