@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/README.md CHANGED
@@ -20,22 +20,17 @@
20
20
 
21
21
  [![npm version](https://img.shields.io/npm/v/@dashx/react-native.svg)](https://www.npmjs.com/package/@dashx/react-native)
22
22
  [![license](https://img.shields.io/npm/l/@dashx/react-native.svg)](https://github.com/dashxhq/dashx-react-native/blob/main/LICENSE)
23
- [![platforms](https://img.shields.io/badge/platforms-Android%20%7C%20iOS-brightgreen.svg)](#requirements)
23
+ [![platforms](https://img.shields.io/badge/platforms-Android%20%7C%20iOS-brightgreen.svg)](#install)
24
24
 
25
25
  _DashX SDK for React Native_
26
26
 
27
- ## Requirements
27
+ ## Install
28
28
 
29
- | Platform | Minimum Version |
30
- |----------|----------------|
31
- | iOS | 13.0+ |
32
- | Android | SDK 26 (Android 8.0)+ |
33
- | React Native | 0.71+ |
34
- | Node.js | 16.0+ |
29
+ Requires **React Native 0.74+**, **iOS 13.0+**, **Android SDK 26+**, and **Node.js 16+**.
35
30
 
36
- > **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.
31
+ Supports both the **Old Architecture** (bridge) and **New Architecture** (TurboModule) from a single install.
37
32
 
38
- ## Install
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.
39
34
 
40
35
  ```sh
41
36
  npm install @dashx/react-native
@@ -47,480 +42,17 @@ or
47
42
  yarn add @dashx/react-native
48
43
  ```
49
44
 
50
- ### Setup for Android
51
-
52
- DashX requires Google Services for Firebase Cloud Messaging:
53
-
54
- 1. Add the `google-services` plugin in your `/android/build.gradle`:
55
-
56
- ```gradle
57
- buildscript {
58
- dependencies {
59
- // ... other dependencies
60
- classpath 'com.google.gms:google-services:4.4.2'
61
- }
62
- }
63
- ```
64
-
65
- 2. Apply the plugin in your `/android/app/build.gradle`:
66
-
67
- ```gradle
68
- apply plugin: 'com.google.gms.google-services'
69
- ```
70
-
71
- 3. Add your Android app on [Firebase Console](https://console.firebase.google.com/): **Project Overview > Add App > Android**
72
-
73
- 4. Download `google-services.json` and place it at `/android/app/google-services.json`
74
-
75
- ### Setup for iOS
76
-
77
- #### Step 1: Add pods to your Podfile
78
-
79
- The DashX iOS SDK is not published to CocoaPods trunk. Add it directly from GitHub along with the Firebase pods required for FCM push notifications.
80
-
81
- Add `use_modular_headers!` at the top of your `ios/Podfile`, then add the pods inside the target block:
82
-
83
- ```ruby
84
- use_modular_headers!
85
-
86
- target 'YourApp' do
87
- # ... other pods
88
-
89
- pod 'DashX', :git => 'https://github.com/dashxhq/dashx-ios.git', :tag => '1.1.4'
90
- pod 'FirebaseCore'
91
- pod 'FirebaseMessaging'
92
-
93
- use_react_native!(...)
94
- end
95
- ```
96
-
97
- Then run:
98
-
99
- ```sh
100
- cd ios && pod install
101
- ```
102
-
103
- #### Step 2: Add GoogleService-Info.plist
104
-
105
- 1. Add your iOS app on [Firebase Console](https://console.firebase.google.com/): **Project Overview > Add App > iOS**
106
- 2. Download `GoogleService-Info.plist` and add it to your Xcode project (right-click project root > **Add Files to "YourApp"**, ensure **Copy items if needed** is checked)
107
-
108
- #### Step 3: Enable Push Notifications capability
109
-
110
- In Xcode, select your app target > **Signing & Capabilities** > **+ Capability** > add **Push Notifications**.
111
-
112
- #### Step 4: Configure your AppDelegate
113
-
114
- The SDK ships `DashXRCTAppDelegate` which handles permission requests, notification display, and universal link forwarding. Subclass it in your `AppDelegate.swift` and configure Firebase directly:
115
-
116
- ```swift
117
- import DashXReactNative
118
- import ReactAppDependencyProvider
119
- import FirebaseCore
120
- import FirebaseMessaging
121
- import DashX
122
- import UserNotifications
123
-
124
- @main
125
- class AppDelegate: DashXRCTAppDelegate {
126
- override var moduleName: String { "YourAppName" }
127
-
128
- override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
129
- self.dependencyProvider = RCTAppDependencyProvider()
130
- FirebaseApp.configure()
131
- Messaging.messaging().delegate = self
132
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
133
- }
134
-
135
- // Forward the APNS token to Firebase so it can exchange it for an FCM token
136
- override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
137
- Messaging.messaging().setAPNSToken(deviceToken, type: .unknown)
138
- }
139
-
140
- override func bundleURL() -> URL? {
141
- #if DEBUG
142
- RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
143
- #else
144
- Bundle.main.url(forResource: "main", withExtension: "jsbundle")
145
- #endif
146
- }
147
- }
148
-
149
- // Receive the FCM token and pass it to DashX
150
- extension AppDelegate: MessagingDelegate {
151
- func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
152
- guard let token = fcmToken else { return }
153
- DashX.setFCMToken(to: token)
154
- }
155
- }
156
- ```
157
-
158
- `DashXRCTAppDelegate` handles the rest automatically:
159
- - Requests notification permission on launch
160
- - Displays notifications (banner + sound by default, even while app is in the foreground)
161
- - Tracks delivery, clicks, and dismissals
162
- - Forwards universal links to the JS `onLinkReceived` listener
45
+ ## Documentation
163
46
 
164
- **Override points** (all optional):
47
+ For detailed documentation, visit [React Native SDK documentation](https://docs.dashx.com/sdks/client-side/react-native-sdk).
165
48
 
166
- ```swift
167
- /// Control foreground notification presentation (default: [.banner, .sound])
168
- override func notificationDeliveredInForeground(message: [AnyHashable: Any]) -> UNNotificationPresentationOptions {
169
- return [.banner, .sound, .badge]
170
- }
49
+ ## Deep linking and push navigation
171
50
 
172
- // Called when the user taps a notification or one of its action buttons
173
- override func notificationClicked(message: [AnyHashable: Any], actionIdentifier: String) {
174
- // custom tap handling
175
- }
176
-
177
- // Called when a universal/deep link arrives
178
- override func handleLink(url: URL) {
179
- // custom link handling
180
- }
181
- ```
182
-
183
- #### Step 5: Subscribe in JavaScript
184
-
185
- Call `DashX.subscribe()` at app startup so the SDK registers the device as soon as the FCM token is available:
186
-
187
- ```js
188
- import { useEffect } from 'react';
189
- import DashX from '@dashx/react-native';
190
-
191
- DashX.configure({ publicKey: 'your-public-key' });
192
-
193
- export default function App() {
194
- useEffect(() => {
195
- DashX.subscribe();
196
- }, []);
197
-
198
- // ...
199
- }
200
- ```
201
-
202
- > **Without Firebase:** If you are not using FCM, omit the Firebase pods and the Firebase-related `AppDelegate` code. The SDK will still handle APNS permission requests and notification display via `DashXRCTAppDelegate`.
203
-
204
- ## Usage
205
-
206
- ```js
207
- import DashX from '@dashx/react-native';
208
- ```
209
-
210
- ### Configure
211
-
212
- Initialize the SDK once at app startup. Call `configure` at the top level of your entry file (e.g. `App.js`) so it runs before any component mounts:
213
-
214
- ```js
215
- // App.js — call at module level, before the component
216
- import DashX from '@dashx/react-native';
217
-
218
- DashX.configure({
219
- publicKey: 'your-public-key',
220
- // baseURI: 'https://api.dashx.com', // optional, custom API endpoint
221
- // targetEnvironment: 'production', // optional
222
- });
223
-
224
- export default function App() {
225
- // ...
226
- }
227
- ```
228
-
229
- Calling from `useEffect` in your root component also works, but module-level init is preferred so the SDK is ready as early as possible.
230
-
231
- | Option | Type | Required | Description |
232
- |--------|------|----------|-------------|
233
- | `publicKey` | `string` | Yes | Your DashX public key |
234
- | `baseURI` | `string` | No | Custom API base URI |
235
- | `targetEnvironment` | `string` | No | Target environment name |
236
-
237
- ### Identify a user
238
-
239
- ```js
240
- DashX.identify({
241
- firstName: 'John',
242
- lastName: 'Doe',
243
- email: 'john@example.com',
244
- });
245
- ```
246
-
247
- ### Set identity with token
248
-
249
- ```js
250
- DashX.setIdentity('user-uid', 'user-token');
251
- ```
252
-
253
- ### Reset
254
-
255
- Clear the current identity:
256
-
257
- ```js
258
- DashX.reset();
259
- ```
260
-
261
- ### Track events
262
-
263
- ```js
264
- DashX.track('Item Purchased', {
265
- itemId: 'sku-123',
266
- price: 9.99,
267
- });
268
- ```
269
-
270
- ### Track screen views
271
-
272
- ```js
273
- DashX.screen('HomeScreen', {
274
- referrer: 'DeepLink',
275
- });
276
- ```
277
-
278
- ### Fetch a record
279
-
280
- Fetch a single record by URN (e.g. `"article/uuid"`). The URN must use the full UUID form of the record ID.
281
-
282
- ```js
283
- const record = await DashX.fetchRecord('article/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', {
284
- preview: false,
285
- language: 'en',
286
- // fields, include, exclude as arrays of objects if needed
287
- });
288
- ```
289
-
290
- | Option | Type | Description |
291
- |--------|------|-------------|
292
- | `preview` | `boolean` | Include draft/preview content |
293
- | `language` | `string` | Content language |
294
- | `fields` | `object[]` | Fields to return |
295
- | `include` | `object[]` | Related data to include |
296
- | `exclude` | `object[]` | Fields to exclude |
297
-
298
- ### Search records
299
-
300
- ```js
301
- const records = await DashX.searchRecords('article', {
302
- filter: { status: 'published' },
303
- order: [{ field: 'createdAt', direction: 'desc' }],
304
- limit: 10,
305
- page: 1,
306
- preview: false,
307
- language: 'en',
308
- });
309
- ```
310
-
311
- | Option | Type | Description |
312
- |--------|------|-------------|
313
- | `filter` | `object` | Filter criteria |
314
- | `order` | `object[]` | Sort order (e.g. `[{ field, direction }]`) |
315
- | `limit` | `number` | Max records to return |
316
- | `page` | `number` | Page number for pagination |
317
- | `preview` | `boolean` | Preview/draft content |
318
- | `language` | `string` | Content language |
319
- | `fields` | `object[]` | Fields to return |
320
- | `include` | `object[]` | Related data to include |
321
- | `exclude` | `object[]` | Fields to exclude |
322
-
323
- ### Push notifications
324
-
325
- Subscribe the current device to push notifications:
326
-
327
- ```js
328
- DashX.subscribe();
329
- ```
330
-
331
- Unsubscribe:
332
-
333
- ```js
334
- DashX.unsubscribe();
335
- ```
336
-
337
- ### Request notification permission (iOS)
338
-
339
- Prompt the user for push notification permission. Returns a `NotificationPermissionStatus` integer matching `UNAuthorizationStatus`:
340
-
341
- | Value | Meaning |
342
- |-------|---------|
343
- | `0` | Not determined |
344
- | `1` | Denied |
345
- | `2` | Authorized |
346
- | `3` | Provisional |
347
- | `4` | Ephemeral |
348
-
349
- ```js
350
- const status = await DashX.requestNotificationPermission();
351
- if (status === 2) {
352
- DashX.subscribe();
353
- }
354
- ```
355
-
356
- To check the current status without prompting the user:
357
-
358
- ```js
359
- const status = await DashX.getNotificationPermissionStatus();
360
- ```
361
-
362
- ### Listen for incoming messages
363
-
364
- Register a listener to handle push notifications when they arrive:
365
-
366
- ```js
367
- import { useEffect } from 'react';
368
-
369
- useEffect(() => {
370
- const subscription = DashX.onMessageReceived((message) => {
371
- console.log('Notification received:', message);
372
- });
373
-
374
- return () => subscription.remove();
375
- }, []);
376
- ```
377
-
378
- ### Listen for deep links / universal links (iOS)
379
-
380
- Register a listener that fires when the SDK receives a universal link. On the native side, `DashXRCTAppDelegate` forwards links via `DashX.handleUserActivity` automatically.
381
-
382
- ```js
383
- useEffect(() => {
384
- const subscription = DashX.onLinkReceived((url) => {
385
- console.log('Link received:', url);
386
- });
387
-
388
- return () => subscription.remove();
389
- }, []);
390
- ```
391
-
392
- ### Notification preferences
393
-
394
- Fetch the user's stored notification preferences:
395
-
396
- ```js
397
- const preferences = await DashX.fetchStoredPreferences();
398
- ```
399
-
400
- Save updated preferences:
401
-
402
- ```js
403
- await DashX.saveStoredPreferences({
404
- marketing_emails: true,
405
- product_updates: false,
406
- });
407
- ```
408
-
409
- ### Upload an asset (iOS)
410
-
411
- Upload a local file to DashX and associate it with a resource and attribute:
412
-
413
- ```js
414
- const response = await DashX.uploadAsset(
415
- '/path/to/local/file.jpg',
416
- 'user', // resource name
417
- 'avatar' // attribute name
418
- );
419
- // response: { status, data: { asset: { status, url, playbackIds } } }
420
- ```
421
-
422
- ### Fetch an asset (iOS)
423
-
424
- Retrieve the status and URL of a previously uploaded asset by its ID:
425
-
426
- ```js
427
- const asset = await DashX.fetchAsset('asset-id');
428
- // asset: { status, data: { asset: { status, url, playbackIds } } }
429
- ```
430
-
431
- ### Lifecycle tracking (iOS)
432
-
433
- Enable automatic app lifecycle event tracking (app opened, backgrounded, etc.):
434
-
435
- ```js
436
- DashX.enableLifecycleTracking(); // no-op on Android
437
- ```
438
-
439
- ### Ad tracking / ATT (iOS)
440
-
441
- Request App Tracking Transparency permission and enable IDFA tracking:
442
-
443
- ```js
444
- DashX.enableAdTracking(); // no-op on Android
445
- ```
446
-
447
- Make sure `NSUserTrackingUsageDescription` is set in your `Info.plist`.
448
-
449
- ### Logging
450
-
451
- Set the SDK log level for debugging:
452
-
453
- ```js
454
- DashX.setLogLevel(1); // 0 = off, 1 = errors, 2 = debug
455
- ```
456
-
457
- ## API Reference
458
-
459
- | Method | Signature | Platforms | Returns | Description |
460
- |--------|-----------|-----------|---------|-------------|
461
- | `configure` | `(options: object)` | iOS, Android | `void` | Initialize the SDK with your public key |
462
- | `identify` | `(options: object)` | iOS, Android | `void` | Identify the current user with traits |
463
- | `setIdentity` | `(uid: string, token: string)` | iOS, Android | `void` | Set user identity with UID and auth token |
464
- | `reset` | `()` | iOS, Android | `void` | Clear the current user identity |
465
- | `track` | `(event: string, data?: object)` | iOS, Android | `void` | Track a named event with optional data |
466
- | `screen` | `(screenName: string, data?: object)` | iOS, Android | `void` | Track a screen view |
467
- | `fetchRecord` | `(urn: string, options?: object)` | iOS, Android | `Promise<object>` | Fetch a single record by URN (`"resource/uuid"`) |
468
- | `searchRecords` | `(resource: string, options?: object)` | iOS, Android | `Promise<object[]>` | Search records with filter, order, limit, page, etc. |
469
- | `subscribe` | `()` | iOS, Android | `void` | Subscribe device for push notifications |
470
- | `unsubscribe` | `()` | iOS, Android | `void` | Unsubscribe device from push notifications |
471
- | `fetchStoredPreferences` | `()` | iOS, Android | `Promise<object>` | Fetch stored notification preferences |
472
- | `saveStoredPreferences` | `(data: object)` | iOS, Android | `Promise<object>` | Save notification preferences |
473
- | `setLogLevel` | `(level: number)` | iOS, Android | `void` | Set SDK log verbosity (0=off, 1=errors, 2=debug) |
474
- | `onMessageReceived` | `(callback: function)` | iOS, Android | `EmitterSubscription` | Listen for incoming push notifications |
475
- | `requestNotificationPermission` | `()` | iOS | `Promise<number>` | Prompt for push permission; resolves with `UNAuthorizationStatus` (0-4) |
476
- | `getNotificationPermissionStatus` | `()` | iOS | `Promise<number>` | Get current push permission status without prompting |
477
- | `onLinkReceived` | `(callback: function)` | iOS | `EmitterSubscription` | Listen for universal/deep link events |
478
- | `uploadAsset` | `(filePath: string, resource: string, attribute: string)` | iOS | `Promise<AssetResponse>` | Upload a local file and associate it with a resource attribute |
479
- | `fetchAsset` | `(assetId: string)` | iOS | `Promise<AssetResponse>` | Fetch status and URL of an uploaded asset |
480
- | `enableLifecycleTracking` | `()` | iOS | `void` | Enable automatic app lifecycle event tracking |
481
- | `enableAdTracking` | `()` | iOS | `void` | Request ATT permission and enable IDFA tracking |
482
-
483
- For detailed usage and advanced features, refer to the [DashX documentation](https://docs.dashx.com).
484
-
485
- ### Error Codes
486
-
487
- Promise rejections from native methods use a consistent error code across both platforms:
488
-
489
- | Code | Description |
490
- |------|-------------|
491
- | `EUNSPECIFIED` | An unspecified error occurred |
492
-
493
- The error code is available on the `code` property of the rejected error:
494
-
495
- ```js
496
- try {
497
- await DashX.fetchRecord('article/123');
498
- } catch (error) {
499
- console.log(error.code); // "EUNSPECIFIED"
500
- console.log(error.message); // human-readable description
501
- }
502
- ```
503
-
504
- A `DashXErrorCode` enum is exported from the TypeScript definitions for type-safe comparisons.
51
+ See the [Deep Linking & Push Navigation](https://docs.dashx.com/apps/messaging/deep-linking) guide for setup instructions, payload fields, and code examples.
505
52
 
506
53
  ## Contributing
507
54
 
508
- Please follow [these steps](https://github.com/dashxhq/dashx-js/tree/master/examples/react-native#setting-up-development-environment) to set up the development environment.
509
-
510
- You'll also need Apollo CLI to generate files:
511
-
512
- ```sh
513
- npm i -g apollo
514
- ```
515
-
516
- ## Publishing
517
-
518
- Use `yarn publish` to bump the version and release, then push the commit:
519
-
520
- ```sh
521
- yarn publish
522
- git push origin main
523
- ```
55
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and workflow.
524
56
 
525
57
  ## License
526
58
 
@@ -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
+ });