@passkeyme/react-auth 2.2.9 → 2.3.0

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
@@ -2249,7 +2249,7 @@ const PasskeymeAuthPanel = ({
2249
2249
  providers = ["google", "github", "apple", "microsoft"], enablePasskeys = true, enableUsernamePassword: _enableUsernamePassword = false, // Reserved for future implementation
2250
2250
  redirectUri, state: _state, // Reserved for future implementation
2251
2251
  // Layout & behavior
2252
- layout = "vertical", spacing = "normal", passkeyFirst = true, hideProvidersInitially = false, autoTriggerPasskey = true,
2252
+ layout = "vertical", spacing = "normal", passkeyFirst = true, hideProvidersInitially = false, autoTriggerPasskey = true, detectDeviceCredentials = true,
2253
2253
  // Content customization
2254
2254
  title = "Sign In", subtitle = "Choose your preferred authentication method", passkeyButtonText = "🔐 Sign in with Passkey", passkeyLoadingText = "⏳ Authenticating...", dividerText = "or continue with", successTitle = "✅ Welcome back!", successSubtitle, logoutButtonText = "Sign Out",
2255
2255
  // Visibility controls
@@ -2266,6 +2266,7 @@ debugMode = false, passkeyOptions = {}, }) => {
2266
2266
  const [availableProviders, setAvailableProviders] = React.useState(providers);
2267
2267
  const [passkeyAttempted, setPasskeyAttempted] = React.useState(false);
2268
2268
  const [internalError, setInternalError] = React.useState(null);
2269
+ const [hasDeviceCredentials, setHasDeviceCredentials] = React.useState(false);
2269
2270
  // Merge theme with defaults
2270
2271
  const mergedTheme = {
2271
2272
  container: { ...defaultTheme.container, ...theme.container },
@@ -2277,14 +2278,41 @@ debugMode = false, passkeyOptions = {}, }) => {
2277
2278
  successState: { ...defaultTheme.successState, ...theme.successState },
2278
2279
  debugInfo: { ...defaultTheme.debugInfo, ...theme.debugInfo },
2279
2280
  };
2280
- // Auto-trigger passkey on mount if enabled
2281
+ // Detect device credentials silently on mount
2282
+ React.useEffect(() => {
2283
+ if (!detectDeviceCredentials || !isPasskeySupported())
2284
+ return;
2285
+ const detectCredentials = async () => {
2286
+ var _a;
2287
+ try {
2288
+ if (typeof ((_a = navigator.credentials) === null || _a === void 0 ? void 0 : _a.get) === "function") {
2289
+ const credentials = await navigator.credentials.get({
2290
+ mediation: "silent",
2291
+ publicKey: {},
2292
+ });
2293
+ setHasDeviceCredentials(!!credentials);
2294
+ if (debugMode) {
2295
+ console.log("🔐 PasskeymeAuthPanel: Device credentials detected:", !!credentials);
2296
+ }
2297
+ }
2298
+ }
2299
+ catch (error) {
2300
+ if (debugMode) {
2301
+ console.log("🔐 PasskeymeAuthPanel: Silent credential detection skipped (expected in some contexts)");
2302
+ }
2303
+ }
2304
+ };
2305
+ detectCredentials();
2306
+ }, [detectDeviceCredentials, isPasskeySupported, debugMode]);
2307
+ // Auto-trigger passkey on mount if enabled and device has credentials
2281
2308
  React.useEffect(() => {
2282
2309
  if (autoTriggerPasskey &&
2283
2310
  passkeyFirst &&
2284
2311
  enablePasskeys &&
2285
2312
  isPasskeySupported() &&
2286
2313
  !passkeyAttempted &&
2287
- !isAuthenticated) {
2314
+ !isAuthenticated &&
2315
+ hasDeviceCredentials) {
2288
2316
  handlePasskeyAuth();
2289
2317
  }
2290
2318
  }, [
@@ -2294,6 +2322,7 @@ debugMode = false, passkeyOptions = {}, }) => {
2294
2322
  isPasskeySupported,
2295
2323
  passkeyAttempted,
2296
2324
  isAuthenticated,
2325
+ hasDeviceCredentials,
2297
2326
  ]);
