@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.js
CHANGED
|
@@ -10667,8 +10667,21 @@ class AuthAPI {
|
|
|
10667
10667
|
accountData: data.accountData,
|
|
10668
10668
|
});
|
|
10669
10669
|
}
|
|
10670
|
-
async loginWithGoogle(
|
|
10671
|
-
|
|
10670
|
+
async loginWithGoogle(token, options) {
|
|
10671
|
+
this.log.log('loginWithGoogle called:', {
|
|
10672
|
+
tokenType: options?.tokenType || 'id_token',
|
|
10673
|
+
hasUserInfo: !!options?.googleUserInfo,
|
|
10674
|
+
userEmail: options?.googleUserInfo?.email,
|
|
10675
|
+
tokenLength: token?.length,
|
|
10676
|
+
});
|
|
10677
|
+
// Note: The SDK only supports ID tokens currently
|
|
10678
|
+
// Access tokens from popup flow may fail with "Invalid or expired Google token"
|
|
10679
|
+
if (options?.tokenType === 'access_token') {
|
|
10680
|
+
this.log.warn('Warning: Popup flow sends access_token, but backend expects id_token. This may fail.');
|
|
10681
|
+
this.log.warn('Consider using OneTap flow (default) or updating backend to handle access tokens.');
|
|
10682
|
+
}
|
|
10683
|
+
// Pass token to SDK - backend verifies with Google
|
|
10684
|
+
return smartlinks__namespace.authKit.googleLogin(this.clientId, token);
|
|
10672
10685
|
}
|
|
10673
10686
|
async sendPhoneCode(phoneNumber) {
|
|
10674
10687
|
return smartlinks__namespace.authKit.sendPhoneCode(this.clientId, phoneNumber);
|
|
@@ -11514,6 +11527,33 @@ const loadGoogleIdentityServices = () => {
|
|
|
11514
11527
|
document.head.appendChild(script);
|
|
11515
11528
|
});
|
|
11516
11529
|
};
|
|
11530
|
+
// Helper to convert generic SDK errors to user-friendly messages
|
|
11531
|
+
const getFriendlyErrorMessage = (errorMessage) => {
|
|
11532
|
+
// Check for common HTTP status codes in the error message
|
|
11533
|
+
if (errorMessage.includes('status 400')) {
|
|
11534
|
+
return 'Invalid request. Please check your input and try again.';
|
|
11535
|
+
}
|
|
11536
|
+
if (errorMessage.includes('status 401')) {
|
|
11537
|
+
return 'Invalid credentials. Please check your email and password.';
|
|
11538
|
+
}
|
|
11539
|
+
if (errorMessage.includes('status 403')) {
|
|
11540
|
+
return 'Access denied. You do not have permission to perform this action.';
|
|
11541
|
+
}
|
|
11542
|
+
if (errorMessage.includes('status 404')) {
|
|
11543
|
+
return 'Account not found. Please check your email or create a new account.';
|
|
11544
|
+
}
|
|
11545
|
+
if (errorMessage.includes('status 409')) {
|
|
11546
|
+
return 'This email is already registered.';
|
|
11547
|
+
}
|
|
11548
|
+
if (errorMessage.includes('status 429')) {
|
|
11549
|
+
return 'Too many attempts. Please wait a moment and try again.';
|
|
11550
|
+
}
|
|
11551
|
+
if (errorMessage.includes('status 500') || errorMessage.includes('status 502') || errorMessage.includes('status 503')) {
|
|
11552
|
+
return 'Server error. Please try again later.';
|
|
11553
|
+
}
|
|
11554
|
+
// Return original message if no pattern matches
|
|
11555
|
+
return errorMessage;
|
|
11556
|
+
};
|
|
11517
11557
|
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, }) => {
|
|
11518
11558
|
const [mode, setMode] = React.useState(initialMode);
|
|
11519
11559
|
const [loading, setLoading] = React.useState(false);
|
|
@@ -11530,6 +11570,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11530
11570
|
const [config, setConfig] = React.useState(null);
|
|
11531
11571
|
const [configLoading, setConfigLoading] = React.useState(!skipConfigFetch);
|
|
11532
11572
|
const [showEmailForm, setShowEmailForm] = React.useState(false); // Track if email form should be shown when emailDisplayMode is 'button'
|
|
11573
|
+
const [sdkReady, setSdkReady] = React.useState(false); // Track SDK initialization state
|
|
11533
11574
|
const log = React.useMemo(() => createLoggerWrapper(logger), [logger]);
|
|
11534
11575
|
const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
|
|
11535
11576
|
const auth = useAuth();
|
|
@@ -11550,6 +11591,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11550
11591
|
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11551
11592
|
React.useEffect(() => {
|
|
11552
11593
|
log.log('SDK reinitialize useEffect triggered', { apiEndpoint });
|
|
11594
|
+
setSdkReady(false); // Mark SDK as not ready during reinitialization
|
|
11553
11595
|
const reinitializeWithToken = async () => {
|
|
11554
11596
|
if (apiEndpoint) {
|
|
11555
11597
|
log.log('Reinitializing SDK with baseURL:', apiEndpoint);
|
|
@@ -11561,15 +11603,20 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11561
11603
|
ngrokSkipBrowserWarning: true,
|
|
11562
11604
|
logger: logger, // Pass logger to SDK for verbose SDK logging
|
|
11563
11605
|
});
|
|
11606
|
+
log.log('SDK reinitialized successfully');
|
|
11564
11607
|
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11565
11608
|
if (currentToken) {
|
|
11566
11609
|
smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
|
|
11567
11610
|
log.warn('Failed to restore bearer token after reinit:', err);
|
|
11568
11611
|
});
|
|
11569
11612
|
}
|
|
11613
|
+
// Mark SDK as ready
|
|
11614
|
+
setSdkReady(true);
|
|
11570
11615
|
}
|
|
11571
11616
|
else {
|
|
11572
|
-
log.log('No apiEndpoint,
|
|
11617
|
+
log.log('No apiEndpoint, SDK already initialized by App');
|
|
11618
|
+
// SDK was initialized by App component, mark as ready
|
|
11619
|
+
setSdkReady(true);
|
|
11573
11620
|
}
|
|
11574
11621
|
};
|
|
11575
11622
|
reinitializeWithToken();
|
|
@@ -11590,8 +11637,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11590
11637
|
clientId,
|
|
11591
11638
|
clientIdType: typeof clientId,
|
|
11592
11639
|
clientIdTruthy: !!clientId,
|
|
11593
|
-
apiEndpoint
|
|
11640
|
+
apiEndpoint,
|
|
11641
|
+
sdkReady
|
|
11594
11642
|
});
|
|
11643
|
+
// Wait for SDK to be ready before fetching config
|
|
11644
|
+
if (!sdkReady) {
|
|
11645
|
+
log.log('SDK not ready yet, waiting...');
|
|
11646
|
+
return;
|
|
11647
|
+
}
|
|
11595
11648
|
if (skipConfigFetch) {
|
|
11596
11649
|
log.log('Skipping config fetch - skipConfigFetch is true');
|
|
11597
11650
|
setConfig(customization || {});
|
|
@@ -11652,7 +11705,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11652
11705
|
}
|
|
11653
11706
|
};
|
|
11654
11707
|
fetchConfig();
|
|
11655
|
-
}, [apiEndpoint, clientId, customization, skipConfigFetch, log]);
|
|
11708
|
+
}, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, log]);
|
|
11656
11709
|
// Reset showEmailForm when mode changes away from login/register
|
|
11657
11710
|
React.useEffect(() => {
|
|
11658
11711
|
if (mode !== 'login' && mode !== 'register') {
|
|
@@ -11865,14 +11918,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11865
11918
|
}
|
|
11866
11919
|
catch (err) {
|
|
11867
11920
|
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
|
11868
|
-
// Check if error is about email already registered
|
|
11869
|
-
|
|
11921
|
+
// Check if error is about email already registered (by content or 409 status code)
|
|
11922
|
+
const isAlreadyRegistered = mode === 'register' && ((errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) ||
|
|
11923
|
+
errorMessage.includes('status 409'));
|
|
11924
|
+
if (isAlreadyRegistered) {
|
|
11870
11925
|
setShowResendVerification(true);
|
|
11871
11926
|
setResendEmail(data.email);
|
|
11872
11927
|
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11873
11928
|
}
|
|
11874
11929
|
else {
|
|
11875
|
-
|
|
11930
|
+
// Try to extract a more meaningful error message from status codes
|
|
11931
|
+
const friendlyMessage = getFriendlyErrorMessage(errorMessage);
|
|
11932
|
+
setError(friendlyMessage);
|
|
11876
11933
|
}
|
|
11877
11934
|
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11878
11935
|
}
|
|
@@ -11928,6 +11985,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11928
11985
|
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
11929
11986
|
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
11930
11987
|
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
11988
|
+
// Log Google Auth configuration for debugging
|
|
11989
|
+
log.log('Google Auth initiated:', {
|
|
11990
|
+
googleClientId,
|
|
11991
|
+
oauthFlow,
|
|
11992
|
+
currentOrigin: window.location.origin,
|
|
11993
|
+
currentHref: window.location.href,
|
|
11994
|
+
configGoogleClientId: config?.googleClientId,
|
|
11995
|
+
usingDefaultClientId: !config?.googleClientId,
|
|
11996
|
+
});
|
|
11931
11997
|
setLoading(true);
|
|
11932
11998
|
setError(undefined);
|
|
11933
11999
|
try {
|
|
@@ -11937,35 +12003,87 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11937
12003
|
if (!google?.accounts) {
|
|
11938
12004
|
throw new Error('Google Identity Services failed to initialize');
|
|
11939
12005
|
}
|
|
12006
|
+
log.log('Google Identity Services loaded, using flow:', oauthFlow);
|
|
11940
12007
|
if (oauthFlow === 'popup') {
|
|
11941
12008
|
// Use OAuth2 popup flow (works in iframes but requires popup permission)
|
|
11942
12009
|
if (!google.accounts.oauth2) {
|
|
11943
12010
|
throw new Error('Google OAuth2 not available');
|
|
11944
12011
|
}
|
|
12012
|
+
log.log('Initializing Google OAuth2 popup flow:', {
|
|
12013
|
+
client_id: googleClientId,
|
|
12014
|
+
scope: 'openid email profile',
|
|
12015
|
+
origin: window.location.origin,
|
|
12016
|
+
});
|
|
11945
12017
|
const client = google.accounts.oauth2.initTokenClient({
|
|
11946
12018
|
client_id: googleClientId,
|
|
11947
12019
|
scope: 'openid email profile',
|
|
11948
12020
|
callback: async (response) => {
|
|
11949
12021
|
try {
|
|
12022
|
+
log.log('Google OAuth2 popup callback received:', {
|
|
12023
|
+
hasAccessToken: !!response.access_token,
|
|
12024
|
+
hasIdToken: !!response.id_token,
|
|
12025
|
+
tokenType: response.token_type,
|
|
12026
|
+
expiresIn: response.expires_in,
|
|
12027
|
+
scope: response.scope,
|
|
12028
|
+
error: response.error,
|
|
12029
|
+
errorDescription: response.error_description,
|
|
12030
|
+
});
|
|
11950
12031
|
if (response.error) {
|
|
11951
12032
|
throw new Error(response.error_description || response.error);
|
|
11952
12033
|
}
|
|
12034
|
+
// OAuth2 popup flow returns access_token, not id_token
|
|
12035
|
+
// We need to use the access token to get user info from Google
|
|
11953
12036
|
const accessToken = response.access_token;
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
if (authResponse.token) {
|
|
11957
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11958
|
-
setAuthSuccess(true);
|
|
11959
|
-
setSuccessMessage('Google login successful!');
|
|
11960
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12037
|
+
if (!accessToken) {
|
|
12038
|
+
throw new Error('No access token received from Google');
|
|
11961
12039
|
}
|
|
11962
|
-
|
|
11963
|
-
|
|
12040
|
+
log.log('Fetching user info from Google using access token...');
|
|
12041
|
+
// Fetch user info from Google's userinfo endpoint
|
|
12042
|
+
const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
|
12043
|
+
headers: {
|
|
12044
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
12045
|
+
},
|
|
12046
|
+
});
|
|
12047
|
+
if (!userInfoResponse.ok) {
|
|
12048
|
+
throw new Error('Failed to fetch user info from Google');
|
|
11964
12049
|
}
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
12050
|
+
const userInfo = await userInfoResponse.json();
|
|
12051
|
+
log.log('Google user info retrieved:', {
|
|
12052
|
+
email: userInfo.email,
|
|
12053
|
+
name: userInfo.name,
|
|
12054
|
+
sub: userInfo.sub,
|
|
12055
|
+
});
|
|
12056
|
+
// For popup flow, send the access token to backend
|
|
12057
|
+
// Note: This may fail if backend only supports ID token verification
|
|
12058
|
+
try {
|
|
12059
|
+
const authResponse = await api.loginWithGoogle(accessToken, {
|
|
12060
|
+
googleUserInfo: userInfo,
|
|
12061
|
+
tokenType: 'access_token',
|
|
12062
|
+
});
|
|
12063
|
+
if (authResponse.token) {
|
|
12064
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12065
|
+
setAuthSuccess(true);
|
|
12066
|
+
setSuccessMessage('Google login successful!');
|
|
12067
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12068
|
+
}
|
|
12069
|
+
else {
|
|
12070
|
+
throw new Error('Authentication failed - no token received');
|
|
12071
|
+
}
|
|
12072
|
+
if (redirectUrl) {
|
|
12073
|
+
setTimeout(() => {
|
|
12074
|
+
window.location.href = redirectUrl;
|
|
12075
|
+
}, 2000);
|
|
12076
|
+
}
|
|
12077
|
+
}
|
|
12078
|
+
catch (apiError) {
|
|
12079
|
+
const errorMessage = apiError instanceof Error ? apiError.message : 'Google login failed';
|
|
12080
|
+
// Check if this is the access token vs ID token mismatch
|
|
12081
|
+
if (errorMessage.includes('Invalid or expired Google token')) {
|
|
12082
|
+
log.error('Popup flow access token rejected by backend. Backend may only support ID tokens.');
|
|
12083
|
+
log.error('User info retrieved from Google:', userInfo);
|
|
12084
|
+
throw new Error('Google authentication failed. The popup flow may not be supported. Please try again or contact support.');
|
|
12085
|
+
}
|
|
12086
|
+
throw apiError;
|
|
11969
12087
|
}
|
|
11970
12088
|
setLoading(false);
|
|
11971
12089
|
}
|
|
@@ -11981,6 +12099,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11981
12099
|
}
|
|
11982
12100
|
else {
|
|
11983
12101
|
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
12102
|
+
log.log('Initializing Google OneTap flow:', {
|
|
12103
|
+
client_id: googleClientId,
|
|
12104
|
+
origin: window.location.origin,
|
|
12105
|
+
});
|
|
11984
12106
|
google.accounts.id.initialize({
|
|
11985
12107
|
client_id: googleClientId,
|
|
11986
12108
|
callback: async (response) => {
|