@explorins/pers-sdk-react-native 2.3.0-alpha.3 → 2.3.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAA2C,MAAM,OAAO,CAAC;AAEvH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAkB3D,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAGhC,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5F,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/C;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CA6NA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
1
+ {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAA2C,MAAM,OAAO,CAAC;AAEvH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAmB3D,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAGhC,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5F,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/C;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CA+OA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
@@ -5,6 +5,7 @@ import { PersSDK } from '@explorins/pers-sdk/core';
5
5
  import { ReactNativeHttpClient } from './react-native-http-client';
6
6
  import { createReactNativeAuthProvider } from './react-native-auth-provider';
7
7
  import { ReactNativeDPoPProvider } from './rn-dpop-provider';
8
+ import { detectReactNativePlatform } from '../utils/platform-detection';
8
9
  // Create the context
9
10
  const SDKContext = createContext(null);
10
11
  /**
@@ -92,6 +93,22 @@ export const PersSDKProvider = ({ children, config }) => {
92
93
  };
93
94
  // Initialize PersSDK with platform-aware auth provider
94
95
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
96
+ // Auto-configure data source and platform for React Native
97
+ // Native apps MUST set channel as backend cannot auto-detect without browser User-Agent
98
+ if (Platform.OS !== 'web') {
99
+ sdkInstance.setDataSource({
100
+ channel: 'mobile',
101
+ source: config.dataSource?.source || 'app',
102
+ medium: config.dataSource?.medium,
103
+ campaign: config.dataSource?.campaign,
104
+ });
105
+ // Auto-detect platform info asynchronously
106
+ detectReactNativePlatform().then(platformInfo => {
107
+ sdkInstance.setPlatform(platformInfo);
108
+ }).catch(error => {
109
+ console.debug('[PersSDK] Platform detection failed:', error);
110
+ });
111
+ }
95
112
  // Subscribe to auth events BEFORE exposing SDK to consumers
96
113
  // This ensures no events are missed due to React timing
97
114
  eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Utility exports for React Native SDK
3
+ */
4
+ export { detectReactNativePlatform, detectReactNativePlatformSync } from './platform-detection';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC9B,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Utility exports for React Native SDK
3
+ */
4
+ export { detectReactNativePlatform, detectReactNativePlatformSync } from './platform-detection';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * React Native Platform Detection Utilities
3
+ *
4
+ * Provides automatic platform info detection for React Native apps.
5
+ *
6
+ * @module @explorins/pers-sdk-react-native/utils
7
+ */
8
+ import type { Platform } from '@explorins/pers-sdk/core';
9
+ /**
10
+ * Detect platform info for React Native apps
11
+ *
12
+ * Automatically gathers OS, version, app info, and device type.
13
+ * Uses react-native-device-info for enhanced detection if installed.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { detectReactNativePlatform } from '@explorins/pers-sdk-react-native';
18
+ *
19
+ * // Get platform info
20
+ * const platform = await detectReactNativePlatform();
21
+ * // { os: 'iOS', osVersion: '17.4', app: 'MyApp', appVersion: '2.3.1', deviceType: 'phone' }
22
+ *
23
+ * // Set in SDK
24
+ * sdk.setPlatform(platform);
25
+ * ```
26
+ */
27
+ export declare function detectReactNativePlatform(): Promise<Platform>;
28
+ /**
29
+ * Synchronous version for immediate use (limited info)
30
+ *
31
+ * Use when you need platform info immediately without async.
32
+ * For full info including device type, use detectReactNativePlatform().
33
+ */
34
+ export declare function detectReactNativePlatformSync(): Platform;
35
+ //# sourceMappingURL=platform-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-detection.d.ts","sourceRoot":"","sources":["../../src/utils/platform-detection.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAWzD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,QAAQ,CAAC,CAsBnE;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,IAAI,QAAQ,CAiBxD"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * React Native Platform Detection Utilities
3
+ *
4
+ * Provides automatic platform info detection for React Native apps.
5
+ *
6
+ * @module @explorins/pers-sdk-react-native/utils
7
+ */
8
+ import { Platform as RNPlatform } from 'react-native';
9
+ // DeviceInfo is optional - dynamically loaded if available
10
+ let DeviceInfo = null;
11
+ try {
12
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
13
+ DeviceInfo = require('react-native-device-info').default;
14
+ }
15
+ catch {
16
+ // react-native-device-info not installed - will use basic detection
17
+ }
18
+ /**
19
+ * Detect platform info for React Native apps
20
+ *
21
+ * Automatically gathers OS, version, app info, and device type.
22
+ * Uses react-native-device-info for enhanced detection if installed.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { detectReactNativePlatform } from '@explorins/pers-sdk-react-native';
27
+ *
28
+ * // Get platform info
29
+ * const platform = await detectReactNativePlatform();
30
+ * // { os: 'iOS', osVersion: '17.4', app: 'MyApp', appVersion: '2.3.1', deviceType: 'phone' }
31
+ *
32
+ * // Set in SDK
33
+ * sdk.setPlatform(platform);
34
+ * ```
35
+ */
36
+ export async function detectReactNativePlatform() {
37
+ const platform = {
38
+ os: RNPlatform.OS === 'ios' ? 'iOS' : RNPlatform.OS === 'android' ? 'Android' : RNPlatform.OS,
39
+ osVersion: RNPlatform.Version?.toString(),
40
+ deviceType: 'phone', // Default fallback
41
+ };
42
+ if (DeviceInfo) {
43
+ try {
44
+ // App info
45
+ platform.app = await DeviceInfo.getApplicationName();
46
+ platform.appVersion = DeviceInfo.getVersion();
47
+ // Device type detection
48
+ const isTablet = await DeviceInfo.isTablet();
49
+ platform.deviceType = isTablet ? 'tablet' : 'phone';
50
+ }
51
+ catch (error) {
52
+ console.debug('[Platform] DeviceInfo error, using basic detection');
53
+ }
54
+ }
55
+ return platform;
56
+ }
57
+ /**
58
+ * Synchronous version for immediate use (limited info)
59
+ *
60
+ * Use when you need platform info immediately without async.
61
+ * For full info including device type, use detectReactNativePlatform().
62
+ */
63
+ export function detectReactNativePlatformSync() {
64
+ const platform = {
65
+ os: RNPlatform.OS === 'ios' ? 'iOS' : RNPlatform.OS === 'android' ? 'Android' : RNPlatform.OS,
66
+ osVersion: RNPlatform.Version?.toString(),
67
+ deviceType: 'phone', // Default, can't detect tablet sync without DeviceInfo
68
+ };
69
+ if (DeviceInfo) {
70
+ try {
71
+ platform.app = DeviceInfo.getApplicationNameSync?.();
72
+ platform.appVersion = DeviceInfo.getVersion?.();
73
+ }
74
+ catch {
75
+ // Ignore if DeviceInfo methods fail
76
+ }
77
+ }
78
+ return platform;
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorins/pers-sdk-react-native",
3
- "version": "2.3.0-alpha.3",
3
+ "version": "2.3.3",
4
4
  "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "author": "eXplorins",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@explorins/pers-sdk": "^2.2.0-alpha.2",
40
+ "@explorins/pers-sdk": "^2.3.3",
41
41
  "@explorins/pers-signer": "^1.0.34",
42
42
  "buffer": "^6.0.3",
43
43
  "ethers": "^6.15.0",
@@ -94,6 +94,7 @@
94
94
  "@react-native-async-storage/async-storage": ">=1.15.0",
95
95
  "react": ">=16.8.0",
96
96
  "react-native": ">=0.60.0",
97
+ "react-native-device-info": "^15.0.2",
97
98
  "react-native-get-random-values": ">=1.8.0",
98
99
  "react-native-keychain": ">=8.0.0",
99
100
  "react-native-passkey": ">=3.0.0",
@@ -103,6 +104,9 @@
103
104
  "@react-native-async-storage/async-storage": {
104
105
  "optional": false
105
106
  },
107
+ "react-native-device-info": {
108
+ "optional": true
109
+ },
106
110
  "react-native-get-random-values": {
107
111
  "optional": false
108
112
  },
@@ -162,7 +166,7 @@
162
166
  "@ethersproject/scrypt": false
163
167
  },
164
168
  "devDependencies": {
165
- "@explorins/pers-shared": "^2.1.128",
169
+ "@explorins/pers-shared": "^2.1.183",
166
170
  "@react-native-async-storage/async-storage": "^3.0.2",
167
171
  "@rollup/plugin-commonjs": "^25.0.7",
168
172
  "@rollup/plugin-json": "^6.1.0",
@@ -14,11 +14,15 @@ import type {
14
14
  DynamicContext,
15
15
  SortOrder
16
16
  } from '@explorins/pers-sdk';
17
- import type { RedemptionRedeemFilters } from '@explorins/pers-sdk/redemption';
17
+ import type { RedemptionRedeemQueryParams, RedemptionQueryParams } from '@explorins/pers-sdk/redemption';
18
18
  import type { PaginationOptions } from '@explorins/pers-sdk';
19
19
 
20
- // Re-export for consumers
21
- export type { RedemptionRedeemFilters, RedemptionFilterOptions } from '@explorins/pers-sdk/redemption';
20
+ // Re-export for consumers (new names + deprecated aliases)
21
+ export type { RedemptionRedeemQueryParams, RedemptionQueryParams } from '@explorins/pers-sdk/redemption';
22
+ /** @deprecated Use RedemptionRedeemQueryParams instead */
23
+ export type RedemptionRedeemFilters = RedemptionRedeemQueryParams;
24
+ /** @deprecated Use RedemptionQueryParams instead */
25
+ export type RedemptionFilterOptions = RedemptionQueryParams;
22
26
 
23
27
  /**
24
28
  * Options for fetching redemptions with filtering and pagination
@@ -105,7 +109,14 @@ export const useRedemptions = () => {
105
109
  }
106
110
 
107
111
  try {
108
- const result = await sdk.redemptions.getRedemptions(options);
112
+ // Convert Date to ISO string for API compatibility
113
+ const apiOptions = options ? {
114
+ ...options,
115
+ startDate: options.startDate instanceof Date ? options.startDate.toISOString() : options.startDate,
116
+ endDate: options.endDate instanceof Date ? options.endDate.toISOString() : options.endDate,
117
+ } : undefined;
118
+
119
+ const result = await sdk.redemptions.getRedemptions(apiOptions);
109
120
  return result;
110
121
  } catch (error) {
111
122
  console.error('Failed to fetch redemptions:', error);
@@ -327,7 +338,7 @@ export const useRedemptions = () => {
327
338
  * ```
328
339
  */
329
340
  const getRedemptionRedeems = useCallback(async (
330
- filters?: RedemptionRedeemFilters,
341
+ filters?: RedemptionRedeemQueryParams,
331
342
  include?: RedemptionRedeemIncludeRelation[]
332
343
  ): Promise<PaginatedResponseDTO<RedemptionRedeemDTO>> => {
333
344
  if (!isInitialized || !sdk) {
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // Polyfills - must be imported first for Hermes engine compatibility
2
+ import 'react-native-url-polyfill/auto';
3
+
1
4
  /**
2
5
  * @fileoverview PERS SDK for React Native - Tourism Loyalty System
3
6
  *
@@ -174,6 +177,7 @@ export {
174
177
  *
175
178
  * **Business & Campaign Management:**
176
179
  * - `useBusiness` - Business profile and operations
180
+ * - `useBookings` - Booking management with status validation
177
181
  * - `useCampaigns` - Marketing campaigns and promotions
178
182
  * - `useRedemptions` - Reward redemption and fulfillment
179
183
  *
@@ -252,6 +256,7 @@ export {
252
256
  useTransactionSigner,
253
257
  SigningStatus,
254
258
  useBusiness,
259
+ useBookings,
255
260
  useCampaigns,
256
261
  useRedemptions,
257
262
  useWeb3,
@@ -306,6 +311,9 @@ export type { TriggerSourceQueryOptions, TriggerSourceHook } from './hooks';
306
311
  // Re-export analytics types for convenience
307
312
  export type { AnalyticsHook } from './hooks';
308
313
 
314
+ // Re-export booking types for convenience
315
+ export type { BookingHook, BookingQueryOptions, BookingValidationOptions } from './hooks';
316
+
309
317
  // ==============================================================================
310
318
  // PLATFORM ADAPTERS
311
319
  // ==============================================================================
@@ -566,4 +574,202 @@ export {
566
574
  PersApiError,
567
575
  AuthenticationError,
568
576
  ErrorUtils
569
- } from '@explorins/pers-sdk/core';
577
+ } from '@explorins/pers-sdk/core';
578
+
579
+ // ==============================================================================
580
+ // DATA SOURCE TRACKING
581
+ // ==============================================================================
582
+
583
+ /**
584
+ * Data source tracking utilities for attribution analytics
585
+ *
586
+ * Native apps MUST configure data source and platform info for proper attribution.
587
+ * Web apps benefit from auto-detection, but can still set explicit values.
588
+ *
589
+ * @example
590
+ * **React Native Setup:**
591
+ * ```typescript
592
+ * import { usePersSDK, detectReactNativePlatform } from '@explorins/pers-sdk-react-native';
593
+ *
594
+ * const { sdk } = usePersSDK();
595
+ *
596
+ * // Set platform info (call once on app start)
597
+ * sdk.setPlatform(detectReactNativePlatform());
598
+ *
599
+ * // Set data source (call once, update on deep links/push)
600
+ * sdk.setDataSource({
601
+ * channel: 'mobile',
602
+ * source: 'app_store',
603
+ * });
604
+ * ```
605
+ *
606
+ * @example
607
+ * **Handle Deep Links:**
608
+ * ```typescript
609
+ * import { parseUtmParams } from '@explorins/pers-sdk-react-native';
610
+ *
611
+ * function handleDeepLink(url: string) {
612
+ * const utm = parseUtmParams(url);
613
+ * sdk.updateDataSource({
614
+ * medium: utm.medium,
615
+ * campaign: utm.campaign,
616
+ * source: utm.source || 'deep_link',
617
+ * });
618
+ * }
619
+ * ```
620
+ *
621
+ * @example
622
+ * **Handle Push Notifications:**
623
+ * ```typescript
624
+ * function handlePushNotification(notification: PushNotification) {
625
+ * sdk.updateDataSource({
626
+ * medium: 'push',
627
+ * source: 'push_notification',
628
+ * campaign: notification.campaignId,
629
+ * });
630
+ * }
631
+ * ```
632
+ */
633
+ export {
634
+ // Types
635
+ type Platform,
636
+ type DataSource,
637
+ type UtmParams,
638
+ type WebDataSourceConfig,
639
+ type NativeDataSourceConfig,
640
+
641
+ // Utilities
642
+ parseUtmParams,
643
+ detectSourceFromReferrer,
644
+ detectWebPlatform,
645
+ } from '@explorins/pers-sdk/core';
646
+
647
+ // React Native specific platform detection
648
+ export { detectReactNativePlatform } from './utils/platform-detection';
649
+
650
+ // ==============================================================================
651
+ // FIELD VALIDATION UTILITIES
652
+ // ==============================================================================
653
+
654
+ /**
655
+ * Field validation utilities for custom fields and forms
656
+ *
657
+ * These functions are re-exported from @explorins/pers-shared for convenience.
658
+ * Use them for client-side validation before submitting data to the backend.
659
+ *
660
+ * @example
661
+ * **Single Field Validation:**
662
+ * ```typescript
663
+ * import { validateFieldValue, FieldType, FieldValidationRules } from '@explorins/pers-sdk-react-native';
664
+ *
665
+ * const rules: FieldValidationRules = { minLength: 3 };
666
+ * const error = validateFieldValue('ab', rules, 'text');
667
+ * // error: { message: 'Must be at least 3 characters' }
668
+ * ```
669
+ *
670
+ * @example
671
+ * **Form Validation:**
672
+ * ```typescript
673
+ * import { validateAllFields, FieldDefinition } from '@explorins/pers-sdk-react-native';
674
+ *
675
+ * const definitions: FieldDefinition[] = [
676
+ * { key: 'email', fieldType: 'email', label: 'Email', validation: { minLength: 1 } },
677
+ * { key: 'phone', fieldType: 'phone', label: 'Phone', validation: {} }
678
+ * ];
679
+ *
680
+ * const formData = { email: 'invalid', phone: '123' };
681
+ * const errors = validateAllFields(formData, definitions);
682
+ * // errors: [{ field: 'email', message: 'Invalid email format' }]
683
+ * ```
684
+ *
685
+ * @see {@link validateFieldValue} - Validate a single field value
686
+ * @see {@link validateAllFields} - Validate all fields against definitions
687
+ * @see {@link FieldDefinition} - Field definition structure
688
+ * @see {@link FieldValidationRules} - Available validation rules
689
+ */
690
+ export {
691
+ validateFieldValue,
692
+ validateAllFields,
693
+ validateFieldComparisons,
694
+ compareValues,
695
+ type FieldDefinition,
696
+ type FieldValidationRules,
697
+ type ValidationErrorDetail,
698
+ type FieldType,
699
+ type FieldComparison,
700
+ type ComparisonOperator,
701
+ } from '@explorins/pers-sdk';
702
+
703
+ // ==============================================================================
704
+ // BOOKING STATUS VALIDATION
705
+ // ==============================================================================
706
+
707
+ /**
708
+ * Booking status types and validation utilities for eligibility checks
709
+ *
710
+ * Use these to validate if a user's booking status meets requirements
711
+ * for redemptions, campaigns, or other booking-dependent features.
712
+ *
713
+ * **BookingStatus:** 'past' | 'active' | 'future'
714
+ * - `past`: User's stay has ended (checkOut ≤ now)
715
+ * - `active`: User is currently staying (checkIn ≤ now < checkOut)
716
+ * - `future`: Upcoming booking (checkIn > now)
717
+ *
718
+ * **BookingRequirementType:** 'active' | 'future' | 'past' | 'active_future' | 'any'
719
+ * - `active`: Only active bookings allowed
720
+ * - `future`: Only future bookings allowed
721
+ * - `past`: Only past bookings allowed
722
+ * - `active_future`: Active OR future bookings allowed
723
+ * - `any`: Any booking status allowed
724
+ *
725
+ * @example
726
+ * **Basic Validation:**
727
+ * ```typescript
728
+ * import {
729
+ * isBookingStatusValid,
730
+ * BookingStatus,
731
+ * BookingRequirementType
732
+ * } from '@explorins/pers-sdk-react-native';
733
+ *
734
+ * const userBookingStatus: BookingStatus = booking.status;
735
+ * const requirement: BookingRequirementType = redemption.bookingRequirement;
736
+ * const canRedeem = isBookingStatusValid(userBookingStatus, requirement);
737
+ * ```
738
+ *
739
+ * @example
740
+ * **Redemption Screen:**
741
+ * ```typescript
742
+ * function RedemptionCard({ redemption, userBooking }) {
743
+ * const isEligible = redemption.bookingRequirement
744
+ * ? isBookingStatusValid(userBooking?.status ?? 'past', redemption.bookingRequirement)
745
+ * : true;
746
+ *
747
+ * return (
748
+ * <View style={[styles.card, !isEligible && styles.disabled]}>
749
+ * <Text>{redemption.name}</Text>
750
+ * {!isEligible && (
751
+ * <Text style={styles.warning}>
752
+ * Requires {redemption.bookingRequirement} booking
753
+ * </Text>
754
+ * )}
755
+ * </View>
756
+ * );
757
+ * }
758
+ * ```
759
+ *
760
+ * @example
761
+ * **Validation Results:**
762
+ * ```typescript
763
+ * isBookingStatusValid('active', 'active'); // true
764
+ * isBookingStatusValid('future', 'active'); // false
765
+ * isBookingStatusValid('active', 'active_future'); // true
766
+ * isBookingStatusValid('future', 'active_future'); // true
767
+ * isBookingStatusValid('past', 'active_future'); // false
768
+ * isBookingStatusValid('past', 'any'); // true
769
+ * ```
770
+ */
771
+ export {
772
+ isBookingStatusValid,
773
+ type BookingStatus,
774
+ type BookingRequirementType
775
+ } from '@explorins/pers-sdk';
@@ -5,6 +5,7 @@ import { ReactNativeHttpClient } from './react-native-http-client';
5
5
  import { createReactNativeAuthProvider } from './react-native-auth-provider';
6
6
  import { ReactNativeDPoPProvider } from './rn-dpop-provider';
7
7
  import { UserDTO, AdminDTO } from '@explorins/pers-shared';
8
+ import { detectReactNativePlatform } from '../utils/platform-detection';
8
9
 
9
10
  // Import manager types for TypeScript
10
11
  import type {
@@ -191,6 +192,24 @@ export const PersSDKProvider: React.FC<{
191
192
  // Initialize PersSDK with platform-aware auth provider
192
193
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
193
194
 
195
+ // Auto-configure data source and platform for React Native
196
+ // Native apps MUST set channel as backend cannot auto-detect without browser User-Agent
197
+ if (Platform.OS !== 'web') {
198
+ sdkInstance.setDataSource({
199
+ channel: 'mobile',
200
+ source: config.dataSource?.source || 'app',
201
+ medium: config.dataSource?.medium,
202
+ campaign: config.dataSource?.campaign,
203
+ });
204
+
205
+ // Auto-detect platform info asynchronously
206
+ detectReactNativePlatform().then(platformInfo => {
207
+ sdkInstance.setPlatform(platformInfo);
208
+ }).catch(error => {
209
+ console.debug('[PersSDK] Platform detection failed:', error);
210
+ });
211
+ }
212
+
194
213
  // Subscribe to auth events BEFORE exposing SDK to consumers
195
214
  // This ensures no events are missed due to React timing
196
215
  eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Utility exports for React Native SDK
3
+ */
4
+
5
+ export {
6
+ detectReactNativePlatform,
7
+ detectReactNativePlatformSync
8
+ } from './platform-detection';
@@ -0,0 +1,86 @@
1
+ /**
2
+ * React Native Platform Detection Utilities
3
+ *
4
+ * Provides automatic platform info detection for React Native apps.
5
+ *
6
+ * @module @explorins/pers-sdk-react-native/utils
7
+ */
8
+
9
+ import { Platform as RNPlatform } from 'react-native';
10
+ import type { Platform } from '@explorins/pers-sdk/core';
11
+
12
+ // DeviceInfo is optional - dynamically loaded if available
13
+ let DeviceInfo: any = null;
14
+ try {
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ DeviceInfo = require('react-native-device-info').default;
17
+ } catch {
18
+ // react-native-device-info not installed - will use basic detection
19
+ }
20
+
21
+ /**
22
+ * Detect platform info for React Native apps
23
+ *
24
+ * Automatically gathers OS, version, app info, and device type.
25
+ * Uses react-native-device-info for enhanced detection if installed.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { detectReactNativePlatform } from '@explorins/pers-sdk-react-native';
30
+ *
31
+ * // Get platform info
32
+ * const platform = await detectReactNativePlatform();
33
+ * // { os: 'iOS', osVersion: '17.4', app: 'MyApp', appVersion: '2.3.1', deviceType: 'phone' }
34
+ *
35
+ * // Set in SDK
36
+ * sdk.setPlatform(platform);
37
+ * ```
38
+ */
39
+ export async function detectReactNativePlatform(): Promise<Platform> {
40
+ const platform: Platform = {
41
+ os: RNPlatform.OS === 'ios' ? 'iOS' : RNPlatform.OS === 'android' ? 'Android' : RNPlatform.OS,
42
+ osVersion: RNPlatform.Version?.toString(),
43
+ deviceType: 'phone', // Default fallback
44
+ };
45
+
46
+ if (DeviceInfo) {
47
+ try {
48
+ // App info
49
+ platform.app = await DeviceInfo.getApplicationName();
50
+ platform.appVersion = DeviceInfo.getVersion();
51
+
52
+ // Device type detection
53
+ const isTablet = await DeviceInfo.isTablet();
54
+ platform.deviceType = isTablet ? 'tablet' : 'phone';
55
+ } catch (error) {
56
+ console.debug('[Platform] DeviceInfo error, using basic detection');
57
+ }
58
+ }
59
+
60
+ return platform;
61
+ }
62
+
63
+ /**
64
+ * Synchronous version for immediate use (limited info)
65
+ *
66
+ * Use when you need platform info immediately without async.
67
+ * For full info including device type, use detectReactNativePlatform().
68
+ */
69
+ export function detectReactNativePlatformSync(): Platform {
70
+ const platform: Platform = {
71
+ os: RNPlatform.OS === 'ios' ? 'iOS' : RNPlatform.OS === 'android' ? 'Android' : RNPlatform.OS,
72
+ osVersion: RNPlatform.Version?.toString(),
73
+ deviceType: 'phone', // Default, can't detect tablet sync without DeviceInfo
74
+ };
75
+
76
+ if (DeviceInfo) {
77
+ try {
78
+ platform.app = DeviceInfo.getApplicationNameSync?.();
79
+ platform.appVersion = DeviceInfo.getVersion?.();
80
+ } catch {
81
+ // Ignore if DeviceInfo methods fail
82
+ }
83
+ }
84
+
85
+ return platform;
86
+ }