@proveanything/smartlinks-auth-ui 0.1.46 → 0.1.47

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.
@@ -1 +1 @@
1
- {"version":3,"file":"SmartlinksAuthUI.d.ts","sourceRoot":"","sources":["../../src/components/SmartlinksAuthUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAW5D,OAAO,KAAK,EAAE,qBAAqB,EAAyF,MAAM,UAAU,CAAC;AAgI7I,QAAA,MAAM,mBAAmB,QAAa,OAAO,CAAC,IAAI,CA+CjD,CAAC;AAyFF,OAAO,EAAE,mBAAmB,EAAE,CAAC;AA8B/B,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAqpD5D,CAAC"}
1
+ {"version":3,"file":"SmartlinksAuthUI.d.ts","sourceRoot":"","sources":["../../src/components/SmartlinksAuthUI.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAW5D,OAAO,KAAK,EAAE,qBAAqB,EAAyF,MAAM,UAAU,CAAC;AAiJ7I,QAAA,MAAM,mBAAmB,QAAa,OAAO,CAAC,IAAI,CA+CjD,CAAC;AAyFF,OAAO,EAAE,mBAAmB,EAAE,CAAC;AA8B/B,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAstD5D,CAAC"}
package/dist/index.esm.js CHANGED
@@ -12235,8 +12235,22 @@ const useAuth = () => {
12235
12235
  };
12236
12236
 
12237
12237
  // VERSION: Update this when making changes to help identify which version is running
12238
- const AUTH_UI_VERSION = '42';
12238
+ const AUTH_UI_VERSION = '44';
12239
12239
  const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
12240
+ // Helper to check for URL auth params synchronously (runs during initialization)
12241
+ // This prevents the form from flashing before detecting deep-link flows
12242
+ const getInitialUrlAuthParams = () => {
12243
+ // Check hash params first (for hash routing like #/test?mode=verifyEmail&token=abc)
12244
+ const hash = window.location.hash;
12245
+ const hashQueryIndex = hash.indexOf('?');
12246
+ const params = hashQueryIndex !== -1
12247
+ ? new URLSearchParams(hash.substring(hashQueryIndex + 1))
12248
+ : new URLSearchParams(window.location.search);
12249
+ return {
12250
+ mode: params.get('mode'),
12251
+ token: params.get('token'),
12252
+ };
12253
+ };
12240
12254
  // Helper to calculate expiration from AuthResponse
12241
12255
  const getExpirationFromResponse = (response) => {
12242
12256
  if (response.expiresAt)
@@ -12470,7 +12484,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12470
12484
  // Determine initial mode based on signupProminence setting
12471
12485
  // 'emphasized' mode defaults to 'register' unless explicitly overridden
12472
12486
  const effectiveInitialMode = initialMode ?? (resolvedSignupProminence === 'emphasized' ? 'register' : 'login');
12473
- const [mode, setMode] = useState(effectiveInitialMode);
12487
+ // Check URL params synchronously to avoid form flash during deep-link flows
12488
+ const initialUrlParams = useMemo(() => getInitialUrlAuthParams(), []);
12489
+ const isUrlAuthFlow = !!(initialUrlParams.mode && initialUrlParams.token);
12490
+ // Track if we're processing a URL-based auth flow (verification, magic link, etc.)
12491
+ const [urlAuthProcessing, setUrlAuthProcessing] = useState(isUrlAuthFlow);
12492
+ const [mode, setMode] = useState(() => {
12493
+ // If URL has reset password token, start in reset-password mode
12494
+ if (initialUrlParams.mode === 'resetPassword' && initialUrlParams.token) {
12495
+ return 'reset-password';
12496
+ }
12497
+ return effectiveInitialMode;
12498
+ });
12474
12499
  const [loading, setLoading] = useState(false);
12475
12500
  const [error, setError] = useState();
12476
12501
  const [resolvedTheme, setResolvedTheme] = useState('light');
@@ -12492,17 +12517,34 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12492
12517
  const log = useMemo(() => createLoggerWrapper(logger), [logger]);
12493
12518
  const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
12494
12519
  const auth = useAuth();
12520
+ // Helper function to strip auth params (mode, token) from URLs
12521
+ // These params are used for deep-link flows and must be removed after processing
12522
+ const stripAuthParams = (url) => {
12523
+ try {
12524
+ const urlObj = new URL(url);
12525
+ urlObj.searchParams.delete('mode');
12526
+ urlObj.searchParams.delete('token');
12527
+ return urlObj.toString();
12528
+ }
12529
+ catch {
12530
+ // If URL parsing fails, try simple string manipulation
12531
+ return url.split('?')[0];
12532
+ }
12533
+ };
12495
12534
  // Helper function to perform redirects, respecting the onRedirect callback
12496
12535
  // When onRedirect is provided, delegates navigation to parent app
12497
12536
  // Otherwise falls back to direct window.location.href for backward compatibility
12537
+ // IMPORTANT: Always strips auth params (mode, token) to prevent re-verification on reload
12498
12538
  const performRedirect = (url, reason) => {
12539
+ const cleanUrl = stripAuthParams(url);
12540
+ log.log(`Redirect requested: ${url} -> cleaned: ${cleanUrl} (${reason})`);
12499
12541
  if (onRedirect) {
12500
- log.log(`Delegating redirect to onRedirect callback: ${url} (${reason})`);
12501
- onRedirect(url, reason);
12542
+ log.log(`Delegating redirect to onRedirect callback: ${cleanUrl} (${reason})`);
12543
+ onRedirect(cleanUrl, reason);
12502
12544
  }
12503
12545
  else {
12504
- log.log(`Performing direct redirect to: ${url} (${reason})`);
12505
- window.location.href = url;
12546
+ log.log(`Performing direct redirect to: ${cleanUrl} (${reason})`);
12547
+ window.location.href = cleanUrl;
12506
12548
  }
12507
12549
  };
12508
12550
  // Dark mode detection and theme management
@@ -12803,6 +12845,25 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12803
12845
  const authCode = params.get('code');
12804
12846
  const state = params.get('state');
12805
12847
  log.log('URL params detected:', { urlMode, token, authCode: !!authCode, state: !!state, hash: window.location.hash, search: window.location.search });
12848
+ // Check if user is already authenticated
12849
+ const isAlreadyAuthenticated = !!auth.user?.uid;
12850
+ if (isAlreadyAuthenticated && (urlMode === 'verifyEmail' || urlMode === 'magicLink')) {
12851
+ // User is already logged in but we have verification params in URL
12852
+ // This happens when parent reloads iframe after successful verification
12853
+ // Don't re-verify - just redirect to clean URL
12854
+ log.log('Already authenticated, skipping re-verification. Redirecting to clean URL.');
12855
+ setUrlAuthProcessing(false); // Clear processing state
12856
+ if (redirectUrl) {
12857
+ performRedirect(redirectUrl, urlMode === 'verifyEmail' ? 'email-verified' : 'magic-link');
12858
+ }
12859
+ else {
12860
+ // No redirect configured - just show success and notify parent
12861
+ setAuthSuccess(true);
12862
+ setSuccessMessage('You are already logged in!');
12863
+ onAuthSuccess(auth.token, auth.user, auth.accountData ?? undefined);
12864
+ }
12865
+ return;
12866
+ }
12806
12867
  if (authCode && state) {
12807
12868
  // Google OAuth redirect callback
12808
12869
  handleGoogleAuthCodeCallback(authCode, state);
@@ -12814,6 +12875,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12814
12875
  const handleURLBasedAuth = async (urlMode, token) => {
12815
12876
  setLoading(true);
12816
12877
  setError(undefined);
12878
+ // Note: urlAuthProcessing is already true from initialization if we got here
12817
12879
  try {
12818
12880
  if (urlMode === 'verifyEmail') {
12819
12881
  log.log('Verifying email with token:', token);
@@ -12826,9 +12888,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12826
12888
  // MUST await to ensure session is ready before any redirect
12827
12889
  await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response), proxyMode // waitForParentAck - ensures parent has validated/persisted before redirect
12828
12890
  );
12829
- // Clean URL parameters regardless of redirect
12830
- const cleanUrl = window.location.href.split('?')[0];
12831
- window.history.replaceState({}, document.title, cleanUrl);
12891
+ setUrlAuthProcessing(false); // Clear processing state before redirect/success
12832
12892
  if (redirectUrl) {
12833
12893
  // Redirect to clean URL and resume flow
12834
12894
  // In proxy mode: parent already validated & persisted via acknowledgment in auth.login()
@@ -12849,6 +12909,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12849
12909
  }
12850
12910
  else {
12851
12911
  // verify-manual-login mode or no token: Show success but require manual login
12912
+ setUrlAuthProcessing(false); // Clear processing state
12852
12913
  setAuthSuccess(true);
12853
12914
  setSuccessMessage('Email verified successfully! Please log in with your credentials.');
12854
12915
  // Clear the URL parameters
@@ -12869,6 +12930,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12869
12930
  if (!verifyResult.valid) {
12870
12931
  throw new Error(verifyResult.message || 'Invalid or expired token');
12871
12932
  }
12933
+ setUrlAuthProcessing(false); // Clear processing state - showing reset form
12872
12934
  setResetToken(token); // Store token for use in password reset
12873
12935
  if (verifyResult.email) {
12874
12936
  setResetEmail(verifyResult.email); // Store email for auto-login after reset
@@ -12887,9 +12949,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12887
12949
  // MUST await to ensure session is ready before any redirect
12888
12950
  await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response), proxyMode // waitForParentAck
12889
12951
  );
12890
- // Clean URL parameters regardless of redirect
12891
- const cleanUrl = window.location.href.split('?')[0];
12892
- window.history.replaceState({}, document.title, cleanUrl);
12952
+ setUrlAuthProcessing(false); // Clear processing state before redirect/success
12893
12953
  if (redirectUrl) {
12894
12954
  // Redirect to clean URL and resume flow
12895
12955
  log.log('Magic link verification complete, redirecting to:', redirectUrl);
@@ -12912,6 +12972,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12912
12972
  catch (err) {
12913
12973
  log.error('URL-based auth error:', err);
12914
12974
  const errorMessage = err instanceof Error ? err.message : 'An error occurred';
12975
+ setUrlAuthProcessing(false); // Clear processing state on error so user can retry
12915
12976
  // If it's an email verification error (expired/invalid token), show resend option
12916
12977
  if (urlMode === 'verifyEmail') {
12917
12978
  setError(`${errorMessage} - Please enter your email below to receive a new verification link.`);
@@ -13495,23 +13556,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13495
13556
  }
13496
13557
  // Update auth context with account data if token is provided
13497
13558
  if (response.token) {
13498
- // Phone auth can be login or signup - use isNewUser flag from backend if available
13499
- // In proxy mode, wait for parent to acknowledge (validate & persist) before redirect
13500
- await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response), proxyMode // waitForParentAck
13559
+ // Phone auth is an INTERACTIVE flow (user entered OTP in UI)
13560
+ // Unlike deep-link flows (email verification, magic link), there's no URL token to clean up
13561
+ // Do NOT auto-redirect - let the parent app control the next step (profile completion, etc.)
13562
+ await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response), false // No waitForParentAck needed - interactive flow doesn't redirect
13501
13563
  );
13502
- if (redirectUrl) {
13503
- // Redirect to clean URL and resume flow
13504
- log.log('Phone verification complete, redirecting to:', redirectUrl);
13505
- performRedirect(redirectUrl, 'phone-verified');
13506
- }
13507
- else {
13508
- // No redirect configured: show success UI
13509
- setAuthSuccess(true);
13510
- setSuccessMessage('Phone verified! You are now logged in.');
13511
- if (!proxyMode) {
13512
- onAuthSuccess(response.token, response.user, response.accountData);
13513
- }
13514
- }
13564
+ setAuthSuccess(true);
13565
+ setSuccessMessage('Phone verified! You are now logged in.');
13566
+ onAuthSuccess(response.token, response.user, response.accountData);
13515
13567
  }
13516
13568
  else {
13517
13569
  // Check if response contains an error message from the backend
@@ -13609,6 +13661,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13609
13661
  setLoading(false);
13610
13662
  }
13611
13663
  };
13664
+ // Show processing state for URL-based auth (verification, magic link, password reset)
13665
+ // This runs BEFORE configLoading check to prevent form flash on deep-link flows
13666
+ if (urlAuthProcessing) {
13667
+ return (jsx(AuthContainer, { theme: resolvedTheme, className: className, minimal: minimal || config?.branding?.minimal || false, children: jsxs("div", { style: { textAlign: 'center', padding: '2rem' }, children: [jsx("div", { className: "auth-spinner", style: { marginBottom: '1rem' } }), jsx("p", { style: {
13668
+ color: resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280',
13669
+ fontSize: '0.875rem'
13670
+ }, children: initialUrlParams.mode === 'verifyEmail' ? 'Verifying your email...' :
13671
+ initialUrlParams.mode === 'magicLink' ? 'Processing magic link...' :
13672
+ initialUrlParams.mode === 'resetPassword' ? 'Validating reset link...' :
13673
+ 'Processing...' })] }) }));
13674
+ }
13612
13675
  if (configLoading) {
13613
13676
  return (jsx(AuthContainer, { theme: resolvedTheme, className: className, minimal: minimal || config?.branding?.minimal || false, children: jsx("div", { style: { textAlign: 'center', padding: '2rem' }, children: jsx("div", { className: "auth-spinner" }) }) }));
13614
13677
  }