@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/api.d.ts.map +1 -1
- package/dist/components/AccountManagement.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts +1 -31
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.esm.js +203 -428
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +203 -428
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +26 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11663
|
+
// No valid session, awaiting login
|
|
11669
11664
|
}
|
|
11670
11665
|
}
|
|
11671
11666
|
catch (error) {
|
|
11672
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
12189
|
-
const
|
|
12190
|
-
const percentUsed = (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
12672
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
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
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
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,
|
|
13416
|
-
googleClientId,
|
|
13417
|
-
serverClientId: googleClientId,
|
|
13170
|
+
clientId,
|
|
13171
|
+
googleClientId,
|
|
13172
|
+
serverClientId: googleClientId,
|
|
13418
13173
|
callbackId,
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
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
|
-
|
|
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}
|
|
13461
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('');
|