@proveanything/smartlinks-auth-ui 0.5.11 → 0.5.13

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/index.js CHANGED
@@ -10893,18 +10893,19 @@ const isDesktop = () => {
10893
10893
  return false;
10894
10894
  return !/Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent);
10895
10895
  };
10896
- const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading = false, error, collectName = false, pollIntervalMs = 3000, }) => {
10896
+ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading = false, error, collectName = false, pollIntervalMs = 3000, initialSent = null, }) => {
10897
10897
  const [displayName, setDisplayName] = React.useState('');
10898
- const [sent, setSent] = React.useState(null);
10898
+ const [sent, setSent] = React.useState(initialSent ?? null);
10899
10899
  const [pollError, setPollError] = React.useState();
10900
10900
  const [qrDataUrl, setQrDataUrl] = React.useState(null);
10901
10901
  const [showOnDesktop] = React.useState(isDesktop());
10902
10902
  const verifiedFiredRef = React.useRef(false);
10903
10903
  const autoSentRef = React.useRef(false);
10904
+ const hydratedInitialTokenRef = React.useRef(initialSent?.token ?? null);
10904
10905
  // Auto-issue the WhatsApp link on mount when we don't need to collect a name.
10905
10906
  // When collectName is true, wait for the user to submit the name first.
10906
10907
  React.useEffect(() => {
10907
- if (collectName)
10908
+ if (collectName || sent)
10908
10909
  return;
10909
10910
  if (autoSentRef.current)
10910
10911
  return;
@@ -10912,13 +10913,24 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
10912
10913
  (async () => {
10913
10914
  try {
10914
10915
  const result = await onSend();
10916
+ hydratedInitialTokenRef.current = result.token;
10915
10917
  setSent(result);
10916
10918
  }
10917
10919
  catch {
10918
10920
  // Surfaced via parent error prop
10919
10921
  }
10920
10922
  })();
10921
- }, [collectName, onSend]);
10923
+ }, [collectName, onSend, sent]);
10924
+ React.useEffect(() => {
10925
+ if (!initialSent?.token)
10926
+ return;
10927
+ if (initialSent.token === hydratedInitialTokenRef.current)
10928
+ return;
10929
+ hydratedInitialTokenRef.current = initialSent.token;
10930
+ verifiedFiredRef.current = false;
10931
+ setPollError(undefined);
10932
+ setSent(initialSent);
10933
+ }, [initialSent]);
10922
10934
  // Generate QR code for the WhatsApp deep-link (great for desktop scan-with-phone UX)
