@proveanything/smartlinks-auth-ui 0.1.11 → 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/index.js CHANGED
@@ -11238,7 +11238,7 @@ const tokenStorage = {
11238
11238
  };
11239
11239
 
11240
11240
  const AuthContext = React.createContext(undefined);
11241
- const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
11241
+ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false }) => {
11242
11242
  const [user, setUser] = React.useState(null);
11243
11243
  const [token, setToken] = React.useState(null);
11244
11244
  const [accountData, setAccountData] = React.useState(null);
@@ -11262,10 +11262,45 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
11262
11262
  }
11263
11263
  });
11264
11264
  }, []);
11265
- // Initialize auth state from persistent storage
11265
+ // Initialize auth state - different behavior for proxy mode vs standalone mode
11266
11266
  React.useEffect(() => {
11267
11267
  const initializeAuth = async () => {
11268
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
11269
11304
  const storedToken = await tokenStorage.getToken();
11270
11305
  const storedUser = await tokenStorage.getUser();
11271
11306
  const storedAccountData = await tokenStorage.getAccountData();
@@ -11292,9 +11327,49 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
11292
11327
  }
11293
11328
  };
11294
11329
  initializeAuth();
11295
- }, []);
11296
- // Cross-tab synchronization - listen for auth changes in other tabs
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
11297
11370
  React.useEffect(() => {
11371
+ if (proxyMode)
11372
+ return; // Skip cross-tab sync in proxy mode
11298
11373
  console.log('[AuthContext] Setting up cross-tab synchronization');
11299
11374
  const unsubscribe = onStorageChange(async (event) => {
11300
11375
  console.log('[AuthContext] Cross-tab storage event:', event.type, event.key);
@@ -11353,27 +11428,38 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
11353
11428
  console.log('[AuthContext] Cleaning up cross-tab synchronization');
11354
11429
  unsubscribe();
11355
11430
  };
11356
- }, [notifyAuthStateChange]);
11431
+ }, [proxyMode, notifyAuthStateChange]);
11357
11432
  const login = React.useCallback(async (authToken, authUser, authAccountData) => {
11358
11433
  try {
11359
- // Store token, user, and account data
11360
- await tokenStorage.saveToken(authToken);
11361
- await tokenStorage.saveUser(authUser);
11362
- if (authAccountData) {
11363
- await tokenStorage.saveAccountData(authAccountData);
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
+ });
11364
11445
  }
11446
+ // Always update memory state
11365
11447
  setToken(authToken);
11366
11448
  setUser(authUser);
11367
11449
  setAccountData(authAccountData || null);
11368
- // Set bearer token in global Smartlinks SDK via auth.verifyToken
11369
- // This both validates the token and sets it for future API calls
11370
- smartlinks__namespace.auth.verifyToken(authToken).catch(err => {
11371
- console.warn('Failed to set bearer token on login:', err);
11372
- });
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
+ }
11373
11460
  notifyAuthStateChange('LOGIN', authUser, authToken, authAccountData || null);
11374
- // Optionally preload account info on login
11375
- if (preloadAccountInfo) {
11376
- // Preload after login completes (non-blocking)
11461
+ // Optionally preload account info on login (standalone mode only)
11462
+ if (!proxyMode && preloadAccountInfo) {
11377
11463
  getAccount(true).catch(error => {
11378
11464
  console.warn('[AuthContext] Failed to preload account info:', error);
11379
11465
  });
@@ -11383,34 +11469,55 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
11383
11469
  console.error('Failed to save auth data to storage:', error);
11384
11470
  throw error;
11385
11471
  }
11386
- }, [notifyAuthStateChange, preloadAccountInfo]);
11472
+ }, [proxyMode, notifyAuthStateChange, preloadAccountInfo]);
11387
11473
  const logout = React.useCallback(async () => {
11388
11474
  try {
11389
- // Clear persistent storage
11390
- await tokenStorage.clearAll();
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
11391
11481
  setToken(null);
11392
11482
  setUser(null);
11393
11483
  setAccountData(null);
11394
11484
  setAccountInfo(null);
11395
- // Clear bearer token from global Smartlinks SDK
11396
- smartlinks__namespace.auth.logout();
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
+ }
11397
11491
  notifyAuthStateChange('LOGOUT', null, null, null);
11398
11492
  }
11399
11493
  catch (error) {
11400
11494
  console.error('Failed to clear auth data from storage:', error);
11401
11495
  }
11402
- }, [notifyAuthStateChange]);
11496
+ }, [proxyMode, notifyAuthStateChange]);
11403
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
+ }
11404
11502
  const storedToken = await tokenStorage.getToken();
11405
11503
  return storedToken ? storedToken.token : null;
11406
- }, []);
11504
+ }, [proxyMode, token]);
11407
11505
  const refreshToken = React.useCallback(async () => {
11408
11506
  throw new Error('Token refresh must be implemented via your backend API');
11409
11507
  }, []);
11410
- // Get account with intelligent caching
11508
+ // Get account with intelligent caching (or direct parent fetch in proxy mode)
11411
11509
  const getAccount = React.useCallback(async (forceRefresh = false) => {
11412
11510
  try {
11413
- // Check if user is authenticated
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
11414
11521
  if (!token) {
11415
11522
  throw new Error('Not authenticated. Please login first.');
11416
11523
  }
@@ -11433,24 +11540,28 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
11433
11540
  }
11434
11541
  catch (error) {
11435
11542
  console.error('[AuthContext] Failed to get account info:', error);
11436
- // Fallback to stale cache if API fails
11437
- const cached = await tokenStorage.getAccountInfo();
11438
- if (cached) {
11439
- console.warn('[AuthContext] Returning stale cached data due to API error');
11440
- return cached.data;
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
+ }
11441
11550
  }
11442
11551
  throw error;
11443
11552
  }
11444
- }, [token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
11553
+ }, [proxyMode, token, accountCacheTTL, user, accountData, notifyAuthStateChange]);
11445
11554
  // Convenience method for explicit refresh
