@dashx/react-native 1.1.5 → 1.3.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
  }
package/.gitignore CHANGED
@@ -42,5 +42,5 @@ buck-out/
42
42
  \.buckd/
43
43
  *.keystore
44
44
 
45
- # Cursor plans
45
+ # Cursor
46
46
  .cursor
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dashx/react-native` are documented in this file. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versions follow [SemVer](https://semver.org/).
4
+
5
+ ## [1.3.0] — 2026-04-20
6
+
7
+ ### Breaking
8
+ - **`DashX` pod → `DashX/SDK`.** The podspec now depends on the `DashX/SDK` subspec of [dashx-ios 1.3.1+](https://github.com/dashxhq/dashx-ios) with no version constraint. Consumers must update their Podfile:
9
+ ```ruby
10
+ # before
11
+ pod 'DashX', :git => 'https://github.com/dashxhq/dashx-ios.git', :tag => '1.1.9'
12
+
13
+ # after
14
+ pod 'DashX/SDK', :git => 'https://github.com/dashxhq/dashx-ios.git', :tag => '1.3.1'
15
+ ```
16
+ - **`FirebaseMessaging` is now a hard dependency.** Previously the bridge guarded Firebase usage with `#if canImport(FirebaseMessaging)`, but `canImport` doesn't see consumer-Podfile pods at this pod's compile time, so the guard silently elided every FCM code path — push registration stopped working on fresh installs. Consumers must enable modular headers in their Podfile:
17
+ ```ruby
18
+ use_modular_headers!
19
+ # or, per-pod:
20
+ pod 'FirebaseMessaging', :modular_headers => true
21
+ ```
22
+
23
+ ### Added
24
+ - `DashX.onNotificationClicked(callback)` — fires when the user taps a DashX notification or one of its action buttons. Callback receives `{ notification, action, actionIdentifier }` where `action` is a resolved `NavigationAction` (`deepLink` / `screen` / `richLanding` / `clickAction`) or `null`.
25
+ - `DashXNotificationResponseResult.navigationAction` — new Swift-only field on the helper result type returned by `DashXNotificationHandler.handleNotificationResponse(_:)`.
26
+ - `NotificationClickedEvent` exported from `index.d.ts`.
27
+
28
+ ### Fixed
29
+ - **iOS dev builds silently failing to receive pushes.** `Messaging.messaging().setAPNSToken(_, type: .unknown)` sometimes mis-tagged the APNs environment on dev-signed binaries, producing an FCM token whose pushes 410'd at Apple. The bridge now forces `.sandbox` in `DEBUG` configurations and `.prod` otherwise.
30
+ - **`DashX.setIdentity(uid, token)` now passes `null` / `""` / `undefined` through to the native SDK unchanged** (apart from normalizing `undefined → null`, since the RN bridge can't carry `undefined`). Previously the JS wrapper threw on `null` / `""` / `undefined` for `uid`, contradicting the nullable signatures on iOS (`NSString?`) and Android (`String?`). TypeScript signature updated: `setIdentity(uid: string | null, token?: string | null): void`.
31
+
32
+ ## [1.2.0] — 2026-04-16
33
+
34
+ Previous release — see git log.
@@ -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,20 @@ 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.9'
24
+ # pod 'DashX/SDK', :git => 'https://github.com/dashxhq/dashx-ios.git', :tag => '1.3.1'
46
25
  # For local development:
47
26
  # pod 'DashX', :path => '../dashx-ios'
48
- s.dependency "DashX", "~> 1.1.9"
27
+ s.dependency "DashX/SDK"
28
+
29
+ # Hard dependency so that `canImport(FirebaseMessaging)`
30
+ # evaluates at this pod's compile time, where consumer-Podfile pods aren't
31
+ # visible — so an "optional" guard silently elides every Firebase code path.
32
+ # Consumers should set `use_modular_headers!` (or `:modular_headers => true`
33
+ # on FirebaseMessaging) in their Podfile so this Swift import resolves.
34
+ s.dependency "FirebaseMessaging"
49
35
  end
package/README.md CHANGED
@@ -26,7 +26,9 @@ _DashX SDK for React Native_
26
26
 
27
27
  ## Install
28
28
 
29
- Requires **React Native 0.71+**, **iOS 13.0+**, **Android SDK 26+**, and **Node.js 16+**.
29
+ Requires **React Native 0.74+**, **iOS 13.0+**, **Android SDK 26+**, and **Node.js 16+**.
30
+
31
+ Supports both the **Old Architecture** (bridge) and **New Architecture** (TurboModule) from a single install.
30
32
 
31
33
  > **Note:** This SDK uses native modules and is **not compatible with Expo Go**. Use a [development build](https://docs.expo.dev/develop/development-builds/introduction/) if you are using Expo.
32
34
 
@@ -0,0 +1,72 @@
1
+ const mockNativeModule = {
2
+ configure: jest.fn(),
3
+ identify: jest.fn(),
4
+ setIdentity: jest.fn(),
5
+ reset: jest.fn(),
6
+ track: jest.fn(),
7
+ screen: jest.fn(),
8
+ fetchRecord: jest.fn(),
9
+ searchRecords: jest.fn(),
10
+ fetchStoredPreferences: jest.fn(),
11
+ saveStoredPreferences: jest.fn(),
12
+ subscribe: jest.fn(),
13
+ unsubscribe: jest.fn(),
14
+ setLogLevel: jest.fn(),
15
+ uploadAsset: jest.fn(),
16
+ fetchAsset: jest.fn(),
17
+ enableLifecycleTracking: jest.fn(),
18
+ enableAdTracking: jest.fn(),
19
+ processURL: jest.fn(),
20
+ trackNotificationNavigation: jest.fn(),
21
+ requestNotificationPermission: jest.fn(),
22
+ getNotificationPermissionStatus: jest.fn(),
23
+ addListener: jest.fn(),
24
+ removeListeners: jest.fn(),
25
+ };
26
+
27
+ jest.mock('react-native', () => ({
28
+ NativeModules: {
29
+ DashXReactNative: mockNativeModule,
30
+ },
31
+ NativeEventEmitter: jest.fn().mockImplementation(() => ({
32
+ addListener: jest.fn(() => ({ remove: jest.fn() })),
33
+ })),
34
+ Platform: {
35
+ OS: 'ios',
36
+ select: jest.fn((obj) => obj.default || ''),
37
+ },
38
+ TurboModuleRegistry: {
39
+ get: jest.fn(() => null),
40
+ getEnforcing: jest.fn(() => {
41
+ throw new Error('not available');
42
+ }),
43
+ },
44
+ }));
45
+
46
+ // TurboModule returns null -> should fall back to NativeModules
47
+ jest.mock('../src/NativeDashXReactNative', () => ({
48
+ __esModule: true,
49
+ default: null,
50
+ }));
51
+
52
+ const DashX = require('../index').default;
53
+
54
+ describe('NativeModules fallback', () => {
55
+ beforeEach(() => {
56
+ jest.clearAllMocks();
57
+ });
58
+
59
+ it('falls back to NativeModules.DashXReactNative when TurboModule is null', () => {
60
+ DashX.configure({ publicKey: 'pk_test' });
61
+ expect(mockNativeModule.configure).toHaveBeenCalledWith({
62
+ publicKey: 'pk_test',
63
+ });
64
+ });
65
+
66
+ it('calls track on the fallback module', () => {
67
+ DashX.track('event_name', { key: 'value' });
68
+ expect(mockNativeModule.track).toHaveBeenCalledWith('event_name', {
69
+ key: 'value',
70
+ });
71
+ });
72
+ });
@@ -37,6 +37,15 @@ jest.mock('react-native', () => ({
37
37
  OS: 'ios',
38
38
  select: jest.fn((obj) => obj.default || ''),
39
39
  },
40
+ TurboModuleRegistry: {
41
+ get: jest.fn(() => mockNativeModule),
42
+ getEnforcing: jest.fn(() => mockNativeModule),
43
+ },
44
+ }));
45
+
46
+ jest.mock('../src/NativeDashXReactNative', () => ({
47
+ __esModule: true,
48
+ default: mockNativeModule,
40
49
  }));
41
50
 
42
51
  const DashX = require('../index').default;
@@ -97,13 +106,45 @@ describe('setIdentity', () => {
97
106
  );
98
107
  });
