@proveanything/smartlinks-auth-ui 0.1.9 → 0.1.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/api.d.ts +9 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +143 -21
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +143 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -10646,8 +10646,21 @@ class AuthAPI {
|
|
|
10646
10646
|
accountData: data.accountData,
|
|
10647
10647
|
});
|
|
10648
10648
|
}
|
|
10649
|
-
async loginWithGoogle(
|
|
10650
|
-
|
|
10649
|
+
async loginWithGoogle(token, options) {
|
|
10650
|
+
this.log.log('loginWithGoogle called:', {
|
|
10651
|
+
tokenType: options?.tokenType || 'id_token',
|
|
10652
|
+
hasUserInfo: !!options?.googleUserInfo,
|
|
10653
|
+
userEmail: options?.googleUserInfo?.email,
|
|
10654
|
+
tokenLength: token?.length,
|
|
10655
|
+
});
|
|
10656
|
+
// Note: The SDK only supports ID tokens currently
|
|
10657
|
+
// Access tokens from popup flow may fail with "Invalid or expired Google token"
|
|
10658
|
+
if (options?.tokenType === 'access_token') {
|
|
10659
|
+
this.log.warn('Warning: Popup flow sends access_token, but backend expects id_token. This may fail.');
|
|
10660
|
+
this.log.warn('Consider using OneTap flow (default) or updating backend to handle access tokens.');
|
|
10661
|
+
}
|
|
10662
|
+
// Pass token to SDK - backend verifies with Google
|
|
10663
|
+
return smartlinks.authKit.googleLogin(this.clientId, token);
|
|
10651
10664
|
}
|
|
10652
10665
|
async sendPhoneCode(phoneNumber) {
|
|
10653
10666
|
return smartlinks.authKit.sendPhoneCode(this.clientId, phoneNumber);
|
|
@@ -11493,6 +11506,33 @@ const loadGoogleIdentityServices = () => {
|
|
|
11493
11506
|
document.head.appendChild(script);
|
|
11494
11507
|
});
|
|
11495
11508
|
};
|
|
11509
|
+
// Helper to convert generic SDK errors to user-friendly messages
|
|
11510
|
+
const getFriendlyErrorMessage = (errorMessage) => {
|
|
11511
|
+
// Check for common HTTP status codes in the error message
|
|
11512
|
+
if (errorMessage.includes('status 400')) {
|
|
11513
|
+
return 'Invalid request. Please check your input and try again.';
|
|
11514
|
+
}
|
|
11515
|
+
if (errorMessage.includes('status 401')) {
|
|
11516
|
+
return 'Invalid credentials. Please check your email and password.';
|
|
11517
|
+
}
|
|
11518
|
+
if (errorMessage.includes('status 403')) {
|
|
11519
|
+
return 'Access denied. You do not have permission to perform this action.';
|
|
11520
|
+
}
|
|
11521
|
+
if (errorMessage.includes('status 404')) {
|
|
11522
|
+
return 'Account not found. Please check your email or create a new account.';
|
|
11523
|
+
}
|
|
11524
|
+
if (errorMessage.includes('status 409')) {
|
|
11525
|
+
return 'This email is already registered.';
|
|
11526
|
+
}
|
|
11527
|
+
if (errorMessage.includes('status 429')) {
|
|
11528
|
+
return 'Too many attempts. Please wait a moment and try again.';
|
|
11529
|
+
}
|
|
11530
|
+
if (errorMessage.includes('status 500') || errorMessage.includes('status 502') || errorMessage.includes('status 503')) {
|
|
11531
|
+
return 'Server error. Please try again later.';
|
|
11532
|
+
}
|
|
11533
|
+
// Return original message if no pattern matches
|
|
11534
|
+
return errorMessage;
|
|
11535
|
+
};
|
|
11496
11536
|
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, }) => {
|
|
11497
11537
|
const [mode, setMode] = useState(initialMode);
|
|
11498
11538
|
const [loading, setLoading] = useState(false);
|
|
@@ -11509,6 +11549,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11509
11549
|
const [config, setConfig] = useState(null);
|
|
11510
11550
|
const [configLoading, setConfigLoading] = useState(!skipConfigFetch);
|
|
11511
11551
|
const [showEmailForm, setShowEmailForm] = useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
|
|
11552
|
+
const [sdkReady, setSdkReady] = useState(false); // Track SDK initialization state
|
|
11512
11553
|
const log = useMemo(() => createLoggerWrapper(logger), [logger]);
|
|
11513
11554
|
const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
|
|
11514
11555
|
const auth = useAuth();
|
|
@@ -11529,6 +11570,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11529
11570
|
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11530
11571
|
useEffect(() => {
|
|
11531
11572
|
log.log('SDK reinitialize useEffect triggered', { apiEndpoint });
|
|
11573
|
+
setSdkReady(false); // Mark SDK as not ready during reinitialization
|
|
11532
11574
|
const reinitializeWithToken = async () => {
|
|
11533
11575
|
if (apiEndpoint) {
|
|
11534
11576
|
log.log('Reinitializing SDK with baseURL:', apiEndpoint);
|
|
@@ -11540,15 +11582,20 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11540
11582
|
ngrokSkipBrowserWarning: true,
|
|
11541
11583
|
logger: logger, // Pass logger to SDK for verbose SDK logging
|
|
11542
11584
|
});
|
|
11585
|
+
log.log('SDK reinitialized successfully');
|
|
11543
11586
|
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11544
11587
|
if (currentToken) {
|
|
11545
11588
|
smartlinks.auth.verifyToken(currentToken).catch(err => {
|
|
11546
11589
|
log.warn('Failed to restore bearer token after reinit:', err);
|
|
11547
11590
|
});
|
|
11548
11591
|
}
|
|
11592
|
+
// Mark SDK as ready
|
|
11593
|
+
setSdkReady(true);
|
|
11549
11594
|
}
|
|
11550
11595
|
else {
|
|
11551
|
-
log.log('No apiEndpoint,
|
|
11596
|
+
log.log('No apiEndpoint, SDK already initialized by App');
|
|
11597
|
+
// SDK was initialized by App component, mark as ready
|
|
11598
|
+
setSdkReady(true);
|
|
11552
11599
|
}
|
|
11553
11600
|
};
|
|
11554
11601
|
reinitializeWithToken();
|
|
@@ -11569,8 +11616,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11569
11616
|
clientId,
|
|
11570
11617
|
clientIdType: typeof clientId,
|
|
11571
11618
|
clientIdTruthy: !!clientId,
|
|
11572
|
-
apiEndpoint
|
|
11619
|
+
apiEndpoint,
|
|
11620
|
+
sdkReady
|
|
11573
11621
|
});
|
|
11622
|
+
// Wait for SDK to be ready before fetching config
|
|
11623
|
+
if (!sdkReady) {
|
|
11624
|
+
log.log('SDK not ready yet, waiting...');
|
|
11625
|
+
return;
|
|
11626
|
+
}
|
|
11574
11627
|
if (skipConfigFetch) {
|
|
11575
11628
|
log.log('Skipping config fetch - skipConfigFetch is true');
|
|
11576
11629
|
setConfig(customization || {});
|
|
@@ -11631,7 +11684,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11631
11684
|
}
|
|
11632
11685
|
};
|
|
11633
11686
|
fetchConfig();
|
|
11634
|
-
}, [apiEndpoint, clientId, customization, skipConfigFetch, log]);
|
|
11687
|
+
}, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, log]);
|
|
11635
11688
|
// Reset showEmailForm when mode changes away from login/register
|
|
11636
11689
|
useEffect(() => {
|
|
11637
11690
|
if (mode !== 'login' && mode !== 'register') {
|
|
@@ -11844,14 +11897,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11844
11897
|
}
|
|
11845
11898
|
catch (err) {
|
|
11846
11899
|
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
|
11847
|
-
// Check if error is about email already registered
|
|
11848
|
-
|
|
11900
|
+
// Check if error is about email already registered (by content or 409 status code)
|
|
11901
|
+
const isAlreadyRegistered = mode === 'register' && ((errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) ||
|
|
11902
|
+
errorMessage.includes('status 409'));
|
|
11903
|
+
if (isAlreadyRegistered) {
|
|
11849
11904
|
setShowResendVerification(true);
|
|
11850
11905
|
setResendEmail(data.email);
|
|
11851
11906
|
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11852
11907
|
}
|
|
11853
11908
|
else {
|
|
11854
|
-
|
|
11909
|
+
// Try to extract a more meaningful error message from status codes
|
|
11910
|
+
const friendlyMessage = getFriendlyErrorMessage(errorMessage);
|
|
11911
|
+
setError(friendlyMessage);
|
|
11855
11912
|
}
|
|
11856
11913
|
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11857
11914
|
}
|
|
@@ -11907,6 +11964,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11907
11964
|
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
11908
11965
|
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
11909
11966
|
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
11967
|
+
// Log Google Auth configuration for debugging
|
|
11968
|
+
log.log('Google Auth initiated:', {
|
|
11969
|
+
googleClientId,
|
|
11970
|
+
oauthFlow,
|
|
11971
|
+
currentOrigin: window.location.origin,
|
|
11972
|
+
currentHref: window.location.href,
|
|
11973
|
+
configGoogleClientId: config?.googleClientId,
|
|
11974
|
+
usingDefaultClientId: !config?.googleClientId,
|
|
11975
|
+
});
|
|
11910
11976
|
setLoading(true);
|
|
11911
11977
|
setError(undefined);
|
|
11912
11978
|
try {
|
|
@@ -11916,35 +11982,87 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11916
11982
|
if (!google?.accounts) {
|
|
11917
11983
|
throw new Error('Google Identity Services failed to initialize');
|
|
11918
11984
|
}
|
|
11985
|
+
log.log('Google Identity Services loaded, using flow:', oauthFlow);
|
|
11919
11986
|
if (oauthFlow === 'popup') {
|
|
11920
11987
|
// Use OAuth2 popup flow (works in iframes but requires popup permission)
|
|
11921
11988
|
if (!google.accounts.oauth2) {
|
|
11922
11989
|
throw new Error('Google OAuth2 not available');
|
|
11923
11990
|
}
|
|
11991
|
+
log.log('Initializing Google OAuth2 popup flow:', {
|
|
11992
|
+
client_id: googleClientId,
|
|
11993
|
+
scope: 'openid email profile',
|
|
11994
|
+
origin: window.location.origin,
|
|
11995
|
+
});
|
|
11924
11996
|
const client = google.accounts.oauth2.initTokenClient({
|
|
11925
11997
|
client_id: googleClientId,
|
|
11926
11998
|
scope: 'openid email profile',
|
|
11927
11999
|
callback: async (response) => {
|
|
11928
12000
|
try {
|
|
12001
|
+
log.log('Google OAuth2 popup callback received:', {
|
|
12002
|
+
hasAccessToken: !!response.access_token,
|
|
12003
|
+
hasIdToken: !!response.id_token,
|
|
12004
|
+
tokenType: response.token_type,
|
|
12005
|
+
expiresIn: response.expires_in,
|
|
12006
|
+
scope: response.scope,
|
|
12007
|
+
error: response.error,
|
|
12008
|
+
errorDescription: response.error_description,
|
|
12009
|
+
});
|
|
11929
12010
|
if (response.error) {
|
|
11930
12011
|
throw new Error(response.error_description || response.error);
|
|
11931
12012
|
}
|
|
12013
|
+
// OAuth2 popup flow returns access_token, not id_token
|
|
12014
|
+
// We need to use the access token to get user info from Google
|
|
11932
12015
|
const accessToken = response.access_token;
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
if (authResponse.token) {
|
|
11936
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11937
|
-
setAuthSuccess(true);
|
|
11938
|
-
setSuccessMessage('Google login successful!');
|
|
11939
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12016
|
+
if (!accessToken) {
|
|
12017
|
+
throw new Error('No access token received from Google');
|
|
11940
12018
|
}
|
|
11941
|
-
|
|
11942
|
-
|
|
12019
|
+
log.log('Fetching user info from Google using access token...');
|
|
12020
|
+
// Fetch user info from Google's userinfo endpoint
|
|
12021
|
+
const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
|
12022
|
+
headers: {
|
|
12023
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
12024
|
+
},
|
|
12025
|
+
});
|
|
12026
|
+
if (!userInfoResponse.ok) {
|
|
12027
|
+
throw new Error('Failed to fetch user info from Google');
|
|
11943
12028
|
}
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
12029
|
+
const userInfo = await userInfoResponse.json();
|
|
12030
|
+
log.log('Google user info retrieved:', {
|
|
12031
|
+
email: userInfo.email,
|
|
12032
|
+
name: userInfo.name,
|
|
12033
|
+
sub: userInfo.sub,
|
|
12034
|
+
});
|
|
12035
|
+
// For popup flow, send the access token to backend
|
|
12036
|
+
// Note: This may fail if backend only supports ID token verification
|
|
12037
|
+
try {
|
|
12038
|
+
const authResponse = await api.loginWithGoogle(accessToken, {
|
|
12039
|
+
googleUserInfo: userInfo,
|
|
12040
|
+
tokenType: 'access_token',
|
|
12041
|
+
});
|
|
12042
|
+
if (authResponse.token) {
|
|
12043
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12044
|
+
setAuthSuccess(true);
|
|
12045
|
+
setSuccessMessage('Google login successful!');
|
|
12046
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12047
|
+
}
|
|
12048
|
+
else {
|
|
12049
|
+
throw new Error('Authentication failed - no token received');
|
|
12050
|
+
}
|
|
12051
|
+
if (redirectUrl) {
|
|
12052
|
+
setTimeout(() => {
|
|
12053
|
+
window.location.href = redirectUrl;
|
|
12054
|
+
}, 2000);
|
|
12055
|
+
}
|
|
12056
|
+
}
|
|
12057
|
+
catch (apiError) {
|
|
12058
|
+
const errorMessage = apiError instanceof Error ? apiError.message : 'Google login failed';
|
|
12059
|
+
// Check if this is the access token vs ID token mismatch
|
|
12060
|
+
if (errorMessage.includes('Invalid or expired Google token')) {
|
|
12061
|
+
log.error('Popup flow access token rejected by backend. Backend may only support ID tokens.');
|
|
12062
|
+
log.error('User info retrieved from Google:', userInfo);
|
|
12063
|
+
throw new Error('Google authentication failed. The popup flow may not be supported. Please try again or contact support.');
|
|
12064
|
+
}
|
|
12065
|
+
throw apiError;
|
|
11948
12066
|
}
|
|
11949
12067
|
setLoading(false);
|
|
11950
12068
|
}
|
|
@@ -11960,6 +12078,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11960
12078
|
}
|
|
11961
12079
|
else {
|
|
11962
12080
|
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
12081
|
+
log.log('Initializing Google OneTap flow:', {
|
|
12082
|
+
client_id: googleClientId,
|
|
12083
|
+
origin: window.location.origin,
|
|
12084
|
+
});
|
|
11963
12085
|
google.accounts.id.initialize({
|
|
11964
12086
|
client_id: googleClientId,
|
|
11965
12087
|
callback: async (response) => {
|