@proveanything/smartlinks-auth-ui 0.3.9 → 0.3.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
@@ -10893,18 +10893,14 @@ class AuthAPI {
10893
10893
  });
10894
10894
  }
10895
10895
  async fetchConfig() {
10896
- console.log('[AuthAPI] 📋 fetchConfig called with clientId:', this.clientId);
10897
10896
  this.log.log('fetchConfig called with clientId:', this.clientId);
10898
10897
  try {
10899
- console.log('[AuthAPI] 🌐 Calling smartlinks.authKit.load() - this uses current SDK config (including proxyMode)');
10900
10898
  this.log.log('Calling smartlinks.authKit.load...');
10901
10899
  const result = await smartlinks.authKit.load(this.clientId);
10902
- console.log('[AuthAPI] ✅ smartlinks.authKit.load returned:', result);
10903
10900
  this.log.log('smartlinks.authKit.load returned:', result);
10904
10901
  return result;
10905
10902
  }
10906
10903
  catch (error) {
10907
- console.log('[AuthAPI] ❌ Failed to fetch UI config:', error);
10908
10904
  this.log.warn('Failed to fetch UI config, using defaults:', error);
10909
10905
  return {
10910
10906
  branding: {
@@ -11503,12 +11499,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11503
11499
  }, []);
11504
11500
  // Sync contact to Smartlinks (non-blocking)
11505
11501
  const syncContact = useCallback(async (authUser, customFields) => {
11506
- if (!collectionId || !shouldSyncContacts) {
11507
- console.log('[AuthContext] Contact sync skipped: no collectionId or disabled');
11502
+ if (!collectionId || !shouldSyncContacts)
11508
11503
  return null;
11509
- }
11510
11504
  try {
11511
- console.log('[AuthContext] Syncing contact for user:', authUser.uid);
11512
11505
  const result = await smartlinks.contact.publicUpsert(collectionId, {
11513
11506
  userId: authUser.uid,
11514
11507
  email: authUser.email,
@@ -11517,13 +11510,10 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11517
11510
  customFields: customFields || {},
11518
11511
  source: 'authkit',
11519
11512
  });
11520
- console.log('[AuthContext] Contact synced:', result.contactId);
11521
- // Store contact ID locally
11522
11513
  if (!proxyMode) {
11523
11514
  await tokenStorage.saveContactId(result.contactId);
11524
11515
  }
11525
11516
  setContactId(result.contactId);
11526
- // Fetch full contact to get customFields using public endpoint
11527
11517
  try {
11528
11518
  const myContactResponse = await smartlinks.contact.publicGetMine(collectionId);
11529
11519
  if (myContactResponse?.contact) {
@@ -11532,44 +11522,64 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11532
11522
  }
11533
11523
  }
11534
11524
  catch (lookupErr) {
11535
- console.warn('[AuthContext] Failed to lookup full contact:', lookupErr);
11525
+ // Non-fatal
11536
11526
  }
11537
11527
  return result.contactId;
11538
11528
  }
11539
11529
  catch (err) {
11540
- console.warn('[AuthContext] Contact sync failed (non-blocking):', err);
11541
11530
  return null;
11542
11531
  }
11543
11532
  }, [collectionId, shouldSyncContacts, proxyMode, token, accountData, accountInfo, isVerified, notifyAuthStateChange]);
11533
+ // Detect legacy vs new interactionConfig shape
11534
+ const isLegacyConfig = interactionConfig && !('interactionId' in interactionConfig) && ('login' in interactionConfig || 'logout' in interactionConfig || 'signup' in interactionConfig);
11535
+ if (isLegacyConfig) {
11536
+ console.warn('[AuthContext] Deprecated: interactionConfig uses legacy per-event-type IDs. Migrate to { interactionId, scopes? } shape. Legacy support will be removed in a future release.');
11537
+ }
11544
11538
  // Track interaction event (non-blocking)
11545
- // IMPORTANT: Only tracks if an explicit interaction ID is configured for the event type.
11546
- // Interaction IDs must be pre-created in SmartLinks - you cannot use arbitrary IDs.
11539
+ // Uses scope-based architecture: single interactionId + scope field to differentiate events.
11547
11540
  const trackInteraction = useCallback(async (eventType, userId, currentContactId, metadata) => {
11548
- if (!collectionId || !shouldTrackInteractions) {
11549
- console.log('[AuthContext] Interaction tracking skipped: no collectionId or disabled');
11541
+ if (!collectionId || !shouldTrackInteractions)
11550
11542
  return;
11543
+ let interactionId;
11544
+ let scope;
11545
+ if (isLegacyConfig) {
11546
+ // Legacy: per-event-type IDs (deprecated)
11547
+ const legacyConfig = interactionConfig;
11548
+ const legacyMap = {
11549
+ login: legacyConfig?.login,
11550
+ logout: legacyConfig?.logout,
11551
+ signup: legacyConfig?.signup,
11552
+ session_restore: legacyConfig?.sessionRestore,
11553
+ };
11554
+ interactionId = legacyMap[eventType];
11555
+ if (!interactionId)
11556
+ return;
11551
11557
  }
11552
- // Only track if explicit interaction ID is configured - no defaults
11553
- const interactionIdMap = {
11554
- login: interactionConfig?.login,
11555
- logout: interactionConfig?.logout,
11556
- signup: interactionConfig?.signup,
11557
- session_restore: interactionConfig?.sessionRestore,
11558
- };
11559
- const interactionId = interactionIdMap[eventType];
11560
- if (!interactionId) {
11561
- console.log(`[AuthContext] No interaction ID configured for '${eventType}', skipping (this is normal if not configured)`);
11558
+ else if (interactionConfig && 'interactionId' in interactionConfig) {
11559
+ // New: single ID + scope
11560
+ interactionId = interactionConfig.interactionId;
11561
+ if (!interactionId)
11562
+ return;
11563
+ const defaultScopes = {
11564
+ login: 'login',
11565
+ logout: 'logout',
11566
+ signup: 'signup',
11567
+ session_restore: 'session_restore',
11568
+ };
11569
+ const customScopes = interactionConfig.scopes || {};
11570
+ scope = customScopes[eventType === 'session_restore' ? 'sessionRestore' : eventType] || defaultScopes[eventType];
11571
+ }
11572
+ else {
11562
11573
  return;
11563
11574
  }
11564
11575
  try {
11565
- console.log(`[AuthContext] Tracking interaction: ${interactionId}`);
11566
11576
  await smartlinks.interactions.submitPublicEvent(collectionId, {
11567
11577
  collectionId,
11568
11578
  interactionId,
11569
11579
  userId,
11570
11580
  contactId: currentContactId || undefined,
11571
11581
  appId: interactionAppId,
11572
- eventType,
11582
+ scope,
11573
11583
  outcome: 'completed',
11574
11584
  metadata: {
11575
11585
  ...metadata,
@@ -11577,13 +11587,12 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11577
11587
  source: 'authkit',
11578
11588
  },
11579
11589
  });
11580
- console.log(`[AuthContext] Tracked interaction: ${interactionId}`);
11581
11590
  notifyAuthStateChange('INTERACTION_TRACKED', user, token, accountData, accountInfo, isVerified, contact, contactId);
11582
11591
  }
11583
11592
  catch (err) {
11584
- console.warn('[AuthContext] Interaction tracking failed (non-blocking):', err);
11593
+ // Non-blocking
11585
11594
  }
11586
- }, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
11595
+ }, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, isLegacyConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
11587
11596
  // Keep refs in sync with latest callbacks (avoids stale closures)
11588
11597
  useEffect(() => {
11589
11598
  syncContactRef.current = syncContact;
@@ -11593,20 +11602,13 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11593
11602
  }, [trackInteraction]);
11594
11603
  // Get contact (with optional refresh)
11595
11604
  const getContact = useCallback(async (forceRefresh = false) => {
11596
- if (!collectionId) {
11597
- console.log('[AuthContext] getContact: no collectionId');
11605
+ if (!collectionId)
11598
11606
  return null;
11599
- }
11600
- // Need either email or userId to lookup contact
11601
- if (!user?.email && !user?.uid) {
11602
- console.log('[AuthContext] getContact: no user email or uid');
11607
+ if (!user?.email && !user?.uid)
11603
11608
  return null;
11604
- }
11605
- if (contact && !forceRefresh) {
11609
+ if (contact && !forceRefresh)
11606
11610
  return contact;
11607
- }
11608
11611
  try {
11609
- console.log('[AuthContext] Fetching contact via publicGetMine');
11610
11612
  const result = await smartlinks.contact.publicGetMine(collectionId);
11611
11613
  if (result?.contact) {
11612
11614
  const contactData = result.contact;
@@ -11617,7 +11619,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11617
11619
  return null;
11618
11620
  }
11619
11621
  catch (err) {
11620
- console.warn('[AuthContext] Failed to get contact:', err);
11621
11622
  return null;
11622
11623
  }
11623
11624
  }, [collectionId, user, contact]);
@@ -11626,24 +11627,19 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11626
11627
  if (!collectionId || !contactId) {
11627
11628
  throw new Error('No contact to update. Ensure collectionId is provided and user is synced.');
11628
11629
  }
11629
- console.log('[AuthContext] Updating contact custom fields:', contactId);
11630
11630
  const updated = await smartlinks.contact.update(collectionId, contactId, { customFields });
11631
11631
  setContact(updated);
11632
11632
  return updated;
11633
11633
  }, [collectionId, contactId]);
11634
11634
  // Initialize auth state
11635
11635
  useEffect(() => {
11636
- if (initializingRef.current) {
11637
- console.log('[AuthContext] Skipping initialization - already in progress');
11636
+ if (initializingRef.current)
11638
11637
  return;
11639
- }
11640
11638
  let isMounted = true;
11641
11639
  initializingRef.current = true;
11642
11640
  const initializeAuth = async () => {
11643
11641
  try {
11644
11642
  if (proxyMode) {
11645
- // PROXY MODE: Check for existing session via parent's auth.getAccount()
11646
- console.log('[AuthContext] Proxy mode: checking for existing session via auth.getAccount()');
11647
11643
  try {
11648
11644
  const accountResponse = await smartlinks.auth.getAccount();
11649
11645
  const accountAny = accountResponse;
@@ -11659,17 +11655,16 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11659
11655
  setAccountData(accountResponse);
11660
11656
  setAccountInfo(accountResponse);
11661
11657
  setIsVerified(true);
11662
- console.log('[AuthContext] Proxy mode: initialized from parent account, uid:', accountAny.uid);
11663
11658
  notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
11664
11659
  // Sync contact in background (proxy mode) - use ref for stable dependency
11665
11660
  syncContactRef.current?.(userFromAccount, accountResponse);
11666
11661
  }
11667
11662
  else if (isMounted) {
11668
- console.log('[AuthContext] Proxy mode: no valid session (no uid), awaiting login');
11663
+ // No valid session, awaiting login
11669
11664
  }
11670
11665
  }
11671
11666
  catch (error) {
11672
- console.log('[AuthContext] Proxy mode: auth.getAccount() failed, awaiting login:', error);
11667
+ // auth.getAccount() failed, awaiting login
11673
11668
  }
11674
11669
  if (isMounted) {
11675
11670
  setIsLoading(false);
@@ -11693,31 +11688,25 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11693
11688
  // Set loading to false IMMEDIATELY after optimistic restore
11694
11689
  // Don't wait for background verification
11695
11690
  setIsLoading(false);
11696
- console.log('[AuthContext] Session restored optimistically (pending verification)');
11697
11691
  notifyAuthStateChange('SESSION_RESTORED_OFFLINE', storedUser, storedToken.token, storedAccountData || null, null, false, null, storedContactId);
11698
11692
  }
11699
- // BACKGROUND: Verify token (non-blocking)
11700
11693
  try {
11701
- console.log('[AuthContext] Verifying stored token in background...');
11702
11694
  await smartlinks.auth.verifyToken(storedToken.token);
11703
11695
  if (isMounted) {
11704
11696
  setIsVerified(true);
11705
11697
  pendingVerificationRef.current = false;
11706
- console.log('[AuthContext] Session verified successfully');
11707
11698
  notifyAuthStateChange('SESSION_VERIFIED', storedUser, storedToken.token, storedAccountData || null, null, true, null, storedContactId);
11708
11699
  // Track session restore interaction (optional) - use ref for stable dependency
11709
- if (interactionConfig?.sessionRestore) {
11700
+ if (interactionConfig && ('interactionId' in interactionConfig || interactionConfig?.sessionRestore)) {
11710
11701
  trackInteractionRef.current?.('session_restore', storedUser.uid, storedContactId);
11711
11702
  }
11712
11703
  }
11713
11704
  }
11714
11705
  catch (err) {
11715
11706
  if (isNetworkError(err)) {
11716
- console.warn('[AuthContext] Network error during verification, will retry on reconnect:', err);
11717
11707
  pendingVerificationRef.current = true;
11718
11708
  }
11719
11709
  else {
11720
- console.warn('[AuthContext] Token verification failed (auth error), clearing credentials:', err);
11721
11710
  if (isMounted) {
11722
11711
  setToken(null);
11723
11712
  setUser(null);
@@ -11754,16 +11743,14 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11754
11743
  return () => {
11755
11744
  isMounted = false;
11756
11745
  };
11757
- }, [proxyMode, notifyAuthStateChange, isNetworkError, interactionConfig?.sessionRestore]);
11746
+ }, [proxyMode, notifyAuthStateChange, isNetworkError, interactionConfig]);
11758
11747
  // Listen for parent auth state changes (proxy mode only)
11759
11748
  useEffect(() => {
11760
11749
  if (!proxyMode)
11761
11750
  return;
11762
- console.log('[AuthContext] Proxy mode: setting up parent message listener');
11763
11751
  const handleParentMessage = (event) => {
11764
11752
  if (event.data?.type === 'smartlinks:authkit:state') {
11765
11753
  const { user: parentUser, accountData: parentAccountData, authenticated } = event.data.payload || {};
11766
- console.log('[AuthContext] Proxy mode: received state from parent:', { authenticated });
11767
11754
  if (authenticated && parentUser) {
11768
11755
  const userObj = {
11769
11756
  uid: parentUser.uid || parentUser.id,
@@ -11776,7 +11763,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11776
11763
  setAccountInfo(parentAccountData || null);
11777
11764
  setIsVerified(true);
11778
11765
  notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null, true);
11779
- // Sync contact on cross-tab state - use ref for stable dependency
11780
11766
  syncContactRef.current?.(userObj, parentAccountData);
11781
11767
  }
11782
11768
  else {
@@ -11793,7 +11779,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11793
11779
  };
11794
11780
  window.addEventListener('message', handleParentMessage);
11795
11781
  return () => {
11796
- console.log('[AuthContext] Proxy mode: cleaning up parent message listener');
11797
11782
  window.removeEventListener('message', handleParentMessage);
11798
11783
  };
11799
11784
  }, [proxyMode, notifyAuthStateChange]);
@@ -11801,12 +11786,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11801
11786
  useEffect(() => {
11802
11787
  if (proxyMode)
11803
11788
  return;
11804
- console.log('[AuthContext] Setting up cross-tab synchronization');
11805
11789
  const unsubscribe = onStorageChange(async (event) => {
11806
- console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
11807
11790
  try {
11808
11791
  if (event.type === 'clear') {
11809
- console.log('[AuthContext] Detected logout in another tab');
11810
11792
  setToken(null);
11811
11793
  setUser(null);
11812
11794
  setAccountData(null);
@@ -11818,7 +11800,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11818
11800
  notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
11819
11801
  }
11820
11802
  else if (event.type === 'remove' && (event.key === 'token' || event.key === 'user')) {
11821
- console.log('[AuthContext] Detected token/user removal in another tab');
11822
11803
  setToken(null);
11823
11804
  setUser(null);
11824
11805
  setAccountData(null);
@@ -11830,7 +11811,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11830
11811
  notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
11831
11812
  }
11832
11813
  else if (event.type === 'set' && event.key === 'token') {
11833
- console.log('[AuthContext] Detected login in another tab');
11834
11814
  const storedToken = await tokenStorage.getToken();
11835
11815
  const storedUser = await tokenStorage.getUser();
11836
11816
  const storedAccountData = await tokenStorage.getAccountData();
@@ -11843,9 +11823,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11843
11823
  setContactId(storedContactId);
11844
11824
  }
11845
11825
  setIsVerified(true);
11846
- smartlinks.auth.verifyToken(storedToken.token).catch(err => {
11847
- console.warn('[AuthContext] Failed to restore bearer token from cross-tab sync:', err);
11848
- });
11826
+ smartlinks.auth.verifyToken(storedToken.token).catch(() => { });
11849
11827
  notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData, null, true, null, storedContactId);
11850
11828
  }
11851
11829
  }
@@ -11853,7 +11831,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11853
11831
  const cached = await tokenStorage.getAccountInfo();
11854
11832
  if (cached && !cached.isStale) {
11855
11833
  setAccountInfo(cached.data);
11856
- console.log('[AuthContext] Account info synced from another tab');
11857
11834
  }
11858
11835
  }
11859
11836
  }
@@ -11862,7 +11839,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11862
11839
  }
11863
11840
  });
11864
11841
  return () => {
11865
- console.log('[AuthContext] Cleaning up cross-tab synchronization');
11866
11842
  unsubscribe();
11867
11843
  };
11868
11844
  }, [proxyMode, notifyAuthStateChange]);
@@ -11877,22 +11853,14 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11877
11853
  event.data?.messageId === messageId) {
11878
11854
  window.removeEventListener('message', handleAck);
11879
11855
  clearTimeout(timeoutHandle);
11880
- if (event.data.success) {
11881
- console.log('[AuthContext] Parent acknowledged login successfully');
11882
- }
11883
- else {
11884
- console.warn('[AuthContext] Parent rejected login:', event.data.error);
11885
- }
11886
11856
  resolve();
11887
11857
  }
11888
11858
  };
11889
11859
  const timeoutHandle = setTimeout(() => {
11890
11860
  window.removeEventListener('message', handleAck);
11891
- console.warn('[AuthContext] Parent login acknowledgment timed out, proceeding anyway');
11892
- resolve(); // Don't block forever - fallback to current behavior
11861
+ resolve();
11893
11862
  }, timeoutMs);
11894
11863
  window.addEventListener('message', handleAck);
11895
- console.log('[AuthContext] Sending login to parent, awaiting acknowledgment:', messageId);
11896
11864
  iframe.sendParentCustom('smartlinks:authkit:login', {
11897
11865
  messageId,
11898
11866
  token: authToken,
@@ -11912,9 +11880,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11912
11880
  if (authAccountData) {
11913
11881
  await tokenStorage.saveAccountData(authAccountData);
11914
11882
  }
11915
- smartlinks.auth.verifyToken(authToken).catch(err => {
11916
- console.warn('Failed to set bearer token on login:', err);
11917
- });
11883
+ smartlinks.auth.verifyToken(authToken).catch(() => { });
11918
11884
  }
11919
11885
  // Always update memory state (but NOT isVerified yet - wait for parent ack in iframe mode)
11920
11886
  setToken(authToken);
@@ -11926,7 +11892,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11926
11892
  // This ensures the parent has validated the token and the server recognizes
11927
11893
  // the session before we attempt any authenticated API calls (like syncContact)
11928
11894
  if (iframe.isIframe()) {
11929
- console.log('[AuthContext] Notifying parent of login and waiting for acknowledgment');
11930
11895
  await notifyParentLoginAndWait(authToken, authUser, authAccountData);
11931
11896
  }
11932
11897
  // NOW set isVerified - after parent has acknowledged and session is ready
@@ -11940,9 +11905,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11940
11905
  });
11941
11906
  // Optionally preload account info on login (standalone mode only)
11942
11907
  if (!proxyMode && preloadAccountInfo) {
11943
- getAccount(true).catch(error => {
11944
- console.warn('[AuthContext] Failed to preload account info:', error);
11945
- });
11908
+ getAccount(true).catch(() => { });
11946
11909
  }
11947
11910
  }
