@proveanything/smartlinks-auth-ui 0.1.10 → 0.1.13
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 +9 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts +2 -0
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +302 -69
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +302 -69
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.esm.js
CHANGED
|
@@ -10646,8 +10646,21 @@ class AuthAPI {
|
|
|
10646
10646
|
accountData: data.accountData,
|
|
10647
10647
|
});
|
|
10648
10648
|
}
|
|
10649
|
-
async loginWithGoogle(
|
|
10650
|
-
|
|
10649
|
+
async loginWithGoogle(token, options) {
|
|
10650
|
+
this.log.log('loginWithGoogle called:', {
|
|
10651
|
+
tokenType: options?.tokenType || 'id_token',
|
|
10652
|
+
hasUserInfo: !!options?.googleUserInfo,
|
|
10653
|
+
userEmail: options?.googleUserInfo?.email,
|
|
10654
|
+
tokenLength: token?.length,
|
|
10655
|
+
});
|
|
10656
|
+
// Note: The SDK only supports ID tokens currently
|
|
10657
|
+
// Access tokens from popup flow may fail with "Invalid or expired Google token"
|
|
10658
|
+
if (options?.tokenType === 'access_token') {
|
|
10659
|
+
this.log.warn('Warning: Popup flow sends access_token, but backend expects id_token. This may fail.');
|
|
10660
|
+
this.log.warn('Consider using OneTap flow (default) or updating backend to handle access tokens.');
|
|
10661
|
+
}
|
|
10662
|
+
// Pass token to SDK - backend verifies with Google
|
|
10663
|
+
return smartlinks.authKit.googleLogin(this.clientId, token);
|
|
10651
10664
|
}
|
|
10652
10665
|
async sendPhoneCode(phoneNumber) {
|
|
10653
10666
|
return smartlinks.authKit.sendPhoneCode(this.clientId, phoneNumber);
|
|
@@ -11204,7 +11217,7 @@ const tokenStorage = {
|
|
|
11204
11217
|
};
|
|
11205
11218
|
|
|
11206
11219
|
const AuthContext = createContext(undefined);
|
|
11207
|
-
const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
|
|
11220
|
+
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
|
|
11208
11221
|
const [user, setUser] = useState(null);
|
|
11209
11222
|
const [token, setToken] = useState(null);
|
|
11210
11223
|
const [accountData, setAccountData] = useState(null);
|
|
@@ -11228,10 +11241,45 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11228
11241
|
}
|
|
11229
11242
|
});
|
|
11230
11243
|
}, []);
|
|
11231
|
-
// Initialize auth state
|
|
11244
|
+
// Initialize auth state - different behavior for proxy mode vs standalone mode
|
|
11232
11245
|
useEffect(() => {
|
|
11233
11246
|
const initializeAuth = async () => {
|
|
11234
11247
|
try {
|
|
11248
|
+
if (proxyMode) {
|
|
11249
|
+
// PROXY MODE: Initialize from URL params and parent via SDK
|
|
11250
|
+
const params = new URLSearchParams(window.location.search);
|
|
11251
|
+
const userId = params.get('userId');
|
|
11252
|
+
if (userId) {
|
|
11253
|
+
console.log('[AuthContext] Proxy mode: userId detected, fetching account from parent');
|
|
11254
|
+
try {
|
|
11255
|
+
// Fetch account details from parent via proxied API call
|
|
11256
|
+
const accountResponse = await smartlinks.auth.getAccount();
|
|
11257
|
+
// Build user object from account response
|
|
11258
|
+
const accountAny = accountResponse;
|
|
11259
|
+
const userFromAccount = {
|
|
11260
|
+
uid: userId,
|
|
11261
|
+
email: accountAny?.email,
|
|
11262
|
+
displayName: accountAny?.displayName || accountAny?.name,
|
|
11263
|
+
phoneNumber: accountAny?.phoneNumber,
|
|
11264
|
+
};
|
|
11265
|
+
setUser(userFromAccount);
|
|
11266
|
+
setAccountData(accountResponse);
|
|
11267
|
+
setAccountInfo(accountResponse);
|
|
11268
|
+
console.log('[AuthContext] Proxy mode: initialized from parent account');
|
|
11269
|
+
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse);
|
|
11270
|
+
}
|
|
11271
|
+
catch (error) {
|
|
11272
|
+
console.warn('[AuthContext] Proxy mode: failed to fetch account from parent:', error);
|
|
11273
|
+
// No session - that's ok, user may need to login
|
|
11274
|
+
}
|
|
11275
|
+
}
|
|
11276
|
+
else {
|
|
11277
|
+
console.log('[AuthContext] Proxy mode: no userId in URL, awaiting login');
|
|
11278
|
+
}
|
|
11279
|
+
setIsLoading(false);
|
|
11280
|
+
return;
|
|
11281
|
+
}
|
|
11282
|
+
// STANDALONE MODE: Load from persistent storage
|
|
11235
11283
|
const storedToken = await tokenStorage.getToken();
|
|
11236
11284
|
const storedUser = await tokenStorage.getUser();
|
|
11237
11285
|
const storedAccountData = await tokenStorage.getAccountData();
|
|
@@ -11258,9 +11306,49 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11258
11306
|
}
|
|
11259
11307
|
};
|
|
11260
11308
|
initializeAuth();
|
|
11261
|
-
}, []);
|
|
11262
|
-
//
|
|
11309
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11310
|
+
// Listen for parent auth state changes (proxy mode only)
|
|
11311
|
+
useEffect(() => {
|
|
11312
|
+
if (!proxyMode)
|
|
11313
|
+
return;
|
|
11314
|
+
console.log('[AuthContext] Proxy mode: setting up parent message listener');
|
|
11315
|
+
const handleParentMessage = (event) => {
|
|
11316
|
+
// Handle auth state pushed from parent
|
|
11317
|
+
if (event.data?.type === 'smartlinks:authkit:state') {
|
|
11318
|
+
const { user: parentUser, accountData: parentAccountData, authenticated } = event.data.payload || {};
|
|
11319
|
+
console.log('[AuthContext] Proxy mode: received state from parent:', { authenticated });
|
|
11320
|
+
if (authenticated && parentUser) {
|
|
11321
|
+
const userObj = {
|
|
11322
|
+
uid: parentUser.uid || parentUser.id,
|
|
11323
|
+
email: parentUser.email,
|
|
11324
|
+
displayName: parentUser.displayName || parentUser.name,
|
|
11325
|
+
phoneNumber: parentUser.phoneNumber,
|
|
11326
|
+
};
|
|
11327
|
+
setUser(userObj);
|
|
11328
|
+
setAccountData(parentAccountData || null);
|
|
11329
|
+
setAccountInfo(parentAccountData || null);
|
|
11330
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null);
|
|
11331
|
+
}
|
|
11332
|
+
else {
|
|
11333
|
+
// Parent indicates no session / logged out
|
|
11334
|
+
setUser(null);
|
|
11335
|
+
setToken(null);
|
|
11336
|
+
setAccountData(null);
|
|
11337
|
+
setAccountInfo(null);
|
|
11338
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null);
|
|
11339
|
+
}
|
|
11340
|
+
}
|
|
11341
|
+
};
|
|
11342
|
+
window.addEventListener('message', handleParentMessage);
|
|
11343
|
+
return () => {
|
|
11344
|
+
console.log('[AuthContext] Proxy mode: cleaning up parent message listener');
|
|
11345
|
+
window.removeEventListener('message', handleParentMessage);
|
|
11346
|
+
};
|
|
11347
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11348
|
+
// Cross-tab synchronization - standalone mode only
|
|
11263
11349
|
useEffect(() => {
|
|
11350
|
+
if (proxyMode)
|
|
11351
|
+
return; // Skip cross-tab sync in proxy mode
|
|
11264
11352
|
console.log('[AuthContext] Setting up cross-tab synchronization');
|
|
11265
11353
|
const unsubscribe = onStorageChange(async (event) => {
|
|
11266
11354
|
console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
|
|
@@ -11319,27 +11407,38 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11319
11407
|
console.log('[AuthContext] Cleaning up cross-tab synchronization');
|
|
11320
11408
|
unsubscribe();
|
|
11321
11409
|
};
|
|
11322
|
-
}, [notifyAuthStateChange]);
|
|
11410
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11323
11411
|
const login = useCallback(async (authToken, authUser, authAccountData) => {
|
|
11324
11412
|
try {
|
|
11325
|
-
//
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11413
|
+
// Only persist to storage in standalone mode
|
|
11414
|
+
if (!proxyMode) {
|
|
11415
|
+
await tokenStorage.saveToken(authToken);
|
|
11416
|
+
await tokenStorage.saveUser(authUser);
|
|
11417
|
+
if (authAccountData) {
|
|
11418
|
+
await tokenStorage.saveAccountData(authAccountData);
|
|
11419
|
+
}
|
|
11420
|
+
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
11421
|
+
smartlinks.auth.verifyToken(authToken).catch(err => {
|
|
11422
|
+
console.warn('Failed to set bearer token on login:', err);
|
|
11423
|
+
});
|
|
11330
11424
|
}
|
|
11425
|
+
// Always update memory state
|
|
11331
11426
|
setToken(authToken);
|
|
11332
11427
|
setUser(authUser);
|
|
11333
11428
|
setAccountData(authAccountData || null);
|
|
11334
|
-
//
|
|
11335
|
-
//
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11429
|
+
// Cross-iframe auth state synchronization
|
|
11430
|
+
// Always notify parent frame of login (both modes, but especially important in proxy mode)
|
|
11431
|
+
const sdk = smartlinks;
|
|
11432
|
+
if (sdk.iframe?.isIframe?.()) {
|
|
11433
|
+
sdk.iframe.sendParentCustom('smartlinks:authkit:login', {
|
|
11434
|
+
token: authToken,
|
|
11435
|
+
user: authUser,
|
|
11436
|
+
accountData: authAccountData || null
|
|
11437
|
+
});
|
|
11438
|
+
}
|
|
11339
11439
|
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
|
|
11340
|
-
// Optionally preload account info on login
|
|
11341
|
-
if (preloadAccountInfo) {
|
|
11342
|
-
// Preload after login completes (non-blocking)
|
|
11440
|
+
// Optionally preload account info on login (standalone mode only)
|
|
11441
|
+
if (!proxyMode && preloadAccountInfo) {
|
|
11343
11442
|
getAccount(true).catch(error => {
|
|
11344
11443
|
console.warn('[AuthContext] Failed to preload account info:', error);
|
|
11345
11444
|
});
|
|
@@ -11349,34 +11448,55 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11349
11448
|
console.error('Failed to save auth data to storage:', error);
|
|
11350
11449
|
throw error;
|
|
11351
11450
|
}
|
|
11352
|
-
}, [notifyAuthStateChange, preloadAccountInfo]);
|
|
11451
|
+
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo]);
|
|
11353
11452
|
const logout = useCallback(async () => {
|
|
11354
11453
|
try {
|
|
11355
|
-
//
|
|
11356
|
-
|
|
11454
|
+
// Only clear persistent storage in standalone mode
|
|
11455
|
+
if (!proxyMode) {
|
|
11456
|
+
await tokenStorage.clearAll();
|
|
11457
|
+
smartlinks.auth.logout();
|
|
11458
|
+
}
|
|
11459
|
+
// Always clear memory state
|
|
11357
11460
|
setToken(null);
|
|
11358
11461
|
setUser(null);
|
|
11359
11462
|
setAccountData(null);
|
|
11360
11463
|
setAccountInfo(null);
|
|
11361
|
-
//
|
|
11362
|
-
|
|
11464
|
+
// Cross-iframe auth state synchronization
|
|
11465
|
+
// Always notify parent frame of logout
|
|
11466
|
+
const sdk = smartlinks;
|
|
11467
|
+
if (sdk.iframe?.isIframe?.()) {
|
|
11468
|
+
sdk.iframe.sendParentCustom('smartlinks:authkit:logout', {});
|
|
11469
|
+
}
|
|
11363
11470
|
notifyAuthStateChange('LOGOUT', null, null, null);
|
|
11364
11471
|
}
|
|
11365
11472
|
catch (error) {
|
|
11366
11473
|
console.error('Failed to clear auth data from storage:', error);
|
|
11367
11474
|
}
|
|
11368
|
-
}, [notifyAuthStateChange]);
|
|
11475
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11369
11476
|
const getToken = useCallback(async () => {
|
|
11477
|
+
if (proxyMode) {
|
|
11478
|
+
// In proxy mode, token is managed by parent - return memory state
|
|
11479
|
+
return token;
|
|
11480
|
+
}
|
|
11370
11481
|
const storedToken = await tokenStorage.getToken();
|
|
11371
11482
|
return storedToken ? storedToken.token : null;
|
|
11372
|
-
}, []);
|
|
11483
|
+
}, [proxyMode, token]);
|
|
11373
11484
|
const refreshToken = useCallback(async () => {
|
|
11374
11485
|
throw new Error('Token refresh must be implemented via your backend API');
|
|
11375
11486
|
}, []);
|
|
11376
|
-
// Get account with intelligent caching
|
|
11487
|
+
// Get account with intelligent caching (or direct parent fetch in proxy mode)
|
|
11377
11488
|
const getAccount = useCallback(async (forceRefresh = false) => {
|
|
11378
11489
|
try {
|
|
11379
|
-
|
|
11490
|
+
if (proxyMode) {
|
|
11491
|
+
// PROXY MODE: Always fetch from parent via proxied API, no local cache
|
|
11492
|
+
console.log('[AuthContext] Proxy mode: fetching account from parent');
|
|
11493
|
+
const freshAccountInfo = await smartlinks.auth.getAccount();
|
|
11494
|
+
setAccountInfo(freshAccountInfo);
|
|
11495
|
+
setAccountData(freshAccountInfo);
|
|
11496
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo);
|
|
11497
|
+
return freshAccountInfo;
|
|
11498
|
+
}
|
|
11499
|
+
// STANDALONE MODE: Use caching
|
|
11380
11500
|
if (!token) {
|
|
11381
11501
|
throw new Error('Not authenticated. Please login first.');
|
|
11382
11502
|
}
|
|
@@ -11399,24 +11519,28 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11399
11519
|
}
|
|
11400
11520
|
catch (error) {
|
|
11401
11521
|
console.error('[AuthContext] Failed to get account info:', error);
|
|
11402
|
-
// Fallback to stale cache if API fails
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11522
|
+
// Fallback to stale cache if API fails (standalone mode only)
|
|
11523
|
+
if (!proxyMode) {
|
|
11524
|
+
const cached = await tokenStorage.getAccountInfo();
|
|
11525
|
+
if (cached) {
|
|
11526
|
+
console.warn('[AuthContext] Returning stale cached data due to API error');
|
|
11527
|
+
return cached.data;
|
|
11528
|
+
}
|
|
11407
11529
|
}
|
|
11408
11530
|
throw error;
|
|
11409
11531
|
}
|
|
11410
|
-
}, [token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11532
|
+
}, [proxyMode, token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11411
11533
|
// Convenience method for explicit refresh
|
|
11412
11534
|
const refreshAccount = useCallback(async () => {
|
|
11413
11535
|
return await getAccount(true);
|
|
11414
11536
|
}, [getAccount]);
|
|
11415
|
-
// Clear account cache
|
|
11537
|
+
// Clear account cache (no-op in proxy mode)
|
|
11416
11538
|
const clearAccountCache = useCallback(async () => {
|
|
11417
|
-
|
|
11539
|
+
if (!proxyMode) {
|
|
11540
|
+
await tokenStorage.clearAccountInfo();
|
|
11541
|
+
}
|
|
11418
11542
|
setAccountInfo(null);
|
|
11419
|
-
}, []);
|
|
11543
|
+
}, [proxyMode]);
|
|
11420
11544
|
const onAuthStateChange = useCallback((callback) => {
|
|
11421
11545
|
callbacksRef.current.add(callback);
|
|
11422
11546
|
// Return unsubscribe function
|
|
@@ -11429,8 +11553,9 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11429
11553
|
token,
|
|
11430
11554
|
accountData,
|
|
11431
11555
|
accountInfo,
|
|
11432
|
-
isAuthenticated: !!
|
|
11556
|
+
isAuthenticated: !!user,
|
|
11433
11557
|
isLoading,
|
|
11558
|
+
proxyMode,
|
|
11434
11559
|
login,
|
|
11435
11560
|
logout,
|
|
11436
11561
|
getToken,
|
|
@@ -11493,7 +11618,34 @@ const loadGoogleIdentityServices = () => {
|
|
|
11493
11618
|
document.head.appendChild(script);
|
|
11494
11619
|
});
|
|
11495
11620
|
};
|
|
11496
|
-
|
|
11621
|
+
// Helper to convert generic SDK errors to user-friendly messages
|
|
11622
|
+
const getFriendlyErrorMessage = (errorMessage) => {
|
|
11623
|
+
// Check for common HTTP status codes in the error message
|
|
11624
|
+
if (errorMessage.includes('status 400')) {
|
|
11625
|
+
return 'Invalid request. Please check your input and try again.';
|
|
11626
|
+
}
|
|
11627
|
+
if (errorMessage.includes('status 401')) {
|
|
11628
|
+
return 'Invalid credentials. Please check your email and password.';
|
|
11629
|
+
}
|
|
11630
|
+
if (errorMessage.includes('status 403')) {
|
|
11631
|
+
return 'Access denied. You do not have permission to perform this action.';
|
|
11632
|
+
}
|
|
11633
|
+
if (errorMessage.includes('status 404')) {
|
|
11634
|
+
return 'Account not found. Please check your email or create a new account.';
|
|
11635
|
+
}
|
|
11636
|
+
if (errorMessage.includes('status 409')) {
|
|
11637
|
+
return 'This email is already registered.';
|
|
11638
|
+
}
|
|
11639
|
+
if (errorMessage.includes('status 429')) {
|
|
11640
|
+
return 'Too many attempts. Please wait a moment and try again.';
|
|
11641
|
+
}
|
|
11642
|
+
if (errorMessage.includes('status 500') || errorMessage.includes('status 502') || errorMessage.includes('status 503')) {
|
|
11643
|
+
return 'Server error. Please try again later.';
|
|
11644
|
+
}
|
|
11645
|
+
// Return original message if no pattern matches
|
|
11646
|
+
return errorMessage;
|
|
11647
|
+
};
|
|
11648
|
+
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, }) => {
|
|
11497
11649
|
const [mode, setMode] = useState(initialMode);
|
|
11498
11650
|
const [loading, setLoading] = useState(false);
|
|
11499
11651
|
const [error, setError] = useState();
|
|
@@ -11526,25 +11678,25 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11526
11678
|
mediaQuery.addEventListener('change', updateTheme);
|
|
11527
11679
|
return () => mediaQuery.removeEventListener('change', updateTheme);
|
|
11528
11680
|
}, [theme]);
|
|
11529
|
-
// Reinitialize Smartlinks SDK when apiEndpoint
|
|
11681
|
+
// Reinitialize Smartlinks SDK when apiEndpoint or proxyMode changes
|
|
11530
11682
|
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11531
11683
|
useEffect(() => {
|
|
11532
|
-
log.log('SDK reinitialize useEffect triggered', { apiEndpoint });
|
|
11684
|
+
log.log('SDK reinitialize useEffect triggered', { apiEndpoint, proxyMode });
|
|
11533
11685
|
setSdkReady(false); // Mark SDK as not ready during reinitialization
|
|
11534
11686
|
const reinitializeWithToken = async () => {
|
|
11535
11687
|
if (apiEndpoint) {
|
|
11536
|
-
log.log('Reinitializing SDK with baseURL:', apiEndpoint);
|
|
11537
|
-
// Get current token before reinitializing
|
|
11538
|
-
const currentToken = await auth.getToken();
|
|
11688
|
+
log.log('Reinitializing SDK with baseURL:', apiEndpoint, 'proxyMode:', proxyMode);
|
|
11689
|
+
// Get current token before reinitializing (only in standalone mode)
|
|
11690
|
+
const currentToken = !proxyMode ? await auth.getToken() : null;
|
|
11539
11691
|
smartlinks.initializeApi({
|
|
11540
11692
|
baseURL: apiEndpoint,
|
|
11541
|
-
proxyMode:
|
|
11693
|
+
proxyMode: proxyMode, // Use prop value
|
|
11542
11694
|
ngrokSkipBrowserWarning: true,
|
|
11543
11695
|
logger: logger, // Pass logger to SDK for verbose SDK logging
|
|
11544
11696
|
});
|
|
11545
11697
|
log.log('SDK reinitialized successfully');
|
|
11546
|
-
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11547
|
-
if (currentToken) {
|
|
11698
|
+
// Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
|
|
11699
|
+
if (currentToken && !proxyMode) {
|
|
11548
11700
|
smartlinks.auth.verifyToken(currentToken).catch(err => {
|
|
11549
11701
|
log.warn('Failed to restore bearer token after reinit:', err);
|
|
11550
11702
|
});
|
|
@@ -11552,6 +11704,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11552
11704
|
// Mark SDK as ready
|
|
11553
11705
|
setSdkReady(true);
|
|
11554
11706
|
}
|
|
11707
|
+
else if (proxyMode) {
|
|
11708
|
+
// In proxy mode without custom endpoint, SDK should already be initialized by parent
|
|
11709
|
+
log.log('Proxy mode without apiEndpoint, SDK already initialized by parent');
|
|
11710
|
+
setSdkReady(true);
|
|
11711
|
+
}
|
|
11555
11712
|
else {
|
|
11556
11713
|
log.log('No apiEndpoint, SDK already initialized by App');
|
|
11557
11714
|
// SDK was initialized by App component, mark as ready
|
|
@@ -11559,7 +11716,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11559
11716
|
}
|
|
11560
11717
|
};
|
|
11561
11718
|
reinitializeWithToken();
|
|
11562
|
-
}, [apiEndpoint, auth, logger, log]);
|
|
11719
|
+
}, [apiEndpoint, proxyMode, auth, logger, log]);
|
|
11563
11720
|
// Get the effective redirect URL (use prop or default to current page)
|
|
11564
11721
|
const getRedirectUrl = () => {
|
|
11565
11722
|
if (redirectUrl)
|
|
@@ -11857,14 +12014,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11857
12014
|
}
|
|
11858
12015
|
catch (err) {
|
|
11859
12016
|
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
|
11860
|
-
// Check if error is about email already registered
|
|
11861
|
-
|
|
12017
|
+
// Check if error is about email already registered (by content or 409 status code)
|
|
12018
|
+
const isAlreadyRegistered = mode === 'register' && ((errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) ||
|
|
12019
|
+
errorMessage.includes('status 409'));
|
|
12020
|
+
if (isAlreadyRegistered) {
|
|
11862
12021
|
setShowResendVerification(true);
|
|
11863
12022
|
setResendEmail(data.email);
|
|
11864
12023
|
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11865
12024
|
}
|
|
11866
12025
|
else {
|
|
11867
|
-
|
|
12026
|
+
// Try to extract a more meaningful error message from status codes
|
|
12027
|
+
const friendlyMessage = getFriendlyErrorMessage(errorMessage);
|
|
12028
|
+
setError(friendlyMessage);
|
|
11868
12029
|
}
|
|
11869
12030
|
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11870
12031
|
}
|
|
@@ -11920,6 +12081,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11920
12081
|
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
11921
12082
|
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
11922
12083
|
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
12084
|
+
// Log Google Auth configuration for debugging
|
|
12085
|
+
log.log('Google Auth initiated:', {
|
|
12086
|
+
googleClientId,
|
|
12087
|
+
oauthFlow,
|
|
12088
|
+
currentOrigin: window.location.origin,
|
|
12089
|
+
currentHref: window.location.href,
|
|
12090
|
+
configGoogleClientId: config?.googleClientId,
|
|
12091
|
+
usingDefaultClientId: !config?.googleClientId,
|
|
12092
|
+
});
|
|
11923
12093
|
setLoading(true);
|
|
11924
12094
|
setError(undefined);
|
|
11925
12095
|
try {
|
|
@@ -11929,35 +12099,87 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11929
12099
|
if (!google?.accounts) {
|
|
11930
12100
|
throw new Error('Google Identity Services failed to initialize');
|
|
11931
12101
|
}
|
|
12102
|
+
log.log('Google Identity Services loaded, using flow:', oauthFlow);
|
|
11932
12103
|
if (oauthFlow === 'popup') {
|
|
11933
12104
|
// Use OAuth2 popup flow (works in iframes but requires popup permission)
|
|
11934
12105
|
if (!google.accounts.oauth2) {
|
|
11935
12106
|
throw new Error('Google OAuth2 not available');
|
|
11936
12107
|
}
|
|
12108
|
+
log.log('Initializing Google OAuth2 popup flow:', {
|
|
12109
|
+
client_id: googleClientId,
|
|
12110
|
+
scope: 'openid email profile',
|
|
12111
|
+
origin: window.location.origin,
|
|
12112
|
+
});
|
|
11937
12113
|
const client = google.accounts.oauth2.initTokenClient({
|
|
11938
12114
|
client_id: googleClientId,
|
|
11939
12115
|
scope: 'openid email profile',
|
|
11940
12116
|
callback: async (response) => {
|
|
11941
12117
|
try {
|
|
12118
|
+
log.log('Google OAuth2 popup callback received:', {
|
|
12119
|
+
hasAccessToken: !!response.access_token,
|
|
12120
|
+
hasIdToken: !!response.id_token,
|
|
12121
|
+
tokenType: response.token_type,
|
|
12122
|
+
expiresIn: response.expires_in,
|
|
12123
|
+
scope: response.scope,
|
|
12124
|
+
error: response.error,
|
|
12125
|
+
errorDescription: response.error_description,
|
|
12126
|
+
});
|
|
11942
12127
|
if (response.error) {
|
|
11943
12128
|
throw new Error(response.error_description || response.error);
|
|
11944
12129
|
}
|
|
12130
|
+
// OAuth2 popup flow returns access_token, not id_token
|
|
12131
|
+
// We need to use the access token to get user info from Google
|
|
11945
12132
|
const accessToken = response.access_token;
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
if (authResponse.token) {
|
|
11949
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11950
|
-
setAuthSuccess(true);
|
|
11951
|
-
setSuccessMessage('Google login successful!');
|
|
11952
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12133
|
+
if (!accessToken) {
|
|
12134
|
+
throw new Error('No access token received from Google');
|
|
11953
12135
|
}
|
|
11954
|
-
|
|
11955
|
-
|
|
12136
|
+
log.log('Fetching user info from Google using access token...');
|
|
12137
|
+
// Fetch user info from Google's userinfo endpoint
|
|
12138
|
+
const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
|
12139
|
+
headers: {
|
|
12140
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
12141
|
+
},
|
|
12142
|
+
});
|
|
12143
|
+
if (!userInfoResponse.ok) {
|
|
12144
|
+
throw new Error('Failed to fetch user info from Google');
|
|
11956
12145
|
}
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
12146
|
+
const userInfo = await userInfoResponse.json();
|
|
12147
|
+
log.log('Google user info retrieved:', {
|
|
12148
|
+
email: userInfo.email,
|
|
12149
|
+
name: userInfo.name,
|
|
12150
|
+
sub: userInfo.sub,
|
|
12151
|
+
});
|
|
12152
|
+
// For popup flow, send the access token to backend
|
|
12153
|
+
// Note: This may fail if backend only supports ID token verification
|
|
12154
|
+
try {
|
|
12155
|
+
const authResponse = await api.loginWithGoogle(accessToken, {
|
|
12156
|
+
googleUserInfo: userInfo,
|
|
12157
|
+
tokenType: 'access_token',
|
|
12158
|
+
});
|
|
12159
|
+
if (authResponse.token) {
|
|
12160
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12161
|
+
setAuthSuccess(true);
|
|
12162
|
+
setSuccessMessage('Google login successful!');
|
|
12163
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12164
|
+
}
|
|
12165
|
+
else {
|
|
12166
|
+
throw new Error('Authentication failed - no token received');
|
|
12167
|
+
}
|
|
12168
|
+
if (redirectUrl) {
|
|
12169
|
+
setTimeout(() => {
|
|
12170
|
+
window.location.href = redirectUrl;
|
|
12171
|
+
}, 2000);
|
|
12172
|
+
}
|
|
12173
|
+
}
|
|
12174
|
+
catch (apiError) {
|
|
12175
|
+
const errorMessage = apiError instanceof Error ? apiError.message : 'Google login failed';
|
|
12176
|
+
// Check if this is the access token vs ID token mismatch
|
|
12177
|
+
if (errorMessage.includes('Invalid or expired Google token')) {
|
|
12178
|
+
log.error('Popup flow access token rejected by backend. Backend may only support ID tokens.');
|
|
12179
|
+
log.error('User info retrieved from Google:', userInfo);
|
|
12180
|
+
throw new Error('Google authentication failed. The popup flow may not be supported. Please try again or contact support.');
|
|
12181
|
+
}
|
|
12182
|
+
throw apiError;
|
|
11961
12183
|
}
|
|
11962
12184
|
setLoading(false);
|
|
11963
12185
|
}
|
|
@@ -11973,6 +12195,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11973
12195
|
}
|
|
11974
12196
|
else {
|
|
11975
12197
|
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
12198
|
+
log.log('Initializing Google OneTap flow:', {
|
|
12199
|
+
client_id: googleClientId,
|
|
12200
|
+
origin: window.location.origin,
|
|
12201
|
+
});
|
|
11976
12202
|
google.accounts.id.initialize({
|
|
11977
12203
|
client_id: googleClientId,
|
|
11978
12204
|
callback: async (response) => {
|
|
@@ -12004,11 +12230,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12004
12230
|
},
|
|
12005
12231
|
auto_select: false,
|
|
12006
12232
|
cancel_on_tap_outside: true,
|
|
12233
|
+
// Note: use_fedcm_for_prompt omitted - requires Permissions-Policy header on hosting server
|
|
12234
|
+
// Will be needed when FedCM becomes mandatory in the future
|
|
12007
12235
|
});
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12236
|
+
// Use timeout fallback instead of deprecated notification methods
|
|
12237
|
+
// (isNotDisplayed/isSkippedMoment will stop working when FedCM becomes mandatory)
|
|
12238
|
+
const promptTimeout = setTimeout(() => {
|
|
12239
|
+
setLoading(false);
|
|
12240
|
+
}, 5000);
|
|
12241
|
+
google.accounts.id.prompt(() => {
|
|
12242
|
+
// Clear timeout if prompt interaction occurs
|
|
12243
|
+
clearTimeout(promptTimeout);
|
|
12244
|
+
setLoading(false);
|
|
12012
12245
|
});
|
|
12013
12246
|
}
|
|
12014
12247
|
}
|