@proveanything/smartlinks-auth-ui 0.5.9 → 0.5.11
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/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.esm.js +177 -19
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +179 -21
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var smartlinks = require('@proveanything/smartlinks');
|
|
6
|
-
var http = require('@proveanything/smartlinks/dist/http');
|
|
7
6
|
|
|
8
7
|
function _interopNamespaceDefault(e) {
|
|
9
8
|
var n = Object.create(null);
|
|
@@ -3117,24 +3116,25 @@ function isPossiblePhoneNumber(input, options, metadata) {
|
|
|
3117
3116
|
}
|
|
3118
3117
|
}
|
|
3119
3118
|
|
|
3120
|
-
// Old metadata (< 1.0.18) had no "possible length" data.
|
|
3119
|
+
// Old (legacy) metadata (< 1.0.18) had no "possible length" data.
|
|
3120
|
+
// So `isPossibleNumber()` function is supported only for non-legacy metadata.
|
|
3121
3121
|
if (metadata.possibleLengths()) {
|
|
3122
3122
|
return isPossibleNumber(input.phone || input.nationalNumber, input.country, metadata);
|
|
3123
|
-
} else {
|
|
3124
|
-
// There was a bug between `1.7.35` and `1.7.37` where "possible_lengths"
|
|
3125
|
-
// were missing for "non-geographical" numbering plans.
|
|
3126
|
-
// Just assume the number is possible in such cases:
|
|
3127
|
-
// it's unlikely that anyone generated their custom metadata
|
|
3128
|
-
// in that short period of time (one day).
|
|
3129
|
-
// This code can be removed in some future major version update.
|
|
3130
|
-
if (input.countryCallingCode && metadata.isNonGeographicCallingCode(input.countryCallingCode)) {
|
|
3131
|
-
// "Non-geographic entities" did't have `possibleLengths`
|
|
3132
|
-
// due to a bug in metadata generation process.
|
|
3133
|
-
return true;
|
|
3134
|
-
} else {
|
|
3135
|
-
throw new Error('Missing "possibleLengths" in metadata. Perhaps the metadata has been generated before v1.0.18.');
|
|
3136
|
-
}
|
|
3137
3123
|
}
|
|
3124
|
+
|
|
3125
|
+
// There was a bug in versions from `1.7.35` to `1.7.37` of `libphonenumber-js`
|
|
3126
|
+
// where "possible_lengths" property was missing from "non-geographical" numbering plans' metadata.
|
|
3127
|
+
// After that, the bug was noticed and fixed.
|
|
3128
|
+
// So for versions from `1.7.35` to `1.7.37`, just assume that all "non=geotraphical" numbers are possible.
|
|
3129
|
+
// The reason is that the bug was noticed relatively quickly (within a day or so)
|
|
3130
|
+
// and it's unlikely that anyone generated their custom metadata in that short time span.
|
|
3131
|
+
// And if they didn't generate any custom metadata then a follow-up package update would've silently fixed the bug.
|
|
3132
|
+
if (input.countryCallingCode && metadata.isNonGeographicCallingCode(input.countryCallingCode)) {
|
|
3133
|
+
return true;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
// `isPossibleNumber()` function is not supported.
|
|
3137
|
+
throw new Error('Missing "possibleLengths" in metadata. Perhaps the metadata has been generated before v1.0.18.');
|
|
3138
3138
|
}
|
|
3139
3139
|
function isPossibleNumber(nationalNumber, country, metadata) {
|
|
3140
3140
|
//, isInternational) {
|
|
@@ -11375,7 +11375,7 @@ class AuthAPI {
|
|
|
11375
11375
|
});
|
|
11376
11376
|
// Exchange authorization code for tokens via backend
|
|
11377
11377
|
// Use direct HTTP call since SDK may not have this method in authKit namespace yet
|
|
11378
|
-
return
|
|
11378
|
+
return smartlinks.post(`/authkit/${this.clientId}/google-code`, {
|
|
11379
11379
|
code,
|
|
11380
11380
|
redirectUri,
|
|
11381
11381
|
});
|
|
@@ -12056,6 +12056,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12056
12056
|
const callbacksRef = React.useRef(new Set());
|
|
12057
12057
|
const initializingRef = React.useRef(false);
|
|
12058
12058
|
const pendingVerificationRef = React.useRef(false);
|
|
12059
|
+
const proxyRefreshInFlightRef = React.useRef(false);
|
|
12059
12060
|
// Stable refs for callbacks to avoid useEffect dependency cycles
|
|
12060
12061
|
const syncContactRef = React.useRef();
|
|
12061
12062
|
const trackInteractionRef = React.useRef();
|
|
@@ -12253,9 +12254,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12253
12254
|
try {
|
|
12254
12255
|
if (proxyMode) {
|
|
12255
12256
|
// Check if credentials exist before making the API call
|
|
12256
|
-
const headers =
|
|
12257
|
+
const headers = smartlinks.getApiHeaders();
|
|
12257
12258
|
const hasBearer = !!headers['Authorization'];
|
|
12258
|
-
const hasSdkProxy =
|
|
12259
|
+
const hasSdkProxy = smartlinks.isProxyEnabled();
|
|
12259
12260
|
console.log('[AuthContext] 🔍 Proxy mode init:', {
|
|
12260
12261
|
hasBearer,
|
|
12261
12262
|
hasSdkProxy,
|
|
@@ -12417,6 +12418,85 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
12417
12418
|
window.removeEventListener('message', handleParentMessage);
|
|
12418
12419
|
};
|
|
12419
12420
|
}, [proxyMode, notifyAuthStateChange]);
|
|
12421
|
+
// In proxy mode, mobile auth often returns from WhatsApp/Google via app switching,
|
|
12422
|
+
// so the page stays mounted while the parent session changes underneath it.
|
|
12423
|
+
// Re-check account state on focus/visibility/page restore so login/logout appears
|
|
12424
|
+
// immediately instead of waiting for a full refresh.
|
|
12425
|
+
React.useEffect(() => {
|
|
12426
|
+
if (!proxyMode)
|
|
12427
|
+
return;
|
|
12428
|
+
const clearProxyState = () => {
|
|
12429
|
+
setUser(null);
|
|
12430
|
+
setToken(null);
|
|
12431
|
+
setAccountData(null);
|
|
12432
|
+
setAccountInfo(null);
|
|
12433
|
+
setContact(null);
|
|
12434
|
+
setContactId(null);
|
|
12435
|
+
setIsVerified(false);
|
|
12436
|
+
pendingVerificationRef.current = false;
|
|
12437
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
12438
|
+
};
|
|
12439
|
+
const refreshProxySession = async () => {
|
|
12440
|
+
if (proxyRefreshInFlightRef.current)
|
|
12441
|
+
return;
|
|
12442
|
+
const headers = smartlinks.getApiHeaders();
|
|
12443
|
+
const hasBearer = !!headers['Authorization'];
|
|
12444
|
+
const hasSdkProxy = smartlinks.isProxyEnabled();
|
|
12445
|
+
if (!hasBearer && !hasSdkProxy) {
|
|
12446
|
+
clearProxyState();
|
|
12447
|
+
return;
|
|
12448
|
+
}
|
|
12449
|
+
proxyRefreshInFlightRef.current = true;
|
|
12450
|
+
try {
|
|
12451
|
+
const accountResponse = await smartlinks__namespace.auth.getAccount();
|
|
12452
|
+
const accountAny = accountResponse;
|
|
12453
|
+
const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
|
|
12454
|
+
if (!hasValidSession) {
|
|
12455
|
+
clearProxyState();
|
|
12456
|
+
return;
|
|
12457
|
+
}
|
|
12458
|
+
const userObj = {
|
|
12459
|
+
uid: accountAny.uid,
|
|
12460
|
+
email: accountAny?.email,
|
|
12461
|
+
displayName: accountAny?.displayName || accountAny?.name,
|
|
12462
|
+
phoneNumber: accountAny?.phoneNumber,
|
|
12463
|
+
};
|
|
12464
|
+
setUser(userObj);
|
|
12465
|
+
setAccountData(accountResponse);
|
|
12466
|
+
setAccountInfo(accountResponse);
|
|
12467
|
+
setIsVerified(true);
|
|
12468
|
+
pendingVerificationRef.current = false;
|
|
12469
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, accountResponse, accountResponse, true);
|
|
12470
|
+
}
|
|
12471
|
+
catch (error) {
|
|
12472
|
+
if (!isNetworkError(error)) {
|
|
12473
|
+
clearProxyState();
|
|
12474
|
+
}
|
|
12475
|
+
}
|
|
12476
|
+
finally {
|
|
12477
|
+
proxyRefreshInFlightRef.current = false;
|
|
12478
|
+
}
|
|
12479
|
+
};
|
|
12480
|
+
const handleFocus = () => {
|
|
12481
|
+
refreshProxySession().catch(() => { });
|
|
12482
|
+
};
|
|
12483
|
+
const handlePageShow = () => {
|
|
12484
|
+
refreshProxySession().catch(() => { });
|
|
12485
|
+
};
|
|
12486
|
+
const handleVisibilityChange = () => {
|
|
12487
|
+
if (document.visibilityState === 'visible') {
|
|
12488
|
+
refreshProxySession().catch(() => { });
|
|
12489
|
+
}
|
|
12490
|
+
};
|
|
12491
|
+
window.addEventListener('focus', handleFocus);
|
|
12492
|
+
window.addEventListener('pageshow', handlePageShow);
|
|
12493
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
12494
|
+
return () => {
|
|
12495
|
+
window.removeEventListener('focus', handleFocus);
|
|
12496
|
+
window.removeEventListener('pageshow', handlePageShow);
|
|
12497
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
12498
|
+
};
|
|
12499
|
+
}, [proxyMode, notifyAuthStateChange, isNetworkError]);
|
|
12420
12500
|
// Cross-tab synchronization - standalone mode only
|
|
12421
12501
|
React.useEffect(() => {
|
|
12422
12502
|
if (proxyMode)
|
|
@@ -12898,13 +12978,18 @@ const getActionResultErrorMessage = (result) => {
|
|
|
12898
12978
|
details: candidate.details,
|
|
12899
12979
|
});
|
|
12900
12980
|
};
|
|
12981
|
+
// ----- WhatsApp reply helpers -----
|
|
12982
|
+
const WHATSAPP_PENDING_SESSION_KEY = 'whatsapp_pending_session';
|
|
12901
12983
|
const interpolateReply = (input, vars) => {
|
|
12902
12984
|
if (!input)
|
|
12903
12985
|
return input;
|
|
12904
12986
|
return input
|
|
12905
12987
|
.replace(/\{\{\s*name\s*\}\}/gi, vars.name?.trim() || 'there')
|
|
12906
12988
|
.replace(/\{\{\s*clientName\s*\}\}/gi, vars.clientName?.trim() || '')
|
|
12907
|
-
.replace(/\{\{\s*phoneNumber\s*\}\}/gi, vars.phoneNumber?.trim() || '')
|
|
12989
|
+
.replace(/\{\{\s*phoneNumber\s*\}\}/gi, vars.phoneNumber?.trim() || '')
|
|
12990
|
+
.replace(/\{\{\s*returnUrl\s*\}\}/gi, vars.returnUrl?.trim() || '')
|
|
12991
|
+
.replace(/\{\{\s*clientId\s*\}\}/gi, vars.clientId?.trim() || '')
|
|
12992
|
+
.replace(/\{\{\s*token\s*\}\}/gi, vars.token?.trim() || '');
|
|
12908
12993
|
};
|
|
12909
12994
|
const buildWhatsAppReply = (cfg, vars) => {
|
|
12910
12995
|
if (!cfg)
|
|
@@ -13230,6 +13315,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13230
13315
|
// Get the full current URL including hash routes, strip query params
|
|
13231
13316
|
return window.location.href.split('?')[0];
|
|
13232
13317
|
};
|
|
13318
|
+
const savePendingWhatsAppSession = async (session) => {
|
|
13319
|
+
if (proxyMode)
|
|
13320
|
+
return;
|
|
13321
|
+
const storage = await getStorage();
|
|
13322
|
+
await storage.setItem(WHATSAPP_PENDING_SESSION_KEY, session);
|
|
13323
|
+
};
|
|
13324
|
+
const loadPendingWhatsAppSession = async () => {
|
|
13325
|
+
if (proxyMode)
|
|
13326
|
+
return null;
|
|
13327
|
+
const storage = await getStorage();
|
|
13328
|
+
return storage.getItem(WHATSAPP_PENDING_SESSION_KEY);
|
|
13329
|
+
};
|
|
13330
|
+
const clearPendingWhatsAppSession = async () => {
|
|
13331
|
+
if (proxyMode)
|
|
13332
|
+
return;
|
|
13333
|
+
const storage = await getStorage();
|
|
13334
|
+
await storage.removeItem(WHATSAPP_PENDING_SESSION_KEY);
|
|
13335
|
+
};
|
|
13233
13336
|
// Fetch UI configuration
|
|
13234
13337
|
React.useEffect(() => {
|
|
13235
13338
|
// Wait for SDK to be ready before fetching config
|
|
@@ -14319,6 +14422,48 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14319
14422
|
};
|
|
14320
14423
|
// Track the most recent WhatsApp send so we can exchange its sessionKey for a real login session.
|
|
14321
14424
|
const whatsappSendRef = React.useRef(null);
|
|
14425
|
+
React.useEffect(() => {
|
|
14426
|
+
if (proxyMode)
|
|
14427
|
+
return;
|
|
14428
|
+
let cancelled = false;
|
|
14429
|
+
const resumePendingWhatsAppSession = async () => {
|
|
14430
|
+
try {
|
|
14431
|
+
const pending = await loadPendingWhatsAppSession();
|
|
14432
|
+
if (!pending || cancelled)
|
|
14433
|
+
return;
|
|
14434
|
+
// Expire stale pending sessions after 30 minutes to avoid resurrecting old attempts.
|
|
14435
|
+
if (Date.now() - pending.createdAt > 30 * 60 * 1000) {
|
|
14436
|
+
await clearPendingWhatsAppSession();
|
|
14437
|
+
return;
|
|
14438
|
+
}
|
|
14439
|
+
whatsappSendRef.current = {
|
|
14440
|
+
token: pending.token,
|
|
14441
|
+
sessionKey: pending.sessionKey,
|
|
14442
|
+
displayName: pending.displayName,
|
|
14443
|
+
};
|
|
14444
|
+
const status = await api.getWhatsAppStatus(pending.token);
|
|
14445
|
+
if (cancelled)
|
|
14446
|
+
return;
|
|
14447
|
+
if (status.verified) {
|
|
14448
|
+
await handleWhatsAppVerified(status);
|
|
14449
|
+
return;
|
|
14450
|
+
}
|
|
14451
|
+
if (status.status === 'pending') {
|
|
14452
|
+
setMode('whatsapp');
|
|
14453
|
+
}
|
|
14454
|
+
else if (status.status === 'failed' || status.status === 'expired' || status.status === 'unknown') {
|
|
14455
|
+
await clearPendingWhatsAppSession();
|
|
14456
|
+
}
|
|
14457
|
+
}
|
|
14458
|
+
catch (err) {
|
|
14459
|
+
log.warn('Failed to resume pending WhatsApp session:', err);
|
|
14460
|
+
}
|
|
14461
|
+
};
|
|
14462
|
+
resumePendingWhatsAppSession();
|
|
14463
|
+
return () => {
|
|
14464
|
+
cancelled = true;
|
|
14465
|
+
};
|
|
14466
|
+
}, [proxyMode, clientId]);
|
|
14322
14467
|
const handleWhatsAppSend = async (displayName) => {
|
|
14323
14468
|
setLoading(true);
|
|
14324
14469
|
setError(undefined);
|
|
@@ -14327,9 +14472,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14327
14472
|
// account from the inbound WhatsApp webhook (sender's number is the proof).
|
|
14328
14473
|
// Resolve reply config: per-call prop wins, otherwise fall back to AuthKit default.
|
|
14329
14474
|
const replyConfig = whatsappReply ?? config?.whatsappReply;
|
|
14475
|
+
const effectiveRedirectUrl = getRedirectUrl();
|
|
14330
14476
|
const reply = buildWhatsAppReply(replyConfig, {
|
|
14331
14477
|
name: displayName,
|
|
14332
14478
|
clientName,
|
|
14479
|
+
returnUrl: effectiveRedirectUrl,
|
|
14480
|
+
clientId,
|
|
14333
14481
|
});
|
|
14334
14482
|
// Resolve outbound prefill message: per-call prop wins, then AuthKit default.
|
|
14335
14483
|
const prefillMessage = whatsappPrefillMessage ?? config?.whatsappPrefillMessage;
|
|
@@ -14338,12 +14486,19 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14338
14486
|
// formed session.user — no follow-up updateProfile call needed.
|
|
14339
14487
|
const trimmedName = displayName?.trim() || undefined;
|
|
14340
14488
|
const contactData = trimmedName ? { name: trimmedName } : undefined;
|
|
14341
|
-
const result = await api.sendWhatsApp(
|
|
14489
|
+
const result = await api.sendWhatsApp(effectiveRedirectUrl, undefined, reply, prefillMessage, contactData);
|
|
14342
14490
|
whatsappSendRef.current = {
|
|
14343
14491
|
token: result.token,
|
|
14344
14492
|
sessionKey: result.sessionKey,
|
|
14345
14493
|
displayName: trimmedName,
|
|
14346
14494
|
};
|
|
14495
|
+
await savePendingWhatsAppSession({
|
|
14496
|
+
token: result.token,
|
|
14497
|
+
sessionKey: result.sessionKey,
|
|
14498
|
+
displayName: trimmedName,
|
|
14499
|
+
redirectUrl: effectiveRedirectUrl,
|
|
14500
|
+
createdAt: Date.now(),
|
|
14501
|
+
});
|
|
14347
14502
|
return result;
|
|
14348
14503
|
}
|
|
14349
14504
|
catch (err) {
|
|
@@ -14374,12 +14529,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14374
14529
|
if (!proxyMode) {
|
|
14375
14530
|
onAuthSuccess(session.token, session.user, session.accountData);
|
|
14376
14531
|
}
|
|
14532
|
+
await clearPendingWhatsAppSession();
|
|
14533
|
+
return;
|
|
14377
14534
|
}
|
|
14378
14535
|
}
|
|
14379
14536
|
catch (err) {
|
|
14380
14537
|
log.warn('WhatsApp session exchange failed, falling back to redirect-only flow:', err);
|
|
14381
14538
|
}
|
|
14382
14539
|
}
|
|
14540
|
+
await clearPendingWhatsAppSession();
|
|
14383
14541
|
performRedirect(target, 'magic-link');
|
|
14384
14542
|
};
|
|
14385
14543
|
// Show processing state for URL-based auth (verification, magic link, password reset)
|