@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 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
@@ -42,5 +42,5 @@ buck-out/
42
42
  \.buckd/
43
43
  *.keystore
44
44
 
45
- # VS Code
46
- .vscode/
45
+ # Cursor
46
+ .cursor
@@ -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
- # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
21
- # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
22
- if respond_to?(:install_modules_dependencies, true)
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.5'
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.5"
27
+ s.dependency "DashX", "~> 1.1.9"
49
28
  end