11446
11555
  const refreshAccount = React.useCallback(async () => {
11447
11556
  return await getAccount(true);
11448
11557
  }, [getAccount]);
11449
- // Clear account cache
11558
+ // Clear account cache (no-op in proxy mode)
11450
11559
  const clearAccountCache = React.useCallback(async () => {
11451
- await tokenStorage.clearAccountInfo();
11560
+ if (!proxyMode) {
11561
+ await tokenStorage.clearAccountInfo();
11562
+ }
11452
11563
  setAccountInfo(null);
11453
- }, []);
11564
+ }, [proxyMode]);
11454
11565
  const onAuthStateChange = React.useCallback((callback) => {
11455
11566
  callbacksRef.current.add(callback);
11456
11567
  // Return unsubscribe function
@@ -11463,8 +11574,9 @@ const AuthProvider = ({ children, accountCacheTTL = 5 * 60 * 1000, preloadAccoun
11463
11574
  token,
11464
11575
  accountData,
11465
11576
  accountInfo,
11466
- isAuthenticated: !!token && !!user,
11577
+ isAuthenticated: !!user,
11467
11578
  isLoading,
11579
+ proxyMode,
11468
11580
  login,
11469
11581
  logout,
11470
11582
  getToken,
@@ -11554,7 +11666,7 @@ const getFriendlyErrorMessage = (errorMessage) => {
11554
11666
  // Return original message if no pattern matches
11555
11667
  return errorMessage;
11556
11668
  };
11557
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, enabledProviders = ['email', 'google', 'phone'], initialMode = 'login', redirectUrl, theme = 'auto', className, customization, skipConfigFetch = false, minimal = false, logger, }) => {
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, }) => {
11558
11670
  const [mode, setMode] = React.useState(initialMode);
11559
11671
  const [loading, setLoading] = React.useState(false);
11560
11672
  const [error, setError] = React.useState();
@@ -11587,25 +11699,25 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11587
11699
  mediaQuery.addEventListener('change', updateTheme);
11588
11700
  return () => mediaQuery.removeEventListener('change', updateTheme);
11589
11701
  }, [theme]);
11590
- // Reinitialize Smartlinks SDK when apiEndpoint changes (for test/dev scenarios)
11702
+ // Reinitialize Smartlinks SDK when apiEndpoint or proxyMode changes
11591
11703
  // IMPORTANT: Preserve bearer token during reinitialization
11592
11704
  React.useEffect(() => {
11593
- log.log('SDK reinitialize useEffect triggered', { apiEndpoint });
11705
+ log.log('SDK reinitialize useEffect triggered', { apiEndpoint, proxyMode });
11594
11706
  setSdkReady(false); // Mark SDK as not ready during reinitialization
11595
11707
  const reinitializeWithToken = async () => {
11596
11708
  if (apiEndpoint) {
11597
- log.log('Reinitializing SDK with baseURL:', apiEndpoint);
11598
- // Get current token before reinitializing
11599
- 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;
11600
11712
  smartlinks__namespace.initializeApi({
11601
11713
  baseURL: apiEndpoint,
11602
- proxyMode: false, // Direct API calls when custom endpoint is provided
11714
+ proxyMode: proxyMode, // Use prop value
11603
11715
  ngrokSkipBrowserWarning: true,
11604
11716
  logger: logger, // Pass logger to SDK for verbose SDK logging
11605
11717
  });
11606
11718
  log.log('SDK reinitialized successfully');
11607
- // Restore bearer token after reinitialization using auth.verifyToken
11608
- if (currentToken) {
11719
+ // Restore bearer token after reinitialization using auth.verifyToken (standalone mode only)
11720
+ if (currentToken && !proxyMode) {
11609
11721
  smartlinks__namespace.auth.verifyToken(currentToken).catch(err => {
11610
11722
  log.warn('Failed to restore bearer token after reinit:', err);
11611
11723
  });
@@ -11613,6 +11725,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11613
11725
  // Mark SDK as ready
11614
11726
  setSdkReady(true);
11615
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
+ }
11616
11733
  else {
11617
11734
  log.log('No apiEndpoint, SDK already initialized by App');
11618
11735
  // SDK was initialized by App component, mark as ready
@@ -11620,7 +11737,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
11620
11737
  }
11621
11738
  };
11622
11739
  reinitializeWithToken();
11623
- }, [apiEndpoint, auth, logger, log]);
11740
+ }, [apiEndpoint, proxyMode, auth, logger, log]);
11624
11741
  // Get the effective redirect URL (use prop or default to current page)
11625
11742
  const getRedirectUrl = () => {
11626
11743
  if (redirectUrl)
@@ -12134,11 +12251,18 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
12134
12251
  },
12135
12252
  auto_select: false,
12136
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
12137
12256
  });
12138
- google.accounts.id.prompt((notification) => {
12139
- if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
12140
- setLoading(false);
12141
- }
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);
12142
12266
  });
12143
12267
  }
12144
12268
  }