@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.
- package/README.md +84 -0
- package/dist/hooks/useRedemptions.d.ts +7 -3
- package/dist/hooks/useRedemptions.d.ts.map +1 -1
- package/dist/hooks/useRedemptions.js +7 -1
- package/dist/hooks/useTokens.d.ts +2 -2
- package/dist/hooks/useTokens.d.ts.map +1 -1
- package/dist/index.d.ts +158 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1178 -72
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +17 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/platform-detection.d.ts +35 -0
- package/dist/utils/platform-detection.d.ts.map +1 -0
- package/dist/utils/platform-detection.js +79 -0
- package/package.json +7 -3
- package/src/hooks/useRedemptions.ts +16 -5
- package/src/index.ts +207 -1
- package/src/providers/PersSDKProvider.tsx +19 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/platform-detection.ts +86 -0
|
@@ -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;
|
|
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 @@
|
|
|
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,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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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?:
|
|
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,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
|
+
}
|