@explorins/pers-sdk-react-native 2.1.17 → 2.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAA2C,MAAM,OAAO,CAAC;AAEvH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAIpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAkB3D,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;GAQG;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+NA,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;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAGhC,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5F,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/C;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CA6NA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
@@ -38,6 +38,7 @@ const SDKContext = createContext(null);
38
38
  */
39
39
  export const PersSDKProvider = ({ children, config }) => {
40
40
  const initializingRef = useRef(false);
41
+ const eventUnsubscribeRef = useRef(null);
41
42
  // State refs for stable functions to read current values
42
43
  const sdkRef = useRef(null);
43
44
  const isInitializedRef = useRef(false);
@@ -91,6 +92,21 @@ export const PersSDKProvider = ({ children, config }) => {
91
92
  };
92
93
  // Initialize PersSDK with platform-aware auth provider
93
94
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
95
+ // Subscribe to auth events BEFORE exposing SDK to consumers
96
+ // This ensures no events are missed due to React timing
97
+ eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
98
+ if (event.type === 'session_restored' || event.type === 'login_success') {
99
+ const details = event.details;
100
+ if (details?.user) {
101
+ setUser(details.user);
102
+ setIsAuthenticated(true);
103
+ }
104
+ }
105
+ if (event.type === 'logout_success' || event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
106
+ setUser(null);
107
+ setIsAuthenticated(false);
108
+ }
109
+ }, { domains: ['authentication'], replay: true });
94
110
  setAuthProvider(authProvider);
95
111
  setSdk(sdkInstance);
96
112
  setIsInitialized(true);
@@ -104,6 +120,15 @@ export const PersSDKProvider = ({ children, config }) => {
104
120
  initializingRef.current = false;
105
121
  }
106
122
  }, [isInitialized]);
123
+ // Cleanup subscription on unmount
124
+ useEffect(() => {
125
+ return () => {
126
+ if (eventUnsubscribeRef.current) {
127
+ eventUnsubscribeRef.current();
128
+ eventUnsubscribeRef.current = null;
129
+ }
130
+ };
131
+ }, []);
107
132
  const setAuthenticationState = useCallback((user, isAuthenticated) => {
108
133
  setUser(user);
109
134
  setIsAuthenticated(isAuthenticated);
@@ -150,32 +175,7 @@ export const PersSDKProvider = ({ children, config }) => {
150
175
  console.error('[PersSDK] Failed to restore session:', error);
151
176
  throw error;
152
177
  }
153
- }, [setAuthenticationState]); // Depends on setAuthenticationState
154
- // Listen for authentication events from core SDK
155
- // Set up immediately when SDK is created (don't wait for isInitialized)
156
- // to catch session_restored events that fire during SDK initialization
157
- useEffect(() => {
158
- if (!sdk)
159
- return;
160
- const unsubscribe = sdk.events.subscribe((event) => {
161
- // Handle both session restoration and fresh login
162
- if (event.type === 'session_restored' || event.type === 'login_success') {
163
- const details = event.details;
164
- if (details?.user) {
165
- console.log(`[PersSDK] ${event.type}: syncing user state`);
166
- setAuthenticationState(details.user, true);
167
- }
168
- }
169
- // Session restoration failed or auth error - clear React state
170
- if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
171
- console.log('[PersSDK] Authentication failed - clearing session');
172
- setAuthenticationState(null, false);
173
- }
174
- }, { domains: ['authentication'], replay: true });
175
- return () => {
176
- unsubscribe();
177
- };
178
- }, [sdk, setAuthenticationState]);
178
+ }, [setAuthenticationState]);
179
179
  // iOS/Android: Monitor app state and validate tokens when app becomes active
