@explorins/pers-sdk-react-native 2.1.1 → 2.1.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/dist/hooks/useTokenBalances.d.ts.map +1 -1
- package/dist/hooks/useTokenBalances.js +21 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +315 -106
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +1 -12
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +67 -77
- package/package.json +2 -2
- package/src/hooks/useTokenBalances.ts +23 -9
- package/src/index.ts +1 -1
- package/src/providers/PersSDKProvider.tsx +75 -96
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
2
|
import { PersSDK, PersConfig, DefaultAuthProvider } from '@explorins/pers-sdk/core';
|
|
3
3
|
import { UserDTO, AdminDTO } from '@explorins/pers-shared';
|
|
4
|
-
import type { AuthManager, UserManager, TokenManager, BusinessManager, CampaignManager, RedemptionManager, TransactionManager, PurchaseManager, TenantManager, AnalyticsManager, DonationManager } from '@explorins/pers-sdk/core';
|
|
5
4
|
export type { PersConfig } from '@explorins/pers-sdk/core';
|
|
6
5
|
export interface PersSDKContext {
|
|
7
6
|
sdk: PersSDK | null;
|
|
8
|
-
auth: AuthManager | null;
|
|
9
|
-
users: UserManager | null;
|
|
10
|
-
tokens: TokenManager | null;
|
|
11
|
-
businesses: BusinessManager | null;
|
|
12
|
-
campaigns: CampaignManager | null;
|
|
13
|
-
redemptions: RedemptionManager | null;
|
|
14
|
-
transactions: TransactionManager | null;
|
|
15
|
-
purchases: PurchaseManager | null;
|
|
16
|
-
tenants: TenantManager | null;
|
|
17
|
-
analytics: AnalyticsManager | null;
|
|
18
|
-
donations: DonationManager | null;
|
|
19
7
|
authProvider: DefaultAuthProvider | null;
|
|
20
8
|
isInitialized: boolean;
|
|
21
9
|
isAuthenticated: boolean;
|
|
@@ -23,6 +11,7 @@ export interface PersSDKContext {
|
|
|
23
11
|
initialize: (config: PersConfig) => Promise<void>;
|
|
24
12
|
setAuthenticationState: (user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => void;
|
|
25
13
|
refreshUserData: () => Promise<void>;
|
|
14
|
+
restoreSession: () => Promise<UserDTO | null>;
|
|
26
15
|
}
|
|
27
16
|
export declare const PersSDKProvider: React.FC<{
|
|
28
17
|
children: ReactNode;
|
|
@@ -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;AAkB3D,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,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;AAMD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAwOA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
|
|
@@ -10,11 +10,19 @@ const SDKContext = createContext(null);
|
|
|
10
10
|
// Provider component
|
|
11
11
|
export const PersSDKProvider = ({ children, config }) => {
|
|
12
12
|
const initializingRef = useRef(false);
|
|
13
|
+
// State refs for stable functions to read current values
|
|
14
|
+
const sdkRef = useRef(null);
|
|
15
|
+
const isInitializedRef = useRef(false);
|
|
16
|
+
const isAuthenticatedRef = useRef(false);
|
|
13
17
|
const [sdk, setSdk] = useState(null);
|
|
14
18
|
const [authProvider, setAuthProvider] = useState(null);
|
|
15
19
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
16
20
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
17
21
|
const [user, setUser] = useState(null);
|
|
22
|
+
// Keep state refs in sync immediately (not in useEffect to avoid race conditions)
|
|
23
|
+
sdkRef.current = sdk;
|
|
24
|
+
isInitializedRef.current = isInitialized;
|
|
25
|
+
isAuthenticatedRef.current = isAuthenticated;
|
|
18
26
|
const initialize = useCallback(async (config) => {
|
|
19
27
|
// Prevent multiple initializations
|
|
20
28
|
if (isInitialized || initializingRef.current) {
|
|
@@ -75,89 +83,81 @@ export const PersSDKProvider = ({ children, config }) => {
|
|
|
75
83
|
// Auto-initialize if config is provided
|
|
76
84
|
useEffect(() => {
|
|
77
85
|
if (config && !isInitialized && !initializingRef.current) {
|
|
78
|
-
initialize(config).
|
|
79
|
-
// Validate stored tokens on startup
|
|
80
|
-
// SDK's initialize() already calls ensureValidToken() which handles expired tokens
|
|
81
|
-
// This provides an additional safety layer for missing/corrupted tokens
|
|
82
|
-
if (authProvider && sdk) {
|
|
83
|
-
try {
|
|
84
|
-
const hasToken = await sdk.auth.hasValidAuth();
|
|
85
|
-
if (!hasToken) {
|
|
86
|
-
console.log('[PersSDK] No tokens found on startup, ensuring clean state');
|
|
87
|
-
await authProvider.clearTokens();
|
|
88
|
-
setAuthenticationState(null, false);
|
|
89
|
-
}
|
|
90
|
-
// Note: Token expiration validation happens automatically in SDK's initialize()
|
|
91
|
-
// which calls ensureValidToken() → checks expiration → triggers AUTH_FAILED if needed
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.warn('[PersSDK] Token validation on startup failed:', error);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}).catch(err => {
|
|
86
|
+
initialize(config).catch(err => {
|
|
98
87
|
console.error('Auto-initialization failed:', err);
|
|
99
88
|
});
|
|
100
89
|
}
|
|
101
|
-
}, [config, isInitialized, initialize
|
|
90
|
+
}, [config, isInitialized, initialize]);
|
|
102
91
|
const refreshUserData = useCallback(async () => {
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
// Read from refs to get current values
|
|
93
|
+
const currentSdk = sdkRef.current;
|
|
94
|
+
const currentIsInitialized = isInitializedRef.current;
|
|
95
|
+
if (!currentSdk || !currentIsInitialized) {
|
|
96
|
+
throw new Error('SDK not initialized. Cannot refresh user data.');
|
|
105
97
|
}
|
|
106
98
|
try {
|
|
107
|
-
const freshUserData = await
|
|
99
|
+
const freshUserData = await currentSdk.users.getCurrentUser();
|
|
108
100
|
setUser(freshUserData);
|
|
109
101
|
}
|
|
110
102
|
catch (error) {
|
|
111
103
|
console.error('Failed to refresh user data:', error);
|
|
112
104
|
throw error;
|
|
113
105
|
}
|
|
114
|
-
}, [
|
|
115
|
-
|
|
106
|
+
}, []); // No dependencies - reads from refs
|
|
107
|
+
const restoreSession = useCallback(async () => {
|
|
108
|
+
// Read from refs to get current values
|
|
109
|
+
const currentSdk = sdkRef.current;
|
|
110
|
+
const currentIsInitialized = isInitializedRef.current;
|
|
111
|
+
if (!currentSdk || !currentIsInitialized) {
|
|
112
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const userData = await currentSdk.restoreSession();
|
|
116
|
+
if (userData) {
|
|
117
|
+
setAuthenticationState(userData, true);
|
|
118
|
+
}
|
|
119
|
+
return userData;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error('[PersSDK] Failed to restore session:', error);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}, [setAuthenticationState]); // Depends on setAuthenticationState
|
|
126
|
+
// Listen for authentication events from core SDK
|
|
127
|
+
// Set up immediately when SDK is created (don't wait for isInitialized)
|
|
128
|
+
// to catch session_restored events that fire during SDK initialization
|
|
116
129
|
useEffect(() => {
|
|
117
|
-
if (!
|
|
130
|
+
if (!sdk)
|
|
118
131
|
return;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
console.error('[PersSDK] Failed to refresh user data after token renewal:', error);
|
|
132
|
+
const unsubscribe = sdk.events.subscribe((event) => {
|
|
133
|
+
if (event.domain !== 'authentication')
|
|
134
|
+
return;
|
|
135
|
+
// Session restored successfully - sync React state
|
|
136
|
+
if (event.type === 'session_restored') {
|
|
137
|
+
console.log('[PersSDK] Session restoration event received, syncing state...');
|
|
138
|
+
// Read user from event details if available, otherwise fetch
|
|
139
|
+
const userId = event.details?.userId;
|
|
140
|
+
if (userId) {
|
|
141
|
+
// User ID available, fetch user data
|
|
142
|
+
sdk.users.getCurrentUser()
|
|
143
|
+
.then(userData => {
|
|
144
|
+
setAuthenticationState(userData, true);
|
|
145
|
+
})
|
|
146
|
+
.catch(error => {
|
|
147
|
+
console.error('[PersSDK] Failed to sync restored session:', error);
|
|
148
|
+
});
|
|
139
149
|
}
|
|
140
150
|
}
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.log('[PersSDK] Authentication failed - session expired');
|
|
145
|
-
// Note: Token clearing already handled by SDK's handleAuthFailure()
|
|
146
|
-
// which calls authProvider.clearTokens() with robust retry logic
|
|
147
|
-
// Clear React state to sync with SDK
|
|
148
|
-
// This triggers re-render, allowing app to show login screen
|
|
151
|
+
// Session restoration failed or auth error - clear React state
|
|
152
|
+
if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
|
|
153
|
+
console.log('[PersSDK] Authentication failed - clearing session');
|
|
149
154
|
setAuthenticationState(null, false);
|
|
150
155
|
}
|
|
151
|
-
};
|
|
152
|
-
// Inject our handler into the auth provider config
|
|
153
|
-
providerConfig.onAuthStatusChange = authStatusHandler;
|
|
154
|
-
// Cleanup
|
|
156
|
+
}, { domains: ['authentication'] });
|
|
155
157
|
return () => {
|
|
156
|
-
|
|
157
|
-
providerConfig.onAuthStatusChange = originalHandler;
|
|
158
|
-
}
|
|
158
|
+
unsubscribe();
|
|
159
159
|
};
|
|
160
|
-
}, [
|
|
160
|
+
}, [sdk, setAuthenticationState]);
|
|
161
161
|
// iOS/Android: Monitor app state and validate tokens when app becomes active
|
|
162
162
|
useEffect(() => {
|
|
163
163
|
if (!sdk || Platform.OS === 'web') {
|
|
@@ -184,28 +184,17 @@ export const PersSDKProvider = ({ children, config }) => {
|
|
|
184
184
|
const contextValue = useMemo(() => ({
|
|
185
185
|
// Main SDK instance
|
|
186
186
|
sdk,
|
|
187
|
-
// Manager shortcuts for convenience
|
|
188
|
-
auth: sdk?.auth || null,
|
|
189
|
-
users: sdk?.users || null,
|
|
190
|
-
tokens: sdk?.tokens || null,
|
|
191
|
-
businesses: sdk?.businesses || null,
|
|
192
|
-
campaigns: sdk?.campaigns || null,
|
|
193
|
-
redemptions: sdk?.redemptions || null,
|
|
194
|
-
transactions: sdk?.transactions || null,
|
|
195
|
-
purchases: sdk?.purchases || null,
|
|
196
|
-
tenants: sdk?.tenants || null,
|
|
197
|
-
analytics: sdk?.analytics || null,
|
|
198
|
-
donations: sdk?.donations || null,
|
|
199
187
|
// Platform-specific providers
|
|
200
188
|
authProvider,
|
|
201
189
|
// State
|
|
202
190
|
isInitialized,
|
|
203
191
|
isAuthenticated,
|
|
204
192
|
user,
|
|
205
|
-
// Methods
|
|
193
|
+
// Methods - expose functions directly, not through refs
|
|
206
194
|
initialize,
|
|
207
195
|
setAuthenticationState,
|
|
208
196
|
refreshUserData,
|
|
197
|
+
restoreSession,
|
|
209
198
|
}), [
|
|
210
199
|
sdk,
|
|
211
200
|
authProvider,
|
|
@@ -214,7 +203,8 @@ export const PersSDKProvider = ({ children, config }) => {
|
|
|
214
203
|
user,
|
|
215
204
|
initialize,
|
|
216
205
|
setAuthenticationState,
|
|
217
|
-
refreshUserData
|
|
206
|
+
refreshUserData,
|
|
207
|
+
restoreSession
|
|
218
208
|
]);
|
|
219
209
|
return (_jsx(SDKContext.Provider, { value: contextValue, children: children }));
|
|
220
210
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@explorins/pers-sdk-react-native",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.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.1.
|
|
40
|
+
"@explorins/pers-sdk": "^2.1.3",
|
|
41
41
|
"@explorins/pers-signer": "^1.0.33",
|
|
42
42
|
"buffer": "^6.0.3",
|
|
43
43
|
"ethers": "^6.15.0",
|
|
@@ -151,7 +151,7 @@ export function useTokenBalances(options: UseTokenBalancesOptions): UseTokenBala
|
|
|
151
151
|
refreshInterval = 0
|
|
152
152
|
} = options;
|
|
153
153
|
|
|
154
|
-
const { isAuthenticated } = usePersSDK();
|
|
154
|
+
const { isAuthenticated, sdk } = usePersSDK();
|
|
155
155
|
const web3 = useWeb3();
|
|
156
156
|
|
|
157
157
|
const [tokenBalances, setTokenBalances] = useState<TokenBalanceWithToken[]>([]);
|
|
@@ -268,16 +268,30 @@ export function useTokenBalances(options: UseTokenBalancesOptions): UseTokenBala
|
|
|
268
268
|
}
|
|
269
269
|
}, [autoLoad, isAvailable, availableTokens.length, loadBalances]);
|
|
270
270
|
|
|
271
|
-
//
|
|
271
|
+
// Event-driven refresh: listen for transaction events instead of polling
|
|
272
272
|
useEffect(() => {
|
|
273
|
-
if (refreshInterval
|
|
274
|
-
|
|
273
|
+
if (!sdk || refreshInterval <= 0 || !isAvailable) return;
|
|
274
|
+
|
|
275
|
+
// Subscribe to transaction domain events
|
|
276
|
+
const unsubscribe = sdk.events.subscribe((event) => {
|
|
277
|
+
if (event.domain === 'transaction' && event.type === 'transaction_completed') {
|
|
278
|
+
console.log('[useTokenBalances] Transaction completed, refreshing balances...');
|
|
275
279
|
loadBalances();
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
280
|
+
}
|
|
281
|
+
}, { domains: ['transaction'] });
|
|
282
|
+
|
|
283
|
+
// Also set up a fallback polling interval (much longer than before)
|
|
284
|
+
// This handles cases where events might be missed
|
|
285
|
+
const fallbackInterval = Math.max(refreshInterval, 60000); // Minimum 1 minute
|
|
286
|
+
const intervalId = setInterval(() => {
|
|
287
|
+
loadBalances();
|
|
288
|
+
}, fallbackInterval);
|
|
289
|
+
|
|
290
|
+
return () => {
|
|
291
|
+
unsubscribe();
|
|
292
|
+
clearInterval(intervalId);
|
|
293
|
+
};
|
|
294
|
+
}, [sdk, refreshInterval, isAvailable, loadBalances]);
|
|
281
295
|
|
|
282
296
|
return {
|
|
283
297
|
tokenBalances,
|
package/src/index.ts
CHANGED
|
@@ -266,7 +266,7 @@ export {
|
|
|
266
266
|
} from './hooks';
|
|
267
267
|
|
|
268
268
|
// Re-export signing status types for convenience
|
|
269
|
-
export type { OnStatusUpdateFn, StatusUpdateData, SigningStatusType } from './hooks';
|
|
269
|
+
export type { OnStatusUpdateFn, StatusUpdateData, SigningStatusType, TransactionSigningResult, SubmissionResult, AuthenticatedUser } from './hooks';
|
|
270
270
|
|
|
271
271
|
// Re-export event types for convenience
|
|
272
272
|
export type { EventsHook, PersEvent, EventHandler, EventFilter, Unsubscribe } from './hooks';
|
|
@@ -28,19 +28,6 @@ export interface PersSDKContext {
|
|
|
28
28
|
// Main SDK instance
|
|
29
29
|
sdk: PersSDK | null;
|
|
30
30
|
|
|
31
|
-
// Manager shortcuts for convenience
|
|
32
|
-
auth: AuthManager | null;
|
|
33
|
-
users: UserManager | null;
|
|
34
|
-
tokens: TokenManager | null;
|
|
35
|
-
businesses: BusinessManager | null;
|
|
36
|
-
campaigns: CampaignManager | null;
|
|
37
|
-
redemptions: RedemptionManager | null;
|
|
38
|
-
transactions: TransactionManager | null;
|
|
39
|
-
purchases: PurchaseManager | null;
|
|
40
|
-
tenants: TenantManager | null;
|
|
41
|
-
analytics: AnalyticsManager | null;
|
|
42
|
-
donations: DonationManager | null;
|
|
43
|
-
|
|
44
31
|
// Platform-specific providers
|
|
45
32
|
authProvider: DefaultAuthProvider | null;
|
|
46
33
|
|
|
@@ -53,6 +40,7 @@ export interface PersSDKContext {
|
|
|
53
40
|
initialize: (config: PersConfig) => Promise<void>;
|
|
54
41
|
setAuthenticationState: (user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => void;
|
|
55
42
|
refreshUserData: () => Promise<void>;
|
|
43
|
+
restoreSession: () => Promise<UserDTO | null>;
|
|
56
44
|
}
|
|
57
45
|
|
|
58
46
|
// Create the context
|
|
@@ -64,6 +52,12 @@ export const PersSDKProvider: React.FC<{
|
|
|
64
52
|
config?: PersConfig;
|
|
65
53
|
}> = ({ children, config }) => {
|
|
66
54
|
const initializingRef = useRef(false);
|
|
55
|
+
|
|
56
|
+
// State refs for stable functions to read current values
|
|
57
|
+
const sdkRef = useRef<PersSDK | null>(null);
|
|
58
|
+
const isInitializedRef = useRef(false);
|
|
59
|
+
const isAuthenticatedRef = useRef(false);
|
|
60
|
+
|
|
67
61
|
const [sdk, setSdk] = useState<PersSDK | null>(null);
|
|
68
62
|
const [authProvider, setAuthProvider] = useState<DefaultAuthProvider | null>(null);
|
|
69
63
|
|
|
@@ -71,6 +65,11 @@ export const PersSDKProvider: React.FC<{
|
|
|
71
65
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
72
66
|
const [user, setUser] = useState<UserDTO | AdminDTO | null>(null);
|
|
73
67
|
|
|
68
|
+
// Keep state refs in sync immediately (not in useEffect to avoid race conditions)
|
|
69
|
+
sdkRef.current = sdk;
|
|
70
|
+
isInitializedRef.current = isInitialized;
|
|
71
|
+
isAuthenticatedRef.current = isAuthenticated;
|
|
72
|
+
|
|
74
73
|
const initialize = useCallback(async (config: PersConfig) => {
|
|
75
74
|
// Prevent multiple initializations
|
|
76
75
|
if (isInitialized || initializingRef.current) {
|
|
@@ -140,97 +139,88 @@ export const PersSDKProvider: React.FC<{
|
|
|
140
139
|
// Auto-initialize if config is provided
|
|
141
140
|
useEffect(() => {
|
|
142
141
|
if (config && !isInitialized && !initializingRef.current) {
|
|
143
|
-
initialize(config).
|
|
144
|
-
// Validate stored tokens on startup
|
|
145
|
-
// SDK's initialize() already calls ensureValidToken() which handles expired tokens
|
|
146
|
-
// This provides an additional safety layer for missing/corrupted tokens
|
|
147
|
-
if (authProvider && sdk) {
|
|
148
|
-
try {
|
|
149
|
-
const hasToken = await sdk.auth.hasValidAuth();
|
|
150
|
-
if (!hasToken) {
|
|
151
|
-
console.log('[PersSDK] No tokens found on startup, ensuring clean state');
|
|
152
|
-
await authProvider.clearTokens();
|
|
153
|
-
setAuthenticationState(null, false);
|
|
154
|
-
}
|
|
155
|
-
// Note: Token expiration validation happens automatically in SDK's initialize()
|
|
156
|
-
// which calls ensureValidToken() → checks expiration → triggers AUTH_FAILED if needed
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.warn('[PersSDK] Token validation on startup failed:', error);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}).catch(err => {
|
|
142
|
+
initialize(config).catch(err => {
|
|
162
143
|
console.error('Auto-initialization failed:', err);
|
|
163
144
|
});
|
|
164
145
|
}
|
|
165
|
-
}, [config, isInitialized, initialize
|
|
146
|
+
}, [config, isInitialized, initialize]);
|
|
166
147
|
|
|
167
148
|
const refreshUserData = useCallback(async (): Promise<void> => {
|
|
168
|
-
|
|
169
|
-
|
|
149
|
+
// Read from refs to get current values
|
|
150
|
+
const currentSdk = sdkRef.current;
|
|
151
|
+
const currentIsInitialized = isInitializedRef.current;
|
|
152
|
+
|
|
153
|
+
if (!currentSdk || !currentIsInitialized) {
|
|
154
|
+
throw new Error('SDK not initialized. Cannot refresh user data.');
|
|
170
155
|
}
|
|
171
156
|
|
|
172
157
|
try {
|
|
173
|
-
const freshUserData = await
|
|
158
|
+
const freshUserData = await currentSdk.users.getCurrentUser();
|
|
174
159
|
setUser(freshUserData);
|
|
175
160
|
} catch (error) {
|
|
176
161
|
console.error('Failed to refresh user data:', error);
|
|
177
162
|
throw error;
|
|
178
163
|
}
|
|
179
|
-
}, [
|
|
180
|
-
|
|
181
|
-
// Listen for authentication status changes and refresh user data when tokens are renewed
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
if (!authProvider || !isInitialized) return;
|
|
164
|
+
}, []); // No dependencies - reads from refs
|
|
184
165
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
166
|
+
const restoreSession = useCallback(async (): Promise<UserDTO | null> => {
|
|
167
|
+
// Read from refs to get current values
|
|
168
|
+
const currentSdk = sdkRef.current;
|
|
169
|
+
const currentIsInitialized = isInitializedRef.current;
|
|
188
170
|
|
|
189
|
-
|
|
190
|
-
|
|
171
|
+
if (!currentSdk || !currentIsInitialized) {
|
|
172
|
+
throw new Error('SDK not initialized. Call initialize() first.');
|
|
173
|
+
}
|
|
191
174
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (originalHandler) {
|
|
197
|
-
await originalHandler(status);
|
|
175
|
+
try {
|
|
176
|
+
const userData = await currentSdk.restoreSession();
|
|
177
|
+
if (userData) {
|
|
178
|
+
setAuthenticationState(userData, true);
|
|
198
179
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
180
|
+
return userData;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error('[PersSDK] Failed to restore session:', error);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}, [setAuthenticationState]); // Depends on setAuthenticationState
|
|
186
|
+
|
|
187
|
+
// Listen for authentication events from core SDK
|
|
188
|
+
// Set up immediately when SDK is created (don't wait for isInitialized)
|
|
189
|
+
// to catch session_restored events that fire during SDK initialization
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!sdk) return;
|
|
192
|
+
|
|
193
|
+
const unsubscribe = sdk.events.subscribe((event) => {
|
|
194
|
+
if (event.domain !== 'authentication') return;
|
|
195
|
+
|
|
196
|
+
// Session restored successfully - sync React state
|
|
197
|
+
if (event.type === 'session_restored') {
|
|
198
|
+
console.log('[PersSDK] Session restoration event received, syncing state...');
|
|
199
|
+
// Read user from event details if available, otherwise fetch
|
|
200
|
+
const userId = event.details?.userId;
|
|
201
|
+
if (userId) {
|
|
202
|
+
// User ID available, fetch user data
|
|
203
|
+
sdk.users.getCurrentUser()
|
|
204
|
+
.then(userData => {
|
|
205
|
+
setAuthenticationState(userData, true);
|
|
206
|
+
})
|
|
207
|
+
.catch(error => {
|
|
208
|
+
console.error('[PersSDK] Failed to sync restored session:', error);
|
|
209
|
+
});
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
console.log('[PersSDK] Authentication failed - session expired');
|
|
214
|
-
|
|
215
|
-
// Note: Token clearing already handled by SDK's handleAuthFailure()
|
|
216
|
-
// which calls authProvider.clearTokens() with robust retry logic
|
|
217
|
-
|
|
218
|
-
// Clear React state to sync with SDK
|
|
219
|
-
// This triggers re-render, allowing app to show login screen
|
|
212
|
+
|
|
213
|
+
// Session restoration failed or auth error - clear React state
|
|
214
|
+
if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
|
|
215
|
+
console.log('[PersSDK] Authentication failed - clearing session');
|
|
220
216
|
setAuthenticationState(null, false);
|
|
221
217
|
}
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// Inject our handler into the auth provider config
|
|
225
|
-
providerConfig.onAuthStatusChange = authStatusHandler;
|
|
226
|
-
|
|
227
|
-
// Cleanup
|
|
218
|
+
}, { domains: ['authentication'] });
|
|
219
|
+
|
|
228
220
|
return () => {
|
|
229
|
-
|
|
230
|
-
providerConfig.onAuthStatusChange = originalHandler;
|
|
231
|
-
}
|
|
221
|
+
unsubscribe();
|
|
232
222
|
};
|
|
233
|
-
}, [
|
|
223
|
+
}, [sdk, setAuthenticationState]);
|
|
234
224
|
|
|
235
225
|
// iOS/Android: Monitor app state and validate tokens when app becomes active
|
|
236
226
|
useEffect(() => {
|
|
@@ -262,19 +252,6 @@ export const PersSDKProvider: React.FC<{
|
|
|
262
252
|
// Main SDK instance
|
|
263
253
|
sdk,
|
|
264
254
|
|
|
265
|
-
// Manager shortcuts for convenience
|
|
266
|
-
auth: sdk?.auth || null,
|
|
267
|
-
users: sdk?.users || null,
|
|
268
|
-
tokens: sdk?.tokens || null,
|
|
269
|
-
businesses: sdk?.businesses || null,
|
|
270
|
-
campaigns: sdk?.campaigns || null,
|
|
271
|
-
redemptions: sdk?.redemptions || null,
|
|
272
|
-
transactions: sdk?.transactions || null,
|
|
273
|
-
purchases: sdk?.purchases || null,
|
|
274
|
-
tenants: sdk?.tenants || null,
|
|
275
|
-
analytics: sdk?.analytics || null,
|
|
276
|
-
donations: sdk?.donations || null,
|
|
277
|
-
|
|
278
255
|
// Platform-specific providers
|
|
279
256
|
authProvider,
|
|
280
257
|
|
|
@@ -283,10 +260,11 @@ export const PersSDKProvider: React.FC<{
|
|
|
283
260
|
isAuthenticated,
|
|
284
261
|
user,
|
|
285
262
|
|
|
286
|
-
// Methods
|
|
263
|
+
// Methods - expose functions directly, not through refs
|
|
287
264
|
initialize,
|
|
288
265
|
setAuthenticationState,
|
|
289
266
|
refreshUserData,
|
|
267
|
+
restoreSession,
|
|
290
268
|
}), [
|
|
291
269
|
sdk,
|
|
292
270
|
authProvider,
|
|
@@ -295,7 +273,8 @@ export const PersSDKProvider: React.FC<{
|
|
|
295
273
|
user,
|
|
296
274
|
initialize,
|
|
297
275
|
setAuthenticationState,
|
|
298
|
-
refreshUserData
|
|
276
|
+
refreshUserData,
|
|
277
|
+
restoreSession
|
|
299
278
|
]);
|
|
300
279
|
|
|
301
280
|
return (
|