@proveanything/smartlinks-auth-ui 0.1.20 → 0.1.22
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/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts +10 -8
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.esm.js +384 -70
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +384 -70
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +32 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/tokenStorage.d.ts +3 -0
- package/dist/utils/tokenStorage.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -10702,14 +10702,18 @@ class AuthAPI {
|
|
|
10702
10702
|
});
|
|
10703
10703
|
}
|
|
10704
10704
|
async fetchConfig() {
|
|
10705
|
+
console.log('[AuthAPI] 📋 fetchConfig called with clientId:', this.clientId);
|
|
10705
10706
|
this.log.log('fetchConfig called with clientId:', this.clientId);
|
|
10706
10707
|
try {
|
|
10708
|
+
console.log('[AuthAPI] 🌐 Calling smartlinks.authKit.load() - this uses current SDK config (including proxyMode)');
|
|
10707
10709
|
this.log.log('Calling smartlinks.authKit.load...');
|
|
10708
10710
|
const result = await smartlinks.authKit.load(this.clientId);
|
|
10711
|
+
console.log('[AuthAPI] ✅ smartlinks.authKit.load returned:', result);
|
|
10709
10712
|
this.log.log('smartlinks.authKit.load returned:', result);
|
|
10710
10713
|
return result;
|
|
10711
10714
|
}
|
|
10712
10715
|
catch (error) {
|
|
10716
|
+
console.log('[AuthAPI] ❌ Failed to fetch UI config:', error);
|
|
10713
10717
|
this.log.warn('Failed to fetch UI config, using defaults:', error);
|
|
10714
10718
|
return {
|
|
10715
10719
|
branding: {
|
|
@@ -11128,6 +11132,7 @@ const TOKEN_KEY = 'token';
|
|
|
11128
11132
|
const USER_KEY = 'user';
|
|
11129
11133
|
const ACCOUNT_DATA_KEY = 'account_data';
|
|
11130
11134
|
const ACCOUNT_INFO_KEY = 'account_info';
|
|
11135
|
+
const CONTACT_ID_KEY = 'contact_id';
|
|
11131
11136
|
const ACCOUNT_INFO_TTL = 5 * 60 * 1000; // 5 minutes default
|
|
11132
11137
|
/**
|
|
11133
11138
|
* Token Storage Layer
|
|
@@ -11178,6 +11183,7 @@ const tokenStorage = {
|
|
|
11178
11183
|
await this.clearUser();
|
|
11179
11184
|
await this.clearAccountData();
|
|
11180
11185
|
await this.clearAccountInfo();
|
|
11186
|
+
await this.clearContactId();
|
|
11181
11187
|
},
|
|
11182
11188
|
async saveAccountData(data) {
|
|
11183
11189
|
const storage = await getStorage();
|
|
@@ -11215,20 +11221,69 @@ const tokenStorage = {
|
|
|
11215
11221
|
const storage = await getStorage();
|
|
11216
11222
|
await storage.removeItem(ACCOUNT_INFO_KEY);
|
|
11217
11223
|
},
|
|
11224
|
+
async saveContactId(contactId) {
|
|
11225
|
+
const storage = await getStorage();
|
|
11226
|
+
await storage.setItem(CONTACT_ID_KEY, contactId);
|
|
11227
|
+
},
|
|
11228
|
+
async getContactId() {
|
|
11229
|
+
const storage = await getStorage();
|
|
11230
|
+
return await storage.getItem(CONTACT_ID_KEY);
|
|
11231
|
+
},
|
|
11232
|
+
async clearContactId() {
|
|
11233
|
+
const storage = await getStorage();
|
|
11234
|
+
await storage.removeItem(CONTACT_ID_KEY);
|
|
11235
|
+
},
|
|
11218
11236
|
};
|
|
11219
11237
|
|
|
11220
11238
|
const AuthContext = createContext(undefined);
|
|
11221
|
-
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false
|
|
11239
|
+
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
|
|
11240
|
+
// Contact & Interaction features
|
|
11241
|
+
collectionId, enableContactSync, enableInteractionTracking, interactionAppId, interactionConfig, }) => {
|
|
11222
11242
|
const [user, setUser] = useState(null);
|
|
11223
11243
|
const [token, setToken] = useState(null);
|
|
11224
11244
|
const [accountData, setAccountData] = useState(null);
|
|
11225
11245
|
const [accountInfo, setAccountInfo] = useState(null);
|
|
11226
11246
|
const [isLoading, setIsLoading] = useState(true);
|
|
11247
|
+
const [isVerified, setIsVerified] = useState(false);
|
|
11248
|
+
const [isOnline, setIsOnline] = useState(typeof navigator !== 'undefined' ? navigator.onLine : true);
|
|
11249
|
+
// Contact state
|
|
11250
|
+
const [contact, setContact] = useState(null);
|
|
11251
|
+
const [contactId, setContactId] = useState(null);
|
|
11227
11252
|
const callbacksRef = useRef(new Set());
|
|
11228
|
-
// Initialization guard to prevent concurrent runs (NOT persisted across remounts)
|
|
11229
11253
|
const initializingRef = useRef(false);
|
|
11254
|
+
const pendingVerificationRef = useRef(false);
|
|
11255
|
+
// Default to enabled if collectionId is provided
|
|
11256
|
+
const shouldSyncContacts = enableContactSync ?? !!collectionId;
|
|
11257
|
+
const shouldTrackInteractions = enableInteractionTracking ?? !!collectionId;
|
|
11258
|
+
// Helper to detect if an error is a network error vs auth error
|
|
11259
|
+
const isNetworkError = useCallback((error) => {
|
|
11260
|
+
if (!error)
|
|
11261
|
+
return false;
|
|
11262
|
+
const errorMessage = error?.message?.toLowerCase() || '';
|
|
11263
|
+
const errorName = error?.name?.toLowerCase() || '';
|
|
11264
|
+
if (errorName === 'typeerror' && errorMessage.includes('fetch'))
|
|
11265
|
+
return true;
|
|
11266
|
+
if (errorMessage.includes('network') || errorMessage.includes('offline'))
|
|
11267
|
+
return true;
|
|
11268
|
+
if (errorMessage.includes('failed to fetch'))
|
|
11269
|
+
return true;
|
|
11270
|
+
if (errorMessage.includes('net::err_'))
|
|
11271
|
+
return true;
|
|
11272
|
+
if (errorMessage.includes('timeout'))
|
|
11273
|
+
return true;
|
|
11274
|
+
if (errorMessage.includes('dns'))
|
|
11275
|
+
return true;
|
|
11276
|
+
if (error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT')
|
|
11277
|
+
return true;
|
|
11278
|
+
const status = error?.status || error?.response?.status;
|
|
11279
|
+
if (status === 401 || status === 403)
|
|
11280
|
+
return false;
|
|
11281
|
+
if (typeof navigator !== 'undefined' && !navigator.onLine)
|
|
11282
|
+
return true;
|
|
11283
|
+
return false;
|
|
11284
|
+
}, []);
|
|
11230
11285
|
// Notify all subscribers of auth state changes
|
|
11231
|
-
const notifyAuthStateChange = useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo) => {
|
|
11286
|
+
const notifyAuthStateChange = useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo, verified, currentContact, currentContactId) => {
|
|
11232
11287
|
callbacksRef.current.forEach(callback => {
|
|
11233
11288
|
try {
|
|
11234
11289
|
callback({
|
|
@@ -11236,7 +11291,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11236
11291
|
user: currentUser,
|
|
11237
11292
|
token: currentToken,
|
|
11238
11293
|
accountData: currentAccountData,
|
|
11239
|
-
accountInfo: currentAccountInfo
|
|
11294
|
+
accountInfo: currentAccountInfo,
|
|
11295
|
+
isVerified: verified,
|
|
11296
|
+
contact: currentContact,
|
|
11297
|
+
contactId: currentContactId,
|
|
11240
11298
|
});
|
|
11241
11299
|
}
|
|
11242
11300
|
catch (error) {
|
|
@@ -11244,9 +11302,128 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11244
11302
|
}
|
|
11245
11303
|
});
|
|
11246
11304
|
}, []);
|
|
11247
|
-
//
|
|
11305
|
+
// Sync contact to Smartlinks (non-blocking)
|
|
11306
|
+
const syncContact = useCallback(async (authUser, customFields) => {
|
|
11307
|
+
if (!collectionId || !shouldSyncContacts) {
|
|
11308
|
+
console.log('[AuthContext] Contact sync skipped: no collectionId or disabled');
|
|
11309
|
+
return null;
|
|
11310
|
+
}
|
|
11311
|
+
try {
|
|
11312
|
+
console.log('[AuthContext] Syncing contact for user:', authUser.uid);
|
|
11313
|
+
const result = await smartlinks.contact.publicUpsert(collectionId, {
|
|
11314
|
+
userId: authUser.uid,
|
|
11315
|
+
email: authUser.email,
|
|
11316
|
+
displayName: authUser.displayName,
|
|
11317
|
+
phone: authUser.phoneNumber,
|
|
11318
|
+
customFields: customFields || {},
|
|
11319
|
+
source: 'authkit',
|
|
11320
|
+
});
|
|
11321
|
+
console.log('[AuthContext] Contact synced:', result.contactId);
|
|
11322
|
+
// Store contact ID locally
|
|
11323
|
+
if (!proxyMode) {
|
|
11324
|
+
await tokenStorage.saveContactId(result.contactId);
|
|
11325
|
+
}
|
|
11326
|
+
setContactId(result.contactId);
|
|
11327
|
+
// Fetch full contact to get customFields
|
|
11328
|
+
try {
|
|
11329
|
+
const fullContact = await smartlinks.contact.lookup(collectionId, {
|
|
11330
|
+
email: authUser.email
|
|
11331
|
+
});
|
|
11332
|
+
setContact(fullContact);
|
|
11333
|
+
notifyAuthStateChange('CONTACT_SYNCED', authUser, token, accountData, accountInfo, isVerified, fullContact, result.contactId);
|
|
11334
|
+
}
|
|
11335
|
+
catch (lookupErr) {
|
|
11336
|
+
console.warn('[AuthContext] Failed to lookup full contact:', lookupErr);
|
|
11337
|
+
}
|
|
11338
|
+
return result.contactId;
|
|
11339
|
+
}
|
|
11340
|
+
catch (err) {
|
|
11341
|
+
console.warn('[AuthContext] Contact sync failed (non-blocking):', err);
|
|
11342
|
+
return null;
|
|
11343
|
+
}
|
|
11344
|
+
}, [collectionId, shouldSyncContacts, proxyMode, token, accountData, accountInfo, isVerified, notifyAuthStateChange]);
|
|
11345
|
+
// Track interaction event (non-blocking)
|
|
11346
|
+
const trackInteraction = useCallback(async (eventType, userId, currentContactId, metadata) => {
|
|
11347
|
+
if (!collectionId || !shouldTrackInteractions) {
|
|
11348
|
+
console.log('[AuthContext] Interaction tracking skipped: no collectionId or disabled');
|
|
11349
|
+
return;
|
|
11350
|
+
}
|
|
11351
|
+
const interactionIdMap = {
|
|
11352
|
+
login: interactionConfig?.login || 'authkit.login',
|
|
11353
|
+
logout: interactionConfig?.logout || 'authkit.logout',
|
|
11354
|
+
signup: interactionConfig?.signup || 'authkit.signup',
|
|
11355
|
+
session_restore: interactionConfig?.sessionRestore,
|
|
11356
|
+
};
|
|
11357
|
+
const interactionId = interactionIdMap[eventType];
|
|
11358
|
+
if (!interactionId) {
|
|
11359
|
+
console.log(`[AuthContext] No interaction ID for ${eventType}, skipping`);
|
|
11360
|
+
return;
|
|
11361
|
+
}
|
|
11362
|
+
try {
|
|
11363
|
+
console.log(`[AuthContext] Tracking interaction: ${interactionId}`);
|
|
11364
|
+
await smartlinks.interactions.submitPublicEvent(collectionId, {
|
|
11365
|
+
collectionId,
|
|
11366
|
+
interactionId,
|
|
11367
|
+
userId,
|
|
11368
|
+
contactId: currentContactId || undefined,
|
|
11369
|
+
appId: interactionAppId,
|
|
11370
|
+
eventType,
|
|
11371
|
+
outcome: 'completed',
|
|
11372
|
+
metadata: {
|
|
11373
|
+
...metadata,
|
|
11374
|
+
timestamp: new Date().toISOString(),
|
|
11375
|
+
source: 'authkit',
|
|
11376
|
+
},
|
|
11377
|
+
});
|
|
11378
|
+
console.log(`[AuthContext] Tracked interaction: ${interactionId}`);
|
|
11379
|
+
notifyAuthStateChange('INTERACTION_TRACKED', user, token, accountData, accountInfo, isVerified, contact, contactId);
|
|
11380
|
+
}
|
|
11381
|
+
catch (err) {
|
|
11382
|
+
console.warn('[AuthContext] Interaction tracking failed (non-blocking):', err);
|
|
11383
|
+
}
|
|
11384
|
+
}, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
|
|
11385
|
+
// Get contact (with optional refresh)
|
|
11386
|
+
const getContact = useCallback(async (forceRefresh = false) => {
|
|
11387
|
+
if (!collectionId) {
|
|
11388
|
+
console.log('[AuthContext] getContact: no collectionId');
|
|
11389
|
+
return null;
|
|
11390
|
+
}
|
|
11391
|
+
// Need either email or userId to lookup contact
|
|
11392
|
+
if (!user?.email && !user?.uid) {
|
|
11393
|
+
console.log('[AuthContext] getContact: no user email or uid');
|
|
11394
|
+
return null;
|
|
11395
|
+
}
|
|
11396
|
+
if (contact && !forceRefresh) {
|
|
11397
|
+
return contact;
|
|
11398
|
+
}
|
|
11399
|
+
try {
|
|
11400
|
+
// Prefer email lookup, fallback to userId for phone-only users
|
|
11401
|
+
const lookupParams = user.email
|
|
11402
|
+
? { email: user.email }
|
|
11403
|
+
: { userId: user.uid };
|
|
11404
|
+
console.log('[AuthContext] Fetching contact with:', lookupParams);
|
|
11405
|
+
const result = await smartlinks.contact.lookup(collectionId, lookupParams);
|
|
11406
|
+
setContact(result);
|
|
11407
|
+
setContactId(result.contactId);
|
|
11408
|
+
return result;
|
|
11409
|
+
}
|
|
11410
|
+
catch (err) {
|
|
11411
|
+
console.warn('[AuthContext] Failed to get contact:', err);
|
|
11412
|
+
return null;
|
|
11413
|
+
}
|
|
11414
|
+
}, [collectionId, user, contact]);
|
|
11415
|
+
// Update contact custom fields
|
|
11416
|
+
const updateContactCustomFields = useCallback(async (customFields) => {
|
|
11417
|
+
if (!collectionId || !contactId) {
|
|
11418
|
+
throw new Error('No contact to update. Ensure collectionId is provided and user is synced.');
|
|
11419
|
+
}
|
|
11420
|
+
console.log('[AuthContext] Updating contact custom fields:', contactId);
|
|
11421
|
+
const updated = await smartlinks.contact.update(collectionId, contactId, { customFields });
|
|
11422
|
+
setContact(updated);
|
|
11423
|
+
return updated;
|
|
11424
|
+
}, [collectionId, contactId]);
|
|
11425
|
+
// Initialize auth state
|
|
11248
11426
|
useEffect(() => {
|
|
11249
|
-
// Prevent concurrent initialization only
|
|
11250
11427
|
if (initializingRef.current) {
|
|
11251
11428
|
console.log('[AuthContext] Skipping initialization - already in progress');
|
|
11252
11429
|
return;
|
|
@@ -11260,12 +11437,9 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11260
11437
|
console.log('[AuthContext] Proxy mode: checking for existing session via auth.getAccount()');
|
|
11261
11438
|
try {
|
|
11262
11439
|
const accountResponse = await smartlinks.auth.getAccount();
|
|
11263
|
-
// auth.getAccount() always returns a response object, but uid will be
|
|
11264
|
-
// empty/undefined if no user is logged in
|
|
11265
11440
|
const accountAny = accountResponse;
|
|
11266
11441
|
const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
|
|
11267
11442
|
if (hasValidSession && isMounted) {
|
|
11268
|
-
// User is logged in with valid account
|
|
11269
11443
|
const userFromAccount = {
|
|
11270
11444
|
uid: accountAny.uid,
|
|
11271
11445
|
email: accountAny?.email,
|
|
@@ -11275,8 +11449,11 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11275
11449
|
setUser(userFromAccount);
|
|
11276
11450
|
setAccountData(accountResponse);
|
|
11277
11451
|
setAccountInfo(accountResponse);
|
|
11452
|
+
setIsVerified(true);
|
|
11278
11453
|
console.log('[AuthContext] Proxy mode: initialized from parent account, uid:', accountAny.uid);
|
|
11279
|
-
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse);
|
|
11454
|
+
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
|
|
11455
|
+
// Sync contact in background (proxy mode)
|
|
11456
|
+
syncContact(userFromAccount, accountResponse);
|
|
11280
11457
|
}
|
|
11281
11458
|
else if (isMounted) {
|
|
11282
11459
|
console.log('[AuthContext] Proxy mode: no valid session (no uid), awaiting login');
|
|
@@ -11291,29 +11468,56 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11291
11468
|
}
|
|
11292
11469
|
return;
|
|
11293
11470
|
}
|
|
11294
|
-
// STANDALONE MODE:
|
|
11471
|
+
// STANDALONE MODE: Optimistic restoration with background verification
|
|
11295
11472
|
const storedToken = await tokenStorage.getToken();
|
|
11296
11473
|
const storedUser = await tokenStorage.getUser();
|
|
11297
11474
|
const storedAccountData = await tokenStorage.getAccountData();
|
|
11475
|
+
const storedContactId = await tokenStorage.getContactId();
|
|
11298
11476
|
if (storedToken && storedUser) {
|
|
11299
|
-
|
|
11477
|
+
if (isMounted) {
|
|
11478
|
+
setToken(storedToken.token);
|
|
11479
|
+
setUser(storedUser);
|
|
11480
|
+
setAccountData(storedAccountData);
|
|
11481
|
+
if (storedContactId) {
|
|
11482
|
+
setContactId(storedContactId);
|
|
11483
|
+
}
|
|
11484
|
+
console.log('[AuthContext] Session restored optimistically (pending verification)');
|
|
11485
|
+
notifyAuthStateChange('SESSION_RESTORED_OFFLINE', storedUser, storedToken.token, storedAccountData || null, null, false, null, storedContactId);
|
|
11486
|
+
}
|
|
11487
|
+
// BACKGROUND: Verify token
|
|
11300
11488
|
try {
|
|
11301
|
-
console.log('[AuthContext] Verifying stored token...');
|
|
11489
|
+
console.log('[AuthContext] Verifying stored token in background...');
|
|
11302
11490
|
await smartlinks.auth.verifyToken(storedToken.token);
|
|
11303
|
-
// Only set state if verification succeeded and component still mounted
|
|
11304
11491
|
if (isMounted) {
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
//
|
|
11310
|
-
|
|
11492
|
+
setIsVerified(true);
|
|
11493
|
+
pendingVerificationRef.current = false;
|
|
11494
|
+
console.log('[AuthContext] Session verified successfully');
|
|
11495
|
+
notifyAuthStateChange('SESSION_VERIFIED', storedUser, storedToken.token, storedAccountData || null, null, true, null, storedContactId);
|
|
11496
|
+
// Track session restore interaction (optional)
|
|
11497
|
+
if (interactionConfig?.sessionRestore) {
|
|
11498
|
+
trackInteraction('session_restore', storedUser.uid, storedContactId);
|
|
11499
|
+
}
|
|
11311
11500
|
}
|
|
11312
11501
|
}
|
|
11313
11502
|
catch (err) {
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11503
|
+
if (isNetworkError(err)) {
|
|
11504
|
+
console.warn('[AuthContext] Network error during verification, will retry on reconnect:', err);
|
|
11505
|
+
pendingVerificationRef.current = true;
|
|
11506
|
+
}
|
|
11507
|
+
else {
|
|
11508
|
+
console.warn('[AuthContext] Token verification failed (auth error), clearing credentials:', err);
|
|
11509
|
+
if (isMounted) {
|
|
11510
|
+
setToken(null);
|
|
11511
|
+
setUser(null);
|
|
11512
|
+
setAccountData(null);
|
|
11513
|
+
setContactId(null);
|
|
11514
|
+
setContact(null);
|
|
11515
|
+
setIsVerified(false);
|
|
11516
|
+
pendingVerificationRef.current = false;
|
|
11517
|
+
}
|
|
11518
|
+
await tokenStorage.clearAll();
|
|
11519
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
11520
|
+
}
|
|
11317
11521
|
}
|
|
11318
11522
|
}
|
|
11319
11523
|
// Load cached account info if available
|
|
@@ -11335,18 +11539,16 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11335
11539
|
}
|
|
11336
11540
|
};
|
|
11337
11541
|
initializeAuth();
|
|
11338
|
-
// Cleanup for hot reload
|
|
11339
11542
|
return () => {
|
|
11340
11543
|
isMounted = false;
|
|
11341
11544
|
};
|
|
11342
|
-
}, [proxyMode, notifyAuthStateChange]);
|
|
11545
|
+
}, [proxyMode, notifyAuthStateChange, isNetworkError, syncContact, trackInteraction, interactionConfig?.sessionRestore]);
|
|
11343
11546
|
// Listen for parent auth state changes (proxy mode only)
|
|
11344
11547
|
useEffect(() => {
|
|
11345
11548
|
if (!proxyMode)
|
|
11346
11549
|
return;
|
|
11347
11550
|
console.log('[AuthContext] Proxy mode: setting up parent message listener');
|
|
11348
11551
|
const handleParentMessage = (event) => {
|
|
11349
|
-
// Handle auth state pushed from parent
|
|
11350
11552
|
if (event.data?.type === 'smartlinks:authkit:state') {
|
|
11351
11553
|
const { user: parentUser, accountData: parentAccountData, authenticated } = event.data.payload || {};
|
|
11352
11554
|
console.log('[AuthContext] Proxy mode: received state from parent:', { authenticated });
|
|
@@ -11360,15 +11562,20 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11360
11562
|
setUser(userObj);
|
|
11361
11563
|
setAccountData(parentAccountData || null);
|
|
11362
11564
|
setAccountInfo(parentAccountData || null);
|
|
11363
|
-
|
|
11565
|
+
setIsVerified(true);
|
|
11566
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null, true);
|
|
11567
|
+
// Sync contact on cross-tab state
|
|
11568
|
+
syncContact(userObj, parentAccountData);
|
|
11364
11569
|
}
|
|
11365
11570
|
else {
|
|
11366
|
-
// Parent indicates no session / logged out
|
|
11367
11571
|
setUser(null);
|
|
11368
11572
|
setToken(null);
|
|
11369
11573
|
setAccountData(null);
|
|
11370
11574
|
setAccountInfo(null);
|
|
11371
|
-
|
|
11575
|
+
setContact(null);
|
|
11576
|
+
setContactId(null);
|
|
11577
|
+
setIsVerified(false);
|
|
11578
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
11372
11579
|
}
|
|
11373
11580
|
}
|
|
11374
11581
|
};
|
|
@@ -11377,54 +11584,60 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11377
11584
|
console.log('[AuthContext] Proxy mode: cleaning up parent message listener');
|
|
11378
11585
|
window.removeEventListener('message', handleParentMessage);
|
|
11379
11586
|
};
|
|
11380
|
-
}, [proxyMode, notifyAuthStateChange]);
|
|
11587
|
+
}, [proxyMode, notifyAuthStateChange, syncContact]);
|
|
11381
11588
|
// Cross-tab synchronization - standalone mode only
|
|
11382
11589
|
useEffect(() => {
|
|
11383
11590
|
if (proxyMode)
|
|
11384
|
-
return;
|
|
11591
|
+
return;
|
|
11385
11592
|
console.log('[AuthContext] Setting up cross-tab synchronization');
|
|
11386
11593
|
const unsubscribe = onStorageChange(async (event) => {
|
|
11387
11594
|
console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
|
|
11388
11595
|
try {
|
|
11389
11596
|
if (event.type === 'clear') {
|
|
11390
|
-
// Another tab cleared all storage (logout)
|
|
11391
11597
|
console.log('[AuthContext] Detected logout in another tab');
|
|
11392
11598
|
setToken(null);
|
|
11393
11599
|
setUser(null);
|
|
11394
11600
|
setAccountData(null);
|
|
11395
11601
|
setAccountInfo(null);
|
|
11602
|
+
setContact(null);
|
|
11603
|
+
setContactId(null);
|
|
11604
|
+
setIsVerified(false);
|
|
11396
11605
|
smartlinks.auth.logout();
|
|
11397
|
-
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
|
|
11606
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
|
|
11398
11607
|
}
|
|
11399
11608
|
else if (event.type === 'remove' && (event.key === 'token' || event.key === 'user')) {
|
|
11400
|
-
// Another tab removed token or user (logout)
|
|
11401
11609
|
console.log('[AuthContext] Detected token/user removal in another tab');
|
|
11402
11610
|
setToken(null);
|
|
11403
11611
|
setUser(null);
|
|
11404
11612
|
setAccountData(null);
|
|
11405
11613
|
setAccountInfo(null);
|
|
11614
|
+
setContact(null);
|
|
11615
|
+
setContactId(null);
|
|
11616
|
+
setIsVerified(false);
|
|
11406
11617
|
smartlinks.auth.logout();
|
|
11407
|
-
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
|
|
11618
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
|
|
11408
11619
|
}
|
|
11409
11620
|
else if (event.type === 'set' && event.key === 'token') {
|
|
11410
|
-
// Another tab set a new token (login)
|
|
11411
11621
|
console.log('[AuthContext] Detected login in another tab');
|
|
11412
11622
|
const storedToken = await tokenStorage.getToken();
|
|
11413
11623
|
const storedUser = await tokenStorage.getUser();
|
|
11414
11624
|
const storedAccountData = await tokenStorage.getAccountData();
|
|
11625
|
+
const storedContactId = await tokenStorage.getContactId();
|
|
11415
11626
|
if (storedToken && storedUser) {
|
|
11416
11627
|
setToken(storedToken.token);
|
|
11417
11628
|
setUser(storedUser);
|
|
11418
11629
|
setAccountData(storedAccountData);
|
|
11419
|
-
|
|
11630
|
+
if (storedContactId) {
|
|
11631
|
+
setContactId(storedContactId);
|
|
11632
|
+
}
|
|
11633
|
+
setIsVerified(true);
|
|
11420
11634
|
smartlinks.auth.verifyToken(storedToken.token).catch(err => {
|
|
11421
11635
|
console.warn('[AuthContext] Failed to restore bearer token from cross-tab sync:', err);
|
|
11422
11636
|
});
|
|
11423
|
-
notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData);
|
|
11637
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData, null, true, null, storedContactId);
|
|
11424
11638
|
}
|
|
11425
11639
|
}
|
|
11426
11640
|
else if (event.type === 'set' && event.key === 'account_info') {
|
|
11427
|
-
// Another tab fetched fresh account info
|
|
11428
11641
|
const cached = await tokenStorage.getAccountInfo();
|
|
11429
11642
|
if (cached && !cached.isStale) {
|
|
11430
11643
|
setAccountInfo(cached.data);
|
|
@@ -11441,7 +11654,7 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11441
11654
|
unsubscribe();
|
|
11442
11655
|
};
|
|
11443
11656
|
}, [proxyMode, notifyAuthStateChange]);
|
|
11444
|
-
const login = useCallback(async (authToken, authUser, authAccountData) => {
|
|
11657
|
+
const login = useCallback(async (authToken, authUser, authAccountData, isNewUser) => {
|
|
11445
11658
|
try {
|
|
11446
11659
|
// Only persist to storage in standalone mode
|
|
11447
11660
|
if (!proxyMode) {
|
|
@@ -11450,7 +11663,6 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11450
11663
|
if (authAccountData) {
|
|
11451
11664
|
await tokenStorage.saveAccountData(authAccountData);
|
|
11452
11665
|
}
|
|
11453
|
-
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
11454
11666
|
smartlinks.auth.verifyToken(authToken).catch(err => {
|
|
11455
11667
|
console.warn('Failed to set bearer token on login:', err);
|
|
11456
11668
|
});
|
|
@@ -11459,8 +11671,9 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11459
11671
|
setToken(authToken);
|
|
11460
11672
|
setUser(authUser);
|
|
11461
11673
|
setAccountData(authAccountData || null);
|
|
11674
|
+
setIsVerified(true);
|
|
11675
|
+
pendingVerificationRef.current = false;
|
|
11462
11676
|
// Cross-iframe auth state synchronization
|
|
11463
|
-
// Always notify parent frame of login (both modes, but especially important in proxy mode)
|
|
11464
11677
|
if (iframe.isIframe()) {
|
|
11465
11678
|
console.log('[AuthContext] Notifying parent of login via postMessage');
|
|
11466
11679
|
iframe.sendParentCustom('smartlinks:authkit:login', {
|
|
@@ -11469,7 +11682,13 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11469
11682
|
accountData: authAccountData || null
|
|
11470
11683
|
});
|
|
11471
11684
|
}
|
|
11472
|
-
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
|
|
11685
|
+
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, null, true);
|
|
11686
|
+
// Sync contact (non-blocking)
|
|
11687
|
+
const newContactId = await syncContact(authUser, authAccountData);
|
|
11688
|
+
// Track interaction (non-blocking)
|
|
11689
|
+
trackInteraction(isNewUser ? 'signup' : 'login', authUser.uid, newContactId, {
|
|
11690
|
+
provider: authUser.email ? 'email' : 'phone',
|
|
11691
|
+
});
|
|
11473
11692
|
// Optionally preload account info on login (standalone mode only)
|
|
11474
11693
|
if (!proxyMode && preloadAccountInfo) {
|
|
11475
11694
|
getAccount(true).catch(error => {
|
|
@@ -11481,8 +11700,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11481
11700
|
console.error('Failed to save auth data to storage:', error);
|
|
11482
11701
|
throw error;
|
|
11483
11702
|
}
|
|
11484
|
-
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo]);
|
|
11703
|
+
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction]);
|
|
11485
11704
|
const logout = useCallback(async () => {
|
|
11705
|
+
const currentUser = user;
|
|
11706
|
+
const currentContactId = contactId;
|
|
11486
11707
|
try {
|
|
11487
11708
|
// Only clear persistent storage in standalone mode
|
|
11488
11709
|
if (!proxyMode) {
|
|
@@ -11494,21 +11715,27 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11494
11715
|
setUser(null);
|
|
11495
11716
|
setAccountData(null);
|
|
11496
11717
|
setAccountInfo(null);
|
|
11718
|
+
setContact(null);
|
|
11719
|
+
setContactId(null);
|
|
11720
|
+
setIsVerified(false);
|
|
11721
|
+
pendingVerificationRef.current = false;
|
|
11497
11722
|
// Cross-iframe auth state synchronization
|
|
11498
|
-
// Always notify parent frame of logout
|
|
11499
11723
|
if (iframe.isIframe()) {
|
|
11500
11724
|
console.log('[AuthContext] Notifying parent of logout via postMessage');
|
|
11501
11725
|
iframe.sendParentCustom('smartlinks:authkit:logout', {});
|
|
11502
11726
|
}
|
|
11503
|
-
notifyAuthStateChange('LOGOUT', null, null, null);
|
|
11727
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
11728
|
+
// Track logout interaction (fire and forget)
|
|
11729
|
+
if (currentUser && collectionId && shouldTrackInteractions) {
|
|
11730
|
+
trackInteraction('logout', currentUser.uid, currentContactId);
|
|
11731
|
+
}
|
|
11504
11732
|
}
|
|
11505
11733
|
catch (error) {
|
|
11506
11734
|
console.error('Failed to clear auth data from storage:', error);
|
|
11507
11735
|
}
|
|
11508
|
-
}, [proxyMode, notifyAuthStateChange]);
|
|
11736
|
+
}, [proxyMode, notifyAuthStateChange, user, contactId, collectionId, shouldTrackInteractions, trackInteraction]);
|
|
11509
11737
|
const getToken = useCallback(async () => {
|
|
11510
11738
|
if (proxyMode) {
|
|
11511
|
-
// In proxy mode, token is managed by parent - return memory state
|
|
11512
11739
|
return token;
|
|
11513
11740
|
}
|
|
11514
11741
|
const storedToken = await tokenStorage.getToken();
|
|
@@ -11517,23 +11744,19 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11517
11744
|
const refreshToken = useCallback(async () => {
|
|
11518
11745
|
throw new Error('Token refresh must be implemented via your backend API');
|
|
11519
11746
|
}, []);
|
|
11520
|
-
// Get account with intelligent caching (or direct parent fetch in proxy mode)
|
|
11521
11747
|
const getAccount = useCallback(async (forceRefresh = false) => {
|
|
11522
11748
|
try {
|
|
11523
11749
|
if (proxyMode) {
|
|
11524
|
-
// PROXY MODE: Always fetch from parent via proxied API, no local cache
|
|
11525
11750
|
console.log('[AuthContext] Proxy mode: fetching account from parent');
|
|
11526
11751
|
const freshAccountInfo = await smartlinks.auth.getAccount();
|
|
11527
11752
|
setAccountInfo(freshAccountInfo);
|
|
11528
11753
|
setAccountData(freshAccountInfo);
|
|
11529
|
-
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo);
|
|
11754
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo, isVerified, contact, contactId);
|
|
11530
11755
|
return freshAccountInfo;
|
|
11531
11756
|
}
|
|
11532
|
-
// STANDALONE MODE: Use caching
|
|
11533
11757
|
if (!token) {
|
|
11534
11758
|
throw new Error('Not authenticated. Please login first.');
|
|
11535
11759
|
}
|
|
11536
|
-
// Check cache unless force refresh
|
|
11537
11760
|
if (!forceRefresh) {
|
|
11538
11761
|
const cached = await tokenStorage.getAccountInfo();
|
|
11539
11762
|
if (cached && !cached.isStale) {
|
|
@@ -11541,18 +11764,15 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11541
11764
|
return cached.data;
|
|
11542
11765
|
}
|
|
11543
11766
|
}
|
|
11544
|
-
// Fetch fresh data from API
|
|
11545
11767
|
console.log('[AuthContext] Fetching fresh account info from API');
|
|
11546
11768
|
const freshAccountInfo = await smartlinks.auth.getAccount();
|
|
11547
|
-
// Cache the fresh data
|
|
11548
11769
|
await tokenStorage.saveAccountInfo(freshAccountInfo, accountCacheTTL);
|
|
11549
11770
|
setAccountInfo(freshAccountInfo);
|
|
11550
|
-
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo);
|
|
11771
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo, isVerified, contact, contactId);
|
|
11551
11772
|
return freshAccountInfo;
|
|
11552
11773
|
}
|
|
11553
11774
|
catch (error) {
|
|
11554
11775
|
console.error('[AuthContext] Failed to get account info:', error);
|
|
11555
|
-
// Fallback to stale cache if API fails (standalone mode only)
|
|
11556
11776
|
if (!proxyMode) {
|
|
11557
11777
|
const cached = await tokenStorage.getAccountInfo();
|
|
11558
11778
|
if (cached) {
|
|
@@ -11562,12 +11782,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11562
11782
|
}
|
|
11563
11783
|
throw error;
|
|
11564
11784
|
}
|
|
11565
|
-
}, [proxyMode, token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11566
|
-
// Convenience method for explicit refresh
|
|
11785
|
+
}, [proxyMode, token, accountCacheTTL, user, accountData, isVerified, contact, contactId, notifyAuthStateChange]);
|
|
11567
11786
|
const refreshAccount = useCallback(async () => {
|
|
11568
11787
|
return await getAccount(true);
|
|
11569
11788
|
}, [getAccount]);
|
|
11570
|
-
// Clear account cache (no-op in proxy mode)
|
|
11571
11789
|
const clearAccountCache = useCallback(async () => {
|
|
11572
11790
|
if (!proxyMode) {
|
|
11573
11791
|
await tokenStorage.clearAccountInfo();
|
|
@@ -11576,19 +11794,77 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11576
11794
|
}, [proxyMode]);
|
|
11577
11795
|
const onAuthStateChange = useCallback((callback) => {
|
|
11578
11796
|
callbacksRef.current.add(callback);
|
|
11579
|
-
// Return unsubscribe function
|
|
11580
11797
|
return () => {
|
|
11581
11798
|
callbacksRef.current.delete(callback);
|
|
11582
11799
|
};
|
|
11583
11800
|
}, []);
|
|
11801
|
+
const retryVerification = useCallback(async () => {
|
|
11802
|
+
if (!token || !user) {
|
|
11803
|
+
console.log('[AuthContext] No session to verify');
|
|
11804
|
+
return false;
|
|
11805
|
+
}
|
|
11806
|
+
if (isVerified) {
|
|
11807
|
+
console.log('[AuthContext] Session already verified');
|
|
11808
|
+
return true;
|
|
11809
|
+
}
|
|
11810
|
+
try {
|
|
11811
|
+
console.log('[AuthContext] Retrying session verification...');
|
|
11812
|
+
await smartlinks.auth.verifyToken(token);
|
|
11813
|
+
setIsVerified(true);
|
|
11814
|
+
pendingVerificationRef.current = false;
|
|
11815
|
+
console.log('[AuthContext] Session verified on retry');
|
|
11816
|
+
notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
|
|
11817
|
+
return true;
|
|
11818
|
+
}
|
|
11819
|
+
catch (err) {
|
|
11820
|
+
if (isNetworkError(err)) {
|
|
11821
|
+
console.warn('[AuthContext] Network still unavailable, will retry later');
|
|
11822
|
+
return false;
|
|
11823
|
+
}
|
|
11824
|
+
else {
|
|
11825
|
+
console.warn('[AuthContext] Session invalid on retry, logging out');
|
|
11826
|
+
await logout();
|
|
11827
|
+
return false;
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11830
|
+
}, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
|
|
11831
|
+
// Online/offline event listener for auto-retry verification
|
|
11832
|
+
useEffect(() => {
|
|
11833
|
+
if (proxyMode)
|
|
11834
|
+
return;
|
|
11835
|
+
const handleOnline = () => {
|
|
11836
|
+
console.log('[AuthContext] Network reconnected');
|
|
11837
|
+
setIsOnline(true);
|
|
11838
|
+
if (pendingVerificationRef.current && token && user) {
|
|
11839
|
+
console.log('[AuthContext] Retrying pending verification after reconnect...');
|
|
11840
|
+
retryVerification();
|
|
11841
|
+
}
|
|
11842
|
+
};
|
|
11843
|
+
const handleOffline = () => {
|
|
11844
|
+
console.log('[AuthContext] Network disconnected');
|
|
11845
|
+
setIsOnline(false);
|
|
11846
|
+
};
|
|
11847
|
+
window.addEventListener('online', handleOnline);
|
|
11848
|
+
window.addEventListener('offline', handleOffline);
|
|
11849
|
+
return () => {
|
|
11850
|
+
window.removeEventListener('online', handleOnline);
|
|
11851
|
+
window.removeEventListener('offline', handleOffline);
|
|
11852
|
+
};
|
|
11853
|
+
}, [proxyMode, token, user, retryVerification]);
|
|
11584
11854
|
const value = {
|
|
11585
11855
|
user,
|
|
11586
11856
|
token,
|
|
11587
11857
|
accountData,
|
|
11588
11858
|
accountInfo,
|
|
11589
11859
|
isAuthenticated: !!user,
|
|
11860
|
+
isVerified,
|
|
11590
11861
|
isLoading,
|
|
11862
|
+
isOnline,
|
|
11591
11863
|
proxyMode,
|
|
11864
|
+
contact,
|
|
11865
|
+
contactId,
|
|
11866
|
+
getContact,
|
|
11867
|
+
updateContactCustomFields,
|
|
11592
11868
|
login,
|
|
11593
11869
|
logout,
|
|
11594
11870
|
getToken,
|
|
@@ -11597,6 +11873,7 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11597
11873
|
refreshAccount,
|
|
11598
11874
|
clearAccountCache,
|
|
11599
11875
|
onAuthStateChange,
|
|
11876
|
+
retryVerification,
|
|
11600
11877
|
};
|
|
11601
11878
|
return jsx(AuthContext.Provider, { value: value, children: children });
|
|
11602
11879
|
};
|
|
@@ -11714,10 +11991,21 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11714
11991
|
// Reinitialize Smartlinks SDK when apiEndpoint or proxyMode changes
|
|
11715
11992
|
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11716
11993
|
useEffect(() => {
|
|
11994
|
+
console.log('[SmartlinksAuthUI] 🔧 SDK INIT useEffect triggered', {
|
|
11995
|
+
apiEndpoint,
|
|
11996
|
+
proxyMode,
|
|
11997
|
+
hasLogger: !!logger,
|
|
11998
|
+
timestamp: new Date().toISOString()
|
|
11999
|
+
});
|
|
11717
12000
|
log.log('SDK reinitialize useEffect triggered', { apiEndpoint, proxyMode });
|
|
11718
12001
|
setSdkReady(false); // Mark SDK as not ready during reinitialization
|
|
11719
12002
|
const reinitializeWithToken = async () => {
|
|
11720
12003
|
if (apiEndpoint) {
|
|
12004
|
+
console.log('[SmartlinksAuthUI] 🔧 Reinitializing SDK with:', {
|
|
12005
|
+
baseURL: apiEndpoint,
|
|
12006
|
+
proxyMode: proxyMode,
|
|
12007
|
+
ngrokSkipBrowserWarning: true
|
|
12008
|
+
});
|
|
11721
12009
|
log.log('Reinitializing SDK with baseURL:', apiEndpoint, 'proxyMode:', proxyMode);
|
|
11722
12010
|
// Get current token before reinitializing (only in standalone mode)
|
|
11723
12011
|
const currentToken = !proxyMode ? await auth.getToken() : null;
|
|
@@ -11727,6 +12015,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11727
12015
|
ngrokSkipBrowserWarning: true,
|
|
11728
12016
|
logger: logger, // Pass logger to SDK for verbose SDK logging
|
|
11729
12017
|
});
|
|
12018
|
+
console.log('[SmartlinksAuthUI] ✅ SDK reinitialized, proxyMode:', proxyMode);
|
|
11730
12019
|
log.log('SDK reinitialized successfully');
|
|
11731
12020
|
// Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
|
|
11732
12021
|
if (currentToken && !proxyMode) {
|
|
@@ -11735,14 +12024,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11735
12024
|
});
|
|
11736
12025
|
}
|
|
11737
12026
|
// Mark SDK as ready
|
|
12027
|
+
console.log('[SmartlinksAuthUI] ✅ Setting sdkReady=true (with apiEndpoint)');
|
|
11738
12028
|
setSdkReady(true);
|
|
11739
12029
|
}
|
|
11740
12030
|
else if (proxyMode) {
|
|
11741
12031
|
// In proxy mode without custom endpoint, SDK should already be initialized by parent
|
|
12032
|
+
console.log('[SmartlinksAuthUI] ⚠️ Proxy mode WITHOUT apiEndpoint - expecting SDK already initialized by parent');
|
|
11742
12033
|
log.log('Proxy mode without apiEndpoint, SDK already initialized by parent');
|
|
11743
12034
|
setSdkReady(true);
|
|
11744
12035
|
}
|
|
11745
12036
|
else {
|
|
12037
|
+
console.log('[SmartlinksAuthUI] ℹ️ No apiEndpoint, no proxyMode - SDK already initialized by App');
|
|
11746
12038
|
log.log('No apiEndpoint, SDK already initialized by App');
|
|
11747
12039
|
// SDK was initialized by App component, mark as ready
|
|
11748
12040
|
setSdkReady(true);
|
|
@@ -11761,6 +12053,14 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11761
12053
|
};
|
|
11762
12054
|
// Fetch UI configuration
|
|
11763
12055
|
useEffect(() => {
|
|
12056
|
+
console.log('[SmartlinksAuthUI] 📋 CONFIG FETCH useEffect triggered', {
|
|
12057
|
+
skipConfigFetch,
|
|
12058
|
+
clientId,
|
|
12059
|
+
apiEndpoint,
|
|
12060
|
+
sdkReady,
|
|
12061
|
+
proxyMode,
|
|
12062
|
+
timestamp: new Date().toISOString()
|
|
12063
|
+
});
|
|
11764
12064
|
log.log('Config fetch useEffect triggered', {
|
|
11765
12065
|
skipConfigFetch,
|
|
11766
12066
|
clientId,
|
|
@@ -11771,10 +12071,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11771
12071
|
});
|
|
11772
12072
|
// Wait for SDK to be ready before fetching config
|
|
11773
12073
|
if (!sdkReady) {
|
|
12074
|
+
console.log('[SmartlinksAuthUI] ⏳ SDK not ready yet, waiting before config fetch...');
|
|
11774
12075
|
log.log('SDK not ready yet, waiting...');
|
|
11775
12076
|
return;
|
|
11776
12077
|
}
|
|
11777
12078
|
if (skipConfigFetch) {
|
|
12079
|
+
console.log('[SmartlinksAuthUI] ⏭️ Skipping config fetch - skipConfigFetch is true');
|
|
11778
12080
|
log.log('Skipping config fetch - skipConfigFetch is true');
|
|
11779
12081
|
setConfig(customization || {});
|
|
11780
12082
|
return;
|
|
@@ -11782,6 +12084,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11782
12084
|
const fetchConfig = async () => {
|
|
11783
12085
|
// If no clientId provided, use default config immediately without API call
|
|
11784
12086
|
if (!clientId) {
|
|
12087
|
+
console.log('[SmartlinksAuthUI] ⚠️ No clientId provided, using default config');
|
|
11785
12088
|
log.log('No clientId provided, using default config');
|
|
11786
12089
|
const defaultConfig = {
|
|
11787
12090
|
...DEFAULT_AUTH_CONFIG,
|
|
@@ -11800,21 +12103,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11800
12103
|
const age = Date.now() - timestamp;
|
|
11801
12104
|
// Use cache if less than 1 hour old
|
|
11802
12105
|
if (age < 3600000) {
|
|
12106
|
+
console.log('[SmartlinksAuthUI] 📦 Using cached config (age:', Math.round(age / 1000), 'seconds)');
|
|
11803
12107
|
setConfig({ ...cachedConfig, ...customization });
|
|
11804
12108
|
setConfigLoading(false);
|
|
11805
12109
|
// Fetch in background to update cache
|
|
12110
|
+
console.log('[SmartlinksAuthUI] 🔄 Background refresh of config via SDK...');
|
|
11806
12111
|
api.fetchConfig().then(freshConfig => {
|
|
12112
|
+
console.log('[SmartlinksAuthUI] ✅ Background config refresh complete');
|
|
11807
12113
|
localStorage.setItem(cacheKey, JSON.stringify({
|
|
11808
12114
|
config: freshConfig,
|
|
11809
12115
|
timestamp: Date.now()
|
|
11810
12116
|
}));
|
|
12117
|
+
}).catch(err => {
|
|
12118
|
+
console.log('[SmartlinksAuthUI] ❌ Background config refresh failed:', err);
|
|
11811
12119
|
});
|
|
11812
12120
|
return;
|
|
11813
12121
|
}
|
|
11814
12122
|
}
|
|
11815
12123
|
// Fetch from API
|
|
12124
|
+
console.log('[SmartlinksAuthUI] 🌐 Fetching config via SDK for clientId:', clientId, 'proxyMode:', proxyMode);
|
|
11816
12125
|
log.log('Fetching config from API for clientId:', clientId);
|
|
11817
12126
|
const fetchedConfig = await api.fetchConfig();
|
|
12127
|
+
console.log('[SmartlinksAuthUI] ✅ Config fetched successfully:', fetchedConfig);
|
|
11818
12128
|
log.log('Received config:', fetchedConfig);
|
|
11819
12129
|
// Merge with customization props (props take precedence)
|
|
11820
12130
|
const mergedConfig = { ...fetchedConfig, ...customization };
|
|
@@ -11826,6 +12136,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11826
12136
|
}));
|
|
11827
12137
|
}
|
|
11828
12138
|
catch (err) {
|
|
12139
|
+
console.log('[SmartlinksAuthUI] ❌ Config fetch failed:', err);
|
|
11829
12140
|
log.error('Failed to fetch config:', err);
|
|
11830
12141
|
setConfig(customization || {});
|
|
11831
12142
|
}
|
|
@@ -11834,7 +12145,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11834
12145
|
}
|
|
11835
12146
|
};
|
|
11836
12147
|
fetchConfig();
|
|
11837
|
-
}, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, log]);
|
|
12148
|
+
}, [apiEndpoint, clientId, customization, skipConfigFetch, sdkReady, proxyMode, log]);
|
|
11838
12149
|
// Reset showEmailForm when mode changes away from login/register
|
|
11839
12150
|
useEffect(() => {
|
|
11840
12151
|
if (mode !== 'login' && mode !== 'register') {
|
|
@@ -11875,7 +12186,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11875
12186
|
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11876
12187
|
if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
|
|
11877
12188
|
// Auto-login modes: Log the user in immediately if token is provided
|
|
11878
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12189
|
+
auth.login(response.token, response.user, response.accountData, true);
|
|
11879
12190
|
setAuthSuccess(true);
|
|
11880
12191
|
setSuccessMessage('Email verified successfully! You are now logged in.');
|
|
11881
12192
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
@@ -11918,7 +12229,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11918
12229
|
const response = await api.verifyMagicLink(token);
|
|
11919
12230
|
// Auto-login with magic link if token is provided
|
|
11920
12231
|
if (response.token) {
|
|
11921
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12232
|
+
auth.login(response.token, response.user, response.accountData, true);
|
|
11922
12233
|
setAuthSuccess(true);
|
|
11923
12234
|
setSuccessMessage('Magic link verified! You are now logged in.');
|
|
11924
12235
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
@@ -11993,8 +12304,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11993
12304
|
if (mode === 'register') {
|
|
11994
12305
|
// Handle different verification modes
|
|
11995
12306
|
if (verificationMode === 'immediate' && response.token) {
|
|
11996
|
-
// Immediate mode: Log in right away if token is provided
|
|
11997
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12307
|
+
// Immediate mode: Log in right away if token is provided (isNewUser=true for registration)
|
|
12308
|
+
auth.login(response.token, response.user, response.accountData, true);
|
|
11998
12309
|
setAuthSuccess(true);
|
|
11999
12310
|
const deadline = response.emailVerificationDeadline
|
|
12000
12311
|
? new Date(response.emailVerificationDeadline).toLocaleString()
|
|
@@ -12030,7 +12341,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12030
12341
|
if (response.requiresEmailVerification) {
|
|
12031
12342
|
throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
|
|
12032
12343
|
}
|
|
12033
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12344
|
+
auth.login(response.token, response.user, response.accountData, false);
|
|
12034
12345
|
setAuthSuccess(true);
|
|
12035
12346
|
setSuccessMessage('Login successful!');
|
|
12036
12347
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
@@ -12190,7 +12501,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12190
12501
|
tokenType: 'access_token',
|
|
12191
12502
|
});
|
|
12192
12503
|
if (authResponse.token) {
|
|
12193
|
-
|
|
12504
|
+
// Google OAuth can be login or signup - use isNewUser flag from backend if available
|
|
12505
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
|
|
12194
12506
|
setAuthSuccess(true);
|
|
12195
12507
|
setSuccessMessage('Google login successful!');
|
|
12196
12508
|
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
@@ -12239,7 +12551,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12239
12551
|
const idToken = response.credential;
|
|
12240
12552
|
const authResponse = await api.loginWithGoogle(idToken);
|
|
12241
12553
|
if (authResponse.token) {
|
|
12242
|
-
|
|
12554
|
+
// Google OAuth can be login or signup - use isNewUser flag from backend if available
|
|
12555
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
|
|
12243
12556
|
setAuthSuccess(true);
|
|
12244
12557
|
setSuccessMessage('Google login successful!');
|
|
12245
12558
|
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
@@ -12300,7 +12613,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12300
12613
|
const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
|
|
12301
12614
|
// Update auth context with account data if token is provided
|
|
12302
12615
|
if (response.token) {
|
|
12303
|
-
auth
|
|
12616
|
+
// Phone auth can be login or signup - use isNewUser flag from backend if available
|
|
12617
|
+
auth.login(response.token, response.user, response.accountData, response.isNewUser);
|
|
12304
12618
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
12305
12619
|
if (redirectUrl) {
|
|
12306
12620
|
window.location.href = redirectUrl;
|