@proveanything/smartlinks-auth-ui 0.1.10 → 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/index.js CHANGED
@@ -10667,8 +10667,21 @@ class AuthAPI {
10667
10667
  accountData: data.accountData,
10668
10668
  });
10669
10669
  }
10670
- async loginWithGoogle(idToken) {
10671
- return smartlinks__namespace.authKit.googleLogin(this.clientId, idToken);
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);
@@ -11878,14 +11918,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11878
11918
  }
11879
11919
  catch (err) {
11880
11920
  const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
11881
- // Check if error is about email already registered
11882
- if (mode === 'register' && errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) {
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) {
11883
11925
  setShowResendVerification(true);
11884
11926
  setResendEmail(data.email);
11885
11927
  setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
11886
11928
  }
11887
11929
  else {
11888
- setError(errorMessage);
11930
+ // Try to extract a more meaningful error message from status codes
11931
+ const friendlyMessage = getFriendlyErrorMessage(errorMessage);
11932
+ setError(friendlyMessage);
11889
11933
  }
11890
11934
  onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
11891
11935
  }
@@ -11941,6 +11985,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11941
11985
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
11942
11986
  // Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
11943
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
+ });
11944
11997
  setLoading(true);
11945
11998
  setError(undefined);
11946
11999
  try {
@@ -11950,35 +12003,87 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11950
12003
  if (!google?.accounts) {
11951
12004
  throw new Error('Google Identity Services failed to initialize');
11952
12005
  }
12006
+ log.log('Google Identity Services loaded, using flow:', oauthFlow);
11953
12007
  if (oauthFlow === 'popup') {
11954
12008
  // Use OAuth2 popup flow (works in iframes but requires popup permission)
11955
12009
  if (!google.accounts.oauth2) {
11956
12010
  throw new Error('Google OAuth2 not available');
11957
12011
  }
12012
+ log.log('Initializing Google OAuth2 popup flow:', {
12013
+ client_id: googleClientId,
12014
+ scope: 'openid email profile',
12015
+ origin: window.location.origin,
12016
+ });
11958
12017
  const client = google.accounts.oauth2.initTokenClient({
11959
12018
  client_id: googleClientId,
11960
12019
  scope: 'openid email profile',
11961
12020
  callback: async (response) => {
11962
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
+ });
11963
12031
  if (response.error) {
11964
12032
  throw new Error(response.error_description || response.error);
11965
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
11966
12036
  const accessToken = response.access_token;
11967
- // Send access token to backend
11968
- const authResponse = await api.loginWithGoogle(accessToken);
11969
- if (authResponse.token) {
11970
- auth.login(authResponse.token, authResponse.user, authResponse.accountData);
11971
- setAuthSuccess(true);
11972
- setSuccessMessage('Google login successful!');
11973
- onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
12037
+ if (!accessToken) {
12038
+ throw new Error('No access token received from Google');
11974
12039
  }
11975
- else {
11976
- throw new Error('Authentication failed - no token received');
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');
11977
12049
  }
11978
- if (redirectUrl) {
11979
- setTimeout(() => {
11980
- window.location.href = redirectUrl;
11981
- }, 2000);
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;
11982
12087
  }
11983
12088
  setLoading(false);
11984
12089
  }
@@ -11994,6 +12099,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11994
12099
  }
11995
12100
  else {
11996
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
+ });
11997
12106
  google.accounts.id.initialize({
11998
12107
  client_id: googleClientId,
11999
12108
  callback: async (response) => {