@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/components/AccountManagement.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/components/WhatsAppAuthForm.d.ts +4 -2
- package/dist/components/WhatsAppAuthForm.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.esm.js +241 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +241 -15
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]);
|