10923
10935
  React.useEffect(() => {
10924
10936
  if (!sent?.waLink)
@@ -10947,15 +10959,26 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
10947
10959
  return;
10948
10960
  let stopped = false;
10949
10961
  let timer;
10962
+ let pollInFlight = false;
10963
+ const scheduleNext = () => {
10964
+ if (stopped)
10965
+ return;
10966
+ timer = setTimeout(tick, pollIntervalMs);
10967
+ };
10950
10968
  const tick = async () => {
10969
+ if (pollInFlight)
10970
+ return;
10971
+ pollInFlight = true;
10951
10972
  try {
10952
10973
  const status = await onPollStatus(sent.token);
10953
10974
  if (stopped)
10954
10975
  return;
10955
10976
  if (status.verified && !verifiedFiredRef.current) {
10956
- verifiedFiredRef.current = true;
10957
- onVerified(status);
10958
- return;
10977
+ const handled = await onVerified(status);
10978
+ if (handled !== false) {
10979
+ verifiedFiredRef.current = true;
10980
+ return;
10981
+ }
10959
10982
  }
10960
10983
  if (status.status === 'failed' || status.status === 'expired') {
10961
10984
  setPollError(status.status === 'expired'
@@ -10967,12 +10990,31 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
10967
10990
  catch {
10968
10991
  // Transient errors — keep polling
10969
10992
  }
10970
- timer = setTimeout(tick, pollIntervalMs);
10993
+ finally {
10994
+ pollInFlight = false;
10995
+ }
10996
+ scheduleNext();
10997
+ };
10998
+ const handleResume = () => {
10999
+ if (document.visibilityState && document.visibilityState !== 'visible')
11000
+ return;
11001
+ tick().catch?.(() => { });
11002
+ };
11003
+ const handleVisibilityChange = () => {
11004
+ if (document.visibilityState === 'visible') {
11005
+ handleResume();
11006
+ }
10971
11007
  };
10972
- timer = setTimeout(tick, pollIntervalMs);
11008
+ tick().catch?.(() => { });
11009
+ window.addEventListener('focus', handleResume);
11010
+ window.addEventListener('pageshow', handleResume);
11011
+ document.addEventListener('visibilitychange', handleVisibilityChange);
10973
11012
  return () => {
10974
11013
  stopped = true;
10975
11014
  clearTimeout(timer);
11015
+ window.removeEventListener('focus', handleResume);
11016
+ window.removeEventListener('pageshow', handleResume);
11017
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
10976
11018
  };
10977
11019
  }, [sent?.token, onPollStatus, onVerified, pollIntervalMs]);
10978
11020
  const handleNameSubmit = async (e) => {
@@ -10981,6 +11023,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
10981
11023
  verifiedFiredRef.current = false;
10982
11024
  try {
10983
11025
  const result = await onSend(displayName.trim() || undefined);
11026
+ hydratedInitialTokenRef.current = result.token;
10984
11027
  setSent(result);
10985
11028
  }
10986
11029
  catch {
@@ -10995,6 +11038,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
10995
11038
  if (!collectName) {
10996
11039
  try {
10997
11040
  const result = await onSend();
11041
+ hydratedInitialTokenRef.current = result.token;
10998
11042
  setSent(result);
10999
11043
  }
11000
11044
  catch {
@@ -12557,6 +12601,140 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12557
12601
  unsubscribe();
12558
12602
  };
12559
12603
  }, [proxyMode, notifyAuthStateChange]);
12604
+ // Mobile app-switching can miss BroadcastChannel/storage events, especially on iOS.
12605
+ // Re-read persisted auth state whenever the page becomes active again so a
12606
+ // WhatsApp-returned tab/window can pick up a session that another page wrote.
12607
+ React.useEffect(() => {
12608
+ if (proxyMode)
12609
+ return;
12610
+ const rehydrateFromStorage = async () => {
12611
+ try {
12612
+ const storedToken = await tokenStorage.getToken();
12613
+ const storedUser = await tokenStorage.getUser();
12614
+ const storedAccountData = await tokenStorage.getAccountData();
12615
+ const storedAccountInfo = await tokenStorage.getAccountInfo();
12616
+ const storedContactId = await tokenStorage.getContactId();
12617
+ if (!storedToken || !storedUser)
12618
+ return;
12619
+ const tokenChanged = storedToken.token !== token;
12620
+ const userChanged = storedUser.uid !== user?.uid;
12621
+ if (tokenChanged || userChanged || !user) {
12622
+ setToken(storedToken.token);
12623
+ setUser(storedUser);
12624
+ setAccountData(storedAccountData);
12625
+ setAccountInfo(storedAccountInfo?.data || storedAccountData || null);
12626
+ setContactId(storedContactId);
12627
+ setIsVerified(true);
12628
+ notifyAuthStateChange('SESSION_RESTORED', storedUser, storedToken.token, storedAccountData || null, storedAccountInfo?.data || storedAccountData || null, true, contact, storedContactId);
12629
+ }
12630
+ if (!isVerified || pendingVerificationRef.current) {
12631
+ smartlinks__namespace.auth.verifyToken(storedToken.token)
12632
+ .then(() => {
12633
+ setIsVerified(true);
12634
+ pendingVerificationRef.current = false;
12635
+ })
12636
+ .catch((error) => {
12637
+ if (!isNetworkError(error)) {
12638
+ console.warn('[AuthContext] Resume verification failed:', error);
12639
+ }
12640
+ });
12641
+ }
12642
+ }
12643
+ catch (error) {
12644
+ console.warn('[AuthContext] Failed to rehydrate auth on resume:', error);
12645
+ }
12646
+ };
12647
+ const handleResume = () => {
12648
+ if (typeof document !== 'undefined' && document.visibilityState !== 'visible')
12649
+ return;
12650
+ rehydrateFromStorage().catch(() => { });
12651
+ };
12652
+ const handleVisibilityChange = () => {
12653
+ if (document.visibilityState === 'visible') {
12654
+ handleResume();
12655
+ }
12656
+ };
12657
+ window.addEventListener('focus', handleResume);
12658
+ window.addEventListener('pageshow', handleResume);
12659
+ document.addEventListener('visibilitychange', handleVisibilityChange);
12660
+ return () => {
12661
+ window.removeEventListener('focus', handleResume);
12662
+ window.removeEventListener('pageshow', handleResume);
12663
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
12664
+ };
12665
+ }, [proxyMode, token, user, isVerified, notifyAuthStateChange, contact, isNetworkError]);
12666
+ // Mobile app-switching can also miss BOTH visibility/storage signals on the newly-opened
12667
+ // page (common with WhatsApp handoff on iOS/Android webviews). Keep a short-lived polling
12668
+ // window after mount while logged out so a session written by the background/original page
12669
+ // is still adopted without requiring a manual refresh.
12670
+ React.useEffect(() => {
12671
+ if (proxyMode || user || token)
12672
+ return;
12673
+ let cancelled = false;
12674
+ let intervalId = null;
12675
+ let timeoutId = null;
12676
+ let pollInFlight = false;
12677
+ const adoptStoredSession = async () => {
12678
+ if (cancelled || pollInFlight)
12679
+ return;
12680
+ pollInFlight = true;
12681
+ try {
12682
+ const storedToken = await tokenStorage.getToken();
12683
+ const storedUser = await tokenStorage.getUser();
12684
+ if (!storedToken?.token || !storedUser)
12685
+ return;
12686
+ const storedAccountData = await tokenStorage.getAccountData();
12687
+ const storedAccountInfo = await tokenStorage.getAccountInfo();
12688
+ const storedContactId = await tokenStorage.getContactId();
12689
+ if (cancelled)
12690
+ return;
12691
+ setToken(storedToken.token);
12692
+ setUser(storedUser);
12693
+ setAccountData(storedAccountData || null);
12694
+ setAccountInfo(storedAccountInfo?.data || storedAccountData || null);
12695
+ setContactId(storedContactId);
12696
+ setIsVerified(true);
12697
+ pendingVerificationRef.current = false;
12698
+ notifyAuthStateChange('SESSION_RESTORED', storedUser, storedToken.token, storedAccountData || null, storedAccountInfo?.data || storedAccountData || null, true, contact, storedContactId);
12699
+ smartlinks__namespace.auth.verifyToken(storedToken.token).catch((error) => {
12700
+ if (!isNetworkError(error)) {
12701
+ console.warn('[AuthContext] Background rehydrate verification failed:', error);
12702
+ }
12703
+ });
12704
+ if (intervalId) {
12705
+ clearInterval(intervalId);
12706
+ intervalId = null;
12707
+ }
12708
+ if (timeoutId) {
12709
+ clearTimeout(timeoutId);
12710
+ timeoutId = null;
12711
+ }
12712
+ }
12713
+ catch (error) {
12714
+ console.warn('[AuthContext] Failed to adopt stored session during mobile handoff:', error);
12715
+ }
12716
+ finally {
12717
+ pollInFlight = false;
12718
+ }
12719
+ };
12720
+ adoptStoredSession().catch(() => { });
12721
+ intervalId = setInterval(() => {
12722
+ adoptStoredSession().catch(() => { });
12723
+ }, 1000);
12724
+ timeoutId = setTimeout(() => {
12725
+ if (intervalId) {
12726
+ clearInterval(intervalId);
12727
+ intervalId = null;
12728
+ }
12729
+ }, 20000);
12730
+ return () => {
12731
+ cancelled = true;
12732
+ if (intervalId)
12733
+ clearInterval(intervalId);
12734
+ if (timeoutId)
12735
+ clearTimeout(timeoutId);
12736
+ };
12737
+ }, [proxyMode, user, token, notifyAuthStateChange, contact, isNetworkError]);
12560
12738
  // Helper: Send login to parent and wait for acknowledgment
12561
12739
  // Used for deep-link flows (email verification, magic link) where we need to ensure
12562
12740
  // the parent has persisted the session before redirecting (which causes page reload)
@@ -12590,10 +12768,12 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12590
12768
  try {
12591
12769
  // Only persist to storage in standalone mode
12592
12770
  if (!proxyMode) {
12771
+ await tokenStorage.clearAccountInfo();
12593
12772
  await tokenStorage.saveToken(authToken, expiresAt);
12594
12773
  await tokenStorage.saveUser(authUser);
12595
12774
  if (authAccountData) {
12596
12775
  await tokenStorage.saveAccountData(authAccountData);
12776
+ await tokenStorage.saveAccountInfo(authAccountData, accountCacheTTL);
12597
12777
  }
12598
12778
  smartlinks__namespace.auth.verifyToken(authToken).catch(() => { });
12599
12779
  }
@@ -12601,6 +12781,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12601
12781
  setToken(authToken);
12602
12782
  setUser(authUser);
12603
12783
  setAccountData(authAccountData || null);
12784
+ setAccountInfo(authAccountData || null);
12604
12785
  pendingVerificationRef.current = false;
12605
12786
  // Cross-iframe auth state synchronization
12606
12787
  // ALWAYS wait for parent acknowledgment in iframe mode before proceeding
@@ -12611,7 +12792,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12611
12792
  }
12612
12793
  // NOW set isVerified - after parent has acknowledged and session is ready
12613
12794
  setIsVerified(true);
12614
- notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, null, true);
12795
+ notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, authAccountData || null, true);
12615
12796
  // Sync contact (non-blocking)
12616
12797
  const newContactId = await syncContact(authUser, authAccountData);
12617
12798
  // Track interaction (non-blocking)
@@ -12627,7 +12808,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12627
12808
  console.error('Failed to save auth data to storage:', error);
12628
12809
  throw error;
12629
12810
  }
12630
- }, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction]);
12811
+ }, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction, accountCacheTTL]);
12631
12812
  const logout = React.useCallback(async () => {
12632
12813
  const currentUser = user;
12633
12814
  const currentContactId = contactId;
@@ -13223,6 +13404,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13223
13404
  const [silentSignInChecked, setSilentSignInChecked] = React.useState(false); // Track if silent sign-in has been checked
13224
13405
  const [googleFallbackToPopup, setGoogleFallbackToPopup] = React.useState(false); // Show popup fallback when FedCM is blocked/dismissed
13225
13406
  const [googleNativeTimedOut, setGoogleNativeTimedOut] = React.useState(false); // Native bridge callback timed out
13407
+ const [restoredWhatsAppSend, setRestoredWhatsAppSend] = React.useState(null);
13226
13408
  const log = React.useMemo(() => createLoggerWrapper(logger), [logger]);
13227
13409
  const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
13228
13410
  const auth = useAuth();
@@ -13286,6 +13468,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13286
13468
  proxyMode: proxyMode, // Use prop value
13287
13469
  ngrokSkipBrowserWarning: true,
13288
13470
  logger: logger, // Pass logger to SDK for verbose SDK logging
13471
+ persistToken: false, // AuthKit's tokenStorage owns persistence (avoid double-writer race)
13289
13472
  });
