@proveanything/smartlinks-auth-ui 0.5.12 → 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 +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountManagement.d.ts","sourceRoot":"","sources":["../../src/components/AccountManagement.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAOhE,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,UAAU,CAAC;AACpE,OAAO,qBAAqB,CAAC;AAK7B,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,
|
|
1
|
+
{"version":3,"file":"AccountManagement.d.ts","sourceRoot":"","sources":["../../src/components/AccountManagement.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAOhE,OAAO,KAAK,EAAE,sBAAsB,EAAe,MAAM,UAAU,CAAC;AACpE,OAAO,qBAAqB,CAAC;AAK7B,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAizB9D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmartlinksAuthUI.d.ts","sourceRoot":"","sources":["../../src/components/SmartlinksAuthUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAcpE,OAAO,KAAK,EAAE,qBAAqB,EAAyF,MAAM,UAAU,CAAC;AAiR7I,QAAA,MAAM,mBAAmB,QAAa,OAAO,CAAC,IAAI,CAqBjD,CAAC;AAqDF,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAI/B,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"SmartlinksAuthUI.d.ts","sourceRoot":"","sources":["../../src/components/SmartlinksAuthUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAcpE,OAAO,KAAK,EAAE,qBAAqB,EAAyF,MAAM,UAAU,CAAC;AAiR7I,QAAA,MAAM,mBAAmB,QAAa,OAAO,CAAC,IAAI,CAqBjD,CAAC;AAqDF,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAI/B,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA+nE5D,CAAC"}
|
|
@@ -17,8 +17,8 @@ interface WhatsAppAuthFormProps {
|
|
|
17
17
|
onSend: (displayName?: string) => Promise<WhatsAppSendResult>;
|
|
18
18
|
/** Poll verification status for the issued token. */
|
|
19
19
|
onPollStatus: (token: string) => Promise<WhatsAppStatusResult>;
|
|
20
|
-
/** Called once verification is confirmed (status === 'verified'). */
|
|
21
|
-
onVerified: (result: WhatsAppStatusResult) => void
|
|
20
|
+
/** Called once verification is confirmed (status === 'verified'). Return false to keep retrying. */
|
|
21
|
+
onVerified: (result: WhatsAppStatusResult) => void | Promise<boolean | void>;
|
|
22
22
|
onBack: () => void;
|
|
23
23
|
loading?: boolean;
|
|
24
24
|
error?: string;
|
|
@@ -26,6 +26,8 @@ interface WhatsAppAuthFormProps {
|
|
|
26
26
|
collectName?: boolean;
|
|
27
27
|
/** Polling interval in ms (default 3000). */
|
|
28
28
|
pollIntervalMs?: number;
|
|
29
|
+
/** Existing in-progress WhatsApp session restored after app switching/reload. */
|
|
30
|
+
initialSent?: WhatsAppSendResult | null;
|
|
29
31
|
}
|
|
30
32
|
export declare const WhatsAppAuthForm: React.FC<WhatsAppAuthFormProps>;
|
|
31
33
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WhatsAppAuthForm.d.ts","sourceRoot":"","sources":["../../src/components/WhatsAppAuthForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,gBAAgB,CAAC;AAExB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,qBAAqB;IAC7B,kGAAkG;IAClG,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC9D,qDAAqD;IACrD,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/D,
|
|
1
|
+
{"version":3,"file":"WhatsAppAuthForm.d.ts","sourceRoot":"","sources":["../../src/components/WhatsAppAuthForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,gBAAgB,CAAC;AAExB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,qBAAqB;IAC7B,kGAAkG;IAClG,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC9D,qDAAqD;IACrD,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/D,oGAAoG;IACpG,UAAU,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC7E,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,WAAW,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzC;AAOD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAmV5D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/context/AuthContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAOnG,OAAO,KAAK,EAAqC,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGvG,eAAO,MAAM,WAAW,6CAAyD,CAAC;AAGlF,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/context/AuthContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAC;AAOnG,OAAO,KAAK,EAAqC,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGvG,eAAO,MAAM,WAAW,6CAAyD,CAAC;AAGlF,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA0oCpD,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,gBAM1B,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -10873,18 +10873,19 @@ const isDesktop = () => {
|
|
|
10873
10873
|
return false;
|
|
10874
10874
|
return !/Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent);
|
|
10875
10875
|
};
|
|
10876
|
-
const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading = false, error, collectName = false, pollIntervalMs = 3000, }) => {
|
|
10876
|
+
const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading = false, error, collectName = false, pollIntervalMs = 3000, initialSent = null, }) => {
|
|
10877
10877
|
const [displayName, setDisplayName] = useState('');
|
|
10878
|
-
const [sent, setSent] = useState(null);
|
|
10878
|
+
const [sent, setSent] = useState(initialSent ?? null);
|
|
10879
10879
|
const [pollError, setPollError] = useState();
|
|
10880
10880
|
const [qrDataUrl, setQrDataUrl] = useState(null);
|
|
10881
10881
|
const [showOnDesktop] = useState(isDesktop());
|
|
10882
10882
|
const verifiedFiredRef = useRef(false);
|
|
10883
10883
|
const autoSentRef = useRef(false);
|
|
10884
|
+
const hydratedInitialTokenRef = useRef(initialSent?.token ?? null);
|
|
10884
10885
|
// Auto-issue the WhatsApp link on mount when we don't need to collect a name.
|
|
10885
10886
|
// When collectName is true, wait for the user to submit the name first.
|
|
10886
10887
|
useEffect(() => {
|
|
10887
|
-
if (collectName)
|
|
10888
|
+
if (collectName || sent)
|
|
10888
10889
|
return;
|
|
10889
10890
|
if (autoSentRef.current)
|
|
10890
10891
|
return;
|
|
@@ -10892,13 +10893,24 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
|
|
|
10892
10893
|
(async () => {
|
|
10893
10894
|
try {
|
|
10894
10895
|
const result = await onSend();
|
|
10896
|
+
hydratedInitialTokenRef.current = result.token;
|
|
10895
10897
|
setSent(result);
|
|
10896
10898
|
}
|
|
10897
10899
|
catch {
|
|
10898
10900
|
// Surfaced via parent error prop
|
|
10899
10901
|
}
|
|
10900
10902
|
})();
|
|
10901
|
-
}, [collectName, onSend]);
|
|
10903
|
+
}, [collectName, onSend, sent]);
|
|
10904
|
+
useEffect(() => {
|
|
10905
|
+
if (!initialSent?.token)
|
|
10906
|
+
return;
|
|
10907
|
+
if (initialSent.token === hydratedInitialTokenRef.current)
|
|
10908
|
+
return;
|
|
10909
|
+
hydratedInitialTokenRef.current = initialSent.token;
|
|
10910
|
+
verifiedFiredRef.current = false;
|
|
10911
|
+
setPollError(undefined);
|
|
10912
|
+
setSent(initialSent);
|
|
10913
|
+
}, [initialSent]);
|
|
10902
10914
|
// Generate QR code for the WhatsApp deep-link (great for desktop scan-with-phone UX)
|
|
10903
10915
|
useEffect(() => {
|
|
10904
10916
|
if (!sent?.waLink)
|
|
@@ -10927,15 +10939,26 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
|
|
|
10927
10939
|
return;
|
|
10928
10940
|
let stopped = false;
|
|
10929
10941
|
let timer;
|
|
10942
|
+
let pollInFlight = false;
|
|
10943
|
+
const scheduleNext = () => {
|
|
10944
|
+
if (stopped)
|
|
10945
|
+
return;
|
|
10946
|
+
timer = setTimeout(tick, pollIntervalMs);
|
|
10947
|
+
};
|
|
10930
10948
|
const tick = async () => {
|
|
10949
|
+
if (pollInFlight)
|
|
10950
|
+
return;
|
|
10951
|
+
pollInFlight = true;
|
|
10931
10952
|
try {
|
|
10932
10953
|
const status = await onPollStatus(sent.token);
|
|
10933
10954
|
if (stopped)
|
|
10934
10955
|
return;
|
|
10935
10956
|
if (status.verified && !verifiedFiredRef.current) {
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10957
|
+
const handled = await onVerified(status);
|
|
10958
|
+
if (handled !== false) {
|
|
10959
|
+
verifiedFiredRef.current = true;
|
|
10960
|
+
return;
|
|
10961
|
+
}
|
|
10939
10962
|
}
|
|
10940
10963
|
if (status.status === 'failed' || status.status === 'expired') {
|
|
10941
10964
|
setPollError(status.status === 'expired'
|
|
@@ -10947,12 +10970,31 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
|
|
|
10947
10970
|
catch {
|
|
10948
10971
|
// Transient errors — keep polling
|
|
10949
10972
|
}
|
|
10950
|
-
|
|
10973
|
+
finally {
|
|
10974
|
+
pollInFlight = false;
|
|
10975
|
+
}
|
|
10976
|
+
scheduleNext();
|
|
10977
|
+
};
|
|
10978
|
+
const handleResume = () => {
|
|
10979
|
+
if (document.visibilityState && document.visibilityState !== 'visible')
|
|
10980
|
+
return;
|
|
10981
|
+
tick().catch?.(() => { });
|
|
10982
|
+
};
|
|
10983
|
+
const handleVisibilityChange = () => {
|
|
10984
|
+
if (document.visibilityState === 'visible') {
|
|
10985
|
+
handleResume();
|
|
10986
|
+
}
|
|
10951
10987
|
};
|
|
10952
|
-
|
|
10988
|
+
tick().catch?.(() => { });
|
|
10989
|
+
window.addEventListener('focus', handleResume);
|
|
10990
|
+
window.addEventListener('pageshow', handleResume);
|
|
10991
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
10953
10992
|
return () => {
|
|
10954
10993
|
stopped = true;
|
|
10955
10994
|
clearTimeout(timer);
|
|
10995
|
+
window.removeEventListener('focus', handleResume);
|
|
10996
|
+
window.removeEventListener('pageshow', handleResume);
|
|
10997
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
10956
10998
|
};
|
|
10957
10999
|
}, [sent?.token, onPollStatus, onVerified, pollIntervalMs]);
|
|
10958
11000
|
const handleNameSubmit = async (e) => {
|
|
@@ -10961,6 +11003,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
|
|
|
10961
11003
|
verifiedFiredRef.current = false;
|
|
10962
11004
|
try {
|
|
10963
11005
|
const result = await onSend(displayName.trim() || undefined);
|
|
11006
|
+
hydratedInitialTokenRef.current = result.token;
|
|
10964
11007
|
setSent(result);
|
|
10965
11008
|
}
|
|
10966
11009
|
catch {
|
|
@@ -10975,6 +11018,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
|
|
|
10975
11018
|
if (!collectName) {
|
|
10976
11019
|
try {
|
|
10977
11020
|
const result = await onSend();
|
|
11021
|
+
hydratedInitialTokenRef.current = result.token;
|
|
10978
11022
|
setSent(result);
|
|
10979
11023
|
}
|
|
10980
11024
|
catch {
|
|
@@ -12537,6 +12581,140 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12537
12581
|
unsubscribe();
|
|
12538
12582
|
};
|
|
12539
12583
|
}, [proxyMode, notifyAuthStateChange]);
|
|
12584
|
+
// Mobile app-switching can miss BroadcastChannel/storage events, especially on iOS.
|
|
12585
|
+
// Re-read persisted auth state whenever the page becomes active again so a
|
|
12586
|
+
// WhatsApp-returned tab/window can pick up a session that another page wrote.
|
|
12587
|
+
useEffect(() => {
|
|
12588
|
+
if (proxyMode)
|
|
12589
|
+
return;
|
|
12590
|
+
const rehydrateFromStorage = async () => {
|
|
12591
|
+
try {
|
|
12592
|
+
const storedToken = await tokenStorage.getToken();
|
|
12593
|
+
const storedUser = await tokenStorage.getUser();
|
|
12594
|
+
const storedAccountData = await tokenStorage.getAccountData();
|
|
12595
|
+
const storedAccountInfo = await tokenStorage.getAccountInfo();
|
|
12596
|
+
const storedContactId = await tokenStorage.getContactId();
|
|
12597
|
+
if (!storedToken || !storedUser)
|
|
12598
|
+
return;
|
|
12599
|
+
const tokenChanged = storedToken.token !== token;
|
|
12600
|
+
const userChanged = storedUser.uid !== user?.uid;
|
|
12601
|
+
if (tokenChanged || userChanged || !user) {
|
|
12602
|
+
setToken(storedToken.token);
|
|
12603
|
+
setUser(storedUser);
|
|
12604
|
+
setAccountData(storedAccountData);
|
|
12605
|
+
setAccountInfo(storedAccountInfo?.data || storedAccountData || null);
|
|
12606
|
+
setContactId(storedContactId);
|
|
12607
|
+
setIsVerified(true);
|
|
12608
|
+
notifyAuthStateChange('SESSION_RESTORED', storedUser, storedToken.token, storedAccountData || null, storedAccountInfo?.data || storedAccountData || null, true, contact, storedContactId);
|
|
12609
|
+
}
|
|
12610
|
+
if (!isVerified || pendingVerificationRef.current) {
|
|
12611
|
+
smartlinks.auth.verifyToken(storedToken.token)
|
|
12612
|
+
.then(() => {
|
|
12613
|
+
setIsVerified(true);
|
|
12614
|
+
pendingVerificationRef.current = false;
|
|
12615
|
+
})
|
|
12616
|
+
.catch((error) => {
|
|
12617
|
+
if (!isNetworkError(error)) {
|
|
12618
|
+
console.warn('[AuthContext] Resume verification failed:', error);
|
|
12619
|
+
}
|
|
12620
|
+
});
|
|
12621
|
+
}
|
|
12622
|
+
}
|
|
12623
|
+
catch (error) {
|
|
12624
|
+
console.warn('[AuthContext] Failed to rehydrate auth on resume:', error);
|
|
12625
|
+
}
|
|
12626
|
+
};
|
|
12627
|
+
const handleResume = () => {
|
|
12628
|
+
if (typeof document !== 'undefined' && document.visibilityState !== 'visible')
|
|
12629
|
+
return;
|
|
12630
|
+
rehydrateFromStorage().catch(() => { });
|
|
12631
|
+
};
|
|
12632
|
+
const handleVisibilityChange = () => {
|
|
12633
|
+
if (document.visibilityState === 'visible') {
|
|
12634
|
+
handleResume();
|
|
12635
|
+
}
|
|
12636
|
+
};
|
|
12637
|
+
window.addEventListener('focus', handleResume);
|
|
12638
|
+
window.addEventListener('pageshow', handleResume);
|
|
12639
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
12640
|
+
return () => {
|
|
12641
|
+
window.removeEventListener('focus', handleResume);
|
|
12642
|
+
window.removeEventListener('pageshow', handleResume);
|
|
12643
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
12644
|
+
};
|
|
12645
|
+
}, [proxyMode, token, user, isVerified, notifyAuthStateChange, contact, isNetworkError]);
|
|
12646
|
+
// Mobile app-switching can also miss BOTH visibility/storage signals on the newly-opened
|
|
12647
|
+
// page (common with WhatsApp handoff on iOS/Android webviews). Keep a short-lived polling
|
|
12648
|
+
// window after mount while logged out so a session written by the background/original page
|
|
12649
|
+
// is still adopted without requiring a manual refresh.
|
|
12650
|
+
useEffect(() => {
|
|
12651
|
+
if (proxyMode || user || token)
|
|
12652
|
+
return;
|
|
12653
|
+
let cancelled = false;
|
|
12654
|
+
let intervalId = null;
|
|
12655
|
+
let timeoutId = null;
|
|
12656
|
+
let pollInFlight = false;
|
|
12657
|
+
const adoptStoredSession = async () => {
|
|
12658
|
+
if (cancelled || pollInFlight)
|
|
12659
|
+
return;
|
|
12660
|
+
pollInFlight = true;
|
|
12661
|
+
try {
|
|
12662
|
+
const storedToken = await tokenStorage.getToken();
|
|
12663
|
+
const storedUser = await tokenStorage.getUser();
|
|
12664
|
+
if (!storedToken?.token || !storedUser)
|
|
12665
|
+
return;
|
|
12666
|
+
const storedAccountData = await tokenStorage.getAccountData();
|
|
12667
|
+
const storedAccountInfo = await tokenStorage.getAccountInfo();
|
|
12668
|
+
const storedContactId = await tokenStorage.getContactId();
|
|
12669
|
+
if (cancelled)
|
|
12670
|
+
return;
|
|
12671
|
+
setToken(storedToken.token);
|
|
12672
|
+
setUser(storedUser);
|
|
12673
|
+
setAccountData(storedAccountData || null);
|
|
12674
|
+
setAccountInfo(storedAccountInfo?.data || storedAccountData || null);
|
|
12675
|
+
setContactId(storedContactId);
|
|
12676
|
+
setIsVerified(true);
|
|
12677
|
+
pendingVerificationRef.current = false;
|
|
12678
|
+
notifyAuthStateChange('SESSION_RESTORED', storedUser, storedToken.token, storedAccountData || null, storedAccountInfo?.data || storedAccountData || null, true, contact, storedContactId);
|
|
12679
|
+
smartlinks.auth.verifyToken(storedToken.token).catch((error) => {
|
|
12680
|
+
if (!isNetworkError(error)) {
|
|
12681
|
+
console.warn('[AuthContext] Background rehydrate verification failed:', error);
|
|
12682
|
+
}
|
|
12683
|
+
});
|
|
12684
|
+
if (intervalId) {
|
|
12685
|
+
clearInterval(intervalId);
|
|
12686
|
+
intervalId = null;
|
|
12687
|
+
}
|
|
12688
|
+
if (timeoutId) {
|
|
12689
|
+
clearTimeout(timeoutId);
|
|
12690
|
+
timeoutId = null;
|
|
12691
|
+
}
|
|
12692
|
+
}
|
|
12693
|
+
catch (error) {
|
|
12694
|
+
console.warn('[AuthContext] Failed to adopt stored session during mobile handoff:', error);
|
|
12695
|
+
}
|
|
12696
|
+
finally {
|
|
12697
|
+
pollInFlight = false;
|
|
12698
|
+
}
|
|
12699
|
+
};
|
|
12700
|
+
adoptStoredSession().catch(() => { });
|
|
12701
|
+
intervalId = setInterval(() => {
|
|
12702
|
+
adoptStoredSession().catch(() => { });
|
|
12703
|
+
}, 1000);
|
|
12704
|
+
timeoutId = setTimeout(() => {
|
|
12705
|
+
if (intervalId) {
|
|
12706
|
+
clearInterval(intervalId);
|
|
12707
|
+
intervalId = null;
|
|
12708
|
+
}
|
|
12709
|
+
}, 20000);
|
|
12710
|
+
return () => {
|
|
12711
|
+
cancelled = true;
|
|
12712
|
+
if (intervalId)
|
|
12713
|
+
clearInterval(intervalId);
|
|
12714
|
+
if (timeoutId)
|
|
12715
|
+
clearTimeout(timeoutId);
|
|
12716
|
+
};
|
|
12717
|
+
}, [proxyMode, user, token, notifyAuthStateChange, contact, isNetworkError]);
|
|
12540
12718
|
// Helper: Send login to parent and wait for acknowledgment
|
|
12541
12719
|
// Used for deep-link flows (email verification, magic link) where we need to ensure
|
|
12542
12720
|
// the parent has persisted the session before redirecting (which causes page reload)
|
|
@@ -12570,10 +12748,12 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12570
12748
|
try {
|
|
12571
12749
|
// Only persist to storage in standalone mode
|
|
12572
12750
|
if (!proxyMode) {
|
|
12751
|
+
await tokenStorage.clearAccountInfo();
|
|
12573
12752
|
await tokenStorage.saveToken(authToken, expiresAt);
|
|
12574
12753
|
await tokenStorage.saveUser(authUser);
|
|
12575
12754
|
if (authAccountData) {
|
|
12576
12755
|
await tokenStorage.saveAccountData(authAccountData);
|
|
12756
|
+
await tokenStorage.saveAccountInfo(authAccountData, accountCacheTTL);
|
|
12577
12757
|
}
|
|
12578
12758
|
smartlinks.auth.verifyToken(authToken).catch(() => { });
|
|
12579
12759
|
}
|
|
@@ -12581,6 +12761,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12581
12761
|
setToken(authToken);
|
|
12582
12762
|
setUser(authUser);
|
|
12583
12763
|
setAccountData(authAccountData || null);
|
|
12764
|
+
setAccountInfo(authAccountData || null);
|
|
12584
12765
|
pendingVerificationRef.current = false;
|
|
12585
12766
|
// Cross-iframe auth state synchronization
|
|
12586
12767
|
// ALWAYS wait for parent acknowledgment in iframe mode before proceeding
|
|
@@ -12591,7 +12772,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12591
12772
|
}
|
|
12592
12773
|
// NOW set isVerified - after parent has acknowledged and session is ready
|
|
12593
12774
|
setIsVerified(true);
|
|
12594
|
-
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, null, true);
|
|
12775
|
+
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, authAccountData || null, true);
|
|
12595
12776
|
// Sync contact (non-blocking)
|
|
12596
12777
|
const newContactId = await syncContact(authUser, authAccountData);
|
|
12597
12778
|
// Track interaction (non-blocking)
|
|
@@ -12607,7 +12788,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12607
12788
|
console.error('Failed to save auth data to storage:', error);
|
|
12608
12789
|
throw error;
|
|
12609
12790
|
}
|
|
12610
|
-
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction]);
|
|
12791
|
+
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction, accountCacheTTL]);
|
|
12611
12792
|
const logout = useCallback(async () => {
|
|
12612
12793
|
const currentUser = user;
|
|
12613
12794
|
const currentContactId = contactId;
|
|
@@ -13203,6 +13384,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13203
13384
|
const [silentSignInChecked, setSilentSignInChecked] = useState(false); // Track if silent sign-in has been checked
|
|
13204
13385
|
const [googleFallbackToPopup, setGoogleFallbackToPopup] = useState(false); // Show popup fallback when FedCM is blocked/dismissed
|
|
13205
13386
|
const [googleNativeTimedOut, setGoogleNativeTimedOut] = useState(false); // Native bridge callback timed out
|
|
13387
|
+
const [restoredWhatsAppSend, setRestoredWhatsAppSend] = useState(null);
|
|
13206
13388
|
const log = useMemo(() => createLoggerWrapper(logger), [logger]);
|
|
13207
13389
|
const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
|
|
13208
13390
|
const auth = useAuth();
|
|
@@ -13266,6 +13448,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13266
13448
|
proxyMode: proxyMode, // Use prop value
|
|
13267
13449
|
ngrokSkipBrowserWarning: true,
|
|
13268
13450
|
logger: logger, // Pass logger to SDK for verbose SDK logging
|
|
13451
|
+
persistToken: false, // AuthKit's tokenStorage owns persistence (avoid double-writer race)
|
|
13269
13452
|
});
|
|
13270
13453
|
log.log('SDK reinitialized successfully');
|
|
13271
13454
|
// Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
|
|
@@ -13313,6 +13496,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13313
13496
|
const storage = await getStorage();
|
|
13314
13497
|
await storage.removeItem(WHATSAPP_PENDING_SESSION_KEY);
|
|
13315
13498
|
};
|
|
13499
|
+
const updatePendingWhatsAppSession = async (updates) => {
|
|
13500
|
+
if (proxyMode)
|
|
13501
|
+
return null;
|
|
13502
|
+
const existing = await loadPendingWhatsAppSession();
|
|
13503
|
+
if (!existing)
|
|
13504
|
+
return null;
|
|
13505
|
+
const next = { ...existing, ...updates };
|
|
13506
|
+
await savePendingWhatsAppSession(next);
|
|
13507
|
+
return next;
|
|
13508
|
+
};
|
|
13316
13509
|
// Fetch UI configuration
|
|
13317
13510
|
useEffect(() => {
|
|
13318
13511
|
// Wait for SDK to be ready before fetching config
|
|
@@ -14421,17 +14614,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14421
14614
|
sessionKey: pending.sessionKey,
|
|
14422
14615
|
displayName: pending.displayName,
|
|
14423
14616
|
};
|
|
14617
|
+
setRestoredWhatsAppSend({
|
|
14618
|
+
waLink: pending.waLink,
|
|
14619
|
+
code: pending.code,
|
|
14620
|
+
token: pending.token,
|
|
14621
|
+
expiresAt: pending.expiresAt,
|
|
14622
|
+
sessionKey: pending.sessionKey,
|
|
14623
|
+
});
|
|
14424
14624
|
const status = await api.getWhatsAppStatus(pending.token);
|
|
14425
14625
|
if (cancelled)
|
|
14426
14626
|
return;
|
|
14427
14627
|
if (status.verified) {
|
|
14428
|
-
await handleWhatsAppVerified(status);
|
|
14628
|
+
const handled = await handleWhatsAppVerified(status);
|
|
14629
|
+
if (handled === false && !cancelled) {
|
|
14630
|
+
setMode('whatsapp');
|
|
14631
|
+
}
|
|
14429
14632
|
return;
|
|
14430
14633
|
}
|
|
14431
14634
|
if (status.status === 'pending') {
|
|
14432
14635
|
setMode('whatsapp');
|
|
14433
14636
|
}
|
|
14434
14637
|
else if (status.status === 'failed' || status.status === 'expired' || status.status === 'unknown') {
|
|
14638
|
+
setRestoredWhatsAppSend(null);
|
|
14435
14639
|
await clearPendingWhatsAppSession();
|
|
14436
14640
|
}
|
|
14437
14641
|
}
|
|
@@ -14472,8 +14676,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14472
14676
|
sessionKey: result.sessionKey,
|
|
14473
14677
|
displayName: trimmedName,
|
|
14474
14678
|
};
|
|
14679
|
+
setRestoredWhatsAppSend(result);
|
|
14475
14680
|
await savePendingWhatsAppSession({
|
|
14681
|
+
waLink: result.waLink,
|
|
14682
|
+
code: result.code,
|
|
14476
14683
|
token: result.token,
|
|
14684
|
+
expiresAt: result.expiresAt,
|
|
14477
14685
|
sessionKey: result.sessionKey,
|
|
14478
14686
|
displayName: trimmedName,
|
|
14479
14687
|
redirectUrl: effectiveRedirectUrl,
|
|
@@ -14503,22 +14711,39 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14503
14711
|
const send = whatsappSendRef.current;
|
|
14504
14712
|
if (send?.sessionKey) {
|
|
14505
14713
|
try {
|
|
14714
|
+
await updatePendingWhatsAppSession({ exchangeStartedAt: Date.now() });
|
|
14506
14715
|
const session = await api.exchangeWhatsAppSession(send.token, send.sessionKey);
|
|
14507
14716
|
if (session?.token && session.user) {
|
|
14508
14717
|
await auth.login(session.token, session.user, session.accountData, true, getExpirationFromResponse(session));
|
|
14509
14718
|
if (!proxyMode) {
|
|
14510
14719
|
onAuthSuccess(session.token, session.user, session.accountData);
|
|
14511
14720
|
}
|
|
14721
|
+
await updatePendingWhatsAppSession({ exchangeCompletedAt: Date.now() });
|
|
14722
|
+
setRestoredWhatsAppSend(null);
|
|
14512
14723
|
await clearPendingWhatsAppSession();
|
|
14513
|
-
return;
|
|
14724
|
+
return true;
|
|
14514
14725
|
}
|
|
14515
14726
|
}
|
|
14516
14727
|
catch (err) {
|
|
14517
|
-
|
|
14728
|
+
const latestPending = await loadPendingWhatsAppSession();
|
|
14729
|
+
if (!latestPending) {
|
|
14730
|
+
return true;
|
|
14731
|
+
}
|
|
14732
|
+
const recentlyCompleted = latestPending.exchangeCompletedAt && (Date.now() - latestPending.exchangeCompletedAt) < 15000;
|
|
14733
|
+
if (recentlyCompleted) {
|
|
14734
|
+
return true;
|
|
14735
|
+
}
|
|
14736
|
+
log.warn('WhatsApp session exchange failed; keeping pending session for retry:', err);
|
|
14737
|
+
setAuthSuccess(false);
|
|
14738
|
+
setSuccessMessage(undefined);
|
|
14739
|
+
setError('WhatsApp verified, but finishing sign-in is taking longer than expected. Retrying…');
|
|
14740
|
+
return false;
|
|
14518
14741
|
}
|
|
14519
14742
|
}
|
|
14743
|
+
setRestoredWhatsAppSend(null);
|
|
14520
14744
|
await clearPendingWhatsAppSession();
|
|
14521
14745
|
performRedirect(target, 'magic-link');
|
|
14746
|
+
return true;
|
|
14522
14747
|
};
|
|
14523
14748
|
// Show processing state for URL-based auth (verification, magic link, password reset)
|
|
14524
14749
|
// This runs BEFORE configLoading check to prevent form flash on deep-link flows
|
|
@@ -14554,7 +14779,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14554
14779
|
? 'hsl(var(--muted-foreground, 215 15% 45%))'
|
|
14555
14780
|
: (resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280'),
|
|
14556
14781
|
fontSize: '0.875rem'
|
|
14557
|
-
}, children: successMessage })] })) : mode === 'magic-link' ? (jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'whatsapp' ? (jsx(WhatsAppAuthForm, { onSend: handleWhatsAppSend, onPollStatus: handleWhatsAppPoll, onVerified: handleWhatsAppVerified, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup ?? true })) : mode === 'phone' ? (jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
14782
|
+
}, children: successMessage })] })) : mode === 'magic-link' ? (jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'whatsapp' ? (jsx(WhatsAppAuthForm, { onSend: handleWhatsAppSend, onPollStatus: handleWhatsAppPoll, onVerified: handleWhatsAppVerified, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup ?? true, initialSent: restoredWhatsAppSend })) : mode === 'phone' ? (jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
14558
14783
|
setMode('login');
|
|
14559
14784
|
setResetSuccess(false);
|
|
14560
14785
|
setResetToken(undefined); // Clear token when going back
|
|
@@ -14905,6 +15130,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
|
|
|
14905
15130
|
baseURL: apiEndpoint,
|
|
14906
15131
|
proxyMode: false,
|
|
14907
15132
|
ngrokSkipBrowserWarning: true,
|
|
15133
|
+
persistToken: false, // AuthKit's tokenStorage is the single source of truth
|
|
14908
15134
|
});
|
|
14909
15135
|
}
|
|
14910
15136
|
}, [apiEndpoint]);
|