99
108
 
100
- it('throws if uid is missing', () => {
101
- expect(() => DashX.setIdentity(null, 'token')).toThrow('uid is required');
109
+ it('passes null uid through to native', () => {
110
+ DashX.setIdentity(null, 'token');
111
+ expect(mockNativeModule.setIdentity).toHaveBeenCalledWith(null, 'token');
112
+ });
113
+
114
+ it('normalizes undefined uid to null', () => {
115
+ DashX.setIdentity(undefined, 'token');
116
+ expect(mockNativeModule.setIdentity).toHaveBeenCalledWith(null, 'token');
117
+ });
118
+
119
+ it('passes empty-string uid through to native without coercing to null', () => {
120
+ DashX.setIdentity('', 'token');
121
+ expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('', 'token');
122
+ });
123
+
124
+ it('throws if uid is not a string or null', () => {
125
+ expect(() => DashX.setIdentity(42, 'token')).toThrow(
126
+ 'uid must be a string or null'
127
+ );
128
+ });
129
+
130
+ it('passes null token through to native', () => {
131
+ DashX.setIdentity('uid', null);
132
+ expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('uid', null);
133
+ });
134
+
135
+ it('passes empty-string token through to native without coercing to null', () => {
136
+ DashX.setIdentity('uid', '');
137
+ expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('uid', '');
138
+ });
139
+
140
+ it('passes undefined token as null to native', () => {
141
+ DashX.setIdentity('uid');
142
+ expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('uid', null);
102
143
  });
103
144
 
104
- it('throws if token is missing', () => {
105
- expect(() => DashX.setIdentity('uid', null)).toThrow(
106
- 'token is required'
145
+ it('throws if token is not a string or null', () => {
146
+ expect(() => DashX.setIdentity('uid', 42)).toThrow(
147
+ 'token must be a string or null'
107
148
  );
108
149
  });
109
150
  });
@@ -3,6 +3,14 @@ import groovy.json.JsonSlurper
3
3
  apply plugin: 'com.android.library'
4
4
  apply plugin: 'kotlin-android'
5
5
 
6
+ def isNewArchitectureEnabled() {
7
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
8
+ }
9
+
10
+ if (isNewArchitectureEnabled()) {
11
+ apply plugin: 'com.facebook.react'
12
+ }
13
+
6
14
  def getVersionFromNpm() {
7
15
  def inputFile = new File("../package.json")
8
16
  def packageJson = new JsonSlurper().parseText(inputFile.text)
@@ -30,6 +38,16 @@ android {
30
38
  abortOnError false
31
39
  }
32
40
 
41
+ sourceSets {
42
+ main {
43
+ if (isNewArchitectureEnabled()) {
44
+ java.srcDirs += ['src/newarch/java']
45
+ } else {
46
+ java.srcDirs += ['src/oldarch/java']
47
+ }
48
+ }
49
+ }
50
+
33
51
  packaging {
34
52
  pickFirst 'lib/arm64-v8a/libc++_shared.so'
35
53
  pickFirst 'lib/arm64-v8a/libfbjni.so'
@@ -42,6 +60,14 @@ android {
42
60
  }
43
61
  }
44
62
 
63
+ if (isNewArchitectureEnabled()) {
64
+ react {
65
+ jsRootDir = file("../src/")
66
+ libraryName = "RNDashXReactNativeSpec"
67
+ codegenJavaPackageName = "com.dashx.rn.sdk"
68
+ }
69
+ }
70
+
45
71
  repositories {
46
72
  mavenCentral()
47
73
  google()
@@ -55,5 +81,5 @@ dependencies {
55
81
  // Ensure Firebase Messaging types are available for FCM service subclass
56
82
  implementation 'com.google.firebase:firebase-messaging-ktx:23.1.2'
57
83
 
58
- implementation "com.dashx:dashx-android:1.2.4"
84
+ implementation "com.dashx:dashx-android:1.2.5"
59
85
  }
@@ -1,5 +1,8 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
2
  xmlns:tools="http://schemas.android.com/tools">
3
+
4
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
5
+
3
6
  <application>
4
7
  <!-- Replace dashx-android's FCM service with our subclass that also emits to JS. -->
5
8
  <service