@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.esm.js CHANGED
@@ -10646,8 +10646,21 @@ class AuthAPI {
10646
10646
  accountData: data.accountData,
10647
10647
  });
10648
10648
  }
10649
- async loginWithGoogle(idToken) {
10650
- return smartlinks.authKit.googleLogin(this.clientId, idToken);
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);
@@ -11857,14 +11897,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11857
11897
  }
11858
11898
  catch (err) {
11859
11899
  const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
11860
- // Check if error is about email already registered
11861
- if (mode === 'register' && errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) {
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) {
11862
11904
  setShowResendVerification(true);
11863
11905
  setResendEmail(data.email);
11864
11906
  setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
11865
11907
  }
11866
11908
  else {
11867
- setError(errorMessage);
11909
+ // Try to extract a more meaningful error message from status codes
11910
+ const friendlyMessage = getFriendlyErrorMessage(errorMessage);
11911
+ setError(friendlyMessage);
11868
11912
  }
11869
11913
  onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
11870
11914
  }
@@ -11920,6 +11964,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11920
11964
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
11921
11965
  // Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
11922
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
+ });
11923
11976
  setLoading(true);
11924
11977
  setError(undefined);
11925
11978
  try {
@@ -11929,35 +11982,87 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11929
11982
  if (!google?.accounts) {
11930
11983
  throw new Error('Google Identity Services failed to initialize');
11931
11984
  }
11985
+ log.log('Google Identity Services loaded, using flow:', oauthFlow);
11932
11986
  if (oauthFlow === 'popup') {
11933
11987
  // Use OAuth2 popup flow (works in iframes but requires popup permission)
11934
11988
  if (!google.accounts.oauth2) {
11935
11989
  throw new Error('Google OAuth2 not available');
11936
11990
  }
11991
+ log.log('Initializing Google OAuth2 popup flow:', {
11992
+ client_id: googleClientId,
11993
+ scope: 'openid email profile',
11994
+ origin: window.location.origin,
11995
+ });
11937
11996
  const client = google.accounts.oauth2.initTokenClient({
11938
11997
  client_id: googleClientId,
11939
11998
  scope: 'openid email profile',
11940
11999
  callback: async (response) => {
11941
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
+ });
11942
12010
  if (response.error) {
11943
12011
  throw new Error(response.error_description || response.error);
11944
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
11945
12015
  const accessToken = response.access_token;
11946
- // Send access token to backend
11947
- const authResponse = await api.loginWithGoogle(accessToken);
11948
- if (authResponse.token) {
11949
- auth.login(authResponse.token, authResponse.user, authResponse.accountData);
11950
- setAuthSuccess(true);
11951
- setSuccessMessage('Google login successful!');
11952
- onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
12016
+ if (!accessToken) {
12017
+ throw new Error('No access token received from Google');
11953
12018
  }
11954
- else {
11955
- throw new Error('Authentication failed - no token received');
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');
11956
12028
  }
11957
- if (redirectUrl) {
11958
- setTimeout(() => {
11959
- window.location.href = redirectUrl;
11960
- }, 2000);
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;
11961
12066
  }
11962
12067
  setLoading(false);
11963
12068
  }
@@ -11973,6 +12078,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11973
12078
  }
11974
12079
  else {
11975
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
+ });
11976
12085
  google.accounts.id.initialize({
11977
12086
  client_id: googleClientId,
11978
12087
  callback: async (response) => {