@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.
@@ -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;AAG3D,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAGlC,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;CACtC;AAMD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAiPA,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;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).then(async () => {
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, authProvider, sdk, setAuthenticationState]);
90
+ }, [config, isInitialized, initialize]);
102
91
  const refreshUserData = useCallback(async () => {
103
- if (!sdk || !isAuthenticated || !isInitialized) {
104
- throw new Error('SDK not initialized or not authenticated. Cannot refresh user data.');
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 sdk.users.getCurrentUser();
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
- }, [sdk, isAuthenticated, isInitialized]);
115
- // Listen for authentication status changes and refresh user data when tokens are renewed
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 (!authProvider || !isInitialized)
130
+ if (!sdk)
118
131
  return;
119
- // Access the config object with proper type safety
120
- const providerConfig = authProvider.config;
121
- if (!providerConfig)
122
- return;
123
- // Set up auth status change handler
124
- const originalHandler = providerConfig.onAuthStatusChange;
125
- const authStatusHandler = async (status) => {
126
- console.log('[PersSDK] Auth status changed:', status);
127
- // Call original handler first if it exists
128
- if (originalHandler) {
129
- await originalHandler(status);
130
- }
131
- // If token was refreshed successfully and user is authenticated, reload user data
132
- if (status === 'authenticated' && isAuthenticated && sdk) {
133
- try {
134
- console.log('[PersSDK] Token refreshed, reloading user data...');
135
- await refreshUserData();
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
- // If authentication failed, clear state
142
- // Frontend app can observe isAuthenticated state change to show custom UI
143
- if (status === 'auth_failed') {
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
- if (originalHandler) {
157
- providerConfig.onAuthStatusChange = originalHandler;
158
- }
158
+ unsubscribe();
159
159
  };
160
- }, [authProvider, isInitialized, isAuthenticated, sdk, refreshUserData, setAuthenticationState]);
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.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.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
- // Optional auto-refresh interval
271
+ // Event-driven refresh: listen for transaction events instead of polling
272
272
  useEffect(() => {
273
- if (refreshInterval > 0 && isAvailable && availableTokens.length > 0) {
274
- const intervalId = setInterval(() => {
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
- }, refreshInterval);
277
-
278
- return () => clearInterval(intervalId);
279
- }
280
- }, [refreshInterval, isAvailable, availableTokens.length, loadBalances]);
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).then(async () => {
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, authProvider, sdk, setAuthenticationState]);
146
+ }, [config, isInitialized, initialize]);
166
147
 
167
148
  const refreshUserData = useCallback(async (): Promise<void> => {
168
- if (!sdk || !isAuthenticated || !isInitialized) {
169
- throw new Error('SDK not initialized or not authenticated. Cannot refresh user data.');
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 sdk.users.getCurrentUser();
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
- }, [sdk, isAuthenticated, isInitialized]);
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
- // Access the config object with proper type safety
186
- const providerConfig = (authProvider as any).config;
187
- if (!providerConfig) return;
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
- // Set up auth status change handler
190
- const originalHandler = providerConfig.onAuthStatusChange;
171
+ if (!currentSdk || !currentIsInitialized) {
172
+ throw new Error('SDK not initialized. Call initialize() first.');
173
+ }
191
174
 
192
- const authStatusHandler = async (status: string) => {
193
- console.log('[PersSDK] Auth status changed:', status);
194
-
195
- // Call original handler first if it exists
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
- // If token was refreshed successfully and user is authenticated, reload user data
201
- if (status === 'authenticated' && isAuthenticated && sdk) {
202
- try {
203
- console.log('[PersSDK] Token refreshed, reloading user data...');
204
- await refreshUserData();
205
- } catch (error) {
206
- console.error('[PersSDK] Failed to refresh user data after token renewal:', error);
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
- // If authentication failed, clear state
211
- // Frontend app can observe isAuthenticated state change to show custom UI
212
- if (status === 'auth_failed') {
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
- if (originalHandler) {
230
- providerConfig.onAuthStatusChange = originalHandler;
231
- }
221
+ unsubscribe();
232
222
  };
233
- }, [authProvider, isInitialized, isAuthenticated, sdk, refreshUserData, setAuthenticationState]);
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 (