13290
13473
  log.log('SDK reinitialized successfully');
13291
13474
  // Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
@@ -13333,6 +13516,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13333
13516
  const storage = await getStorage();
13334
13517
  await storage.removeItem(WHATSAPP_PENDING_SESSION_KEY);
13335
13518
  };
13519
+ const updatePendingWhatsAppSession = async (updates) => {
13520
+ if (proxyMode)
13521
+ return null;
13522
+ const existing = await loadPendingWhatsAppSession();
13523
+ if (!existing)
13524
+ return null;
13525
+ const next = { ...existing, ...updates };
13526
+ await savePendingWhatsAppSession(next);
13527
+ return next;
13528
+ };
13336
13529
  // Fetch UI configuration
13337
13530
  React.useEffect(() => {
13338
13531
  // Wait for SDK to be ready before fetching config
@@ -14441,17 +14634,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14441
14634
  sessionKey: pending.sessionKey,
14442
14635
  displayName: pending.displayName,
14443
14636
  };
14637
+ setRestoredWhatsAppSend({
14638
+ waLink: pending.waLink,
14639
+ code: pending.code,
14640
+ token: pending.token,
14641
+ expiresAt: pending.expiresAt,
14642
+ sessionKey: pending.sessionKey,
14643
+ });
14444
14644
  const status = await api.getWhatsAppStatus(pending.token);
14445
14645
  if (cancelled)
14446
14646
  return;
14447
14647
  if (status.verified) {
14448
- await handleWhatsAppVerified(status);
14648
+ const handled = await handleWhatsAppVerified(status);
14649
+ if (handled === false && !cancelled) {
14650
+ setMode('whatsapp');
14651
+ }
14449
14652
  return;
14450
14653
  }
14451
14654
  if (status.status === 'pending') {
14452
14655
  setMode('whatsapp');
14453
14656
  }
14454
14657
  else if (status.status === 'failed' || status.status === 'expired' || status.status === 'unknown') {
14658
+ setRestoredWhatsAppSend(null);
14455
14659
  await clearPendingWhatsAppSession();
14456
14660
  }
14457
14661
  }
