@explorins/pers-sdk-react-native 2.3.6 → 2.3.8
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/LICENSE +21 -0
- package/README.md +1266 -1244
- package/dist/__tests__/PersSDKProvider.test.js +33 -0
- package/dist/hooks/useBusiness.d.ts +2 -2
- package/dist/hooks/useBusiness.d.ts.map +1 -1
- package/dist/hooks/useBusiness.js +2 -2
- package/dist/hooks/useIPFSUrl.d.ts +3 -3
- package/dist/hooks/useIPFSUrl.d.ts.map +1 -1
- package/dist/hooks/useIPFSUrl.js +7 -7
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/hooks/useTransactions.js +13 -5
- package/dist/hooks/useWeb3.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2412 -492
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +4 -0
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +14 -0
- package/dist/providers/react-native-http-client.d.ts +2 -0
- package/dist/providers/react-native-http-client.d.ts.map +1 -1
- package/dist/providers/react-native-http-client.js +5 -2
- package/dist/storage/rn-secure-storage.d.ts.map +1 -1
- package/dist/storage/rn-secure-storage.js +21 -8
- package/package.json +3 -3
- package/src/__tests__/PersSDKProvider.test.tsx +484 -420
- package/src/__tests__/setup.ts +40 -40
- package/src/hooks/index.ts +60 -60
- package/src/hooks/useAnalytics.ts +327 -327
- package/src/hooks/useAuth.ts +265 -265
- package/src/hooks/useBookings.ts +191 -191
- package/src/hooks/useBusiness.ts +251 -251
- package/src/hooks/useCampaigns.ts +500 -500
- package/src/hooks/useDonations.ts +66 -66
- package/src/hooks/useEvents.ts +372 -372
- package/src/hooks/useFiles.ts +155 -155
- package/src/hooks/useIPFSUrl.ts +192 -192
- package/src/hooks/usePurchases.ts +65 -65
- package/src/hooks/useRedemptions.ts +467 -467
- package/src/hooks/useTenants.ts +72 -72
- package/src/hooks/useTokenBalances.ts +385 -385
- package/src/hooks/useTokens.ts +313 -313
- package/src/hooks/useTransactionSigner.ts +611 -611
- package/src/hooks/useTransactions.ts +299 -290
- package/src/hooks/useTriggerSources.ts +301 -301
- package/src/hooks/useUserStatus.ts +61 -61
- package/src/hooks/useUsers.ts +167 -167
- package/src/hooks/useWeb3.ts +355 -355
- package/src/index.ts +777 -774
- package/src/polyfills/index.ts +160 -160
- package/src/polyfills/web3-polyfills.ts +97 -97
- package/src/providers/PersSDKProvider.tsx +393 -374
- package/src/providers/react-native-auth-provider.ts +110 -110
- package/src/providers/react-native-http-client.ts +150 -145
- package/src/providers/rn-dpop-provider.ts +190 -190
- package/src/storage/async-storage-token-storage.ts +185 -185
- package/src/storage/rn-secure-storage.ts +182 -170
- package/src/types/react-native-globals.d.ts +45 -45
- package/src/utils/index.ts +8 -8
- package/src/utils/platform-detection.ts +86 -86
- package/dist/index-CzLxqePZ.js +0 -7717
- package/dist/index-CzLxqePZ.js.map +0 -1
|
@@ -1,375 +1,394 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState, ReactNode, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
2
|
-
import { Platform, AppState, AppStateStatus } from 'react-native';
|
|
3
|
-
import { PersSDK, PersConfig, DefaultAuthProvider } from '@explorins/pers-sdk/core';
|
|
4
|
-
import { ReactNativeHttpClient } from './react-native-http-client';
|
|
5
|
-
import { createReactNativeAuthProvider } from './react-native-auth-provider';
|
|
6
|
-
import { ReactNativeDPoPProvider } from './rn-dpop-provider';
|
|
7
|
-
import { UserDTO, AdminDTO } from '@explorins/pers-shared';
|
|
8
|
-
import { detectReactNativePlatform } from '../utils/platform-detection';
|
|
9
|
-
|
|
10
|
-
// Import manager types for TypeScript
|
|
11
|
-
import type {
|
|
12
|
-
AuthManager,
|
|
13
|
-
UserManager,
|
|
14
|
-
TokenManager,
|
|
15
|
-
BusinessManager,
|
|
16
|
-
CampaignManager,
|
|
17
|
-
RedemptionManager,
|
|
18
|
-
TransactionManager,
|
|
19
|
-
PurchaseManager,
|
|
20
|
-
TenantManager,
|
|
21
|
-
AnalyticsManager,
|
|
22
|
-
DonationManager
|
|
23
|
-
} from '@explorins/pers-sdk/core';
|
|
24
|
-
|
|
25
|
-
// Re-export PersConfig for external use
|
|
26
|
-
export type { PersConfig } from '@explorins/pers-sdk/core';
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Context interface for PERS SDK React Native integration
|
|
30
|
-
*
|
|
31
|
-
* ## Session Restoration Behavior
|
|
32
|
-
*
|
|
33
|
-
* Session restoration is **asynchronous**. When the app starts:
|
|
34
|
-
* 1. `isInitialized` becomes `true` when SDK is ready
|
|
35
|
-
* 2. Session restoration happens automatically
|
|
36
|
-
* 3. `isAuthenticated` and `user` update when restoration completes
|
|
37
|
-
*
|
|
38
|
-
* **Always check `isInitialized` AND `isAuthenticated` before using `user`:**
|
|
39
|
-
*
|
|
40
|
-
* ```tsx
|
|
41
|
-
* const { user, isInitialized, isAuthenticated } = usePersSDK();
|
|
42
|
-
*
|
|
43
|
-
* if (!isInitialized) return <LoadingSpinner />;
|
|
44
|
-
* if (!isAuthenticated || !user) return <LoginScreen />;
|
|
45
|
-
* return <WalletContent user={user} />;
|
|
46
|
-
* ```
|
|
47
|
-
*
|
|
48
|
-
* **Why `user` might be null on app restart:**
|
|
49
|
-
* - Access tokens expire after 1 hour (auto-refreshed if app is active)
|
|
50
|
-
* - Refresh tokens expire after 30 days (user must re-login)
|
|
51
|
-
* - Session restoration is in progress
|
|
52
|
-
*
|
|
53
|
-
* Listen for auth events to debug:
|
|
54
|
-
* ```typescript
|
|
55
|
-
* sdk.events.subscribe((event) => {
|
|
56
|
-
* console.log('[Auth Event]', event.type, event.details);
|
|
57
|
-
* }, { domains: ['authentication'], replay: true });
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* | Event | Meaning |
|
|
61
|
-
* |-------|---------|
|
|
62
|
-
* | `session_restored` | Tokens valid, user logged in |
|
|
63
|
-
* | `session_restoration_failed` | Tokens expired, needs re-login |
|
|
64
|
-
*
|
|
65
|
-
* @property sdk - Main SDK instance (null until initialized)
|
|
66
|
-
* @property authProvider - Platform-specific auth provider
|
|
67
|
-
* @property isInitialized - Whether SDK has been initialized (not whether user is authenticated!)
|
|
68
|
-
* @property isAuthenticated - Whether user is currently authenticated
|
|
69
|
-
* @property user - Current user data (null if not authenticated OR restoration in progress)
|
|
70
|
-
*/
|
|
71
|
-
export interface PersSDKContext {
|
|
72
|
-
// Main SDK instance
|
|
73
|
-
sdk: PersSDK | null;
|
|
74
|
-
|
|
75
|
-
// Platform-specific providers
|
|
76
|
-
authProvider: DefaultAuthProvider | null;
|
|
77
|
-
|
|
78
|
-
// State
|
|
79
|
-
isInitialized: boolean;
|
|
80
|
-
isAuthenticated: boolean;
|
|
81
|
-
user: UserDTO | AdminDTO | null;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* @param config
|
|
104
|
-
*
|
|
105
|
-
* @
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* ```
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const [
|
|
139
|
-
const [
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
1
|
+
import React, { createContext, useContext, useState, ReactNode, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
2
|
+
import { Platform, AppState, AppStateStatus } from 'react-native';
|
|
3
|
+
import { PersSDK, PersConfig, DefaultAuthProvider } from '@explorins/pers-sdk/core';
|
|
4
|
+
import { ReactNativeHttpClient } from './react-native-http-client';
|
|
5
|
+
import { createReactNativeAuthProvider } from './react-native-auth-provider';
|
|
6
|
+
import { ReactNativeDPoPProvider } from './rn-dpop-provider';
|
|
7
|
+
import { UserDTO, AdminDTO } from '@explorins/pers-shared';
|
|
8
|
+
import { detectReactNativePlatform } from '../utils/platform-detection';
|
|
9
|
+
|
|
10
|
+
// Import manager types for TypeScript
|
|
11
|
+
import type {
|
|
12
|
+
AuthManager,
|
|
13
|
+
UserManager,
|
|
14
|
+
TokenManager,
|
|
15
|
+
BusinessManager,
|
|
16
|
+
CampaignManager,
|
|
17
|
+
RedemptionManager,
|
|
18
|
+
TransactionManager,
|
|
19
|
+
PurchaseManager,
|
|
20
|
+
TenantManager,
|
|
21
|
+
AnalyticsManager,
|
|
22
|
+
DonationManager
|
|
23
|
+
} from '@explorins/pers-sdk/core';
|
|
24
|
+
|
|
25
|
+
// Re-export PersConfig for external use
|
|
26
|
+
export type { PersConfig } from '@explorins/pers-sdk/core';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Context interface for PERS SDK React Native integration
|
|
30
|
+
*
|
|
31
|
+
* ## Session Restoration Behavior
|
|
32
|
+
*
|
|
33
|
+
* Session restoration is **asynchronous**. When the app starts:
|
|
34
|
+
* 1. `isInitialized` becomes `true` when SDK is ready
|
|
35
|
+
* 2. Session restoration happens automatically
|
|
36
|
+
* 3. `isAuthenticated` and `user` update when restoration completes
|
|
37
|
+
*
|
|
38
|
+
* **Always check `isInitialized` AND `isAuthenticated` before using `user`:**
|
|
39
|
+
*
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const { user, isInitialized, isAuthenticated } = usePersSDK();
|
|
42
|
+
*
|
|
43
|
+
* if (!isInitialized) return <LoadingSpinner />;
|
|
44
|
+
* if (!isAuthenticated || !user) return <LoginScreen />;
|
|
45
|
+
* return <WalletContent user={user} />;
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* **Why `user` might be null on app restart:**
|
|
49
|
+
* - Access tokens expire after 1 hour (auto-refreshed if app is active)
|
|
50
|
+
* - Refresh tokens expire after 30 days (user must re-login)
|
|
51
|
+
* - Session restoration is in progress
|
|
52
|
+
*
|
|
53
|
+
* Listen for auth events to debug:
|
|
54
|
+
* ```typescript
|
|
55
|
+
* sdk.events.subscribe((event) => {
|
|
56
|
+
* console.log('[Auth Event]', event.type, event.details);
|
|
57
|
+
* }, { domains: ['authentication'], replay: true });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* | Event | Meaning |
|
|
61
|
+
* |-------|---------|
|
|
62
|
+
* | `session_restored` | Tokens valid, user logged in |
|
|
63
|
+
* | `session_restoration_failed` | Tokens expired, needs re-login |
|
|
64
|
+
*
|
|
65
|
+
* @property sdk - Main SDK instance (null until initialized)
|
|
66
|
+
* @property authProvider - Platform-specific auth provider
|
|
67
|
+
* @property isInitialized - Whether SDK has been initialized (not whether user is authenticated!)
|
|
68
|
+
* @property isAuthenticated - Whether user is currently authenticated
|
|
69
|
+
* @property user - Current user data (null if not authenticated OR restoration in progress)
|
|
70
|
+
*/
|
|
71
|
+
export interface PersSDKContext {
|
|
72
|
+
// Main SDK instance
|
|
73
|
+
sdk: PersSDK | null;
|
|
74
|
+
|
|
75
|
+
// Platform-specific providers
|
|
76
|
+
authProvider: DefaultAuthProvider | null;
|
|
77
|
+
|
|
78
|
+
// State
|
|
79
|
+
isInitialized: boolean;
|
|
80
|
+
isAuthenticated: boolean;
|
|
81
|
+
user: UserDTO | AdminDTO | null;
|
|
82
|
+
/** Content locale in effect (normalized BCP 47 tag), or null when localization is off */
|
|
83
|
+
locale: string | null;
|
|
84
|
+
|
|
85
|
+
// Methods
|
|
86
|
+
initialize: (config: PersConfig) => Promise<void>;
|
|
87
|
+
/** Change the content locale (`'auto'`, a BCP 47 tag, or null). Refetch content afterwards. */
|
|
88
|
+
setLocale: (locale: string | null) => void;
|
|
89
|
+
setAuthenticationState: (user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => void;
|
|
90
|
+
refreshUserData: () => Promise<void>;
|
|
91
|
+
restoreSession: () => Promise<UserDTO | null>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create the context
|
|
95
|
+
const SDKContext = createContext<PersSDKContext | null>(null);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* PERS SDK Provider for React Native
|
|
99
|
+
*
|
|
100
|
+
* Wraps your app to provide SDK context to all child components.
|
|
101
|
+
* Handles platform-specific initialization (DPoP, storage, etc.).
|
|
102
|
+
*
|
|
103
|
+
* @param config - SDK configuration (see PersConfig)
|
|
104
|
+
* @param config.apiProjectKey - Your PERS project key (required)
|
|
105
|
+
* @param config.environment - 'staging' | 'production' (default: 'staging')
|
|
106
|
+
* @param config.captureWalletEvents - Enable real-time blockchain events (default: true)
|
|
107
|
+
* @param config.dpop - DPoP configuration for enhanced security
|
|
108
|
+
*
|
|
109
|
+
* @example Basic usage
|
|
110
|
+
* ```tsx
|
|
111
|
+
* <PersSDKProvider config={{ apiProjectKey: 'my-project' }}>
|
|
112
|
+
* <App />
|
|
113
|
+
* </PersSDKProvider>
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example Disable wallet events
|
|
117
|
+
* ```tsx
|
|
118
|
+
* <PersSDKProvider config={{
|
|
119
|
+
* apiProjectKey: 'my-project',
|
|
120
|
+
* captureWalletEvents: false // Disable auto-connect to blockchain events
|
|
121
|
+
* }}>
|
|
122
|
+
* <App />
|
|
123
|
+
* </PersSDKProvider>
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export const PersSDKProvider: React.FC<{
|
|
127
|
+
children: ReactNode;
|
|
128
|
+
config?: PersConfig;
|
|
129
|
+
}> = ({ children, config }) => {
|
|
130
|
+
const initializingRef = useRef(false);
|
|
131
|
+
const eventUnsubscribeRef = useRef<(() => void) | null>(null);
|
|
132
|
+
|
|
133
|
+
// State refs for stable functions to read current values
|
|
134
|
+
const sdkRef = useRef<PersSDK | null>(null);
|
|
135
|
+
const isInitializedRef = useRef(false);
|
|
136
|
+
const isAuthenticatedRef = useRef(false);
|
|
137
|
+
|
|
138
|
+
const [sdk, setSdk] = useState<PersSDK | null>(null);
|
|
139
|
+
const [authProvider, setAuthProvider] = useState<DefaultAuthProvider | null>(null);
|
|
140
|
+
|
|
141
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
142
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
143
|
+
const [user, setUser] = useState<UserDTO | AdminDTO | null>(null);
|
|
144
|
+
const [locale, setLocaleState] = useState<string | null>(null);
|
|
145
|
+
|
|
146
|
+
// Keep state refs in sync immediately (not in useEffect to avoid race conditions)
|
|
147
|
+
sdkRef.current = sdk;
|
|
148
|
+
isInitializedRef.current = isInitialized;
|
|
149
|
+
isAuthenticatedRef.current = isAuthenticated;
|
|
150
|
+
|
|
151
|
+
const initialize = useCallback(async (config: PersConfig) => {
|
|
152
|
+
// Prevent multiple initializations
|
|
153
|
+
if (isInitialized || initializingRef.current) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
initializingRef.current = true;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Create HTTP client
|
|
161
|
+
const httpClient = new ReactNativeHttpClient();
|
|
162
|
+
|
|
163
|
+
const dpopConfig = config.dpop ?? {};
|
|
164
|
+
|
|
165
|
+
const isDpopEnabled = dpopConfig.enabled ?? true;
|
|
166
|
+
|
|
167
|
+
// Prepare DPoP settings for Auth Provider
|
|
168
|
+
const dpopSettings = {
|
|
169
|
+
enabled: isDpopEnabled,
|
|
170
|
+
cryptoProvider: dpopConfig.cryptoProvider
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Inject Native DPoP Provider (crypto-based) for mobile platforms if not already provided
|
|
174
|
+
if (Platform.OS !== 'web' && isDpopEnabled && !dpopSettings.cryptoProvider) {
|
|
175
|
+
dpopSettings.cryptoProvider = new ReactNativeDPoPProvider();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Create platform-aware auth provider if not provided
|
|
179
|
+
let authProvider: DefaultAuthProvider;
|
|
180
|
+
if (config.authProvider) {
|
|
181
|
+
authProvider = config.authProvider as DefaultAuthProvider;
|
|
182
|
+
} else {
|
|
183
|
+
// Use our platform-aware auth provider that automatically selects LocalStorage (web) or AsyncStorage (mobile)
|
|
184
|
+
authProvider = createReactNativeAuthProvider(config.apiProjectKey || 'default-project', {
|
|
185
|
+
debug: false,
|
|
186
|
+
dpop: dpopSettings
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Enhanced config with platform-appropriate auth provider
|
|
191
|
+
const sdkConfig: PersConfig = {
|
|
192
|
+
...config,
|
|
193
|
+
authProvider,
|
|
194
|
+
dpop: dpopSettings
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Initialize PersSDK with platform-aware auth provider
|
|
198
|
+
const sdkInstance = new PersSDK(httpClient, sdkConfig);
|
|
199
|
+
|
|
200
|
+
// Auto-configure data source and platform for React Native
|
|
201
|
+
// Native apps MUST set channel as backend cannot auto-detect without browser User-Agent
|
|
202
|
+
if (Platform.OS !== 'web') {
|
|
203
|
+
sdkInstance.setDataSource({
|
|
204
|
+
channel: 'mobile',
|
|
205
|
+
source: config.dataSource?.source || 'app',
|
|
206
|
+
medium: config.dataSource?.medium,
|
|
207
|
+
campaign: config.dataSource?.campaign,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Auto-detect platform info asynchronously
|
|
211
|
+
detectReactNativePlatform().then(platformInfo => {
|
|
212
|
+
sdkInstance.setPlatform(platformInfo);
|
|
213
|
+
}).catch(error => {
|
|
214
|
+
console.debug('[PersSDK] Platform detection failed:', error);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Subscribe to auth events BEFORE exposing SDK to consumers
|
|
219
|
+
// This ensures no events are missed due to React timing
|
|
220
|
+
eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
|
|
221
|
+
if (event.type === 'session_restored' || event.type === 'login_success') {
|
|
222
|
+
const details = event.details as { user?: UserDTO | AdminDTO };
|
|
223
|
+
if (details?.user) {
|
|
224
|
+
setUser(details.user);
|
|
225
|
+
setIsAuthenticated(true);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (event.type === 'logout_success' || event.type === 'session_restoration_failed') {
|
|
230
|
+
setUser(null);
|
|
231
|
+
setIsAuthenticated(false);
|
|
232
|
+
}
|
|
233
|
+
}, { domains: ['authentication'], replay: true });
|
|
234
|
+
|
|
235
|
+
setAuthProvider(authProvider);
|
|
236
|
+
setSdk(sdkInstance);
|
|
237
|
+
setLocaleState(sdkInstance.getLocale());
|
|
238
|
+
setIsInitialized(true);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('Failed to initialize PERS SDK:', error);
|
|
241
|
+
initializingRef.current = false;
|
|
242
|
+
throw error;
|
|
243
|
+
} finally {
|
|
244
|
+
initializingRef.current = false;
|
|
245
|
+
}
|
|
246
|
+
}, [isInitialized]);
|
|
247
|
+
|
|
248
|
+
// Cleanup subscription on unmount
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
return () => {
|
|
251
|
+
if (eventUnsubscribeRef.current) {
|
|
252
|
+
eventUnsubscribeRef.current();
|
|
253
|
+
eventUnsubscribeRef.current = null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}, []);
|
|
257
|
+
|
|
258
|
+
const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => {
|
|
259
|
+
setUser(user);
|
|
260
|
+
setIsAuthenticated(isAuthenticated);
|
|
261
|
+
}, []);
|
|
262
|
+
|
|
263
|
+
// Auto-initialize if config is provided
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (config && !isInitialized && !initializingRef.current) {
|
|
266
|
+
initialize(config).catch(err => {
|
|
267
|
+
console.error('Auto-initialization failed:', err);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}, [config, isInitialized, initialize]);
|
|
271
|
+
|
|
272
|
+
const setLocale = useCallback((next: string | null) => {
|
|
273
|
+
const currentSdk = sdkRef.current;
|
|
274
|
+
if (!currentSdk) {
|
|
275
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
276
|
+
}
|
|
277
|
+
currentSdk.setLocale(next);
|
|
278
|
+
setLocaleState(currentSdk.getLocale());
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
281
|
+
const refreshUserData = useCallback(async (): Promise<void> => {
|
|
282
|
+
// Read from refs to get current values
|
|
283
|
+
const currentSdk = sdkRef.current;
|
|
284
|
+
const currentIsInitialized = isInitializedRef.current;
|
|
285
|
+
|
|
286
|
+
if (!currentSdk || !currentIsInitialized) {
|
|
287
|
+
throw new Error('SDK not initialized. Cannot refresh user data.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const freshUserData = await currentSdk.users.getCurrentUser();
|
|
292
|
+
setUser(freshUserData);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('Failed to refresh user data:', error);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}, []); // No dependencies - reads from refs
|
|
298
|
+
|
|
299
|
+
const restoreSession = useCallback(async (): Promise<UserDTO | null> => {
|
|
300
|
+
// Read from refs to get current values
|
|
301
|
+
const currentSdk = sdkRef.current;
|
|
302
|
+
const currentIsInitialized = isInitializedRef.current;
|
|
303
|
+
|
|
304
|
+
if (!currentSdk || !currentIsInitialized) {
|
|
305
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const userData = await currentSdk.restoreSession();
|
|
310
|
+
if (userData) {
|
|
311
|
+
setAuthenticationState(userData, true);
|
|
312
|
+
}
|
|
313
|
+
return userData;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error('[PersSDK] Failed to restore session:', error);
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
}, [setAuthenticationState]);
|
|
319
|
+
|
|
320
|
+
// iOS/Android: Monitor app state and validate tokens when app becomes active
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
if (!sdk || Platform.OS === 'web') {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
|
|
327
|
+
if (nextAppState === 'active' && isAuthenticated) {
|
|
328
|
+
try {
|
|
329
|
+
// Trigger token validation when app resumes from background
|
|
330
|
+
// This ensures tokens are checked after extended inactivity
|
|
331
|
+
await sdk.auth.ensureValidToken();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.warn('[PersSDK] Token validation failed on app resume:', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
|
339
|
+
|
|
340
|
+
return () => {
|
|
341
|
+
subscription?.remove();
|
|
342
|
+
};
|
|
343
|
+
}, [sdk, isAuthenticated]);
|
|
344
|
+
|
|
345
|
+
const contextValue: PersSDKContext = useMemo(() => ({
|
|
346
|
+
// Main SDK instance
|
|
347
|
+
sdk,
|
|
348
|
+
|
|
349
|
+
// Platform-specific providers
|
|
350
|
+
authProvider,
|
|
351
|
+
|
|
352
|
+
// State
|
|
353
|
+
isInitialized,
|
|
354
|
+
isAuthenticated,
|
|
355
|
+
user,
|
|
356
|
+
locale,
|
|
357
|
+
|
|
358
|
+
// Methods - expose functions directly, not through refs
|
|
359
|
+
initialize,
|
|
360
|
+
setLocale,
|
|
361
|
+
setAuthenticationState,
|
|
362
|
+
refreshUserData,
|
|
363
|
+
restoreSession,
|
|
364
|
+
}), [
|
|
365
|
+
sdk,
|
|
366
|
+
authProvider,
|
|
367
|
+
isInitialized,
|
|
368
|
+
isAuthenticated,
|
|
369
|
+
user,
|
|
370
|
+
locale,
|
|
371
|
+
initialize,
|
|
372
|
+
setLocale,
|
|
373
|
+
setAuthenticationState,
|
|
374
|
+
refreshUserData,
|
|
375
|
+
restoreSession
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<SDKContext.Provider value={contextValue}>
|
|
380
|
+
{children}
|
|
381
|
+
</SDKContext.Provider>
|
|
382
|
+
);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Custom hook to use the SDK context
|
|
386
|
+
export const usePersSDK = (): PersSDKContext => {
|
|
387
|
+
const context = useContext(SDKContext);
|
|
388
|
+
|
|
389
|
+
if (!context) {
|
|
390
|
+
throw new Error('usePersSDK must be used within a PersSDKProvider');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return context;
|
|
375
394
|
};
|