@proveanything/smartlinks-auth-ui 0.1.20 → 0.1.21
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/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 +345 -69
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +345 -69
- 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.js
CHANGED
|
@@ -11148,6 +11148,7 @@ const TOKEN_KEY = 'token';
|
|
|
11148
11148
|
const USER_KEY = 'user';
|
|
11149
11149
|
const ACCOUNT_DATA_KEY = 'account_data';
|
|
11150
11150
|
const ACCOUNT_INFO_KEY = 'account_info';
|
|
11151
|
+
const CONTACT_ID_KEY = 'contact_id';
|
|
11151
11152
|
const ACCOUNT_INFO_TTL = 5 * 60 * 1000; // 5 minutes default
|
|
11152
11153
|
/**
|
|
11153
11154
|
* Token Storage Layer
|
|
@@ -11198,6 +11199,7 @@ const tokenStorage = {
|
|
|
11198
11199
|
await this.clearUser();
|
|
11199
11200
|
await this.clearAccountData();
|
|
11200
11201
|
await this.clearAccountInfo();
|
|
11202
|
+
await this.clearContactId();
|
|
11201
11203
|
},
|
|
11202
11204
|
async saveAccountData(data) {
|
|
11203
11205
|
const storage = await getStorage();
|
|
@@ -11235,20 +11237,69 @@ const tokenStorage = {
|
|
|
11235
11237
|
const storage = await getStorage();
|
|
11236
11238
|
await storage.removeItem(ACCOUNT_INFO_KEY);
|
|
11237
11239
|
},
|
|
11240
|
+
async saveContactId(contactId) {
|
|
11241
|
+
const storage = await getStorage();
|
|
11242
|
+
await storage.setItem(CONTACT_ID_KEY, contactId);
|
|
11243
|
+
},
|
|
11244
|
+
async getContactId() {
|
|
11245
|
+
const storage = await getStorage();
|
|
11246
|
+
return await storage.getItem(CONTACT_ID_KEY);
|
|
11247
|
+
},
|
|
11248
|
+
async clearContactId() {
|
|
11249
|
+
const storage = await getStorage();
|
|
11250
|
+
await storage.removeItem(CONTACT_ID_KEY);
|
|
11251
|
+
},
|
|
11238
11252
|
};
|
|
11239
11253
|
|
|
11240
11254
|
const AuthContext = React.createContext(undefined);
|
|
11241
|
-
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false
|
|
11255
|
+
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
|
|
11256
|
+
// Contact & Interaction features
|
|
11257
|
+
collectionId, enableContactSync, enableInteractionTracking, interactionAppId, interactionConfig, }) => {
|
|
11242
11258
|
const [user, setUser] = React.useState(null);
|
|
11243
11259
|
const [token, setToken] = React.useState(null);
|
|
11244
11260
|
const [accountData, setAccountData] = React.useState(null);
|
|
11245
11261
|
const [accountInfo, setAccountInfo] = React.useState(null);
|
|
11246
11262
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
11263
|
+
const [isVerified, setIsVerified] = React.useState(false);
|
|
11264
|
+
const [isOnline, setIsOnline] = React.useState(typeof navigator !== 'undefined' ? navigator.onLine : true);
|
|
11265
|
+
// Contact state
|
|
11266
|
+
const [contact, setContact] = React.useState(null);
|
|
11267
|
+
const [contactId, setContactId] = React.useState(null);
|
|
11247
11268
|
const callbacksRef = React.useRef(new Set());
|
|
11248
|
-
// Initialization guard to prevent concurrent runs (NOT persisted across remounts)
|
|
11249
11269
|
const initializingRef = React.useRef(false);
|
|
11270
|
+
const pendingVerificationRef = React.useRef(false);
|
|
11271
|
+
// Default to enabled if collectionId is provided
|
|
11272
|
+
const shouldSyncContacts = enableContactSync ?? !!collectionId;
|
|
11273
|
+
const shouldTrackInteractions = enableInteractionTracking ?? !!collectionId;
|
|
11274
|
+
// Helper to detect if an error is a network error vs auth error
|
|
11275
|
+
const isNetworkError = React.useCallback((error) => {
|
|
11276
|
+
if (!error)
|
|
11277
|
+
return false;
|
|
11278
|
+
const errorMessage = error?.message?.toLowerCase() || '';
|
|
11279
|
+
const errorName = error?.name?.toLowerCase() || '';
|
|
11280
|
+
if (errorName === 'typeerror' && errorMessage.includes('fetch'))
|
|
11281
|
+
return true;
|
|
11282
|
+
if (errorMessage.includes('network') || errorMessage.includes('offline'))
|
|
11283
|
+
return true;
|
|
11284
|
+
if (errorMessage.includes('failed to fetch'))
|
|
11285
|
+
return true;
|
|
11286
|
+
if (errorMessage.includes('net::err_'))
|
|
11287
|
+
return true;
|
|
11288
|
+
if (errorMessage.includes('timeout'))
|
|
11289
|
+
return true;
|
|
11290
|
+
if (errorMessage.includes('dns'))
|
|
11291
|
+
return true;
|
|
11292
|
+
if (error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT')
|
|
11293
|
+
return true;
|
|
11294
|
+
const status = error?.status || error?.response?.status;
|
|
11295
|
+
if (status === 401 || status === 403)
|
|
11296
|
+
return false;
|
|
11297
|
+
if (typeof navigator !== 'undefined' && !navigator.onLine)
|
|
11298
|
+
return true;
|
|
11299
|
+
return false;
|
|
11300
|
+
}, []);
|
|
11250
11301
|
// Notify all subscribers of auth state changes
|
|
11251
|
-
const notifyAuthStateChange = React.useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo) => {
|
|
11302
|
+
const notifyAuthStateChange = React.useCallback((type, currentUser, currentToken, currentAccountData, currentAccountInfo, verified, currentContact, currentContactId) => {
|
|
11252
11303
|
callbacksRef.current.forEach(callback => {
|
|
11253
11304
|
try {
|
|
11254
11305
|
callback({
|
|
@@ -11256,7 +11307,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11256
11307
|
user: currentUser,
|
|
11257
11308
|
token: currentToken,
|
|
11258
11309
|
accountData: currentAccountData,
|
|
11259
|
-
accountInfo: currentAccountInfo
|
|
11310
|
+
accountInfo: currentAccountInfo,
|
|
11311
|
+
isVerified: verified,
|
|
11312
|
+
contact: currentContact,
|
|
11313
|
+
contactId: currentContactId,
|
|
11260
11314
|
});
|
|
11261
11315
|
}
|
|
11262
11316
|
catch (error) {
|
|
@@ -11264,9 +11318,128 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11264
11318
|
}
|
|
11265
11319
|
});
|
|
11266
11320
|
}, []);
|
|
11267
|
-
//
|
|
11321
|
+
// Sync contact to Smartlinks (non-blocking)
|
|
11322
|
+
const syncContact = React.useCallback(async (authUser, customFields) => {
|
|
11323
|
+
if (!collectionId || !shouldSyncContacts) {
|
|
11324
|
+
console.log('[AuthContext] Contact sync skipped: no collectionId or disabled');
|
|
11325
|
+
return null;
|
|
11326
|
+
}
|
|
11327
|
+
try {
|
|
11328
|
+
console.log('[AuthContext] Syncing contact for user:', authUser.uid);
|
|
11329
|
+
const result = await smartlinks__namespace.contact.publicUpsert(collectionId, {
|
|
11330
|
+
userId: authUser.uid,
|
|
11331
|
+
email: authUser.email,
|
|
11332
|
+
displayName: authUser.displayName,
|
|
11333
|
+
phone: authUser.phoneNumber,
|
|
11334
|
+
customFields: customFields || {},
|
|
11335
|
+
source: 'authkit',
|
|
11336
|
+
});
|
|
11337
|
+
console.log('[AuthContext] Contact synced:', result.contactId);
|
|
11338
|
+
// Store contact ID locally
|
|
11339
|
+
if (!proxyMode) {
|
|
11340
|
+
await tokenStorage.saveContactId(result.contactId);
|
|
11341
|
+
}
|
|
11342
|
+
setContactId(result.contactId);
|
|
11343
|
+
// Fetch full contact to get customFields
|
|
11344
|
+
try {
|
|
11345
|
+
const fullContact = await smartlinks__namespace.contact.lookup(collectionId, {
|
|
11346
|
+
email: authUser.email
|
|
11347
|
+
});
|
|
11348
|
+
setContact(fullContact);
|
|
11349
|
+
notifyAuthStateChange('CONTACT_SYNCED', authUser, token, accountData, accountInfo, isVerified, fullContact, result.contactId);
|
|
11350
|
+
}
|
|
11351
|
+
catch (lookupErr) {
|
|
11352
|
+
console.warn('[AuthContext] Failed to lookup full contact:', lookupErr);
|
|
11353
|
+
}
|
|
11354
|
+
return result.contactId;
|
|
11355
|
+
}
|
|
11356
|
+
catch (err) {
|
|
11357
|
+
console.warn('[AuthContext] Contact sync failed (non-blocking):', err);
|
|
11358
|
+
return null;
|
|
11359
|
+
}
|
|
11360
|
+
}, [collectionId, shouldSyncContacts, proxyMode, token, accountData, accountInfo, isVerified, notifyAuthStateChange]);
|
|
11361
|
+
// Track interaction event (non-blocking)
|
|
11362
|
+
const trackInteraction = React.useCallback(async (eventType, userId, currentContactId, metadata) => {
|
|
11363
|
+
if (!collectionId || !shouldTrackInteractions) {
|
|
11364
|
+
console.log('[AuthContext] Interaction tracking skipped: no collectionId or disabled');
|
|
11365
|
+
return;
|
|
11366
|
+
}
|
|
11367
|
+
const interactionIdMap = {
|
|
11368
|
+
login: interactionConfig?.login || 'authkit.login',
|
|
11369
|
+
logout: interactionConfig?.logout || 'authkit.logout',
|
|
11370
|
+
signup: interactionConfig?.signup || 'authkit.signup',
|
|
11371
|
+
session_restore: interactionConfig?.sessionRestore,
|
|
11372
|
+
};
|
|
11373
|
+
const interactionId = interactionIdMap[eventType];
|
|
11374
|
+
if (!interactionId) {
|
|
11375
|
+
console.log(`[AuthContext] No interaction ID for ${eventType}, skipping`);
|
|
11376
|
+
return;
|
|
11377
|
+
}
|
|
11378
|
+
try {
|
|
11379
|
+
console.log(`[AuthContext] Tracking interaction: ${interactionId}`);
|
|
11380
|
+
await smartlinks__namespace.interactions.submitPublicEvent(collectionId, {
|
|
11381
|
+
collectionId,
|
|
11382
|
+
interactionId,
|
|
11383
|
+
userId,
|
|
11384
|
+
contactId: currentContactId || undefined,
|
|
11385
|
+
appId: interactionAppId,
|
|
11386
|
+
eventType,
|
|
11387
|
+
outcome: 'completed',
|
|
11388
|
+
metadata: {
|
|
11389
|
+
...metadata,
|
|
11390
|
+
timestamp: new Date().toISOString(),
|
|
11391
|
+
source: 'authkit',
|
|
11392
|
+
},
|
|
11393
|
+
});
|
|
11394
|
+
console.log(`[AuthContext] Tracked interaction: ${interactionId}`);
|
|
11395
|
+
notifyAuthStateChange('INTERACTION_TRACKED', user, token, accountData, accountInfo, isVerified, contact, contactId);
|
|
11396
|
+
}
|
|
11397
|
+
catch (err) {
|
|
11398
|
+
console.warn('[AuthContext] Interaction tracking failed (non-blocking):', err);
|
|
11399
|
+
}
|
|
11400
|
+
}, [collectionId, shouldTrackInteractions, interactionAppId, interactionConfig, user, token, accountData, accountInfo, isVerified, contact, contactId, notifyAuthStateChange]);
|
|
11401
|
+
// Get contact (with optional refresh)
|
|
11402
|
+
const getContact = React.useCallback(async (forceRefresh = false) => {
|
|
11403
|
+
if (!collectionId) {
|
|
11404
|
+
console.log('[AuthContext] getContact: no collectionId');
|
|
11405
|
+
return null;
|
|
11406
|
+
}
|
|
11407
|
+
// Need either email or userId to lookup contact
|
|
11408
|
+
if (!user?.email && !user?.uid) {
|
|
11409
|
+
console.log('[AuthContext] getContact: no user email or uid');
|
|
11410
|
+
return null;
|
|
11411
|
+
}
|
|
11412
|
+
if (contact && !forceRefresh) {
|
|
11413
|
+
return contact;
|
|
11414
|
+
}
|
|
11415
|
+
try {
|
|
11416
|
+
// Prefer email lookup, fallback to userId for phone-only users
|
|
11417
|
+
const lookupParams = user.email
|
|
11418
|
+
? { email: user.email }
|
|
11419
|
+
: { userId: user.uid };
|
|
11420
|
+
console.log('[AuthContext] Fetching contact with:', lookupParams);
|
|
11421
|
+
const result = await smartlinks__namespace.contact.lookup(collectionId, lookupParams);
|
|
11422
|
+
setContact(result);
|
|
11423
|
+
setContactId(result.contactId);
|
|
11424
|
+
return result;
|
|
11425
|
+
}
|
|
11426
|
+
catch (err) {
|
|
11427
|
+
console.warn('[AuthContext] Failed to get contact:', err);
|
|
11428
|
+
return null;
|
|
11429
|
+
}
|
|
11430
|
+
}, [collectionId, user, contact]);
|
|
11431
|
+
// Update contact custom fields
|
|
11432
|
+
const updateContactCustomFields = React.useCallback(async (customFields) => {
|
|
11433
|
+
if (!collectionId || !contactId) {
|
|
11434
|
+
throw new Error('No contact to update. Ensure collectionId is provided and user is synced.');
|
|
11435
|
+
}
|
|
11436
|
+
console.log('[AuthContext] Updating contact custom fields:', contactId);
|
|
11437
|
+
const updated = await smartlinks__namespace.contact.update(collectionId, contactId, { customFields });
|
|
11438
|
+
setContact(updated);
|
|
11439
|
+
return updated;
|
|
11440
|
+
}, [collectionId, contactId]);
|
|
11441
|
+
// Initialize auth state
|
|
11268
11442
|
React.useEffect(() => {
|
|
11269
|
-
// Prevent concurrent initialization only
|
|
11270
11443
|
if (initializingRef.current) {
|
|
11271
11444
|
console.log('[AuthContext] Skipping initialization - already in progress');
|
|
11272
11445
|
return;
|
|
@@ -11280,12 +11453,9 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11280
11453
|
console.log('[AuthContext] Proxy mode: checking for existing session via auth.getAccount()');
|
|
11281
11454
|
try {
|
|
11282
11455
|
const accountResponse = await smartlinks__namespace.auth.getAccount();
|
|
11283
|
-
// auth.getAccount() always returns a response object, but uid will be
|
|
11284
|
-
// empty/undefined if no user is logged in
|
|
11285
11456
|
const accountAny = accountResponse;
|
|
11286
11457
|
const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
|
|
11287
11458
|
if (hasValidSession && isMounted) {
|
|
11288
|
-
// User is logged in with valid account
|
|
11289
11459
|
const userFromAccount = {
|
|
11290
11460
|
uid: accountAny.uid,
|
|
11291
11461
|
email: accountAny?.email,
|
|
@@ -11295,8 +11465,11 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11295
11465
|
setUser(userFromAccount);
|
|
11296
11466
|
setAccountData(accountResponse);
|
|
11297
11467
|
setAccountInfo(accountResponse);
|
|
11468
|
+
setIsVerified(true);
|
|
11298
11469
|
console.log('[AuthContext] Proxy mode: initialized from parent account, uid:', accountAny.uid);
|
|
11299
|
-
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse);
|
|
11470
|
+
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse, true);
|
|
11471
|
+
// Sync contact in background (proxy mode)
|
|
11472
|
+
syncContact(userFromAccount, accountResponse);
|
|
11300
11473
|
}
|
|
11301
11474
|
else if (isMounted) {
|
|
11302
11475
|
console.log('[AuthContext] Proxy mode: no valid session (no uid), awaiting login');
|
|
@@ -11311,29 +11484,56 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11311
11484
|
}
|
|
11312
11485
|
return;
|
|
11313
11486
|
}
|
|
11314
|
-
// STANDALONE MODE:
|
|
11487
|
+
// STANDALONE MODE: Optimistic restoration with background verification
|
|
11315
11488
|
const storedToken = await tokenStorage.getToken();
|
|
11316
11489
|
const storedUser = await tokenStorage.getUser();
|
|
11317
11490
|
const storedAccountData = await tokenStorage.getAccountData();
|
|
11491
|
+
const storedContactId = await tokenStorage.getContactId();
|
|
11318
11492
|
if (storedToken && storedUser) {
|
|
11319
|
-
|
|
11493
|
+
if (isMounted) {
|
|
11494
|
+
setToken(storedToken.token);
|
|
11495
|
+
setUser(storedUser);
|
|
11496
|
+
setAccountData(storedAccountData);
|
|
11497
|
+
if (storedContactId) {
|
|
11498
|
+
setContactId(storedContactId);
|
|
11499
|
+
}
|
|
11500
|
+
console.log('[AuthContext] Session restored optimistically (pending verification)');
|
|
11501
|
+
notifyAuthStateChange('SESSION_RESTORED_OFFLINE', storedUser, storedToken.token, storedAccountData || null, null, false, null, storedContactId);
|
|
11502
|
+
}
|
|
11503
|
+
// BACKGROUND: Verify token
|
|
11320
11504
|
try {
|
|
11321
|
-
console.log('[AuthContext] Verifying stored token...');
|
|
11505
|
+
console.log('[AuthContext] Verifying stored token in background...');
|
|
11322
11506
|
await smartlinks__namespace.auth.verifyToken(storedToken.token);
|
|
11323
|
-
// Only set state if verification succeeded and component still mounted
|
|
11324
11507
|
if (isMounted) {
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
//
|
|
11330
|
-
|
|
11508
|
+
setIsVerified(true);
|
|
11509
|
+
pendingVerificationRef.current = false;
|
|
11510
|
+
console.log('[AuthContext] Session verified successfully');
|
|
11511
|
+
notifyAuthStateChange('SESSION_VERIFIED', storedUser, storedToken.token, storedAccountData || null, null, true, null, storedContactId);
|
|
11512
|
+
// Track session restore interaction (optional)
|
|
11513
|
+
if (interactionConfig?.sessionRestore) {
|
|
11514
|
+
trackInteraction('session_restore', storedUser.uid, storedContactId);
|
|
11515
|
+
}
|
|
11331
11516
|
}
|
|
11332
11517
|
}
|
|
11333
11518
|
catch (err) {
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11519
|
+
if (isNetworkError(err)) {
|
|
11520
|
+
console.warn('[AuthContext] Network error during verification, will retry on reconnect:', err);
|
|
11521
|
+
pendingVerificationRef.current = true;
|
|
11522
|
+
}
|
|
11523
|
+
else {
|
|
11524
|
+
console.warn('[AuthContext] Token verification failed (auth error), clearing credentials:', err);
|
|
11525
|
+
if (isMounted) {
|
|
11526
|
+
setToken(null);
|
|
11527
|
+
setUser(null);
|
|
11528
|
+
setAccountData(null);
|
|
11529
|
+
setContactId(null);
|
|
11530
|
+
setContact(null);
|
|
11531
|
+
setIsVerified(false);
|
|
11532
|
+
pendingVerificationRef.current = false;
|
|
11533
|
+
}
|
|
11534
|
+
await tokenStorage.clearAll();
|
|
11535
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
11536
|
+
}
|
|
11337
11537
|
}
|
|
11338
11538
|
}
|
|
11339
11539
|
// Load cached account info if available
|
|
@@ -11355,18 +11555,16 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11355
11555
|
}
|
|
11356
11556
|
};
|
|
11357
11557
|
initializeAuth();
|
|
11358
|
-
// Cleanup for hot reload
|
|
11359
11558
|
return () => {
|
|
11360
11559
|
isMounted = false;
|
|
11361
11560
|
};
|
|
11362
|
-
}, [proxyMode, notifyAuthStateChange]);
|
|
11561
|
+
}, [proxyMode, notifyAuthStateChange, isNetworkError, syncContact, trackInteraction, interactionConfig?.sessionRestore]);
|
|
11363
11562
|
// Listen for parent auth state changes (proxy mode only)
|
|
11364
11563
|
React.useEffect(() => {
|
|
11365
11564
|
if (!proxyMode)
|
|
11366
11565
|
return;
|
|
11367
11566
|
console.log('[AuthContext] Proxy mode: setting up parent message listener');
|
|
11368
11567
|
const handleParentMessage = (event) => {
|
|
11369
|
-
// Handle auth state pushed from parent
|
|
11370
11568
|
if (event.data?.type === 'smartlinks:authkit:state') {
|
|
11371
11569
|
const { user: parentUser, accountData: parentAccountData, authenticated } = event.data.payload || {};
|
|
11372
11570
|
console.log('[AuthContext] Proxy mode: received state from parent:', { authenticated });
|
|
@@ -11380,15 +11578,20 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11380
11578
|
setUser(userObj);
|
|
11381
11579
|
setAccountData(parentAccountData || null);
|
|
11382
11580
|
setAccountInfo(parentAccountData || null);
|
|
11383
|
-
|
|
11581
|
+
setIsVerified(true);
|
|
11582
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null, true);
|
|
11583
|
+
// Sync contact on cross-tab state
|
|
11584
|
+
syncContact(userObj, parentAccountData);
|
|
11384
11585
|
}
|
|
11385
11586
|
else {
|
|
11386
|
-
// Parent indicates no session / logged out
|
|
11387
11587
|
setUser(null);
|
|
11388
11588
|
setToken(null);
|
|
11389
11589
|
setAccountData(null);
|
|
11390
11590
|
setAccountInfo(null);
|
|
11391
|
-
|
|
11591
|
+
setContact(null);
|
|
11592
|
+
setContactId(null);
|
|
11593
|
+
setIsVerified(false);
|
|
11594
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
11392
11595
|
}
|
|
11393
11596
|
}
|
|
11394
11597
|
};
|
|
@@ -11397,54 +11600,60 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11397
11600
|
console.log('[AuthContext] Proxy mode: cleaning up parent message listener');
|
|
11398
11601
|
window.removeEventListener('message', handleParentMessage);
|
|
11399
11602
|
};
|
|
11400
|
-
}, [proxyMode, notifyAuthStateChange]);
|
|
11603
|
+
}, [proxyMode, notifyAuthStateChange, syncContact]);
|
|
11401
11604
|
// Cross-tab synchronization - standalone mode only
|
|
11402
11605
|
React.useEffect(() => {
|
|
11403
11606
|
if (proxyMode)
|
|
11404
|
-
return;
|
|
11607
|
+
return;
|
|
11405
11608
|
console.log('[AuthContext] Setting up cross-tab synchronization');
|
|
11406
11609
|
const unsubscribe = onStorageChange(async (event) => {
|
|
11407
11610
|
console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
|
|
11408
11611
|
try {
|
|
11409
11612
|
if (event.type === 'clear') {
|
|
11410
|
-
// Another tab cleared all storage (logout)
|
|
11411
11613
|
console.log('[AuthContext] Detected logout in another tab');
|
|
11412
11614
|
setToken(null);
|
|
11413
11615
|
setUser(null);
|
|
11414
11616
|
setAccountData(null);
|
|
11415
11617
|
setAccountInfo(null);
|
|
11618
|
+
setContact(null);
|
|
11619
|
+
setContactId(null);
|
|
11620
|
+
setIsVerified(false);
|
|
11416
11621
|
smartlinks__namespace.auth.logout();
|
|
11417
|
-
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
|
|
11622
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
|
|
11418
11623
|
}
|
|
11419
11624
|
else if (event.type === 'remove' && (event.key === 'token' || event.key === 'user')) {
|
|
11420
|
-
// Another tab removed token or user (logout)
|
|
11421
11625
|
console.log('[AuthContext] Detected token/user removal in another tab');
|
|
11422
11626
|
setToken(null);
|
|
11423
11627
|
setUser(null);
|
|
11424
11628
|
setAccountData(null);
|
|
11425
11629
|
setAccountInfo(null);
|
|
11630
|
+
setContact(null);
|
|
11631
|
+
setContactId(null);
|
|
11632
|
+
setIsVerified(false);
|
|
11426
11633
|
smartlinks__namespace.auth.logout();
|
|
11427
|
-
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null);
|
|
11634
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', null, null, null, null, false);
|
|
11428
11635
|
}
|
|
11429
11636
|
else if (event.type === 'set' && event.key === 'token') {
|
|
11430
|
-
// Another tab set a new token (login)
|
|
11431
11637
|
console.log('[AuthContext] Detected login in another tab');
|
|
11432
11638
|
const storedToken = await tokenStorage.getToken();
|
|
11433
11639
|
const storedUser = await tokenStorage.getUser();
|
|
11434
11640
|
const storedAccountData = await tokenStorage.getAccountData();
|
|
11641
|
+
const storedContactId = await tokenStorage.getContactId();
|
|
11435
11642
|
if (storedToken && storedUser) {
|
|
11436
11643
|
setToken(storedToken.token);
|
|
11437
11644
|
setUser(storedUser);
|
|
11438
11645
|
setAccountData(storedAccountData);
|
|
11439
|
-
|
|
11646
|
+
if (storedContactId) {
|
|
11647
|
+
setContactId(storedContactId);
|
|
11648
|
+
}
|
|
11649
|
+
setIsVerified(true);
|
|
11440
11650
|
smartlinks__namespace.auth.verifyToken(storedToken.token).catch(err => {
|
|
11441
11651
|
console.warn('[AuthContext] Failed to restore bearer token from cross-tab sync:', err);
|
|
11442
11652
|
});
|
|
11443
|
-
notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData);
|
|
11653
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', storedUser, storedToken.token, storedAccountData, null, true, null, storedContactId);
|
|
11444
11654
|
}
|
|
11445
11655
|
}
|
|
11446
11656
|
else if (event.type === 'set' && event.key === 'account_info') {
|
|
11447
|
-
// Another tab fetched fresh account info
|
|
11448
11657
|
const cached = await tokenStorage.getAccountInfo();
|
|
11449
11658
|
if (cached && !cached.isStale) {
|
|
11450
11659
|
setAccountInfo(cached.data);
|
|
@@ -11461,7 +11670,7 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11461
11670
|
unsubscribe();
|
|
11462
11671
|
};
|
|
11463
11672
|
}, [proxyMode, notifyAuthStateChange]);
|
|
11464
|
-
const login = React.useCallback(async (authToken, authUser, authAccountData) => {
|
|
11673
|
+
const login = React.useCallback(async (authToken, authUser, authAccountData, isNewUser) => {
|
|
11465
11674
|
try {
|
|
11466
11675
|
// Only persist to storage in standalone mode
|
|
11467
11676
|
if (!proxyMode) {
|
|
@@ -11470,7 +11679,6 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11470
11679
|
if (authAccountData) {
|
|
11471
11680
|
await tokenStorage.saveAccountData(authAccountData);
|
|
11472
11681
|
}
|
|
11473
|
-
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
11474
11682
|
smartlinks__namespace.auth.verifyToken(authToken).catch(err => {
|
|
11475
11683
|
console.warn('Failed to set bearer token on login:', err);
|
|
11476
11684
|
});
|
|
@@ -11479,8 +11687,9 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11479
11687
|
setToken(authToken);
|
|
11480
11688
|
setUser(authUser);
|
|
11481
11689
|
setAccountData(authAccountData || null);
|
|
11690
|
+
setIsVerified(true);
|
|
11691
|
+
pendingVerificationRef.current = false;
|
|
11482
11692
|
// Cross-iframe auth state synchronization
|
|
11483
|
-
// Always notify parent frame of login (both modes, but especially important in proxy mode)
|
|
11484
11693
|
if (smartlinks.iframe.isIframe()) {
|
|
11485
11694
|
console.log('[AuthContext] Notifying parent of login via postMessage');
|
|
11486
11695
|
smartlinks.iframe.sendParentCustom('smartlinks:authkit:login', {
|
|
@@ -11489,7 +11698,13 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11489
11698
|
accountData: authAccountData || null
|
|
11490
11699
|
});
|
|
11491
11700
|
}
|
|
11492
|
-
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
|
|
11701
|
+
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null, null, true);
|
|
11702
|
+
// Sync contact (non-blocking)
|
|
11703
|
+
const newContactId = await syncContact(authUser, authAccountData);
|
|
11704
|
+
// Track interaction (non-blocking)
|
|
11705
|
+
trackInteraction(isNewUser ? 'signup' : 'login', authUser.uid, newContactId, {
|
|
11706
|
+
provider: authUser.email ? 'email' : 'phone',
|
|
11707
|
+
});
|
|
11493
11708
|
// Optionally preload account info on login (standalone mode only)
|
|
11494
11709
|
if (!proxyMode && preloadAccountInfo) {
|
|
11495
11710
|
getAccount(true).catch(error => {
|
|
@@ -11501,8 +11716,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11501
11716
|
console.error('Failed to save auth data to storage:', error);
|
|
11502
11717
|
throw error;
|
|
11503
11718
|
}
|
|
11504
|
-
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo]);
|
|
11719
|
+
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo, syncContact, trackInteraction]);
|
|
11505
11720
|
const logout = React.useCallback(async () => {
|
|
11721
|
+
const currentUser = user;
|
|
11722
|
+
const currentContactId = contactId;
|
|
11506
11723
|
try {
|
|
11507
11724
|
// Only clear persistent storage in standalone mode
|
|
11508
11725
|
if (!proxyMode) {
|
|
@@ -11514,21 +11731,27 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11514
11731
|
setUser(null);
|
|
11515
11732
|
setAccountData(null);
|
|
11516
11733
|
setAccountInfo(null);
|
|
11734
|
+
setContact(null);
|
|
11735
|
+
setContactId(null);
|
|
11736
|
+
setIsVerified(false);
|
|
11737
|
+
pendingVerificationRef.current = false;
|
|
11517
11738
|
// Cross-iframe auth state synchronization
|
|
11518
|
-
// Always notify parent frame of logout
|
|
11519
11739
|
if (smartlinks.iframe.isIframe()) {
|
|
11520
11740
|
console.log('[AuthContext] Notifying parent of logout via postMessage');
|
|
11521
11741
|
smartlinks.iframe.sendParentCustom('smartlinks:authkit:logout', {});
|
|
11522
11742
|
}
|
|
11523
|
-
notifyAuthStateChange('LOGOUT', null, null, null);
|
|
11743
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null, false);
|
|
11744
|
+
// Track logout interaction (fire and forget)
|
|
11745
|
+
if (currentUser && collectionId && shouldTrackInteractions) {
|
|
11746
|
+
trackInteraction('logout', currentUser.uid, currentContactId);
|
|
11747
|
+
}
|
|
11524
11748
|
}
|
|
11525
11749
|
catch (error) {
|
|
11526
11750
|
console.error('Failed to clear auth data from storage:', error);
|
|
11527
11751
|
}
|
|
11528
|
-
}, [proxyMode, notifyAuthStateChange]);
|
|
11752
|
+
}, [proxyMode, notifyAuthStateChange, user, contactId, collectionId, shouldTrackInteractions, trackInteraction]);
|
|
11529
11753
|
const getToken = React.useCallback(async () => {
|
|
11530
11754
|
if (proxyMode) {
|
|
11531
|
-
// In proxy mode, token is managed by parent - return memory state
|
|
11532
11755
|
return token;
|
|
11533
11756
|
}
|
|
11534
11757
|
const storedToken = await tokenStorage.getToken();
|
|
@@ -11537,23 +11760,19 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11537
11760
|
const refreshToken = React.useCallback(async () => {
|
|
11538
11761
|
throw new Error('Token refresh must be implemented via your backend API');
|
|
11539
11762
|
}, []);
|
|
11540
|
-
// Get account with intelligent caching (or direct parent fetch in proxy mode)
|
|
11541
11763
|
const getAccount = React.useCallback(async (forceRefresh = false) => {
|
|
11542
11764
|
try {
|
|
11543
11765
|
if (proxyMode) {
|
|
11544
|
-
// PROXY MODE: Always fetch from parent via proxied API, no local cache
|
|
11545
11766
|
console.log('[AuthContext] Proxy mode: fetching account from parent');
|
|
11546
11767
|
const freshAccountInfo = await smartlinks__namespace.auth.getAccount();
|
|
11547
11768
|
setAccountInfo(freshAccountInfo);
|
|
11548
11769
|
setAccountData(freshAccountInfo);
|
|
11549
|
-
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo);
|
|
11770
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo, isVerified, contact, contactId);
|
|
11550
11771
|
return freshAccountInfo;
|
|
11551
11772
|
}
|
|
11552
|
-
// STANDALONE MODE: Use caching
|
|
11553
11773
|
if (!token) {
|
|
11554
11774
|
throw new Error('Not authenticated. Please login first.');
|
|
11555
11775
|
}
|
|
11556
|
-
// Check cache unless force refresh
|
|
11557
11776
|
if (!forceRefresh) {
|
|
11558
11777
|
const cached = await tokenStorage.getAccountInfo();
|
|
11559
11778
|
if (cached && !cached.isStale) {
|
|
@@ -11561,18 +11780,15 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11561
11780
|
return cached.data;
|
|
11562
11781
|
}
|
|
11563
11782
|
}
|
|
11564
|
-
// Fetch fresh data from API
|
|
11565
11783
|
console.log('[AuthContext] Fetching fresh account info from API');
|
|
11566
11784
|
const freshAccountInfo = await smartlinks__namespace.auth.getAccount();
|
|
11567
|
-
// Cache the fresh data
|
|
11568
11785
|
await tokenStorage.saveAccountInfo(freshAccountInfo, accountCacheTTL);
|
|
11569
11786
|
setAccountInfo(freshAccountInfo);
|
|
11570
|
-
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo);
|
|
11787
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, accountData, freshAccountInfo, isVerified, contact, contactId);
|
|
11571
11788
|
return freshAccountInfo;
|
|
11572
11789
|
}
|
|
11573
11790
|
catch (error) {
|
|
11574
11791
|
console.error('[AuthContext] Failed to get account info:', error);
|
|
11575
|
-
// Fallback to stale cache if API fails (standalone mode only)
|
|
11576
11792
|
if (!proxyMode) {
|
|
11577
11793
|
const cached = await tokenStorage.getAccountInfo();
|
|
11578
11794
|
if (cached) {
|
|
@@ -11582,12 +11798,10 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11582
11798
|
}
|
|
11583
11799
|
throw error;
|
|
11584
11800
|
}
|
|
11585
|
-
}, [proxyMode, token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11586
|
-
// Convenience method for explicit refresh
|
|
11801
|
+
}, [proxyMode, token, accountCacheTTL, user, accountData, isVerified, contact, contactId, notifyAuthStateChange]);
|
|
11587
11802
|
const refreshAccount = React.useCallback(async () => {
|
|
11588
11803
|
return await getAccount(true);
|
|
11589
11804
|
}, [getAccount]);
|
|
11590
|
-
// Clear account cache (no-op in proxy mode)
|
|
11591
11805
|
const clearAccountCache = React.useCallback(async () => {
|
|
11592
11806
|
if (!proxyMode) {
|
|
11593
11807
|
await tokenStorage.clearAccountInfo();
|
|
@@ -11596,19 +11810,77 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11596
11810
|
}, [proxyMode]);
|
|
11597
11811
|
const onAuthStateChange = React.useCallback((callback) => {
|
|
11598
11812
|
callbacksRef.current.add(callback);
|
|
11599
|
-
// Return unsubscribe function
|
|
11600
11813
|
return () => {
|
|
11601
11814
|
callbacksRef.current.delete(callback);
|
|
11602
11815
|
};
|
|
11603
11816
|
}, []);
|
|
11817
|
+
const retryVerification = React.useCallback(async () => {
|
|
11818
|
+
if (!token || !user) {
|
|
11819
|
+
console.log('[AuthContext] No session to verify');
|
|
11820
|
+
return false;
|
|
11821
|
+
}
|
|
11822
|
+
if (isVerified) {
|
|
11823
|
+
console.log('[AuthContext] Session already verified');
|
|
11824
|
+
return true;
|
|
11825
|
+
}
|
|
11826
|
+
try {
|
|
11827
|
+
console.log('[AuthContext] Retrying session verification...');
|
|
11828
|
+
await smartlinks__namespace.auth.verifyToken(token);
|
|
11829
|
+
setIsVerified(true);
|
|
11830
|
+
pendingVerificationRef.current = false;
|
|
11831
|
+
console.log('[AuthContext] Session verified on retry');
|
|
11832
|
+
notifyAuthStateChange('SESSION_VERIFIED', user, token, accountData, accountInfo, true, contact, contactId);
|
|
11833
|
+
return true;
|
|
11834
|
+
}
|
|
11835
|
+
catch (err) {
|
|
11836
|
+
if (isNetworkError(err)) {
|
|
11837
|
+
console.warn('[AuthContext] Network still unavailable, will retry later');
|
|
11838
|
+
return false;
|
|
11839
|
+
}
|
|
11840
|
+
else {
|
|
11841
|
+
console.warn('[AuthContext] Session invalid on retry, logging out');
|
|
11842
|
+
await logout();
|
|
11843
|
+
return false;
|
|
11844
|
+
}
|
|
11845
|
+
}
|
|
11846
|
+
}, [token, user, isVerified, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
|
|
11847
|
+
// Online/offline event listener for auto-retry verification
|
|
11848
|
+
React.useEffect(() => {
|
|
11849
|
+
if (proxyMode)
|
|
11850
|
+
return;
|
|
11851
|
+
const handleOnline = () => {
|
|
11852
|
+
console.log('[AuthContext] Network reconnected');
|
|
11853
|
+
setIsOnline(true);
|
|
11854
|
+
if (pendingVerificationRef.current && token && user) {
|
|
11855
|
+
console.log('[AuthContext] Retrying pending verification after reconnect...');
|
|
11856
|
+
retryVerification();
|
|
11857
|
+
}
|
|
11858
|
+
};
|
|
11859
|
+
const handleOffline = () => {
|
|
11860
|
+
console.log('[AuthContext] Network disconnected');
|
|
11861
|
+
setIsOnline(false);
|
|
11862
|
+
};
|
|
11863
|
+
window.addEventListener('online', handleOnline);
|
|
11864
|
+
window.addEventListener('offline', handleOffline);
|
|
11865
|
+
return () => {
|
|
11866
|
+
window.removeEventListener('online', handleOnline);
|
|
11867
|
+
window.removeEventListener('offline', handleOffline);
|
|
11868
|
+
};
|
|
11869
|
+
}, [proxyMode, token, user, retryVerification]);
|
|
11604
11870
|
const value = {
|
|
11605
11871
|
user,
|
|
11606
11872
|
token,
|
|
11607
11873
|
accountData,
|
|
11608
11874
|
accountInfo,
|
|
11609
11875
|
isAuthenticated: !!user,
|
|
11876
|
+
isVerified,
|
|
11610
11877
|
isLoading,
|
|
11878
|
+
isOnline,
|
|
11611
11879
|
proxyMode,
|
|
11880
|
+
contact,
|
|
11881
|
+
contactId,
|
|
11882
|
+
getContact,
|
|
11883
|
+
updateContactCustomFields,
|
|
11612
11884
|
login,
|
|
11613
11885
|
logout,
|
|
11614
11886
|
getToken,
|
|
@@ -11617,6 +11889,7 @@ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 *
|
|
|
11617
11889
|
refreshAccount,
|
|
11618
11890
|
clearAccountCache,
|
|
11619
11891
|
onAuthStateChange,
|
|
11892
|
+
retryVerification,
|
|
11620
11893
|
};
|
|
11621
11894
|
return jsxRuntime.jsx(AuthContext.Provider, { value: value, children: children });
|
|
11622
11895
|
};
|
|
@@ -11895,7 +12168,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11895
12168
|
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-then-auto-login';
|
|
11896
12169
|
if ((verificationMode === 'verify-then-auto-login' || verificationMode === 'immediate') && response.token) {
|
|
11897
12170
|
// Auto-login modes: Log the user in immediately if token is provided
|
|
11898
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12171
|
+
auth.login(response.token, response.user, response.accountData, true);
|
|
11899
12172
|
setAuthSuccess(true);
|
|
11900
12173
|
setSuccessMessage('Email verified successfully! You are now logged in.');
|
|
11901
12174
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
@@ -11938,7 +12211,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11938
12211
|
const response = await api.verifyMagicLink(token);
|
|
11939
12212
|
// Auto-login with magic link if token is provided
|
|
11940
12213
|
if (response.token) {
|
|
11941
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12214
|
+
auth.login(response.token, response.user, response.accountData, true);
|
|
11942
12215
|
setAuthSuccess(true);
|
|
11943
12216
|
setSuccessMessage('Magic link verified! You are now logged in.');
|
|
11944
12217
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
@@ -12013,8 +12286,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12013
12286
|
if (mode === 'register') {
|
|
12014
12287
|
// Handle different verification modes
|
|
12015
12288
|
if (verificationMode === 'immediate' && response.token) {
|
|
12016
|
-
// Immediate mode: Log in right away if token is provided
|
|
12017
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12289
|
+
// Immediate mode: Log in right away if token is provided (isNewUser=true for registration)
|
|
12290
|
+
auth.login(response.token, response.user, response.accountData, true);
|
|
12018
12291
|
setAuthSuccess(true);
|
|
12019
12292
|
const deadline = response.emailVerificationDeadline
|
|
12020
12293
|
? new Date(response.emailVerificationDeadline).toLocaleString()
|
|
@@ -12050,7 +12323,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12050
12323
|
if (response.requiresEmailVerification) {
|
|
12051
12324
|
throw new Error('Please verify your email before logging in. Check your inbox for the verification link.');
|
|
12052
12325
|
}
|
|
12053
|
-
auth.login(response.token, response.user, response.accountData);
|
|
12326
|
+
auth.login(response.token, response.user, response.accountData, false);
|
|
12054
12327
|
setAuthSuccess(true);
|
|
12055
12328
|
setSuccessMessage('Login successful!');
|
|
12056
12329
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
@@ -12210,7 +12483,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12210
12483
|
tokenType: 'access_token',
|
|
12211
12484
|
});
|
|
12212
12485
|
if (authResponse.token) {
|
|
12213
|
-
|
|
12486
|
+
// Google OAuth can be login or signup - use isNewUser flag from backend if available
|
|
12487
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
|
|
12214
12488
|
setAuthSuccess(true);
|
|
12215
12489
|
setSuccessMessage('Google login successful!');
|
|
12216
12490
|
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
@@ -12259,7 +12533,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12259
12533
|
const idToken = response.credential;
|
|
12260
12534
|
const authResponse = await api.loginWithGoogle(idToken);
|
|
12261
12535
|
if (authResponse.token) {
|
|
12262
|
-
|
|
12536
|
+
// Google OAuth can be login or signup - use isNewUser flag from backend if available
|
|
12537
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser);
|
|
12263
12538
|
setAuthSuccess(true);
|
|
12264
12539
|
setSuccessMessage('Google login successful!');
|
|
12265
12540
|
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
@@ -12320,7 +12595,8 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12320
12595
|
const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
|
|
12321
12596
|
// Update auth context with account data if token is provided
|
|
12322
12597
|
if (response.token) {
|
|
12323
|
-
auth
|
|
12598
|
+
// Phone auth can be login or signup - use isNewUser flag from backend if available
|
|
12599
|
+
auth.login(response.token, response.user, response.accountData, response.isNewUser);
|
|
12324
12600
|
onAuthSuccess(response.token, response.user, response.accountData);
|
|
12325
12601
|
if (redirectUrl) {
|
|
12326
12602
|
window.location.href = redirectUrl;
|