@proveanything/smartlinks-auth-ui 0.1.28 → 0.1.29

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
@@ -3,6 +3,7 @@
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');
6
7
 
7
8
  function _interopNamespaceDefault(e) {
8
9
  var n = Object.create(null);
@@ -10809,6 +10810,18 @@ class AuthAPI {
10809
10810
  // Pass token to SDK - backend verifies with Google
10810
10811
  return smartlinks__namespace.authKit.googleLogin(this.clientId, token);
10811
10812
  }
10813
+ async loginWithGoogleCode(code, redirectUri) {
10814
+ this.log.log('loginWithGoogleCode called:', {
10815
+ codeLength: code?.length,
10816
+ redirectUri,
10817
+ });
10818
+ // Exchange authorization code for tokens via backend
10819
+ // Use direct HTTP call since SDK may not have this method in authKit namespace yet
10820
+ return http.post(`/api/v1/authkit/${this.clientId}/google-code`, {
10821
+ code,
10822
+ redirectUri,
10823
+ });
10824
+ }
10812
10825
  async sendPhoneCode(phoneNumber) {
10813
10826
  return smartlinks__namespace.authKit.sendPhoneCode(this.clientId, phoneNumber);
10814
10827
  }
@@ -12196,6 +12209,28 @@ const loadGoogleIdentityServices = () => {
12196
12209
  document.head.appendChild(script);
12197
12210
  });
12198
12211
  };
12212
+ // Helper to detect WebView environments (Android/iOS)
12213
+ const detectWebView = () => {
12214
+ const ua = navigator.userAgent;
12215
+ // Android WebView detection
12216
+ if (/Android/i.test(ua)) {
12217
+ // Modern Android WebViews include "wv" in UA string
12218
+ if (/\bwv\b/i.test(ua))
12219
+ return true;
12220
+ // Check for legacy Android bridge
12221
+ if (typeof window.Android !== 'undefined')
12222
+ return true;
12223
+ }
12224
+ // iOS WKWebView detection
12225
+ if (/iPhone|iPad|iPod/i.test(ua)) {
12226
+ const hasWebKitHandlers = !!window.webkit?.messageHandlers;
12227
+ const isSafari = !!window.safari;
12228
+ // WKWebView has webkit handlers but no safari object
12229
+ if (hasWebKitHandlers && !isSafari)
12230
+ return true;
12231
+ }
12232
+ return false;
12233
+ };
12199
12234
  // Helper to convert generic SDK errors to user-friendly messages
12200
12235
  const getFriendlyErrorMessage = (errorMessage) => {
12201
12236
  // Check for common HTTP status codes in the error message
@@ -12457,8 +12492,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12457
12492
  const params = getUrlParams();
12458
12493
  const urlMode = params.get('mode');
12459
12494
  const token = params.get('token');
12460
- log.log('URL params detected:', { urlMode, token, hash: window.location.hash, search: window.location.search });
12461
- if (urlMode && token) {
12495
+ // Check for Google OAuth redirect callback
12496
+ const authCode = params.get('code');
12497
+ const state = params.get('state');
12498
+ log.log('URL params detected:', { urlMode, token, authCode: !!authCode, state: !!state, hash: window.location.hash, search: window.location.search });
12499
+ if (authCode && state) {
12500
+ // Google OAuth redirect callback
12501
+ handleGoogleAuthCodeCallback(authCode, state);
12502
+ }
12503
+ else if (urlMode && token) {
12462
12504
  handleURLBasedAuth(urlMode, token);
12463
12505
  }
12464
12506
  }, []);
@@ -12571,6 +12613,43 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12571
12613
  setLoading(false);
12572
12614
  }
12573
12615
  };