@@ -14492,8 +14696,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14492
14696
  sessionKey: result.sessionKey,
14493
14697
  displayName: trimmedName,
14494
14698
  };
14699
+ setRestoredWhatsAppSend(result);
14495
14700
  await savePendingWhatsAppSession({
14701
+ waLink: result.waLink,
14702
+ code: result.code,
14496
14703
  token: result.token,
14704
+ expiresAt: result.expiresAt,
14497
14705
  sessionKey: result.sessionKey,
14498
14706
  displayName: trimmedName,
14499
14707
  redirectUrl: effectiveRedirectUrl,
@@ -14523,22 +14731,39 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14523
14731
  const send = whatsappSendRef.current;
14524
14732
  if (send?.sessionKey) {
14525
14733
  try {
14734
+ await updatePendingWhatsAppSession({ exchangeStartedAt: Date.now() });
14526
14735
  const session = await api.exchangeWhatsAppSession(send.token, send.sessionKey);
14527
14736
  if (session?.token && session.user) {
14528
14737
  await auth.login(session.token, session.user, session.accountData, true, getExpirationFromResponse(session));
14529
14738
  if (!proxyMode) {
14530
14739
  onAuthSuccess(session.token, session.user, session.accountData);
14531
14740
  }
14741
+ await updatePendingWhatsAppSession({ exchangeCompletedAt: Date.now() });
14742
+ setRestoredWhatsAppSend(null);
14532
14743
  await clearPendingWhatsAppSession();
14533
- return;
14744
+ return true;
14534
14745
  }
14535
14746
  }
14536
14747
  catch (err) {
14537
- log.warn('WhatsApp session exchange failed, falling back to redirect-only flow:', err);
14748
+ const latestPending = await loadPendingWhatsAppSession();
14749
+ if (!latestPending) {
14750
+ return true;
14751
+ }
14752
+ const recentlyCompleted = latestPending.exchangeCompletedAt && (Date.now() - latestPending.exchangeCompletedAt) < 15000;
14753
+ if (recentlyCompleted) {
14754
+ return true;
14755
+ }
14756
+ log.warn('WhatsApp session exchange failed; keeping pending session for retry:', err);
14757
+ setAuthSuccess(false);
14758
+ setSuccessMessage(undefined);
14759
+ setError('WhatsApp verified, but finishing sign-in is taking longer than expected. Retrying…');
14760
+ return false;
14538
14761
  }
14539
14762
  }
14763
+ setRestoredWhatsAppSend(null);
14540
14764
  await clearPendingWhatsAppSession();
14541
14765
  performRedirect(target, 'magic-link');
14766
+ return true;
14542
14767
  };
14543
14768
  // Show processing state for URL-based auth (verification, magic link, password reset)
14544
14769
  // This runs BEFORE configLoading check to prevent form flash on deep-link flows
@@ -14574,7 +14799,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14574
14799
  ? 'hsl(var(--muted-foreground, 215 15% 45%))'
14575
14800
  : (resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280'),
14576
14801
  fontSize: '0.875rem'
14577
- }, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'whatsapp' ? (jsxRuntime.jsx(WhatsAppAuthForm, { onSend: handleWhatsAppSend, onPollStatus: handleWhatsAppPoll, onVerified: handleWhatsAppVerified, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup ?? true })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
14802
+ }, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'whatsapp' ? (jsxRuntime.jsx(WhatsAppAuthForm, { onSend: handleWhatsAppSend, onPollStatus: handleWhatsAppPoll, onVerified: handleWhatsAppVerified, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup ?? true, initialSent: restoredWhatsAppSend })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
14578
14803
  setMode('login');
14579
14804
  setResetSuccess(false);
14580
14805
  setResetToken(undefined); // Clear token when going back
@@ -14925,6 +15150,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14925
15150
  baseURL: apiEndpoint,
14926
15151
  proxyMode: false,
14927
15152
  ngrokSkipBrowserWarning: true,
15153
+ persistToken: false, // AuthKit's tokenStorage is the single source of truth
14928
15154
  });
14929
15155
  }
14930
15156
  }, [apiEndpoint]);