180
180
  useEffect(() => {
181
181
  if (!sdk || Platform.OS === 'web') {
@@ -183,7 +183,6 @@ export const PersSDKProvider = ({ children, config }) => {
183
183
  }
184
184
  const handleAppStateChange = async (nextAppState) => {
185
185
  if (nextAppState === 'active' && isAuthenticated) {
186
- console.log('[PersSDK] App became active - validating tokens');
187
186
  try {
188
187
  // Trigger token validation when app resumes from background
189
188
  // This ensures tokens are checked after extended inactivity
package/package.json CHANGED
@@ -1,182 +1,182 @@
1
- {
2
- "name": "@explorins/pers-sdk-react-native",
3
- "version": "2.1.17",
4
- "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc && rollup -c --bundleConfigAsCjs",
9
- "build:watch": "rollup -c --bundleConfigAsCjs --watch",
10
- "dev": "tsc --watch",
11
- "clean": "rimraf dist",
12
- "docs": "typedoc",
13
- "docs:dev": "typedoc --watch",
14
- "docs:json": "typedoc --json docs/api.json",
15
- "docs:serve": "npx http-server docs -p 8080",
16
- "prepublishOnly": "npm run clean && npm run build",
17
- "publish-version": "npm run clean && npm run build && npm version patch && npm publish",
18
- "publish-with-docs": "npm run clean && npm run build && npm run docs && npm version patch && npm publish",
19
- "yalc:publish": "npm version patch --no-git-tag-version && npm run build && yalc publish",
20
- "yalc:push": "npm version patch --no-git-tag-version && npm run build && yalc push"
21
- },
22
- "keywords": [
23
- "pers",
24
- "sdk",
25
- "react-native",
26
- "tourism",
27
- "loyalty",
28
- "blockchain",
29
- "web3",
30
- "webauthn",
31
- "transaction-signing",
32
- "rewards",
33
- "authentication",
34
- "biometrics",
35
- "secure-storage"
36
- ],
37
- "author": "eXplorins",
38
- "license": "MIT",
39
- "dependencies": {
40
- "@explorins/pers-sdk": "^2.1.24",
41
- "@explorins/pers-signer": "^1.0.33",
42
- "buffer": "^6.0.3",
43
- "ethers": "^6.15.0",
44
- "react-native-get-random-values": "^2.0.0",
45
- "react-native-keychain": "^10.0.0",
46
- "react-native-passkey": "^3.3.2",
47
- "react-native-quick-crypto": "^1.0.6"
48
- },
49
- "browser": {
50
- "crypto": false,
51
- "url": false,
52
- "http": false,
53
- "https": false,
54
- "zlib": false,
55
- "stream": false,
56
- "assert": false,
57
- "path": false,
58
- "fs": false,
59
- "os": false,
60
- "readable-stream": false,
61
- "util": false,
62
- "events": false,
63
- "net": false,
64
- "tls": false,
65
- "child_process": false,
66
- "pngjs": false,
67
- "pngjs/lib/bitmapper": false,
68
- "pngjs/lib/parser-async": false,
69
- "pngjs/lib/parser-sync": false,
70
- "./bitmapper": false,
71
- "hash-base": false,
72
- "@ethereumjs/common": false,
73
- "@ethereumjs/tx": false,
74
- "@ethereumjs/util": false,
75
- "stream-browserify": false,
76
- "crypto-browserify": false,
77
- "inherits": false,
78
- "safe-buffer": false,
79
- "string_decoder": false,
80
- "process-nextick-args": false,
81
- "isarray": false,
82
- "core-util-is": false,
83
- "util-deprecate": false,
84
- "ethereum-cryptography": false,
85
- "keccak": false,
86
- "secp256k1": false,
87
- "scrypt-js": false,
88
- "bech32": false,
89
- "ws": false,
90
- "xhr2-cookies": false,
91
- "aes-js": false,
92
- "@ethersproject/sha2": false,
93
- "@ethersproject/pbkdf2": false,
94
- "@ethersproject/scrypt": false
95
- },
96
- "peerDependencies": {
97
- "@react-native-async-storage/async-storage": ">=1.15.0",
98
- "react": ">=16.8.0",
99
- "react-native": ">=0.60.0"
100
- },
101
- "peerDependenciesMeta": {
102
- "react-native-keychain": {
103
- "optional": true
104
- },
105
- "@react-native-async-storage/async-storage": {
106
- "optional": false
107
- },
108
- "react-native-get-random-values": {
109
- "optional": true
110
- },
111
- "react-native-url-polyfill": {
112
- "optional": true
113
- },
114
- "react-native-polyfill-globals": {
115
- "optional": true
116
- },
117
- "web-streams-polyfill": {
118
- "optional": true
119
- }
120
- },
121
- "overrides": {
122
- "readable-stream": false,
123
- "@ethereumjs/common": false,
124
- "@ethereumjs/util": false,
125
- "@ethereumjs/rlp": false,
126
- "@ethereumjs/tx": false,
127
- "stream": false,
128
- "util": false,
129
- "events": false,
130
- "crypto": false,
131
- "fs": false,
132
- "path": false,
133
- "os": false,
134
- "net": false,
135
- "tls": false,
136
- "child_process": false,
137
- "hash-base": false,
138
- "inherits": false,
139
- "safe-buffer": false,
140
- "string_decoder": false,
141
- "process-nextick-args": false,
142
- "isarray": false,
143
- "core-util-is": false,
144
- "util-deprecate": false,
145
- "ethereum-cryptography": false,
146
- "keccak": false,
147
- "secp256k1": false,
148
- "scrypt-js": false,
149
- "bech32": false,
150
- "ws": false,
151
- "xhr2-cookies": false,
152
- "aes-js": false,
153
- "@ethersproject/sha2": false,
154
- "@ethersproject/pbkdf2": false,
155
- "@ethersproject/scrypt": false
156
- },
157
- "devDependencies": {
158
- "@explorins/pers-shared": "^2.1.128",
159
- "@react-native-async-storage/async-storage": "^3.0.2",
160
- "@rollup/plugin-commonjs": "^25.0.7",
161
- "@rollup/plugin-json": "^6.1.0",
162
- "@rollup/plugin-node-resolve": "^15.2.3",
163
- "@types/react": "^19.1.1",
164
- "@types/react-native": "^0.72.8",
165
- "rimraf": "^5.0.0",
166
- "rollup": "^4.0.0",
167
- "rollup-plugin-typescript2": "^0.36.0",
168
- "typedoc": "^0.25.13",
169
- "typedoc-plugin-markdown": "^3.17.1",
170
- "typescript": "^5.2.0"
171
- },
172
- "files": [
173
- "dist/**/*",
174
- "src/**/*",
175
- "README.md"
176
- ],
177
- "repository": {
178
- "type": "git",
179
- "url": "git+https://github.com/eXplorins/pers-sdk.git",
180
- "directory": "packages/pers-sdk-react-native"
181
- }
182
- }
1
+ {
2
+ "name": "@explorins/pers-sdk-react-native",
3
+ "version": "2.1.18",
4
+ "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc && rollup -c --bundleConfigAsCjs",
9
+ "build:watch": "rollup -c --bundleConfigAsCjs --watch",
10
+ "dev": "tsc --watch",
11
+ "clean": "rimraf dist",
12
+ "docs": "typedoc",
13
+ "docs:dev": "typedoc --watch",
14
+ "docs:json": "typedoc --json docs/api.json",
15
+ "docs:serve": "npx http-server docs -p 8080",
16
+ "prepublishOnly": "npm run clean && npm run build",
17
+ "publish-version": "npm run clean && npm run build && npm version patch && npm publish",
18
+ "publish-with-docs": "npm run clean && npm run build && npm run docs && npm version patch && npm publish",
19
+ "yalc:publish": "npm version patch --no-git-tag-version && npm run build && yalc publish",
20
+ "yalc:push": "npm version patch --no-git-tag-version && npm run build && yalc push"
21
+ },
22
+ "keywords": [
23
+ "pers",
24
+ "sdk",
25
+ "react-native",
26
+ "tourism",
27
+ "loyalty",
28
+ "blockchain",
29
+ "web3",
30
+ "webauthn",
31
+ "transaction-signing",
32
+ "rewards",
33
+ "authentication",
34
+ "biometrics",
35
+ "secure-storage"
36
+ ],
37
+ "author": "eXplorins",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "@explorins/pers-sdk": "^2.1.26",
41
+ "@explorins/pers-signer": "^1.0.33",
42
+ "buffer": "^6.0.3",
43
+ "ethers": "^6.15.0",
44
+ "react-native-get-random-values": "^2.0.0",
45
+ "react-native-keychain": "^10.0.0",
46
+ "react-native-passkey": "^3.3.2",
47
+ "react-native-quick-crypto": "^1.0.6"
48
+ },
49
+ "browser": {
50
+ "crypto": false,
51
+ "url": false,
52
+ "http": false,
53
+ "https": false,
54
+ "zlib": false,
55
+ "stream": false,
56
+ "assert": false,
57
+ "path": false,
58
+ "fs": false,
59
+ "os": false,
60
+ "readable-stream": false,
61
+ "util": false,
62
+ "events": false,
63
+ "net": false,
64
+ "tls": false,
65
+ "child_process": false,
66
+ "pngjs": false,
67
+ "pngjs/lib/bitmapper": false,
68
+ "pngjs/lib/parser-async": false,
69
+ "pngjs/lib/parser-sync": false,
70
+ "./bitmapper": false,
71
+ "hash-base": false,
72
+ "@ethereumjs/common": false,
73
+ "@ethereumjs/tx": false,
74
+ "@ethereumjs/util": false,
75
+ "stream-browserify": false,
76
+ "crypto-browserify": false,
77
+ "inherits": false,
78
+ "safe-buffer": false,
79
+ "string_decoder": false,
80
+ "process-nextick-args": false,
81
+ "isarray": false,
82
+ "core-util-is": false,
83
+ "util-deprecate": false,
84
+ "ethereum-cryptography": false,
85
+ "keccak": false,
86
+ "secp256k1": false,
87
+ "scrypt-js": false,
88
+ "bech32": false,
89
+ "ws": false,
90
+ "xhr2-cookies": false,
91
+ "aes-js": false,
92
+ "@ethersproject/sha2": false,
93
+ "@ethersproject/pbkdf2": false,
94
+ "@ethersproject/scrypt": false
95
+ },
96
+ "peerDependencies": {
97
+ "@react-native-async-storage/async-storage": ">=1.15.0",
98
+ "react": ">=16.8.0",
99
+ "react-native": ">=0.60.0"
100
+ },
101
+ "peerDependenciesMeta": {
102
+ "react-native-keychain": {
103
+ "optional": true
104
+ },
105
+ "@react-native-async-storage/async-storage": {
106
+ "optional": false
107
+ },
108
+ "react-native-get-random-values": {
109
+ "optional": true
110
+ },
111
+ "react-native-url-polyfill": {
112
+ "optional": true
113
+ },
114
+ "react-native-polyfill-globals": {
115
+ "optional": true
116
+ },
117
+ "web-streams-polyfill": {
118
+ "optional": true
119
+ }
120
+ },
121
+ "overrides": {
122
+ "readable-stream": false,
123
+ "@ethereumjs/common": false,
124
+ "@ethereumjs/util": false,
125
+ "@ethereumjs/rlp": false,
126
+ "@ethereumjs/tx": false,
127
+ "stream": false,
128
+ "util": false,
129
+ "events": false,
130
+ "crypto": false,
131
+ "fs": false,
132
+ "path": false,
133
+ "os": false,
134
+ "net": false,
135
+ "tls": false,
136
+ "child_process": false,
137
+ "hash-base": false,
138
+ "inherits": false,
139
+ "safe-buffer": false,
140
+ "string_decoder": false,
141
+ "process-nextick-args": false,
142
+ "isarray": false,
143
+ "core-util-is": false,
144
+ "util-deprecate": false,
145
+ "ethereum-cryptography": false,
146
+ "keccak": false,
147
+ "secp256k1": false,
148
+ "scrypt-js": false,
149
+ "bech32": false,
150
+ "ws": false,
151
+ "xhr2-cookies": false,
152
+ "aes-js": false,
153
+ "@ethersproject/sha2": false,
154
+ "@ethersproject/pbkdf2": false,
155
+ "@ethersproject/scrypt": false
156
+ },
157
+ "devDependencies": {
158
+ "@explorins/pers-shared": "^2.1.128",
159
+ "@react-native-async-storage/async-storage": "^3.0.2",
160
+ "@rollup/plugin-commonjs": "^25.0.7",
161
+ "@rollup/plugin-json": "^6.1.0",
162
+ "@rollup/plugin-node-resolve": "^15.2.3",
163
+ "@types/react": "^19.1.1",
164
+ "@types/react-native": "^0.72.8",
165
+ "rimraf": "^5.0.0",
166
+ "rollup": "^4.0.0",
167
+ "rollup-plugin-typescript2": "^0.36.0",
168
+ "typedoc": "^0.25.13",
169
+ "typedoc-plugin-markdown": "^3.17.1",
170
+ "typescript": "^5.2.0"
171
+ },
172
+ "files": [
173
+ "dist/**/*",
174
+ "src/**/*",
175
+ "README.md"
176
+ ],
177
+ "repository": {
178
+ "type": "git",
179
+ "url": "git+https://github.com/eXplorins/pers-sdk.git",
180
+ "directory": "packages/pers-sdk-react-native"
181
+ }
182
+ }
@@ -50,8 +50,7 @@ export const useAuth = () => {
50
50
  authProvider,
51
51
  isInitialized,
52
52
  isAuthenticated,
53
- user,
54
- setAuthenticationState,
53
+ user,
55
54
  refreshUserData
56
55
  } = usePersSDK();
57
56
 
@@ -79,8 +78,7 @@ export const useAuth = () => {
79
78
  // Set token in auth provider
80
79
  await authProvider.setAccessToken(jwtToken);
81
80
 
82
- // Perform login using the manager
83
- // SDK emits login_success event with user data - PersSDKProvider handles state sync
81
+ // Perform login - emits login_success event which Provider handles
84
82
  const result = await sdk.auth.loginWithToken(jwtToken, userType);
85
83
 
86
84
  return result;
@@ -108,8 +106,7 @@ export const useAuth = () => {
108
106
  }
109
107
 
110
108
  try {
111
- // Use the raw data login from the auth manager
112
- // SDK emits login_success event with user data - PersSDKProvider handles state sync
109
+ // Use the raw data login - emits login_success event which Provider handles
113
110
  const result = await sdk.auth.loginWithRawData(rawUserData);
114
111
 
115
112
  // Set token from result
@@ -135,17 +132,18 @@ export const useAuth = () => {
135
132
  * ```
136
133
  */
137
134
  const logout = useCallback(async (): Promise<void> => {
135
+ if (!sdk) {
136
+ throw new Error('SDK not initialized. Call initialize() first.');
137
+ }
138
+
138
139
  try {
139
- if (authProvider) {
140
- await authProvider.clearTokens();
141
- }
142
-
143
- setAuthenticationState(null, false);
140
+ // clearAuth emits logout_success event which Provider handles
141
+ await sdk.auth.clearAuth();
144
142
  } catch (error) {
145
143
  console.error('Logout failed:', error);
146
144
  throw error;
147
145
  }
148
- }, [authProvider, setAuthenticationState]);
146
+ }, [sdk]);
149
147
 
150
148
  /**
151
149
  * Retrieves the current authenticated user data
@@ -223,9 +221,9 @@ export const useAuth = () => {
223
221
  if (!sdk) {
224
222
  throw new Error('SDK not initialized. Call initialize() first.');
225
223
  }
224
+ // clearAuth emits logout_success event which Provider handles
226
225
  await sdk.auth.clearAuth();
227
- setAuthenticationState(null, false);
228
- }, [sdk, setAuthenticationState]);
226
+ }, [sdk]);
229
227
 
230
228
  /**
231
229
  * Checks if the current authentication is valid (non-expired)
@@ -89,6 +89,7 @@ export const PersSDKProvider: React.FC<{
89
89
  config?: PersConfig;
90
90
  }> = ({ children, config }) => {
91
91
  const initializingRef = useRef(false);
92
+ const eventUnsubscribeRef = useRef<(() => void) | null>(null);
92
93
 
93
94
  // State refs for stable functions to read current values
94
95
  const sdkRef = useRef<PersSDK | null>(null);
@@ -156,6 +157,23 @@ export const PersSDKProvider: React.FC<{
156
157
  // Initialize PersSDK with platform-aware auth provider
157
158
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
158
159
 
160
+ // Subscribe to auth events BEFORE exposing SDK to consumers
161
+ // This ensures no events are missed due to React timing
162
+ eventUnsubscribeRef.current = sdkInstance.events.subscribe((event) => {
163
+ if (event.type === 'session_restored' || event.type === 'login_success') {
164
+ const details = event.details as { user?: UserDTO | AdminDTO };
165
+ if (details?.user) {
166
+ setUser(details.user);
167
+ setIsAuthenticated(true);
168
+ }
169
+ }
170
+
171
+ if (event.type === 'logout_success' || event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
172
+ setUser(null);
173
+ setIsAuthenticated(false);
174
+ }
175
+ }, { domains: ['authentication'], replay: true });
176
+
159
177
  setAuthProvider(authProvider);
160
178
  setSdk(sdkInstance);
161
179
  setIsInitialized(true);
@@ -167,6 +185,16 @@ export const PersSDKProvider: React.FC<{
167
185
  initializingRef.current = false;
168
186
  }
169
187
  }, [isInitialized]);
188
+
189
+ // Cleanup subscription on unmount
190
+ useEffect(() => {
191
+ return () => {
192
+ if (eventUnsubscribeRef.current) {
193
+ eventUnsubscribeRef.current();
194
+ eventUnsubscribeRef.current = null;
195
+ }
196
+ };
197
+ }, []);
170
198
 
171
199
  const setAuthenticationState = useCallback((user: UserDTO | AdminDTO | null, isAuthenticated: boolean) => {
172
200
  setUser(user);
@@ -219,36 +247,7 @@ export const PersSDKProvider: React.FC<{
219
247
  console.error('[PersSDK] Failed to restore session:', error);
220
248
  throw error;
221
249
  }
222
- }, [setAuthenticationState]); // Depends on setAuthenticationState
223
-
224
- // Listen for authentication events from core SDK
225
- // Set up immediately when SDK is created (don't wait for isInitialized)
226
- // to catch session_restored events that fire during SDK initialization
227
- useEffect(() => {
228
- if (!sdk) return;
229
-
230
- const unsubscribe = sdk.events.subscribe((event) => {
231
- // Handle both session restoration and fresh login
232
- if (event.type === 'session_restored' || event.type === 'login_success') {
233
- const details = event.details as { user?: UserDTO | AdminDTO };
234
-
235
- if (details?.user) {
236
- console.log(`[PersSDK] ${event.type}: syncing user state`);
237
- setAuthenticationState(details.user, true);
238
- }
239
- }
240
-
241
- // Session restoration failed or auth error - clear React state
242
- if (event.type === 'session_restoration_failed' || event.code === 'AUTH_FAILED') {
243
- console.log('[PersSDK] Authentication failed - clearing session');
244
- setAuthenticationState(null, false);
245
- }
246
- }, { domains: ['authentication'], replay: true });
247
-
248
- return () => {
249
- unsubscribe();
250
- };
251
- }, [sdk, setAuthenticationState]);
250
+ }, [setAuthenticationState]);
252
251
 
253
252
  // iOS/Android: Monitor app state and validate tokens when app becomes active
254
253
  useEffect(() => {
@@ -258,7 +257,6 @@ export const PersSDKProvider: React.FC<{
258
257
 
259
258
  const handleAppStateChange = async (nextAppState: AppStateStatus) => {
260
259
  if (nextAppState === 'active' && isAuthenticated) {
261
- console.log('[PersSDK] App became active - validating tokens');
262
260
  try {
263
261
  // Trigger token validation when app resumes from background
264
262
  // This ensures tokens are checked after extended inactivity