11948
11911
  catch (error) {
@@ -11954,18 +11917,12 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11954
11917
  const currentUser = user;
11955
11918
  const currentContactId = contactId;
11956
11919
  try {
11957
- // Clear Google Sign-In session on native side (fire-and-forget with timeout)
11958
- // This ensures the next login shows the account picker instead of auto-signing in
11959
- console.log('[AuthContext] Checking for native Google sign-out...');
11960
11920
  try {
11961
- // Dynamic import to avoid circular dependency
11962
11921
  const { signOutGoogleNative } = await Promise.resolve().then(function () { return SmartlinksAuthUI$1; });
11963
11922
  await signOutGoogleNative();
11964
- console.log('[AuthContext] Native Google sign-out completed');
11965
11923
  }
11966
11924
  catch (err) {
11967
- // signOutGoogleNative is fire-and-forget, errors are expected if no native bridge
11968
- console.log('[AuthContext] Native Google sign-out skipped or failed (non-blocking):', err);
11925
+ // signOutGoogleNative is fire-and-forget
11969
11926
  }
11970
11927
  // Only clear persistent storage in standalone mode
11971
11928
  if (!proxyMode) {
@@ -11983,7 +11940,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
11983
11940
  pendingVerificationRef.current = false;
11984
11941
  // Cross-iframe auth state synchronization
11985
11942
  if (iframe.isIframe()) {
11986
- console.log('[AuthContext] Notifying parent of logout via postMessage');
11987
11943
  iframe.sendParentCustom('smartlinks:authkit:logout', {});
11988
11944
  }
11989
11945
  notifyAuthStateChange('LOGOUT', null, null, null, null, false);
@@ -12025,7 +11981,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12025
11981
  // Refresh token - validates current token and extends session if backend supports it
12026
11982
  const refreshToken = useCallback(async () => {
12027
11983
  if (proxyMode) {
12028
- console.log('[AuthContext] Proxy mode: token refresh handled by parent');
12029
11984
  throw new Error('Token refresh in proxy mode is handled by the parent application');
12030
11985
  }
12031
11986
  const storedToken = await tokenStorage.getToken();
@@ -12033,20 +11988,13 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12033
11988
  throw new Error('No token to refresh. Please login first.');
12034
11989
  }
12035
11990
  try {
12036
- console.log('[AuthContext] Refreshing token...');
12037
- // Verify current token is still valid
12038
11991
  const verifyResult = await smartlinks.auth.verifyToken(storedToken.token);
12039
11992
  if (!verifyResult.valid) {
12040
- console.warn('[AuthContext] Token is no longer valid, clearing session');
12041
11993
  await logout();
12042
11994
  throw new Error('Token expired or invalid. Please login again.');
12043
11995
  }
12044
- // Token is valid - extend its expiration locally
12045
- // Backend JWT remains valid, we just update our local tracking
12046
- const newExpiresAt = Date.now() + (7 * 24 * 60 * 60 * 1000); // 7 days from now
11996
+ const newExpiresAt = Date.now() + (7 * 24 * 60 * 60 * 1000);
12047
11997
  await tokenStorage.saveToken(storedToken.token, newExpiresAt);
12048
- console.log('[AuthContext] Token verified and expiration extended to:', new Date(newExpiresAt).toISOString());
12049
- // Update verified state
12050
11998
  setIsVerified(true);
12051
11999
  pendingVerificationRef.current = false;
12052
12000
  notifyAuthStateChange('TOKEN_REFRESH', user, storedToken.token, accountData, accountInfo, true, contact, contactId);
@@ -12054,12 +12002,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12054
12002
  }
12055
12003
  catch (error) {
12056
12004
  console.error('[AuthContext] Token refresh failed:', error);
12057
- // If it's a network error, don't logout
12058
12005
  if (isNetworkError(error)) {
12059
- console.warn('[AuthContext] Network error during refresh, keeping session');
12060
12006
  throw error;
12061
12007
  }
12062
- // Auth error - clear session
12063
12008
  await logout();
12064
12009
  throw new Error('Token refresh failed. Please login again.');
12065
12010
  }
@@ -12067,7 +12012,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12067
12012
  const getAccount = useCallback(async (forceRefresh = false) => {
12068
12013
  try {
12069
12014
  if (proxyMode) {
12070
- console.log('[AuthContext] Proxy mode: fetching account from parent');
12071
12015
  const freshAccountInfo = await smartlinks.auth.getAccount();
12072
12016
  setAccountInfo(freshAccountInfo);
12073
12017
  setAccountData(freshAccountInfo);
@@ -12080,11 +12024,9 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12080
12024
  if (!forceRefresh) {
12081
12025
  const cached = await tokenStorage.getAccountInfo();
12082
12026
  if (cached && !cached.isStale) {
12083
- console.log('[AuthContext] Returning cached account info');
12084
12027
  return cached.data;
12085
12028
  }
12086
12029
  }
12087
- console.log('[AuthContext] Fetching fresh account info from API');
12088
12030
  const freshAccountInfo = await smartlinks.auth.getAccount();
12089
12031
  await tokenStorage.saveAccountInfo(freshAccountInfo, accountCacheTTL);
12090
12032
  setAccountInfo(freshAccountInfo);
@@ -12096,7 +12038,6 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12096
12038
  if (!proxyMode) {
12097
12039
  const cached = await tokenStorage.getAccountInfo();
12098
12040
  if (cached) {
12099
- console.warn('[AuthContext] Returning stale cached data due to API error');
12100
12041
  return cached.data;
12101
12042
  }
12102
12043
  }
@@ -12119,30 +12060,22 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12119
12060
  };
12120
12061
  }, []);
12121
12062
  const retryVerification = useCallback(async () => {
12122
- if (!token || !user) {
12123
- console.log('[AuthContext] No session to verify');
12063
+ if (!token || !user)
12124
12064
  return false;
12125
- }
12126
- if (isVerified) {
12127
- console.log('[AuthContext] Session already verified');
12065
+ if (isVerified)
12128
12066
  return true;
12129
- }
12130
12067
  try {
12131
- console.log('[AuthContext] Retrying session verification...');
12132
12068
  await smartlinks.auth.verifyToken(token);
12133
12069
  setIsVerified(true);
12134
12070
  pendingVerificationRef.current = false;
12135
- console.log('[AuthContext] Session verified on retry');
12136
12071
  notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
12137
12072
  return true;
12138
12073
  }
12139
12074
  catch (err) {
12140
12075
  if (isNetworkError(err)) {
12141
- console.warn('[AuthContext] Network still unavailable, will retry later');
12142
12076
  return false;
12143
12077
  }
12144
12078
  else {
12145
- console.warn('[AuthContext] Session invalid on retry, logging out');
12146
12079
  await logout();
12147
12080
  return false;
12148
12081
  }
@@ -12153,15 +12086,12 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12153
12086
  if (proxyMode)
12154
12087
  return;
12155
12088
  const handleOnline = () => {
12156
- console.log('[AuthContext] Network reconnected');
12157
12089
  setIsOnline(true);
12158
12090
  if (pendingVerificationRef.current && token && user) {
12159
- console.log('[AuthContext] Retrying pending verification after reconnect...');
12160
12091
  retryVerification();
12161
12092
  }
12162
12093
  };
12163
12094
  const handleOffline = () => {
12164
- console.log('[AuthContext] Network disconnected');
12165
12095
  setIsOnline(false);
12166
12096
  };
12167
12097
  window.addEventListener('online', handleOnline);
@@ -12176,46 +12106,31 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12176
12106
  if (proxyMode || !enableAutoRefresh || !token || !user) {
12177
12107
  return;
12178
12108
  }
12179
- console.log('[AuthContext] Setting up automatic token refresh (interval:', refreshCheckInterval, 'ms, threshold:', refreshThresholdPercent, '%)');
12180
12109
  const checkAndRefresh = async () => {
12181
12110
  try {
12182
12111
  const storedToken = await tokenStorage.getToken();
12183
- if (!storedToken?.expiresAt) {
12184
- console.log('[AuthContext] No token expiration info, skipping refresh check');
12112
+ if (!storedToken?.expiresAt)
12185
12113
  return;
12186
- }
12187
12114
  const now = Date.now();
12188
- const tokenLifetime = storedToken.expiresAt - (storedToken.expiresAt - (7 * 24 * 60 * 60 * 1000)); // Assume 7-day lifetime
12189
- const tokenAge = now - (storedToken.expiresAt - (7 * 24 * 60 * 60 * 1000));
12190
- const percentUsed = (tokenAge / tokenLifetime) * 100;
12191
- // Calculate time remaining
12192
- const timeRemaining = storedToken.expiresAt - now;
12193
- const hoursRemaining = Math.round(timeRemaining / (60 * 60 * 1000));
12115
+ const remainingMs = storedToken.expiresAt - now;
12116
+ const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
12117
+ const percentUsed = totalLifetimeMs > 0 ? ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100 : 100;
12194
12118
  if (percentUsed >= refreshThresholdPercent) {
12195
- console.log(`[AuthContext] Token at ${Math.round(percentUsed)}% lifetime (${hoursRemaining}h remaining), refreshing...`);
12196
12119
  try {
12197
12120
  await refreshToken();
12198
- console.log('[AuthContext] Automatic token refresh successful');
12199
12121
  }
12200
12122
  catch (refreshError) {
12201
- console.warn('[AuthContext] Automatic token refresh failed:', refreshError);
12202
- // Don't logout on refresh failure - user can still use the app until token actually expires
12123
+ // Don't logout on refresh failure
12203
12124
  }
12204
12125
  }
12205
- else {
12206
- console.log(`[AuthContext] Token at ${Math.round(percentUsed)}% lifetime (${hoursRemaining}h remaining), no refresh needed`);
12207
- }
12208
12126
  }
12209
12127
  catch (error) {
12210
12128
  console.error('[AuthContext] Error checking token for refresh:', error);
12211
12129
  }
12212
12130
  };
12213
- // Check immediately on mount
12214
12131
  checkAndRefresh();
12215
- // Set up periodic check
12216
12132
  const intervalId = setInterval(checkAndRefresh, refreshCheckInterval);
12217
12133
  return () => {
12218
- console.log('[AuthContext] Cleaning up automatic token refresh timer');
12219
12134
  clearInterval(intervalId);
12220
12135
  };
12221
12136
  }, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
@@ -12395,6 +12310,25 @@ const getExpirationFromResponse = (response) => {
12395
12310
  };
12396
12311
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
12397
12312
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
12313
+ // Default Google OAuth proxy URL (hosted on our whitelisted domain)
12314
+ const DEFAULT_GOOGLE_PROXY_URL = 'https://smartlinks-auth-kit.lovable.app/google-proxy.html';
12315
+ // Exact hostnames where Google OAuth is registered and inline/OneTap flow works directly.
12316
+ // Only specific registered origins — NOT broad wildcards like *.lovable.app
12317
+ const WHITELISTED_GOOGLE_OAUTH_HOSTS = [
12318
+ 'smartlinks-auth-kit.lovable.app', // This app's dev/preview domain (registered in Google Console)
12319
+ 'smartlinks.app', // Production root
12320
+ 'localhost', // Local dev
12321
+ '127.0.0.1', // Local dev
12322
+ ];
12323
+ /**
12324
+ * Check if the current domain is whitelisted for direct Google OAuth.
12325
+ * Uses exact hostname match (plus subdomain match for smartlinks.app production).
12326
+ * Returns true if OneTap/inline flow can work without a proxy.
12327
+ */
12328
+ const isWhitelistedGoogleDomain = () => {
12329
+ const hostname = window.location.hostname;
12330
+ return WHITELISTED_GOOGLE_OAUTH_HOSTS.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));
12331
+ };
12398
12332
  // Default auth UI configuration when no clientId is provided
12399
12333
  const DEFAULT_AUTH_CONFIG = {
12400
12334
  branding: {
@@ -12439,75 +12373,42 @@ const loadGoogleIdentityServices = () => {
12439
12373
  // Helper to detect WebView environments (Android/iOS)
12440
12374
  const detectWebView = () => {
12441
12375
  const ua = navigator.userAgent;
12442
- console.log(`${LOG_PREFIX} 🔍 detectWebView checking UA:`, ua);
12443
12376
  // Android WebView detection
12444
12377
  if (/Android/i.test(ua)) {
12445
- console.log(`${LOG_PREFIX} 🔍 Android device detected`);
12446
- // Modern Android WebViews include "wv" in UA string
12447
- if (/\bwv\b/i.test(ua)) {
12448
- console.log(`${LOG_PREFIX} ✅ Android WebView detected (wv in UA)`);
12378
+ if (/\bwv\b/i.test(ua))
12449
12379
  return true;
12450
- }
12451
- // Check for AuthKit native bridge
12452
- if (typeof window.AuthKit !== 'undefined') {
12453
- console.log(`${LOG_PREFIX} ✅ Android WebView detected (AuthKit bridge exists)`);
12380
+ if (typeof window.AuthKit !== 'undefined')
12454
12381
  return true;
12455
- }
12456
- console.log(`${LOG_PREFIX} ❌ Android but not WebView`);
12457
12382
  }
12458
12383
  // iOS WKWebView detection
12459
12384
  if (/iPhone|iPad|iPod/i.test(ua)) {
12460
- console.log(`${LOG_PREFIX} 🔍 iOS device detected`);
12461
12385
  const hasWebKitHandlers = !!window.webkit?.messageHandlers;
12462
12386
  const isSafari = !!window.safari;
12463
- console.log(`${LOG_PREFIX} 🔍 iOS check:`, { hasWebKitHandlers, isSafari });
12464
- // WKWebView has webkit handlers but no safari object
12465
- if (hasWebKitHandlers && !isSafari) {
12466
- console.log(`${LOG_PREFIX} ✅ iOS WKWebView detected`);
12387
+ if (hasWebKitHandlers && !isSafari)
12467
12388
  return true;
12468
- }
12469
- console.log(`${LOG_PREFIX} ❌ iOS but not WKWebView (likely Safari)`);
12470
12389
  }
12471
- console.log(`${LOG_PREFIX} ❌ Not a WebView environment`);
12472
12390
  return false;
12473
12391
  };
12474
12392
  const getNativeBridge = () => {
12475
- console.log(`${LOG_PREFIX} 🔍 getNativeBridge checking for AuthKit bridge...`);
12476
- console.log(`${LOG_PREFIX} 🔍 window.AuthKit:`, window.AuthKit);
12477
12393
  const native = window.AuthKit;
12478
- if (native?.signInWithGoogle) {
12479
- console.log(`${LOG_PREFIX} ✅ Native bridge found!`, {
12480
- signInWithGoogle: !!native.signInWithGoogle,
12481
- signOutGoogle: !!native.signOutGoogle,
12482
- checkGoogleSignIn: !!native.checkGoogleSignIn,
12483
- });
12394
+ if (native?.signInWithGoogle)
12484
12395
  return native;
12485
- }
12486
- console.log(`${LOG_PREFIX} ❌ No native bridge found (AuthKit.signInWithGoogle not available)`);
12487
12396
  return null;
12488
12397
  };
12489
12398
  // Sign out from Google on the native side (clears cached Google account)
12490
12399
  // This is fire-and-forget with a timeout - gracefully degrades if not supported
12491
12400
  const signOutGoogleNative = async () => {
12492
12401
  const nativeBridge = getNativeBridge();
12493
- if (!nativeBridge?.signOutGoogle) {
12494
- console.log(`${LOG_PREFIX} 🚪 signOutGoogleNative: no native bridge or signOutGoogle not available`);
12402
+ if (!nativeBridge?.signOutGoogle)
12495
12403
  return;
12496
- }
12497
12404
  const callbackId = `google_signout_${Date.now()}`;
12498
- console.log(`${LOG_PREFIX} 🚪 Initiating native Google sign-out, callbackId:`, callbackId);
12499
12405
  return new Promise((resolve) => {
12500
- // Timeout after 3 seconds - don't block logout on native response
12501
- const timeout = setTimeout(() => {
12502
- console.log(`${LOG_PREFIX} 🚪 Native sign-out timed out (continuing anyway)`);
12503
- resolve();
12504
- }, 3000);
12406
+ const timeout = setTimeout(() => resolve(), 3000);
12505
12407
  // Store original callback to restore later
12506
12408
  const originalCallback = window.smartlinksNativeCallback;
12507
12409
  window.smartlinksNativeCallback = (result) => {
12508
12410
  if (result.callbackId === callbackId) {
12509
12411
  clearTimeout(timeout);
12510
- console.log(`${LOG_PREFIX} 🚪 Native Google sign-out result:`, result);
12511
12412
  // Restore original callback
12512
12413
  window.smartlinksNativeCallback = originalCallback;
12513
12414
  resolve();
@@ -12521,37 +12422,21 @@ const signOutGoogleNative = async () => {
12521
12422
  type: 'GOOGLE_SIGN_OUT',
12522
12423
  callbackId,
12523
12424
  });
12524
- console.log(`${LOG_PREFIX} 🚪 Calling nativeBridge.signOutGoogle with payload:`, payload);
12525
- // Use non-null assertion - method exists (verified by guard check above)
12526
- // IMPORTANT: Must call directly on nativeBridge object for WebView proxy binding
12527
12425
  nativeBridge.signOutGoogle(payload);
12528
12426
  });
12529
12427
  };
12530
12428
  const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
12531
12429
  const nativeBridge = getNativeBridge();
12532
- if (!nativeBridge?.checkGoogleSignIn) {
12533
- console.log(`${LOG_PREFIX} 🔇 checkSilentGoogleSignIn: no native bridge or checkGoogleSignIn not available`);
12430
+ if (!nativeBridge?.checkGoogleSignIn)
12534
12431
  return null;
12535
- }
12536
12432
  const callbackId = `google_check_${Date.now()}`;
12537
- console.log(`${LOG_PREFIX} 🔇 Checking for silent Google sign-in, callbackId:`, callbackId);
12538
12433
  return new Promise((resolve) => {
12539
- // Timeout after 5 seconds
12540
- const timeout = setTimeout(() => {
12541
- console.log(`${LOG_PREFIX} 🔇 Silent sign-in check timed out`);
12542
- resolve(null);
12543
- }, 5000);
12434
+ const timeout = setTimeout(() => resolve(null), 5000);
12544
12435
  // Store original callback to restore later
12545
12436
  const originalCallback = window.smartlinksNativeCallback;
12546
12437
  window.smartlinksNativeCallback = (result) => {
12547
12438
  if (result.callbackId === callbackId) {
12548
12439
  clearTimeout(timeout);
12549
- console.log(`${LOG_PREFIX} 🔇 Silent sign-in check result:`, {
12550
- success: result.success,
12551
- isSignedIn: result.isSignedIn,
12552
- hasIdToken: !!result.idToken,
12553
- email: result.email,
12554
- });
12555
12440
  // Restore original callback
12556
12441
  window.smartlinksNativeCallback = originalCallback;
12557
12442
  if (result.success) {
@@ -12579,9 +12464,6 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
12579
12464
  serverClientId: googleClientId, // Alias for Android SDK
12580
12465
  callbackId,
12581
12466
  });
12582
- console.log(`${LOG_PREFIX} 🔇 Calling nativeBridge.checkGoogleSignIn with payload:`, payload);
12583
- // Use non-null assertion - method exists (verified by guard check above)
12584
- // IMPORTANT: Must call directly on nativeBridge object for WebView proxy binding
12585
12467
  nativeBridge.checkGoogleSignIn(payload);
12586
12468
  });
12587
12469
  };
@@ -12668,43 +12550,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12668
12550
  mediaQuery.addEventListener('change', updateTheme);
12669
12551
  return () => mediaQuery.removeEventListener('change', updateTheme);
12670
12552
  }, [theme]);
12671
- // Log all props on mount for debugging
12672
- useEffect(() => {
12673
- console.log(`${LOG_PREFIX} 🚀 COMPONENT MOUNTED with props:`, {
12674
- apiEndpoint,
12675
- clientId,
12676
- clientName,
12677
- redirectUrl,
12678
- redirectUrlType: typeof redirectUrl,
12679
- redirectUrlTruthy: !!redirectUrl,
12680
- proxyMode,
12681
- theme,
12682
- initialMode,
12683
- skipConfigFetch,
12684
- minimal,
12685
- enableSilentGoogleSignIn,
12686
- enabledProviders,
12687
- timestamp: new Date().toISOString()
12688
- });
12689
- }, []); // Only log on mount
12553
+ // Version tracking for debugging if needed
12554
+ // console.log(`${LOG_PREFIX} Component mounted, v${AUTH_UI_VERSION}`);
12690
12555
  // Reinitialize Smartlinks SDK when apiEndpoint or proxyMode changes
12691
12556
  // IMPORTANT: Preserve bearer token during reinitialization
12692
12557
  useEffect(() => {
12693
- console.log(`${LOG_PREFIX} 🔧 SDK INIT useEffect triggered`, {
12694
- apiEndpoint,
12695
- proxyMode,
12696
- hasLogger: !!logger,
12697
- timestamp: new Date().toISOString()
12698
- });
12699
12558
  log.log('SDK reinitialize useEffect triggered', { apiEndpoint, proxyMode });
12700
12559
  setSdkReady(false); // Mark SDK as not ready during reinitialization
12701
12560
  const reinitializeWithToken = async () => {
12702
12561
  if (apiEndpoint) {
12703
- console.log(`${LOG_PREFIX} 🔧 Reinitializing SDK with:`, {
12704
- baseURL: apiEndpoint,
12705
- proxyMode: proxyMode,
12706
- ngrokSkipBrowserWarning: true
12707
- });
12708
12562
  log.log('Reinitializing SDK with baseURL:', apiEndpoint, 'proxyMode:', proxyMode);
12709
12563
  // Get current token before reinitializing (only in standalone mode)
12710
12564
  const currentToken = !proxyMode ? await auth.getToken() : null;
@@ -12714,7 +12568,6 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12714
12568
  ngrokSkipBrowserWarning: true,
12715
12569
  logger: logger, // Pass logger to SDK for verbose SDK logging
12716
12570
  });
12717
- console.log(`${LOG_PREFIX} ✅ SDK reinitialized, proxyMode:`, proxyMode);
12718
12571
  log.log('SDK reinitialized successfully');
12719
12572
  // Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
12720
12573
  if (currentToken && !proxyMode) {
@@ -12722,20 +12575,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12722
12575
  log.warn('Failed to restore bearer token after reinit:', err);
12723
12576
  });
12724
12577
  }
12725
- // Mark SDK as ready
12726
- console.log(`${LOG_PREFIX} ✅ Setting sdkReady=true (with apiEndpoint)`);
12727
12578
  setSdkReady(true);
12728
12579
  }
12729
12580
  else if (proxyMode) {
12730
12581
  // In proxy mode without custom endpoint, SDK should already be initialized by parent
12731
- console.log(`${LOG_PREFIX} ⚠️ Proxy mode WITHOUT apiEndpoint - expecting SDK already initialized by parent`);
12732
12582
  log.log('Proxy mode without apiEndpoint, SDK already initialized by parent');
12733
12583
  setSdkReady(true);
12734
12584
  }
12735
12585
  else {
12736
- console.log(`${LOG_PREFIX} ℹ️ No apiEndpoint, no proxyMode - SDK already initialized by App`);
12737
12586
  log.log('No apiEndpoint, SDK already initialized by App');
12738
- // SDK was initialized by App component, mark as ready
12739
12587
  setSdkReady(true);
12740
12588
  }
12741
12589
  };
@@ -12743,48 +12591,19 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12743
12591
  }, [apiEndpoint, proxyMode, auth, logger, log]);
12744
12592
  // Get the effective redirect URL (use prop or default to current page)
12745
12593
  const getRedirectUrl = () => {
12746
- console.log(`${LOG_PREFIX} 🔗 getRedirectUrl() called`, {
12747
- redirectUrlProp: redirectUrl,
12748
- redirectUrlPropType: typeof redirectUrl,
12749
- redirectUrlPropTruthy: !!redirectUrl,
12750
- currentHref: window.location.href
12751
- });
12752
- if (redirectUrl) {
12753
- console.log(`${LOG_PREFIX} 🔗 Using redirectUrl prop:`, redirectUrl);
12594
+ if (redirectUrl)
12754
12595
  return redirectUrl;
12755
- }
12756
- // Get the full current URL including hash routes
12757
- // Remove any existing query parameters to avoid duplication
12758
- const currentUrl = window.location.href.split('?')[0];
12759
- console.log(`${LOG_PREFIX} 🔗 No redirectUrl prop, using current URL:`, currentUrl);
12760
- return currentUrl;
12596
+ // Get the full current URL including hash routes, strip query params
12597
+ return window.location.href.split('?')[0];
12761
12598
  };
12762
12599
  // Fetch UI configuration
12763
12600
  useEffect(() => {
12764
- console.log(`${LOG_PREFIX} 📋 CONFIG FETCH useEffect triggered`, {
12765
- skipConfigFetch,
12766
- clientId,
12767
- apiEndpoint,
12768
- sdkReady,
12769
- proxyMode,
12770
- timestamp: new Date().toISOString()
12771
- });
12772
- log.log('Config fetch useEffect triggered', {
12773
- skipConfigFetch,
12774
- clientId,
12775
- clientIdType: typeof clientId,
12776
- clientIdTruthy: !!clientId,
12777
- apiEndpoint,
12778
- sdkReady
12779
- });
12780
12601
  // Wait for SDK to be ready before fetching config
12781
12602
  if (!sdkReady) {
12782
- console.log(`${LOG_PREFIX} ⏳ SDK not ready yet, waiting before config fetch...`);
12783
12603
  log.log('SDK not ready yet, waiting...');
12784
12604
  return;
12785
12605
  }
12786
12606
  if (skipConfigFetch) {
12787
- console.log(`${LOG_PREFIX} ⏭️ Skipping config fetch - skipConfigFetch is true`);
12788
12607
  log.log('Skipping config fetch - skipConfigFetch is true');
12789
12608
  setConfig(customization || {});
12790
12609
  return;
@@ -12792,7 +12611,6 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12792
12611
  const fetchConfig = async () => {
12793
12612
  // If no clientId provided, use default config immediately without API call
12794
12613
  if (!clientId) {
12795
- console.log(`${LOG_PREFIX} ⚠️ No clientId provided, using default config`);
12796
12614
  log.log('No clientId provided, using default config');
12797
12615
  const defaultConfig = {
12798
12616
  ...DEFAULT_AUTH_CONFIG,
@@ -12812,39 +12630,25 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12812
12630
  const age = Date.now() - timestamp;
12813
12631
  // Use cache if less than 1 hour old
12814
12632
  if (age < 3600000) {
12815
- console.log(`${LOG_PREFIX} 📦 Using cached config (age:`, Math.round(age / 1000), 'seconds)');
12816
12633
  setConfig({ ...cachedConfig, ...customization });
12817
12634
  setConfigLoading(false);
12818
12635
  // Fetch in background to update cache
12819
- console.log(`${LOG_PREFIX} 🔄 Background refresh of config via SDK...`);
12820
12636
  api.fetchConfig().then(freshConfig => {
12821
- console.log(`${LOG_PREFIX} ✅ Background config refresh complete`);
12822
12637
  localStorage.setItem(cacheKey, JSON.stringify({
12823
12638
  config: freshConfig,
12824
12639
  timestamp: Date.now()
12825
12640
  }));
12826
- // Update config if it changed
12827
12641
  setConfig({ ...freshConfig, ...customization });
12828
- }).catch(err => {
12829
- console.log(`${LOG_PREFIX} ❌ Background config refresh failed:`, err);
12830
- });
12642
+ }).catch(() => { });
12831
12643
  return;
12832
12644
  }
12833
12645
  }
12834
12646
  }
12835
- else {
12836
- console.log(`${LOG_PREFIX} ⚠️ Config caching disabled, fetching fresh config`);
12837
- }
12838
- // Fetch from API
12839
- console.log(`${LOG_PREFIX} 🌐 Fetching config via SDK for clientId:`, clientId, 'proxyMode:', proxyMode);
12840
12647
  log.log('Fetching config from API for clientId:', clientId);
12841
12648
  const fetchedConfig = await api.fetchConfig();
12842
- console.log(`${LOG_PREFIX} ✅ Config fetched successfully:`, fetchedConfig);
12843
12649
  log.log('Received config:', fetchedConfig);
12844
- // Merge with customization props (props take precedence)
12845
12650
  const mergedConfig = { ...fetchedConfig, ...customization };
12846
12651
  setConfig(mergedConfig);
12847
- // Cache the fetched config (unless caching is disabled)
12848
12652
  if (!disableConfigCache) {
12849
12653
  localStorage.setItem(cacheKey, JSON.stringify({
12850
12654
  config: fetchedConfig,
@@ -12853,7 +12657,6 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12853
12657
  }
12854
12658
  }
12855
12659
  catch (err) {
12856
- console.log(`${LOG_PREFIX} ❌ Config fetch failed:`, err);
12857
12660
  log.error('Failed to fetch config:', err);
12858
12661
  setConfig(customization || {});
12859
12662
  }
@@ -12869,13 +12672,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12869
12672
  return;
12870
12673
  const fetchSchema = async () => {
12871
12674
  try {
12872
- console.log(`${LOG_PREFIX} 📋 Fetching contact schema for collection:`, collectionId);
12873
12675
  const schema = await smartlinks.contact.publicGetSchema(collectionId);
12874
- console.log(`${LOG_PREFIX} ✅ Schema loaded:`, schema);
12875
12676
  setContactSchema(schema);
12876
12677
  }
12877
12678
  catch (err) {
12878
- console.warn(`${LOG_PREFIX} ⚠️ Failed to fetch schema (non-fatal):`, err);
12879
12679
  // Non-fatal - registration will work without schema fields
12880
12680
  }
12881
12681
  };
@@ -12889,17 +12689,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12889
12689
  }
12890
12690
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
12891
12691
  const performSilentSignIn = async () => {
12892
- console.log(`${LOG_PREFIX} 🔇 Silent Google Sign-In check enabled, checking native session...`);
12893
12692
  try {
12894
12693
  const result = await checkSilentGoogleSignIn(clientId, googleClientId);
12895
12694
  setSilentSignInChecked(true);
12896
12695
  if (result?.isSignedIn && result.idToken) {
12897
- console.log(`${LOG_PREFIX} 🔇 Silent sign-in found existing Google session, authenticating...`);
12898
12696
  setLoading(true);
12899
12697
  try {
12900
12698
  const authResponse = await api.loginWithGoogle(result.idToken);
12901
12699
  if (authResponse.token) {
12902
- console.log(`${LOG_PREFIX} 🔇 Silent sign-in successful!`);
12903
12700
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, false, getExpirationFromResponse(authResponse));
12904
12701
  setAuthSuccess(true);
12905
12702
  setSuccessMessage('Signed in automatically with Google!');
@@ -12907,19 +12704,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12907
12704
  }
12908
12705
  }
12909
12706
  catch (err) {
12910
- console.warn(`${LOG_PREFIX} 🔇 Silent sign-in backend auth failed:`, err);
12911
12707
  // Don't show error - user can still log in manually
12912
12708
  }
12913
12709
  finally {
12914
12710
  setLoading(false);
12915
12711
  }
12916
12712
  }
12917
- else {
12918
- console.log(`${LOG_PREFIX} 🔇 No existing Google session found`);
12919
- }
12920
12713
  }
12921
12714
  catch (err) {
12922
- console.warn(`${LOG_PREFIX} 🔇 Silent sign-in check failed:`, err);
12923
12715
  setSilentSignInChecked(true);
12924
12716
  }
12925
12717
  };
@@ -13164,7 +12956,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13164
12956
  // Defensive check: validate response before accessing properties
13165
12957
  // SDK should throw on 401/error responses, but handle edge cases gracefully
13166
12958
  if (!response) {
13167
- throw new Error('Authentication failed - no response received');
12959
+ // For registration, a missing response might indicate a 409 that wasn't thrown
12960
+ if (mode === 'login') {
12961
+ throw new Error('Authentication failed - no response received');
12962
+ }
12963
+ // For register mode, throw a more specific error
12964
+ throw new Error('Registration failed - please try again');
13168
12965
  }
13169
12966
  // Get email verification mode from response or config (default: verify-auto-login)
13170
12967
  const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-auto-login';
@@ -13240,7 +13037,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13240
13037
  }
13241
13038
  catch (err) {
13242
13039
  // Check if error is about email already registered (409 conflict)
13243
- if (mode === 'register' && isConflictError(err)) {
13040
+ // Handle both SmartlinksApiError (statusCode 409) and plain Error with keyword matching
13041
+ if (mode === 'register' && (isConflictError(err) ||
13042
+ (err instanceof Error && /already (registered|exists)/i.test(err.message)))) {
13244
13043
  setShowResendVerification(true);
13245
13044
  setResendEmail(data.email);
13246
13045
  setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
@@ -13298,184 +13097,168 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13298
13097
  }
13299
13098
  };
13300
13099
  const handleGoogleLogin = async () => {
13301
- console.log(`${LOG_PREFIX} 🚀 handleGoogleLogin called`);
13302
- // Use custom client ID from config, or fall back to default Smartlinks client ID
13100
+ const hasCustomGoogleClientId = !!config?.googleClientId;
13303
13101
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
13304
- // Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
13305
13102
  const configuredFlow = config?.googleOAuthFlow || 'oneTap';
13306
- // Check for native bridge and WebView environment
13307
- console.log(`${LOG_PREFIX} 🔍 Checking environment...`);
13308
13103
  const isWebView = detectWebView();
13309
13104
  const nativeBridge = getNativeBridge();
13310
- // For oneTap, automatically use redirect flow in WebView environments (if no native bridge)
13311
13105
  const oauthFlow = (configuredFlow === 'oneTap' && isWebView && !nativeBridge) ? 'redirect' : configuredFlow;
13312
- // Log Google Auth configuration for debugging
13313
- console.log(`${LOG_PREFIX} 📋 Google Auth configuration:`, {
13314
- googleClientId,
13315
- configuredFlow,
13316
- effectiveFlow: oauthFlow,
13317
- isWebView,
13318
- hasNativeBridge: !!nativeBridge,
13319
- currentOrigin: window.location.origin,
13320
- currentHref: window.location.href,
13321
- configGoogleClientId: config?.googleClientId,
13322
- usingDefaultClientId: !config?.googleClientId,
13323
- });
13106
+ // Auto-detect proxy need:
13107
+ // - If explicitly configured, use that URL
13108
+ // - If user has their own Google Client ID, they've registered their domains — no proxy needed
13109
+ // - If on a whitelisted SmartLinks domain, inline flow works directly
13110
+ // - Otherwise, auto-use the default proxy URL
13111
+ const isWhitelisted = isWhitelistedGoogleDomain();
13112
+ const googleProxyUrl = config?.googleOAuthProxyUrl
13113
+ || (!hasCustomGoogleClientId && !isWhitelisted ? DEFAULT_GOOGLE_PROXY_URL : undefined);
13324
13114
  log.log('Google Auth initiated:', {
13325
- googleClientId,
13326
13115
  configuredFlow,
13327
13116
  effectiveFlow: oauthFlow,
13328
13117
  isWebView,
13329
13118
  hasNativeBridge: !!nativeBridge,
13330
- currentOrigin: window.location.origin,
13331
- currentHref: window.location.href,
13332
- configGoogleClientId: config?.googleClientId,
13333
- usingDefaultClientId: !config?.googleClientId,
13119
+ hasCustomGoogleClientId,
13120
+ isWhitelistedDomain: isWhitelisted,
13121
+ usingProxy: !!googleProxyUrl,
13122
+ proxyUrl: googleProxyUrl,
13334
13123
  });
13335
13124
  setLoading(true);
13336
13125
  setError(undefined);
13337
13126
  try {
13338
- // Priority 1: Use native bridge if available (for WebView environments)
13339
13127
  if (nativeBridge) {
13340
- console.log(`${LOG_PREFIX} 🌉 NATIVE BRIDGE PATH - Using native bridge for Google Sign-In`);
13341
13128
  log.log('Using native bridge for Google Sign-In');
13342
13129
  const callbackId = `google_auth_${Date.now()}`;
13343
- console.log(`${LOG_PREFIX} 🔑 Generated callbackId:`, callbackId);
13344
- // Set up callback for native response
13345
- console.log(`${LOG_PREFIX} 📡 Registering window.smartlinksNativeCallback...`);
13346
13130
  window.smartlinksNativeCallback = async (result) => {
13347
- console.log(`${LOG_PREFIX} 📨 smartlinksNativeCallback INVOKED with:`, {
13348
- callbackId: result.callbackId,
13349
- success: result.success,
13350
- hasIdToken: !!result.idToken,
13351
- idTokenLength: result.idToken?.length,
13352
- email: result.email,
13353
- name: result.name,
13354
- error: result.error,
13355
- errorCode: result.errorCode,
13356
- });
13357
13131
  // Ignore stale callbacks
13358
13132
  if (result.callbackId !== callbackId) {
13359
- console.log(`${LOG_PREFIX} ⚠️ Ignoring stale callback. Expected:`, callbackId, 'Got:', result.callbackId);
13360
13133
  log.log('Ignoring stale native callback:', result.callbackId);
13361
13134
  return;
13362
13135
  }
13363
- console.log(`${LOG_PREFIX} ✅ Callback ID matches, processing result...`);
13364
13136
  log.log('Native callback received:', {
13365
13137
  success: result.success,
13366
13138
  hasIdToken: !!result.idToken,
13367
13139
  email: result.email,
13368
13140
  error: result.error,
13369
- errorCode: result.errorCode,
13370
13141
  });
13371
13142
  try {
13372
13143
  if (result.success && result.idToken) {
13373
- console.log(`${LOG_PREFIX} 🔐 Success! Calling api.loginWithGoogle with idToken...`);
13374
- // Process through existing Google auth flow
13375
13144
  const authResponse = await api.loginWithGoogle(result.idToken);
13376
- console.log(`${LOG_PREFIX} 📦 api.loginWithGoogle response:`, {
13377
- hasToken: !!authResponse.token,
13378
- hasUser: !!authResponse.user,
13379
- isNewUser: authResponse.isNewUser,
13380
- });
13381
13145
  if (authResponse.token) {
13382
- console.log(`${LOG_PREFIX} 🎉 Login successful! Calling auth.login and onAuthSuccess...`);
13383
13146
  await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
13384
13147
  setAuthSuccess(true);
13385
13148
  setSuccessMessage('Google login successful!');
13386
13149
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
13387
13150
  }
13388
13151
  else {
13389
- console.log(`${LOG_PREFIX} ❌ No token in authResponse`);
13390
13152
  throw new Error('Authentication failed - no token received');
13391
13153
  }
13392
13154
  }
13393
13155
  else {
13394
- // Handle error from native
13395
- console.log(`${LOG_PREFIX} ❌ Native returned error:`, result.error, result.errorCode);
13396
13156
  setError(getFriendlyErrorMessage(result.error || 'Google Sign-In failed'));
13397
13157
  onAuthError?.(new Error(result.error || 'Google Sign-In failed'));
13398
13158
  }
13399
13159
  }
13400
13160
  catch (err) {
13401
- console.log(`${LOG_PREFIX} 💥 Exception in callback handler:`, err);
13402
13161
  setError(getFriendlyErrorMessage(err));
13403
13162
  onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13404
13163
  }
13405
13164
  finally {
13406
- console.log(`${LOG_PREFIX} 🏁 Callback processing complete, setting loading=false`);
13407
13165
  setLoading(false);
13408
13166
  }
13409
13167
  };
13410
- console.log(`${LOG_PREFIX} ✅ window.smartlinksNativeCallback registered`);
13411
- // Invoke native sign-in
13412
- // Pass comprehensive payload for Android to configure Google Sign-In correctly
13413
13168
  const payloadObj = {
13414
13169
  type: 'GOOGLE_SIGN_IN',
13415
- clientId, // Smartlinks Auth Kit client ID
13416
- googleClientId, // Web/Server Client ID - use this for requestIdToken()
13417
- serverClientId: googleClientId, // Explicit alias - Android should use this for GoogleSignInOptions.requestIdToken()
13170
+ clientId,
13171
+ googleClientId,
13172
+ serverClientId: googleClientId,
13418
13173
  callbackId,
13419
- // Additional context that might help Android configuration
13420
- scopes: ['email', 'profile'], // Requested OAuth scopes
13421
- requestIdToken: true, // We need an ID token for backend verification
13422
- requestServerAuthCode: false, // We don't need server auth code
13174
+ scopes: ['email', 'profile'],
13175
+ requestIdToken: true,
13176
+ requestServerAuthCode: false,
13423
13177
  };
13424
13178
  const payload = JSON.stringify(payloadObj);
13425
- console.log(`${LOG_PREFIX} 📤 Calling nativeBridge.signInWithGoogle with payload object:`, payloadObj);
13426
- console.log(`${LOG_PREFIX} 📤 Payload JSON string:`, payload);
13427
- log.log('Invoking native signInWithGoogle with payload:', payloadObj);
13428
- // 🔍 DEBUG: Re-validate bridge before invocation
13429
- console.log(`${LOG_PREFIX} 🔍 Re-validating bridge before invocation...`);
13430
- console.log(`${LOG_PREFIX} 🔍 window.AuthKit:`, window.AuthKit);
13431
- console.log(`${LOG_PREFIX} 🔍 window.AuthKit.signInWithGoogle:`, window.AuthKit?.signInWithGoogle);
13432
- console.log(`${LOG_PREFIX} 🔍 typeof signInWithGoogle:`, typeof window.AuthKit?.signInWithGoogle);
13433
- console.log(`${LOG_PREFIX} 🔍 nativeBridge === window.AuthKit:`, nativeBridge === window.AuthKit);
13434
- console.log(`${LOG_PREFIX} 🔍 nativeBridge.signInWithGoogle:`, nativeBridge.signInWithGoogle);
13435
- console.log(`${LOG_PREFIX} 🔍 typeof nativeBridge.signInWithGoogle:`, typeof nativeBridge.signInWithGoogle);
13436
- // 🔍 DEBUG: Bridge method comparison
13437
- console.log(`${LOG_PREFIX} 🔍 Bridge method comparison:`);
13438
- const authKit = window.AuthKit;
13439
- if (authKit) {
13440
- console.log(`${LOG_PREFIX} - signInWithGoogle: ${typeof authKit.signInWithGoogle} | descriptor:`, Object.getOwnPropertyDescriptor(authKit, 'signInWithGoogle'));
13441
- console.log(`${LOG_PREFIX} - signOutGoogle: ${typeof authKit.signOutGoogle} | descriptor:`, Object.getOwnPropertyDescriptor(authKit, 'signOutGoogle'));
13442
- console.log(`${LOG_PREFIX} - checkGoogleSignIn: ${typeof authKit.checkGoogleSignIn} | descriptor:`, Object.getOwnPropertyDescriptor(authKit, 'checkGoogleSignIn'));
13443
- console.log(`${LOG_PREFIX} - All keys:`, Object.keys(authKit));
13444
- try {
13445
- console.log(`${LOG_PREFIX} - Prototype:`, Object.getPrototypeOf(authKit));
13446
- }
13447
- catch (e) {
13448
- console.log(`${LOG_PREFIX} - Prototype: (error getting prototype)`, e);
13449
- }
13450
- }
13451
- // 🔍 DEBUG: Wrap invocation in try/catch to capture exact exception
13179
+ log.log('Invoking native signInWithGoogle');
13452
13180
  try {
13453
- console.log(`${LOG_PREFIX} 📤 About to invoke nativeBridge.signInWithGoogle...`);
13454
13181
  nativeBridge.signInWithGoogle(payload);
13455
- console.log(`${LOG_PREFIX} ✅ nativeBridge.signInWithGoogle called successfully`);
13456
- console.log(`${LOG_PREFIX} ⏳ Waiting for native callback...`);
13457
- console.log(`${LOG_PREFIX} 💡 Android should use serverClientId for GoogleSignInOptions.Builder().requestIdToken(serverClientId)`);
13458
13182
  }
13459
13183
  catch (invokeError) {
13460
- console.error(`${LOG_PREFIX} 💥 EXCEPTION invoking signInWithGoogle:`, invokeError);
13461
- console.error(`${LOG_PREFIX} 💥 Error type:`, typeof invokeError);
13462
- console.error(`${LOG_PREFIX} 💥 Error constructor:`, invokeError?.constructor?.name);
13463
- console.error(`${LOG_PREFIX} 💥 Error message:`, invokeError?.message);
13464
- console.error(`${LOG_PREFIX} 💥 Error stack:`, invokeError?.stack);
13465
- throw invokeError; // Re-throw so it propagates normally
13184
+ console.error(`${LOG_PREFIX} Exception invoking signInWithGoogle:`, invokeError);
13185
+ throw invokeError;
13466
13186
  }
13467
13187
  // Don't set loading to false - waiting for native callback
13468
13188
  return;
13469
13189
  }
13470
13190
  // Priority 2: If WebView but no native bridge, show helpful error
13471
13191
  if (isWebView) {
13472
- console.log(`${LOG_PREFIX} ⚠️ WebView detected but NO native bridge - showing error`);
13473
13192
  log.log('WebView detected but no native bridge available');
13474
13193
  setError('Google Sign-In is not available in this app. Please use email or phone login instead.');
13475
13194
  setLoading(false);
13476
13195
  return;
13477
13196
  }
13478
- console.log(`${LOG_PREFIX} 🌐 WEB PATH - Using web-based OAuth flow:`, oauthFlow);
13197
+ // Priority 3: Google OAuth Proxy popup (for custom domains not registered in Google Console)
13198
+ if (googleProxyUrl) {
13199
+ log.log('Using Google OAuth proxy popup:', googleProxyUrl);
13200
+ const proxyParams = new URLSearchParams({
13201
+ clientId,
13202
+ returnOrigin: window.location.origin,
13203
+ ...(apiEndpoint ? { apiEndpoint } : {}),
13204
+ });
13205
+ const popupUrl = `${googleProxyUrl}?${proxyParams.toString()}`;
13206
+ const popup = window.open(popupUrl, 'google-oauth-proxy', 'width=500,height=600,menubar=no,toolbar=no,location=yes');
13207
+ if (!popup) {
13208
+ setError('Popup blocked. Please allow popups for this site and try again.');
13209
+ setLoading(false);
13210
+ return;
13211
+ }
13212
+ // Listen for result from proxy popup
13213
+ const handleProxyMessage = async (event) => {
13214
+ // Validate origin matches proxy URL
13215
+ try {
13216
+ const proxyOrigin = new URL(googleProxyUrl).origin;
13217
+ if (event.origin !== proxyOrigin)
13218
+ return;
13219
+ }
13220
+ catch {
13221
+ return;
13222
+ }
13223
+ if (event.data?.type !== 'smartlinks:google-proxy:result')
13224
+ return;
13225
+ window.removeEventListener('message', handleProxyMessage);
13226
+ clearInterval(checkClosed);
13227
+ try {
13228
+ if (event.data.success && event.data.token) {
13229
+ const { token, user, accountData, isNewUser, expiresAt, expiresIn } = event.data;
13230
+ const expiration = expiresAt || (expiresIn ? Date.now() + expiresIn : undefined);
13231
+ await auth.login(token, user, accountData, isNewUser, expiration);
13232
+ setAuthSuccess(true);
13233
+ setSuccessMessage('Google login successful!');
13234
+ onAuthSuccess(token, user, accountData);
13235
+ }
13236
+ else {
13237
+ const errorMsg = event.data.error || 'Google login failed';
13238
+ setError(getFriendlyErrorMessage(errorMsg));
13239
+ onAuthError?.(new Error(errorMsg));
13240
+ }
13241
+ }
13242
+ catch (err) {
13243
+ setError(getFriendlyErrorMessage(err));
13244
+ onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
13245
+ }
13246
+ finally {
13247
+ setLoading(false);
13248
+ }
13249
+ };
13250
+ window.addEventListener('message', handleProxyMessage);
13251
+ // Monitor popup close without result (user closed it)
13252
+ const checkClosed = setInterval(() => {
13253
+ if (popup.closed) {
13254
+ clearInterval(checkClosed);
13255
+ window.removeEventListener('message', handleProxyMessage);
13256
+ setLoading(false);
13257
+ }
13258
+ }, 500);
13259
+ return; // Don't set loading to false - waiting for popup
13260
+ }
13261
+ log.log('Using web-based OAuth flow:', oauthFlow);
13479
13262
  // Priority 3: Web-based flows (redirect, popup, oneTap)
13480
13263
  // Dynamically load Google Identity Services if not already loaded
13481
13264
  await loadGoogleIdentityServices();
@@ -13704,21 +13487,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13704
13487
  setLoading(true);
13705
13488
  setError(undefined);
13706
13489
  const effectiveRedirectUrl = getRedirectUrl();
13707
- console.log(`${LOG_PREFIX} 🔑 handlePasswordReset called`, {
13708
- email: emailOrPassword,
13709
- hasResetToken: !!resetToken,
13710
- hasConfirmPassword: !!confirmPassword,
13711
- effectiveRedirectUrl,
13712
- redirectUrlProp: redirectUrl
13713
- });
13714
13490
  try {
13715
13491
  if (resetToken && confirmPassword) {
13716
13492
  // Complete password reset with token
13717
- console.log(`${LOG_PREFIX} 🔑 Completing password reset with token`);
13718
13493
  await api.completePasswordReset(resetToken, emailOrPassword);
13719
13494
  // Auto-login with the new password if we have the email
13720
13495
  if (resetEmail) {
13721
- console.log(`${LOG_PREFIX} 🔑 Auto-signing in after password reset`);
13722
13496
  try {
13723
13497
  const loginResponse = await api.login(resetEmail, emailOrPassword);
13724
13498
  if (loginResponse.token) {
@@ -13726,15 +13500,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13726
13500
  setAuthSuccess(true);
13727
13501
  setSuccessMessage('Password reset successful! You are now signed in.');
13728
13502
  onAuthSuccess(loginResponse.token, loginResponse.user, loginResponse.accountData);
13729
- // Clear reset state
13730
13503
  setResetToken(undefined);
13731
13504
  setResetEmail(undefined);
13732
- return; // Exit early - we've handled everything
13505
+ return;
13733
13506
  }
13734
13507
  }
13735
13508
  catch (loginErr) {
13736
13509
  // Auto-login failed, fall back to showing success message
13737
- console.log(`${LOG_PREFIX} ⚠️ Auto-login after reset failed, showing manual login prompt`, loginErr);
13738
13510
  }
13739
13511
  }
13740
13512
  // Fallback: show success but require manual login
@@ -13745,7 +13517,6 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13745
13517
  }
13746
13518
  else {
13747
13519
  // Request password reset email
13748
- console.log(`${LOG_PREFIX} 🔑 Requesting password reset email with redirectUrl:`, effectiveRedirectUrl);
13749
13520
  const result = await api.requestPasswordReset(emailOrPassword, effectiveRedirectUrl);
13750
13521
  setResetSuccess(true);
13751
13522
  // Use backend message if available
@@ -14044,6 +13815,12 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14044
13815
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
14045
13816
  const { showProfileSection = true, showEmailSection = true, showPasswordSection = true, showPhoneSection = true, showDeleteAccount = false, showCustomFields = true, // New: show schema-driven custom fields
14046
13817
  } = customization;
13818
+ // Provider awareness helpers
13819
+ // If providers is undefined (backend not updated yet), fall back to showing everything
13820
+ const providers = profile?.providers;
13821
+ const hasPassword = profile?.hasPassword ?? (providers === undefined ? undefined : providers?.includes('password'));
13822
+ const isGoogleOnly = providers !== undefined && providers.includes('google.com') && !providers.includes('password');
13823
+ const isPhoneOnly = providers !== undefined && providers.includes('phone') && providers.length === 1;
14047
13824
  // Reinitialize Smartlinks SDK when apiEndpoint changes
14048
13825
  useEffect(() => {
14049
13826
  if (apiEndpoint) {
@@ -14061,13 +13838,11 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14061
13838
  const loadSchema = async () => {
14062
13839
  setSchemaLoading(true);
14063
13840
  try {
14064
- console.log('[AccountManagement] Loading schema for collection:', collectionId);
14065
13841
  const schemaResult = await smartlinks.contact.publicGetSchema(collectionId);
14066
- console.log('[AccountManagement] Schema loaded:', schemaResult);
14067
13842
  setSchema(schemaResult);
14068
13843
  }
14069
13844
  catch (err) {
14070
- console.warn('[AccountManagement] Failed to load schema:', err);
13845
+ // Non-fatal - component works without schema
14071
13846
  // Non-fatal - component works without schema
14072
13847
  }
14073
13848
  finally {
@@ -14082,7 +13857,6 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14082
13857
  // where getContact() is called before the server knows about the user.
14083
13858
  useEffect(() => {
14084
13859
  if (!auth.isAuthenticated || !auth.isVerified) {
14085
- console.log('[AccountManagement] Waiting for verified authentication before loading profile...');
14086
13860
  return;
14087
13861
  }
14088
13862
  loadProfile();
@@ -14183,7 +13957,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14183
13957
  setError(undefined);
14184
13958
  setSuccess(undefined);
14185
13959
  try {
14186
- console.log('[AccountManagement] Updating custom fields:', customFieldValues);
13960
+ await auth.updateContactCustomFields?.(customFieldValues);
14187
13961
  await auth.updateContactCustomFields?.(customFieldValues);
14188
13962
  setSuccess('Profile updated successfully!');
14189
13963
  setEditingSection(null);
@@ -14309,7 +14083,8 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14309
14083
  setError('Please type DELETE to confirm account deletion');
14310
14084
  return;
14311
14085
  }
14312
- if (!deletePassword) {
14086
+ // Only require password for users who have one
14087
+ if (hasPassword !== false && !deletePassword) {
14313
14088
  setError('Password is required');
14314
14089
  return;
14315
14090
  }
@@ -14339,7 +14114,7 @@ const AccountManagement = ({ apiEndpoint, clientId, collectionId, onError, class
14339
14114
  }
14340
14115
  return (jsxs("div", { className: `account-management ${className}`, style: { maxWidth: '600px' }, children: [error && (jsx("div", { className: "auth-error", role: "alert", children: error })), success && (jsx("div", { className: "auth-success", role: "alert", children: success })), showProfileSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Display Name" }), jsx("div", { className: "field-value", children: profile?.displayName || 'Not set' })] }), editingSection !== 'profile' && (jsx("button", { type: "button", onClick: () => setEditingSection('profile'), className: "auth-button button-secondary", children: "Change" }))] }), editingSection === 'profile' && (jsxs("form", { onSubmit: handleUpdateProfile, className: "edit-form", children: [jsx("div", { className: "form-group", children: jsx("input", { id: "displayName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), placeholder: "Your display name", className: "auth-input" }) }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showCustomFields && collectionId && editableCustomFields.length > 0 && (jsxs("section", { className: "account-section", children: [jsx("h3", { className: "section-title", style: { fontSize: '0.9rem', fontWeight: 600, marginBottom: '12px' }, children: "Additional Information" }), editingSection !== 'customFields' ? (jsxs(Fragment, { children: [editableCustomFields.map((field) => (jsx("div", { className: "account-field", children: jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: field.label }), jsx("div", { className: "field-value", children: customFieldValues[field.key] !== undefined && customFieldValues[field.key] !== ''
14341
14116
  ? String(customFieldValues[field.key])
14342
- : 'Not set' })] }) }, field.key))), jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Email Address" }), jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] })] }), editingSection !== 'email' && (jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && (jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Password" }), jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Phone Number" }), jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), showDeleteAccount && (jsxs("section", { className: "account-section danger-zone", children: [jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxs("div", { className: "delete-confirm", children: [jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsx("button", { type: "button", onClick: () => {
14117
+ : 'Not set' })] }) }, field.key))), jsx("button", { type: "button", onClick: () => setEditingSection('customFields'), className: "auth-button button-secondary", style: { marginTop: '8px' }, children: "Edit Information" })] })) : (jsxs("form", { onSubmit: handleUpdateCustomFields, className: "edit-form", children: [editableCustomFields.map((field) => (jsx(SchemaFieldRenderer, { field: field, value: customFieldValues[field.key], onChange: handleCustomFieldChange, disabled: loading }, field.key))), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Saving...' : 'Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showEmailSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Email Address" }), jsxs("div", { className: "field-value", children: [profile?.email || 'Not set', profile?.emailVerified && (jsx("span", { className: "verification-badge verified", children: "Verified" })), profile?.email && !profile?.emailVerified && (jsx("span", { className: "verification-badge unverified", children: "Unverified" }))] }), isGoogleOnly && (jsx("div", { className: "field-hint", style: { fontSize: '0.8rem', color: 'var(--color-muted-foreground, #888)', marginTop: '4px' }, children: "This email is managed by your Google account" }))] }), editingSection !== 'email' && !isGoogleOnly && !isPhoneOnly && (jsx("button", { type: "button", onClick: () => setEditingSection('email'), className: "auth-button button-secondary", children: "Change Email" }))] }), editingSection === 'email' && !isGoogleOnly && !isPhoneOnly && (jsxs("form", { onSubmit: handleChangeEmail, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newEmail", children: "New Email" }), jsx("input", { id: "newEmail", type: "email", value: newEmail, onChange: (e) => setNewEmail(e.target.value), placeholder: "new.email@example.com", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "emailPassword", children: "Confirm Password" }), jsx("input", { id: "emailPassword", type: "password", value: emailPassword, onChange: (e) => setEmailPassword(e.target.value), placeholder: "Enter your password", className: "auth-input", required: true })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Email' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPasswordSection && !isGoogleOnly && !isPhoneOnly && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Password" }), jsx("div", { className: "field-value", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), editingSection !== 'password' && (jsx("button", { type: "button", onClick: () => setEditingSection('password'), className: "auth-button button-secondary", children: "Change Password" }))] }), editingSection === 'password' && (jsxs("form", { onSubmit: handleChangePassword, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "currentPassword", children: "Current Password" }), jsx("input", { id: "currentPassword", type: "password", value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value), placeholder: "Enter current password", className: "auth-input", required: true })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPassword", children: "New Password" }), jsx("input", { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "Enter new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "confirmPassword", children: "Confirm New Password" }), jsx("input", { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), placeholder: "Confirm new password", className: "auth-input", required: true, minLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Changing...' : 'Change Password' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] })), showPhoneSection && (jsxs("section", { className: "account-section", children: [jsxs("div", { className: "account-field", children: [jsxs("div", { className: "field-info", children: [jsx("label", { className: "field-label", children: "Phone Number" }), jsx("div", { className: "field-value", children: profile?.phoneNumber || 'Not set' })] }), editingSection !== 'phone' && (jsx("button", { type: "button", onClick: () => setEditingSection('phone'), className: "auth-button button-secondary", children: "Change Phone" }))] }), editingSection === 'phone' && (jsxs("form", { onSubmit: handleUpdatePhone, className: "edit-form", children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "newPhone", children: "New Phone Number" }), jsx("input", { id: "newPhone", type: "tel", value: newPhone, onChange: (e) => setNewPhone(e.target.value), placeholder: "+1234567890", className: "auth-input", required: true })] }), !phoneCodeSent ? (jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleSendPhoneCode, className: "auth-button", disabled: loading || !newPhone, children: loading ? 'Sending...' : 'Send Code' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })) : (jsxs(Fragment, { children: [jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "phoneCode", children: "Verification Code" }), jsx("input", { id: "phoneCode", type: "text", value: phoneCode, onChange: (e) => setPhoneCode(e.target.value), placeholder: "Enter 6-digit code", className: "auth-input", required: true, maxLength: 6 })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "submit", className: "auth-button", disabled: loading, children: loading ? 'Verifying...' : 'Verify & Save' }), jsx("button", { type: "button", onClick: cancelEdit, className: "auth-button button-secondary", children: "Cancel" })] })] }))] }))] })), showDeleteAccount && (jsxs("section", { className: "account-section danger-zone", children: [jsx("h3", { className: "section-title text-danger", children: "Danger Zone" }), !showDeleteConfirm ? (jsx("button", { type: "button", onClick: () => setShowDeleteConfirm(true), className: "auth-button button-danger", children: "Delete Account" })) : (jsxs("div", { className: "delete-confirm", children: [jsx("p", { className: "warning-text", children: "\u26A0\uFE0F This action cannot be undone. This will permanently delete your account and all associated data." }), hasPassword !== false && (jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deletePassword", children: "Confirm Password" }), jsx("input", { id: "deletePassword", type: "password", value: deletePassword, onChange: (e) => setDeletePassword(e.target.value), placeholder: "Enter your password", className: "auth-input" })] })), jsxs("div", { className: "form-group", children: [jsx("label", { htmlFor: "deleteConfirm", children: "Type DELETE to confirm" }), jsx("input", { id: "deleteConfirm", type: "text", value: deleteConfirmText, onChange: (e) => setDeleteConfirmText(e.target.value), placeholder: "DELETE", className: "auth-input" })] }), jsxs("div", { className: "button-group", children: [jsx("button", { type: "button", onClick: handleDeleteAccount, className: "auth-button button-danger", disabled: loading, children: loading ? 'Deleting...' : 'Permanently Delete Account' }), jsx("button", { type: "button", onClick: () => {
14343
14118
  setShowDeleteConfirm(false);
14344
14119
  setDeletePassword('');
14345
14120
  setDeleteConfirmText('');