@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.js
CHANGED
|
@@ -10667,8 +10667,21 @@ class AuthAPI {
|
|
|
10667
10667
|
accountData: data.accountData,
|
|
10668
10668
|
});
|
|
10669
10669
|
}
|
|
10670
|
-
async loginWithGoogle(
|
|
10671
|
-
|
|
10670
|
+
async loginWithGoogle(token, options) {
|
|
10671
|
+
this.log.log('loginWithGoogle called:', {
|
|
10672
|
+
tokenType: options?.tokenType || 'id_token',
|
|
10673
|
+
hasUserInfo: !!options?.googleUserInfo,
|
|
10674
|
+
userEmail: options?.googleUserInfo?.email,
|
|
10675
|
+
tokenLength: token?.length,
|
|
10676
|
+
});
|
|
10677
|
+
// Note: The SDK only supports ID tokens currently
|
|
10678
|
+
// Access tokens from popup flow may fail with "Invalid or expired Google token"
|
|
10679
|
+
if (options?.tokenType === 'access_token') {
|
|
10680
|
+
this.log.warn('Warning: Popup flow sends access_token, but backend expects id_token. This may fail.');
|
|
10681
|
+
this.log.warn('Consider using OneTap flow (default) or updating backend to handle access tokens.');
|
|
10682
|
+
}
|
|
10683
|
+
// Pass token to SDK - backend verifies with Google
|
|
10684
|
+
return smartlinks__namespace.authKit.googleLogin(this.clientId, token);
|
|
10672
10685
|
}
|
|
10673
10686
|
async sendPhoneCode(phoneNumber) {
|
|
10674
10687
|
return smartlinks__namespace.authKit.sendPhoneCode(this.clientId, phoneNumber);
|
|
@@ -11225,7 +11238,7 @@ const tokenStorage = {
|
|
|
11225
11238
|
};
|
|
11226
11239
|
|
|
11227
11240
|
const AuthContext = React.createContext(undefined);
|
|
11228
|
-
const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
|
|
11241
|
+
const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
|
|
11229
11242
|
const [user, setUser] = React.useState(null);
|
|
11230
11243
|
const [token, setToken] = React.useState(null);
|
|
11231
11244
|
const [accountData, setAccountData] = React.useState(null);
|
|
@@ -11249,10 +11262,45 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11249
11262
|
}
|
|
11250
11263
|
});
|
|
11251
11264
|
}, []);
|
|
11252
|
-
// Initialize auth state
|
|
11265
|
+
// Initialize auth state - different behavior for proxy mode vs standalone mode
|
|
11253
11266
|
React.useEffect(() => {
|
|
11254
11267
|
const initializeAuth = async () => {
|
|
11255
11268
|
try {
|
|
11269
|
+
if (proxyMode) {
|
|
11270
|
+
// PROXY MODE: Initialize from URL params and parent via SDK
|
|
11271
|
+
const params = new URLSearchParams(window.location.search);
|
|
11272
|
+
const userId = params.get('userId');
|
|
11273
|
+
if (userId) {
|
|
11274
|
+
console.log('[AuthContext] Proxy mode: userId detected, fetching account from parent');
|
|
11275
|
+
try {
|
|
11276
|
+
// Fetch account details from parent via proxied API call
|
|
11277
|
+
const accountResponse = await smartlinks__namespace.auth.getAccount();
|
|
11278
|
+
// Build user object from account response
|
|
11279
|
+
const accountAny = accountResponse;
|
|
11280
|
+
const userFromAccount = {
|
|
11281
|
+
uid: userId,
|
|
11282
|
+
email: accountAny?.email,
|
|
11283
|
+
displayName: accountAny?.displayName || accountAny?.name,
|
|
11284
|
+
phoneNumber: accountAny?.phoneNumber,
|
|
11285
|
+
};
|
|
11286
|
+
setUser(userFromAccount);
|
|
11287
|
+
setAccountData(accountResponse);
|
|
11288
|
+
setAccountInfo(accountResponse);
|
|
11289
|
+
console.log('[AuthContext] Proxy mode: initialized from parent account');
|
|
11290
|
+
notifyAuthStateChange('LOGIN', userFromAccount, null, accountResponse, accountResponse);
|
|
11291
|
+
}
|
|
11292
|
+
catch (error) {
|
|
11293
|
+
console.warn('[AuthContext] Proxy mode: failed to fetch account from parent:', error);
|
|
11294
|
+
// No session - that's ok, user may need to login
|
|
11295
|
+
}
|
|
11296
|
+
}
|
|
11297
|
+
else {
|
|
11298
|
+
console.log('[AuthContext] Proxy mode: no userId in URL, awaiting login');
|
|
11299
|
+
}
|
|
11300
|
+
setIsLoading(false);
|
|
11301
|
+
return;
|
|
11302
|
+
}
|
|
11303
|
+
// STANDALONE MODE: Load from persistent storage
|
|
11256
11304
|
const storedToken = await tokenStorage.getToken();
|
|
11257
11305
|
const storedUser = await tokenStorage.getUser();
|
|
11258
11306
|
const storedAccountData = await tokenStorage.getAccountData();
|
|
@@ -11279,9 +11327,49 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11279
11327
|
}
|
|
11280
11328
|
};
|
|
11281
11329
|
initializeAuth();
|
|
11282
|
-
}, []);
|
|
11283
|
-
//
|
|
11330
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11331
|
+
// Listen for parent auth state changes (proxy mode only)
|
|
11332
|
+
React.useEffect(() => {
|
|
11333
|
+
if (!proxyMode)
|
|
11334
|
+
return;
|
|
11335
|
+
console.log('[AuthContext] Proxy mode: setting up parent message listener');
|
|
11336
|
+
const handleParentMessage = (event) => {
|
|
11337
|
+
// Handle auth state pushed from parent
|
|
11338
|
+
if (event.data?.type === 'smartlinks:authkit:state') {
|
|
11339
|
+
const { user: parentUser, accountData: parentAccountData, authenticated } = event.data.payload || {};
|
|
11340
|
+
console.log('[AuthContext] Proxy mode: received state from parent:', { authenticated });
|
|
11341
|
+
if (authenticated && parentUser) {
|
|
11342
|
+
const userObj = {
|
|
11343
|
+
uid: parentUser.uid || parentUser.id,
|
|
11344
|
+
email: parentUser.email,
|
|
11345
|
+
displayName: parentUser.displayName || parentUser.name,
|
|
11346
|
+
phoneNumber: parentUser.phoneNumber,
|
|
11347
|
+
};
|
|
11348
|
+
setUser(userObj);
|
|
11349
|
+
setAccountData(parentAccountData || null);
|
|
11350
|
+
setAccountInfo(parentAccountData || null);
|
|
11351
|
+
notifyAuthStateChange('CROSS_TAB_SYNC', userObj, null, parentAccountData || null, parentAccountData || null);
|
|
11352
|
+
}
|
|
11353
|
+
else {
|
|
11354
|
+
// Parent indicates no session / logged out
|
|
11355
|
+
setUser(null);
|
|
11356
|
+
setToken(null);
|
|
11357
|
+
setAccountData(null);
|
|
11358
|
+
setAccountInfo(null);
|
|
11359
|
+
notifyAuthStateChange('LOGOUT', null, null, null, null);
|
|
11360
|
+
}
|
|
11361
|
+
}
|
|
11362
|
+
};
|
|
11363
|
+
window.addEventListener('message', handleParentMessage);
|
|
11364
|
+
return () => {
|
|
11365
|
+
console.log('[AuthContext] Proxy mode: cleaning up parent message listener');
|
|
11366
|
+
window.removeEventListener('message', handleParentMessage);
|
|
11367
|
+
};
|
|
11368
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11369
|
+
// Cross-tab synchronization - standalone mode only
|
|
11284
11370
|
React.useEffect(() => {
|
|
11371
|
+
if (proxyMode)
|
|
11372
|
+
return; // Skip cross-tab sync in proxy mode
|
|
11285
11373
|
console.log('[AuthContext] Setting up cross-tab synchronization');
|
|
11286
11374
|
const unsubscribe = onStorageChange(async (event) => {
|
|
11287
11375
|
console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
|
|
@@ -11340,27 +11428,38 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11340
11428
|
console.log('[AuthContext] Cleaning up cross-tab synchronization');
|
|
11341
11429
|
unsubscribe();
|
|
11342
11430
|
};
|
|
11343
|
-
}, [notifyAuthStateChange]);
|
|
11431
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11344
11432
|
const login = React.useCallback(async (authToken, authUser, authAccountData) => {
|
|
11345
11433
|
try {
|
|
11346
|
-
//
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11434
|
+
// Only persist to storage in standalone mode
|
|
11435
|
+
if (!proxyMode) {
|
|
11436
|
+
await tokenStorage.saveToken(authToken);
|
|
11437
|
+
await tokenStorage.saveUser(authUser);
|
|
11438
|
+
if (authAccountData) {
|
|
11439
|
+
await tokenStorage.saveAccountData(authAccountData);
|
|
11440
|
+
}
|
|
11441
|
+
// Set bearer token in global Smartlinks SDK via auth.verifyToken
|
|
11442
|
+
smartlinks__namespace.auth.verifyToken(authToken).catch(err => {
|
|
11443
|
+
console.warn('Failed to set bearer token on login:', err);
|
|
11444
|
+
});
|
|
11351
11445
|
}
|
|
11446
|
+
// Always update memory state
|
|
11352
11447
|
setToken(authToken);
|
|
11353
11448
|
setUser(authUser);
|
|
11354
11449
|
setAccountData(authAccountData || null);
|
|
11355
|
-
//
|
|
11356
|
-
//
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11450
|
+
// Cross-iframe auth state synchronization
|
|
11451
|
+
// Always notify parent frame of login (both modes, but especially important in proxy mode)
|
|
11452
|
+
const sdk = smartlinks__namespace;
|
|
11453
|
+
if (sdk.iframe?.isIframe?.()) {
|
|
11454
|
+
sdk.iframe.sendParentCustom('smartlinks:authkit:login', {
|
|
11455
|
+
token: authToken,
|
|
11456
|
+
user: authUser,
|
|
11457
|
+
accountData: authAccountData || null
|
|
11458
|
+
});
|
|
11459
|
+
}
|
|
11360
11460
|
notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
|
|
11361
|
-
// Optionally preload account info on login
|
|
11362
|
-
if (preloadAccountInfo) {
|
|
11363
|
-
// Preload after login completes (non-blocking)
|
|
11461
|
+
// Optionally preload account info on login (standalone mode only)
|
|
11462
|
+
if (!proxyMode && preloadAccountInfo) {
|
|
11364
11463
|
getAccount(true).catch(error => {
|
|
11365
11464
|
console.warn('[AuthContext] Failed to preload account info:', error);
|
|
11366
11465
|
});
|
|
@@ -11370,34 +11469,55 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11370
11469
|
console.error('Failed to save auth data to storage:', error);
|
|
11371
11470
|
throw error;
|
|
11372
11471
|
}
|
|
11373
|
-
}, [notifyAuthStateChange, preloadAccountInfo]);
|
|
11472
|
+
}, [proxyMode, notifyAuthStateChange, preloadAccountInfo]);
|
|
11374
11473
|
const logout = React.useCallback(async () => {
|
|
11375
11474
|
try {
|
|
11376
|
-
//
|
|
11377
|
-
|
|
11475
|
+
// Only clear persistent storage in standalone mode
|
|
11476
|
+
if (!proxyMode) {
|
|
11477
|
+
await tokenStorage.clearAll();
|
|
11478
|
+
smartlinks__namespace.auth.logout();
|
|
11479
|
+
}
|
|
11480
|
+
// Always clear memory state
|
|
11378
11481
|
setToken(null);
|
|
11379
11482
|
setUser(null);
|
|
11380
11483
|
setAccountData(null);
|
|
11381
11484
|
setAccountInfo(null);
|
|
11382
|
-
//
|
|
11383
|
-
|
|
11485
|
+
// Cross-iframe auth state synchronization
|
|
11486
|
+
// Always notify parent frame of logout
|
|
11487
|
+
const sdk = smartlinks__namespace;
|
|
11488
|
+
if (sdk.iframe?.isIframe?.()) {
|
|
11489
|
+
sdk.iframe.sendParentCustom('smartlinks:authkit:logout', {});
|
|
11490
|
+
}
|
|
11384
11491
|
notifyAuthStateChange('LOGOUT', null, null, null);
|
|
11385
11492
|
}
|
|
11386
11493
|
catch (error) {
|
|
11387
11494
|
console.error('Failed to clear auth data from storage:', error);
|
|
11388
11495
|
}
|
|
11389
|
-
}, [notifyAuthStateChange]);
|
|
11496
|
+
}, [proxyMode, notifyAuthStateChange]);
|
|
11390
11497
|
const getToken = React.useCallback(async () => {
|
|
11498
|
+
if (proxyMode) {
|
|
11499
|
+
// In proxy mode, token is managed by parent - return memory state
|
|
11500
|
+
return token;
|
|
11501
|
+
}
|
|
11391
11502
|
const storedToken = await tokenStorage.getToken();
|
|
11392
11503
|
return storedToken ? storedToken.token : null;
|
|
11393
|
-
}, []);
|
|
11504
|
+
}, [proxyMode, token]);
|
|
11394
11505
|
const refreshToken = React.useCallback(async () => {
|
|
11395
11506
|
throw new Error('Token refresh must be implemented via your backend API');
|
|
11396
11507
|
}, []);
|
|
11397
|
-
// Get account with intelligent caching
|
|
11508
|
+
// Get account with intelligent caching (or direct parent fetch in proxy mode)
|
|
11398
11509
|
const getAccount = React.useCallback(async (forceRefresh = false) => {
|
|
11399
11510
|
try {
|
|
11400
|
-
|
|
11511
|
+
if (proxyMode) {
|
|
11512
|
+
// PROXY MODE: Always fetch from parent via proxied API, no local cache
|
|
11513
|
+
console.log('[AuthContext] Proxy mode: fetching account from parent');
|
|
11514
|
+
const freshAccountInfo = await smartlinks__namespace.auth.getAccount();
|
|
11515
|
+
setAccountInfo(freshAccountInfo);
|
|
11516
|
+
setAccountData(freshAccountInfo);
|
|
11517
|
+
notifyAuthStateChange('ACCOUNT_REFRESH', user, token, freshAccountInfo, freshAccountInfo);
|
|
11518
|
+
return freshAccountInfo;
|
|
11519
|
+
}
|
|
11520
|
+
// STANDALONE MODE: Use caching
|
|
11401
11521
|
if (!token) {
|
|
11402
11522
|
throw new Error('Not authenticated. Please login first.');
|
|
11403
11523
|
}
|
|
@@ -11420,24 +11540,28 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11420
11540
|
}
|
|
11421
11541
|
catch (error) {
|
|
11422
11542
|
console.error('[AuthContext] Failed to get account info:', error);
|
|
11423
|
-
// Fallback to stale cache if API fails
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11543
|
+
// Fallback to stale cache if API fails (standalone mode only)
|
|
11544
|
+
if (!proxyMode) {
|
|
11545
|
+
const cached = await tokenStorage.getAccountInfo();
|
|
11546
|
+
if (cached) {
|
|
11547
|
+
console.warn('[AuthContext] Returning stale cached data due to API error');
|
|
11548
|
+
return cached.data;
|
|
11549
|
+
}
|
|
11428
11550
|
}
|
|
11429
11551
|
throw error;
|
|
11430
11552
|
}
|
|
11431
|
-
}, [token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11553
|
+
}, [proxyMode, token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
|
|
11432
11554
|
// Convenience method for explicit refresh
|
|
11433
11555
|
const refreshAccount = React.useCallback(async () => {
|
|
11434
11556
|
return await getAccount(true);
|
|
11435
11557
|
}, [getAccount]);
|
|
11436
|
-
// Clear account cache
|
|
11558
|
+
// Clear account cache (no-op in proxy mode)
|
|
11437
11559
|
const clearAccountCache = React.useCallback(async () => {
|
|
11438
|
-
|
|
11560
|
+
if (!proxyMode) {
|
|
11561
|
+
await tokenStorage.clearAccountInfo();
|
|
11562
|
+
}
|
|
11439
11563
|
setAccountInfo(null);
|
|
11440
|
-
}, []);
|
|
11564
|
+
}, [proxyMode]);
|
|
11441
11565
|
const onAuthStateChange = React.useCallback((callback) => {
|
|
11442
11566
|
callbacksRef.current.add(callback);
|
|
11443
11567
|
// Return unsubscribe function
|
|
@@ -11450,8 +11574,9 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
|
|
|
11450
11574
|
token,
|
|
11451
11575
|
accountData,
|
|
11452
11576
|
accountInfo,
|
|
11453
|
-
isAuthenticated: !!
|
|
11577
|
+
isAuthenticated: !!user,
|
|
11454
11578
|
isLoading,
|
|
11579
|
+
proxyMode,
|
|
11455
11580
|
login,
|
|
11456
11581
|
logout,
|
|
11457
11582
|
getToken,
|
|
@@ -11514,7 +11639,34 @@ const loadGoogleIdentityServices = () => {
|
|
|
11514
11639
|
document.head.appendChild(script);
|
|
11515
11640
|
});
|
|
11516
11641
|
};
|
|
11517
|
-
|
|
11642
|
+
// Helper to convert generic SDK errors to user-friendly messages
|
|
11643
|
+
const getFriendlyErrorMessage = (errorMessage) => {
|
|
11644
|
+
// Check for common HTTP status codes in the error message
|
|
11645
|
+
if (errorMessage.includes('status 400')) {
|
|
11646
|
+
return 'Invalid request. Please check your input and try again.';
|
|
11647
|
+
}
|
|
11648
|
+
if (errorMessage.includes('status 401')) {
|
|
11649
|
+
return 'Invalid credentials. Please check your email and password.';
|
|
11650
|
+
}
|
|
11651
|
+
if (errorMessage.includes('status 403')) {
|
|
11652
|
+
return 'Access denied. You do not have permission to perform this action.';
|
|
11653
|
+
}
|
|
11654
|
+
if (errorMessage.includes('status 404')) {
|
|
11655
|
+
return 'Account not found. Please check your email or create a new account.';
|
|
11656
|
+
}
|
|
11657
|
+
if (errorMessage.includes('status 409')) {
|
|
11658
|
+
return 'This email is already registered.';
|
|
11659
|
+
}
|
|
11660
|
+
if (errorMessage.includes('status 429')) {
|
|
11661
|
+
return 'Too many attempts. Please wait a moment and try again.';
|
|
11662
|
+
}
|
|
11663
|
+
if (errorMessage.includes('status 500') || errorMessage.includes('status 502') || errorMessage.includes('status 503')) {
|
|
11664
|
+
return 'Server error. Please try again later.';
|
|
11665
|
+
}
|
|
11666
|
+
// Return original message if no pattern matches
|
|
11667
|
+
return errorMessage;
|
|
11668
|
+
};
|
|
11669
|
+
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, }) => {
|
|
11518
11670
|
const [mode, setMode] = React.useState(initialMode);
|
|
11519
11671
|
const [loading, setLoading] = React.useState(false);
|
|
11520
11672
|
const [error, setError] = React.useState();
|
|
@@ -11547,25 +11699,25 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11547
11699
|
mediaQuery.addEventListener('change', updateTheme);
|
|
11548
11700
|
return () => mediaQuery.removeEventListener('change', updateTheme);
|
|
11549
11701
|
}, [theme]);
|
|
11550
|
-
// Reinitialize Smartlinks SDK when apiEndpoint
|
|
11702
|
+
// Reinitialize Smartlinks SDK when apiEndpoint or proxyMode changes
|
|
11551
11703
|
// IMPORTANT: Preserve bearer token during reinitialization
|
|
11552
11704
|
React.useEffect(() => {
|
|
11553
|
-
log.log('SDK reinitialize useEffect triggered', { apiEndpoint });
|
|
11705
|
+
log.log('SDK reinitialize useEffect triggered', { apiEndpoint, proxyMode });
|
|
11554
11706
|
setSdkReady(false); // Mark SDK as not ready during reinitialization
|
|
11555
11707
|
const reinitializeWithToken = async () => {
|
|
11556
11708
|
if (apiEndpoint) {
|
|
11557
|
-
log.log('Reinitializing SDK with baseURL:', apiEndpoint);
|
|
11558
|
-
// Get current token before reinitializing
|
|
11559
|
-
const currentToken = await auth.getToken();
|
|
11709
|
+
log.log('Reinitializing SDK with baseURL:', apiEndpoint, 'proxyMode:', proxyMode);
|
|
11710
|
+
// Get current token before reinitializing (only in standalone mode)
|
|
11711
|
+
const currentToken = !proxyMode ? await auth.getToken() : null;
|
|
11560
11712
|
smartlinks__namespace.initializeApi({
|
|
11561
11713
|
baseURL: apiEndpoint,
|
|
11562
|
-
proxyMode:
|
|
11714
|
+
proxyMode: proxyMode, // Use prop value
|
|
11563
11715
|
ngrokSkipBrowserWarning: true,
|
|
11564
11716
|
logger: logger, // Pass logger to SDK for verbose SDK logging
|
|
11565
11717
|
});
|
|
11566
11718
|
log.log('SDK reinitialized successfully');
|
|
11567
|
-
// Restore bearer token after reinitialization using auth.verifyToken
|
|
11568
|
-
if (currentToken) {
|
|
11719
|
+
// Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
|
|
11720
|
+
if (currentToken && !proxyMode) {
|
|
11569
11721
|
smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
|
|
11570
11722
|
log.warn('Failed to restore bearer token after reinit:', err);
|
|
11571
11723
|
});
|
|
@@ -11573,6 +11725,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11573
11725
|
// Mark SDK as ready
|
|
11574
11726
|
setSdkReady(true);
|
|
11575
11727
|
}
|
|
11728
|
+
else if (proxyMode) {
|
|
11729
|
+
// In proxy mode without custom endpoint, SDK should already be initialized by parent
|
|
11730
|
+
log.log('Proxy mode without apiEndpoint, SDK already initialized by parent');
|
|
11731
|
+
setSdkReady(true);
|
|
11732
|
+
}
|
|
11576
11733
|
else {
|
|
11577
11734
|
log.log('No apiEndpoint, SDK already initialized by App');
|
|
11578
11735
|
// SDK was initialized by App component, mark as ready
|
|
@@ -11580,7 +11737,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11580
11737
|
}
|
|
11581
11738
|
};
|
|
11582
11739
|
reinitializeWithToken();
|
|
11583
|
-
}, [apiEndpoint, auth, logger, log]);
|
|
11740
|
+
}, [apiEndpoint, proxyMode, auth, logger, log]);
|
|
11584
11741
|
// Get the effective redirect URL (use prop or default to current page)
|
|
11585
11742
|
const getRedirectUrl = () => {
|
|
11586
11743
|
if (redirectUrl)
|
|
@@ -11878,14 +12035,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11878
12035
|
}
|
|
11879
12036
|
catch (err) {
|
|
11880
12037
|
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
|
11881
|
-
// Check if error is about email already registered
|
|
11882
|
-
|
|
12038
|
+
// Check if error is about email already registered (by content or 409 status code)
|
|
12039
|
+
const isAlreadyRegistered = mode === 'register' && ((errorMessage.toLowerCase().includes('already') && errorMessage.toLowerCase().includes('email')) ||
|
|
12040
|
+
errorMessage.includes('status 409'));
|
|
12041
|
+
if (isAlreadyRegistered) {
|
|
11883
12042
|
setShowResendVerification(true);
|
|
11884
12043
|
setResendEmail(data.email);
|
|
11885
12044
|
setError('This email is already registered. If you didn\'t receive the verification email, you can resend it below.');
|
|
11886
12045
|
}
|
|
11887
12046
|
else {
|
|
11888
|
-
|
|
12047
|
+
// Try to extract a more meaningful error message from status codes
|
|
12048
|
+
const friendlyMessage = getFriendlyErrorMessage(errorMessage);
|
|
12049
|
+
setError(friendlyMessage);
|
|
11889
12050
|
}
|
|
11890
12051
|
onAuthError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
11891
12052
|
}
|
|
@@ -11941,6 +12102,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11941
12102
|
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
11942
12103
|
// Determine OAuth flow: default to 'oneTap' for better UX, but allow 'popup' for iframe compatibility
|
|
11943
12104
|
const oauthFlow = config?.googleOAuthFlow || 'oneTap';
|
|
12105
|
+
// Log Google Auth configuration for debugging
|
|
12106
|
+
log.log('Google Auth initiated:', {
|
|
12107
|
+
googleClientId,
|
|
12108
|
+
oauthFlow,
|
|
12109
|
+
currentOrigin: window.location.origin,
|
|
12110
|
+
currentHref: window.location.href,
|
|
12111
|
+
configGoogleClientId: config?.googleClientId,
|
|
12112
|
+
usingDefaultClientId: !config?.googleClientId,
|
|
12113
|
+
});
|
|
11944
12114
|
setLoading(true);
|
|
11945
12115
|
setError(undefined);
|
|
11946
12116
|
try {
|
|
@@ -11950,35 +12120,87 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11950
12120
|
if (!google?.accounts) {
|
|
11951
12121
|
throw new Error('Google Identity Services failed to initialize');
|
|
11952
12122
|
}
|
|
12123
|
+
log.log('Google Identity Services loaded, using flow:', oauthFlow);
|
|
11953
12124
|
if (oauthFlow === 'popup') {
|
|
11954
12125
|
// Use OAuth2 popup flow (works in iframes but requires popup permission)
|
|
11955
12126
|
if (!google.accounts.oauth2) {
|
|
11956
12127
|
throw new Error('Google OAuth2 not available');
|
|
11957
12128
|
}
|
|
12129
|
+
log.log('Initializing Google OAuth2 popup flow:', {
|
|
12130
|
+
client_id: googleClientId,
|
|
12131
|
+
scope: 'openid email profile',
|
|
12132
|
+
origin: window.location.origin,
|
|
12133
|
+
});
|
|
11958
12134
|
const client = google.accounts.oauth2.initTokenClient({
|
|
11959
12135
|
client_id: googleClientId,
|
|
11960
12136
|
scope: 'openid email profile',
|
|
11961
12137
|
callback: async (response) => {
|
|
11962
12138
|
try {
|
|
12139
|
+
log.log('Google OAuth2 popup callback received:', {
|
|
12140
|
+
hasAccessToken: !!response.access_token,
|
|
12141
|
+
hasIdToken: !!response.id_token,
|
|
12142
|
+
tokenType: response.token_type,
|
|
12143
|
+
expiresIn: response.expires_in,
|
|
12144
|
+
scope: response.scope,
|
|
12145
|
+
error: response.error,
|
|
12146
|
+
errorDescription: response.error_description,
|
|
12147
|
+
});
|
|
11963
12148
|
if (response.error) {
|
|
11964
12149
|
throw new Error(response.error_description || response.error);
|
|
11965
12150
|
}
|
|
12151
|
+
// OAuth2 popup flow returns access_token, not id_token
|
|
12152
|
+
// We need to use the access token to get user info from Google
|
|
11966
12153
|
const accessToken = response.access_token;
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
if (authResponse.token) {
|
|
11970
|
-
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
11971
|
-
setAuthSuccess(true);
|
|
11972
|
-
setSuccessMessage('Google login successful!');
|
|
11973
|
-
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12154
|
+
if (!accessToken) {
|
|
12155
|
+
throw new Error('No access token received from Google');
|
|
11974
12156
|
}
|
|
11975
|
-
|
|
11976
|
-
|
|
12157
|
+
log.log('Fetching user info from Google using access token...');
|
|
12158
|
+
// Fetch user info from Google's userinfo endpoint
|
|
12159
|
+
const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
|
12160
|
+
headers: {
|
|
12161
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
12162
|
+
},
|
|
12163
|
+
});
|
|
12164
|
+
if (!userInfoResponse.ok) {
|
|
12165
|
+
throw new Error('Failed to fetch user info from Google');
|
|
11977
12166
|
}
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
12167
|
+
const userInfo = await userInfoResponse.json();
|
|
12168
|
+
log.log('Google user info retrieved:', {
|
|
12169
|
+
email: userInfo.email,
|
|
12170
|
+
name: userInfo.name,
|
|
12171
|
+
sub: userInfo.sub,
|
|
12172
|
+
});
|
|
12173
|
+
// For popup flow, send the access token to backend
|
|
12174
|
+
// Note: This may fail if backend only supports ID token verification
|
|
12175
|
+
try {
|
|
12176
|
+
const authResponse = await api.loginWithGoogle(accessToken, {
|
|
12177
|
+
googleUserInfo: userInfo,
|
|
12178
|
+
tokenType: 'access_token',
|
|
12179
|
+
});
|
|
12180
|
+
if (authResponse.token) {
|
|
12181
|
+
auth.login(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12182
|
+
setAuthSuccess(true);
|
|
12183
|
+
setSuccessMessage('Google login successful!');
|
|
12184
|
+
onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
|
|
12185
|
+
}
|
|
12186
|
+
else {
|
|
12187
|
+
throw new Error('Authentication failed - no token received');
|
|
12188
|
+
}
|
|
12189
|
+
if (redirectUrl) {
|
|
12190
|
+
setTimeout(() => {
|
|
12191
|
+
window.location.href = redirectUrl;
|
|
12192
|
+
}, 2000);
|
|
12193
|
+
}
|
|
12194
|
+
}
|
|
12195
|
+
catch (apiError) {
|
|
12196
|
+
const errorMessage = apiError instanceof Error ? apiError.message : 'Google login failed';
|
|
12197
|
+
// Check if this is the access token vs ID token mismatch
|
|
12198
|
+
if (errorMessage.includes('Invalid or expired Google token')) {
|
|
12199
|
+
log.error('Popup flow access token rejected by backend. Backend may only support ID tokens.');
|
|
12200
|
+
log.error('User info retrieved from Google:', userInfo);
|
|
12201
|
+
throw new Error('Google authentication failed. The popup flow may not be supported. Please try again or contact support.');
|
|
12202
|
+
}
|
|
12203
|
+
throw apiError;
|
|
11982
12204
|
}
|
|
11983
12205
|
setLoading(false);
|
|
11984
12206
|
}
|
|
@@ -11994,6 +12216,10 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
11994
12216
|
}
|
|
11995
12217
|
else {
|
|
11996
12218
|
// Use One Tap / Sign-In button flow (smoother UX but doesn't work in iframes)
|
|
12219
|
+
log.log('Initializing Google OneTap flow:', {
|
|
12220
|
+
client_id: googleClientId,
|
|
12221
|
+
origin: window.location.origin,
|
|
12222
|
+
});
|
|
11997
12223
|
google.accounts.id.initialize({
|
|
11998
12224
|
client_id: googleClientId,
|
|
11999
12225
|
callback: async (response) => {
|
|
@@ -12025,11 +12251,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12025
12251
|
},
|
|
12026
12252
|
auto_select: false,
|
|
12027
12253
|
cancel_on_tap_outside: true,
|
|
12254
|
+
// Note: use_fedcm_for_prompt omitted - requires Permissions-Policy header on hosting server
|
|
12255
|
+
// Will be needed when FedCM becomes mandatory in the future
|
|
12028
12256
|
});
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12257
|
+
// Use timeout fallback instead of deprecated notification methods
|
|
12258
|
+
// (isNotDisplayed/isSkippedMoment will stop working when FedCM becomes mandatory)
|
|
12259
|
+
const promptTimeout = setTimeout(() => {
|
|
12260
|
+
setLoading(false);
|
|
12261
|
+
}, 5000);
|
|
12262
|
+
google.accounts.id.prompt(() => {
|
|
12263
|
+
// Clear timeout if prompt interaction occurs
|
|
12264
|
+
clearTimeout(promptTimeout);
|
|
12265
|
+
setLoading(false);
|
|
12033
12266
|
});
|
|
12034
12267
|
}
|
|
12035
12268
|
}
|