12616
+ // Handle Google OAuth authorization code callback (from redirect flow)
12617
+ const handleGoogleAuthCodeCallback = async (code, stateParam) => {
12618
+ setLoading(true);
12619
+ setError(undefined);
12620
+ try {
12621
+ // Parse state to get context
12622
+ const state = JSON.parse(decodeURIComponent(stateParam));
12623
+ log.log('Google OAuth redirect callback:', { clientId: state.clientId, returnPath: state.returnPath });
12624
+ // Determine the redirect URI that was used (must match exactly)
12625
+ const redirectUri = state.redirectUri || window.location.origin + window.location.pathname;
12626
+ // Exchange authorization code for tokens
12627
+ const response = await api.loginWithGoogleCode(code, redirectUri);
12628
+ if (response.token) {
12629
+ auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
12630
+ setAuthSuccess(true);
12631
+ setSuccessMessage('Google login successful!');
12632
+ onAuthSuccess(response.token, response.user, response.accountData);
12633
+ }
12634
+ else {
12635
+ throw new Error('Authentication failed - no token received');
12636
+ }
12637
+ // Clean URL parameters
12638
+ const cleanUrl = window.location.origin + window.location.pathname + (state.returnPath?.includes('#') ? state.returnPath.split('?')[0] : window.location.hash.split('?')[0]);
12639
+ window.history.replaceState({}, document.title, cleanUrl);
12640
+ }
12641
+ catch (err) {
12642
+ const errorMessage = err instanceof Error ? err.message : 'Google login failed';
12643
+ setError(getFriendlyErrorMessage(errorMessage));
12644
+ onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
12645
+ // Clean URL parameters even on error
12646
+ const cleanUrl = window.location.origin + window.location.pathname + window.location.hash.split('?')[0];
12647
+ window.history.replaceState({}, document.title, cleanUrl);
12648
+ }
12649
+ finally {
12650
+ setLoading(false);
12651
+ }
12652
+ };
12574
12653
  const handleEmailAuth = async (data) => {
12575
12654
  setLoading(true);
12576
12655
  setError(undefined);
@@ -12723,11 +12802,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12723
12802
  // Use custom client ID from config, or fall back to default Smartlinks client ID
12724
12803
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
12725
12804
  // Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
12726
- const oauthFlow = config?.googleOAuthFlow || 'oneTap';
12805
+ const configuredFlow = config?.googleOAuthFlow || 'oneTap';
12806
+ // For oneTap, automatically use redirect flow in WebView environments
12807
+ const isWebView = detectWebView();
12808
+ const oauthFlow = (configuredFlow === 'oneTap' && isWebView) ? 'redirect' : configuredFlow;
12727
12809
  // Log Google Auth configuration for debugging
12728
12810
  log.log('Google Auth initiated:', {
12729
12811
  googleClientId,
12730
- oauthFlow,
12812
+ configuredFlow,
12813
+ effectiveFlow: oauthFlow,
12814
+ isWebView,
12731
12815
  currentOrigin: window.location.origin,
12732
12816
  currentHref: window.location.href,
12733
12817
  configGoogleClientId: config?.googleClientId,
@@ -12743,7 +12827,37 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12743
12827
  throw new Error('Google Identity Services failed to initialize');
12744
12828
  }
12745
12829
  log.log('Google Identity Services loaded, using flow:', oauthFlow);
12746
- if (oauthFlow === 'popup') {
12830
+ if (oauthFlow === 'redirect') {
12831
+ // Use OAuth2 redirect flow (works in WebViews and everywhere)
12832
+ if (!google.accounts.oauth2) {
12833
+ throw new Error('Google OAuth2 not available');
12834
+ }
12835
+ // Build the redirect URI - use current URL without query params
12836
+ const redirectUri = getRedirectUrl();
12837
+ // Build state parameter to preserve context across redirect
12838
+ const state = encodeURIComponent(JSON.stringify({
12839
+ clientId,
12840
+ returnPath: window.location.hash || window.location.pathname,
12841
+ redirectUri,
12842
+ }));
12843
+ log.log('Initializing Google OAuth2 redirect flow:', {
12844
+ client_id: googleClientId,
12845
+ scope: 'openid email profile',
12846
+ redirect_uri: redirectUri,
12847
+ state,
12848
+ });
12849
+ const client = google.accounts.oauth2.initCodeClient({
12850
+ client_id: googleClientId,
12851
+ scope: 'openid email profile',
12852
+ ux_mode: 'redirect',
12853
+ redirect_uri: redirectUri,
12854
+ state,
12855
+ });
12856
+ // This will navigate away from the page
12857
+ client.requestCode();
12858
+ return; // Don't set loading to false - we're navigating away
12859
+ }
12860
+ else if (oauthFlow === 'popup') {
12747
12861
  // Use OAuth2 popup flow (works in iframes but requires popup permission)
12748
12862
  if (!google.accounts.oauth2) {
12749
12863
  throw new Error('Google OAuth2 not available');
@@ -12834,7 +12948,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12834
12948
  client.requestAccessToken();
12835
12949
  }
12836
12950
  else {
12837
- // Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
12951
+ // Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes or WebViews)
12838
12952
  log.log('Initializing Google OneTap flow:', {
12839
12953
  client_id: googleClientId,
12840
12954
  origin: window.location.origin,
@@ -12867,8 +12981,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12867
12981
  },
12868
12982
  auto_select: false,
12869
12983
  cancel_on_tap_outside: true,
12870
- // Note: use_fedcm_for_prompt omitted - requires Permissions-Policy header on hosting server
12871
- // Will be needed when FedCM becomes mandatory in the future
12984
+ use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
12872
12985
  });
12873
12986
  // Use timeout fallback instead of deprecated notification methods
12874
12987
  // (isNotDisplayed/isSkippedMoment will stop working when FedCM becomes mandatory)