2298
2327
  const handlePasskeyAuth = () => {
2299
2328
  if (debugMode)
@@ -2403,7 +2432,7 @@ debugMode = false, passkeyOptions = {}, }) => {
2403
2432
  fontSize: "14px",
2404
2433
  width: "100%",
2405
2434
  textAlign: "center",
2406
- }, children: internalError })), enablePasskeys && showOAuthOptions && (jsxRuntime.jsx("button", { onClick: handlePasskeyAuth, disabled: authLoading, style: passkeyButtonStyles, onMouseEnter: e => {
2435
+ }, children: internalError })), enablePasskeys && showOAuthOptions && hasDeviceCredentials && (jsxRuntime.jsx("button", { onClick: handlePasskeyAuth, disabled: authLoading, style: passkeyButtonStyles, onMouseEnter: e => {
2407
2436
  var _a;
2408
2437
  if (!authLoading) {
2409
2438
  e.currentTarget.style.backgroundColor =
@@ -2418,13 +2447,39 @@ debugMode = false, passkeyOptions = {}, }) => {
2418
2447
  }, children: authLoading ? passkeyLoadingText : passkeyButtonText })), showDividerText &&
2419
2448
  showOAuthOptions &&
2420
2449
  enablePasskeys &&
2450
+ hasDeviceCredentials &&
2421
2451
  availableProviders.length > 0 && (jsxRuntime.jsx("p", { style: { margin: 0, ...mergedTheme.dividerText }, children: dividerText })), showOAuthOptions && availableProviders.length > 0 && (jsxRuntime.jsx("div", { style: oauthContainerStyles, children: availableProviders.map(provider => (jsxRuntime.jsx("div", { style: {
2422
2452
  flex: layout === "horizontal" ? 1 : undefined,
2423
2453
  minWidth: layout === "grid" ? "140px" : undefined,
2424
2454
  width: layout === "vertical" ? "100%" : undefined,
2425
2455
  }, children: jsxRuntime.jsxs(PasskeymeOAuthButton, { provider: provider, redirectUri: redirectUri, onClick: () => handleOAuthClick(provider), style: {
2426
2456
  width: layout === "vertical" ? "100%" : undefined,
2427
- }, children: ["Continue with", " ", provider.charAt(0).toUpperCase() + provider.slice(1)] }) }, provider))) })), (showDebugInfo || debugMode) && (jsxRuntime.jsxs("div", { style: mergedTheme.debugInfo, children: [jsxRuntime.jsx("strong", { children: "\uD83D\uDC1B Debug Info:" }), jsxRuntime.jsx("br", {}), "Passkey Support: ", isPasskeySupported() ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Authenticated: ", isAuthenticated ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Auth Loading: ", authLoading ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Passkey Attempted: ", passkeyAttempted ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Show OAuth: ", showOAuthOptions ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Available Providers: ", availableProviders.join(", ") || "None"] }))] }));
2457
+ }, children: ["Continue with", " ", provider.charAt(0).toUpperCase() + provider.slice(1)] }) }, provider))) })), enablePasskeys && showOAuthOptions && !hasDeviceCredentials && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("p", { style: {
2458
+ margin: 0,
2459
+ ...mergedTheme.dividerText,
2460
+ fontSize: "12px",
2461
+ opacity: "0.7",
2462
+ }, children: dividerText }), jsxRuntime.jsx("button", { onClick: handlePasskeyAuth, disabled: authLoading, style: {
2463
+ backgroundColor: "#e5e7eb",
2464
+ color: "#374151",
2465
+ border: "none",
2466
+ borderRadius: "8px",
2467
+ padding: "10px 20px",
2468
+ fontSize: "14px",
2469
+ fontWeight: "500",
2470
+ cursor: authLoading ? "not-allowed" : "pointer",
2471
+ transition: "all 0.2s ease",
2472
+ width: "100%",
2473
+ minHeight: "40px",
2474
+ }, onMouseEnter: e => {
2475
+ if (!authLoading) {
2476
+ e.currentTarget.style.backgroundColor = "#d1d5db";
2477
+ }
2478
+ }, onMouseLeave: e => {
2479
+ if (!authLoading) {
2480
+ e.currentTarget.style.backgroundColor = "#e5e7eb";
2481
+ }
2482
+ }, children: authLoading ? passkeyLoadingText : "🔑 Already have a passkey?" })] })), (showDebugInfo || debugMode) && (jsxRuntime.jsxs("div", { style: mergedTheme.debugInfo, children: [jsxRuntime.jsx("strong", { children: "\uD83D\uDC1B Debug Info:" }), jsxRuntime.jsx("br", {}), "Passkey Support: ", isPasskeySupported() ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Authenticated: ", isAuthenticated ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Auth Loading: ", authLoading ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Passkey Attempted: ", passkeyAttempted ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Device Credentials: ", hasDeviceCredentials ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Show OAuth: ", showOAuthOptions ? "Yes" : "No", jsxRuntime.jsx("br", {}), "Available Providers: ", availableProviders.join(", ") || "None"] }))] }));
2428
2483
  };
2429
2484
 
2430
2485
  /**
@@ -6608,7 +6663,7 @@ const PasskeymeCallbackHandler = ({ loadingComponent: LoadingComponent, errorCom
6608
6663
  showPasskeyPrompt: false,
6609
6664
  });
6610
6665
  handlePasskeyRegistrationComplete(true);
6611
- proceedWithRedirect();
6666
+ proceedWithRedirect(currentUser);
6612
6667
  }
6613
6668
  catch (error) {
6614
6669
  console.error("Passkey registration failed:", error);
@@ -6652,7 +6707,7 @@ const PasskeymeCallbackHandler = ({ loadingComponent: LoadingComponent, errorCom
6652
6707
  }
6653
6708
  // This is actually a "success" case - user has passkeys, just localStorage was out of sync
6654
6709
  handlePasskeyRegistrationComplete(true, "Passkey already registered and ready to use.");
6655
- proceedWithRedirect();
6710
+ proceedWithRedirect(currentUser);
6656
6711
  return;
6657
6712
  }
6658
6713
  else if (msg.includes("user cancelled") ||
@@ -6672,7 +6727,7 @@ const PasskeymeCallbackHandler = ({ loadingComponent: LoadingComponent, errorCom
6672
6727
  handlePasskeyRegistrationComplete(false, "Passkey registration failed. You can try again later.");
6673
6728
  }
6674
6729
  // Always proceed with redirect - don't block user flow
6675
- proceedWithRedirect();
6730
+ proceedWithRedirect(currentUser);
6676
6731
  }
6677
6732
  };
6678
6733
  // Handle skipping passkey registration
@@ -6683,16 +6738,17 @@ const PasskeymeCallbackHandler = ({ loadingComponent: LoadingComponent, errorCom
6683
6738
  sessionStorage.setItem("passkey_declined", Date.now().toString());
6684
6739
  updateState({ showPasskeyPrompt: false });
6685
6740
  handlePasskeyRegistrationComplete(false, "User chose to skip passkey registration");
6686
- proceedWithRedirect();
6741
+ proceedWithRedirect(state.authenticatedUser);
6687
6742
  };
6688
6743
  // Proceed with redirect after auth completion
6689
- const proceedWithRedirect = async () => {
6744
+ const proceedWithRedirect = async (authenticatedUser) => {
6690
6745
  // CRITICAL: Clean URL synchronously BEFORE any async operations
6691
6746
  // This prevents Next.js App Router from remounting the component with stale URL params
6692
6747
  // Must happen outside setTimeout to execute immediately
6693
6748
  window.history.replaceState({}, document.title, window.location.pathname);
6694
6749
  setTimeout(async () => {
6695
- const currentUser = state.authenticatedUser || user;
6750
+ // Use passed user or fall back to state/context user
6751
+ const currentUser = authenticatedUser || state.authenticatedUser || user;
6696
6752
  // If custom onSuccess callback provided, use it and let parent handle navigation
6697
6753
  if (onSuccess) {
6698
6754
  if (!currentUser) {
@@ -6784,8 +6840,8 @@ const PasskeymeCallbackHandler = ({ loadingComponent: LoadingComponent, errorCom
6784
6840
  // Mark callback as processed to prevent re-execution
6785
6841
  callbackProcessedRef.current = true;
6786
6842
  if (!shouldPrompt) {
6787
- // Proceed with normal redirect
6788
- proceedWithRedirect();
6843
+ // Proceed with normal redirect, pass user directly to avoid state timing issues
6844
+ proceedWithRedirect(authenticatedUser);
6789
6845
  }
6790
6846
  }
6791
6847
  catch (err) {
@@ -6835,8 +6891,8 @@ const PasskeymeCallbackHandler = ({ loadingComponent: LoadingComponent, errorCom
6835
6891
  // Mark callback as processed to prevent re-execution
6836
6892
  callbackProcessedRef.current = true;
6837
6893
  if (!shouldPrompt) {
6838
- // Proceed with normal redirect
6839
- proceedWithRedirect();
6894
+ // Proceed with normal redirect, pass user directly to avoid state timing issues
6895
+ proceedWithRedirect(authenticatedUser);
6840
6896
  }
6841
6897
  }
6842
6898
  catch (err) {