@proveanything/smartlinks-auth-ui 0.5.22 → 0.6.1

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
@@ -336,7 +336,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
336
336
  }, disabled: loading })), jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: title }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Smith" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsxRuntime.jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsxRuntime.jsx("div", { className: "auth-form-footer", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && signupProminence !== 'none' && (jsxRuntime.jsxs("div", { className: "auth-divider", children: [jsxRuntime.jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), signupRedirectUrl && mode === 'login' ? (jsxRuntime.jsx("a", { href: signupRedirectUrl, target: "_top", className: "auth-link auth-link-bold", children: "Sign up" })) : (jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' }))] }))] }));
337
337
  };
338
338
 
339
- const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, onWhatsAppLogin, loading, }) => {
339
+ const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onAppleLogin, onPhoneLogin, onMagicLinkLogin, onWhatsAppLogin, loading, }) => {
340
340
  if (enabledProviders.length === 0)
341
341
  return null;
342
342
  // Determine the order of providers to display
@@ -359,6 +359,11 @@ const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoog
359
359
  icon: (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [jsxRuntime.jsx("path", { d: "M19.6 10.227c0-.709-.064-1.39-.182-2.045H10v3.868h5.382a4.6 4.6 0 01-1.996 3.018v2.51h3.232c1.891-1.742 2.982-4.305 2.982-7.35z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M10 20c2.7 0 4.964-.895 6.618-2.423l-3.232-2.509c-.895.6-2.04.955-3.386.955-2.605 0-4.81-1.76-5.595-4.123H1.064v2.59A9.996 9.996 0 0010 20z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M4.405 11.9c-.2-.6-.314-1.24-.314-1.9 0-.66.114-1.3.314-1.9V5.51H1.064A9.996 9.996 0 000 10c0 1.614.386 3.14 1.064 4.49l3.34-2.59z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M10 3.977c1.468 0 2.786.505 3.823 1.496l2.868-2.868C14.959.99 12.695 0 10 0 6.09 0 2.71 2.24 1.064 5.51l3.34 2.59C5.19 5.736 7.395 3.977 10 3.977z", fill: "#EA4335" })] })),
360
360
  onClick: onGoogleLogin
361
361
  },
362
+ apple: {
363
+ label: 'Continue with Apple',
364
+ icon: (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" }) })),
365
+ onClick: () => onAppleLogin?.()
366
+ },
362
367
  phone: {
363
368
  label: 'Continue with Phone',
364
369
  icon: (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V15a2 2 0 01-2 2h-1C7.82 17 2 11.18 2 5V4z" }) })),
@@ -11430,6 +11435,25 @@ class AuthAPI {
11430
11435
  redirectUri,
11431
11436
  });
11432
11437
  }
11438
+ /**
11439
+ * Sign in with Apple via an identity token (from native "Sign in with Apple"
11440
+ * or the web JS flow). The backend verifies the JWT against Apple's keys.
11441
+ *
11442
+ * Delegates to `smartlinks.authKit.appleLogin`, which on success stores the
11443
+ * bearer token automatically and invalidates the SDK cache.
11444
+ */
11445
+ async loginWithApple(identityToken, options) {
11446
+ this.log.log('loginWithApple called:', {
11447
+ tokenLength: identityToken?.length,
11448
+ hasAuthCode: !!options?.authorizationCode,
11449
+ hasUserInfo: !!options?.appleUserInfo,
11450
+ });
11451
+ return smartlinks__namespace.authKit.appleLogin(this.clientId, identityToken, {
11452
+ authorizationCode: options?.authorizationCode,
11453
+ nonce: options?.nonce,
11454
+ userInfo: options?.appleUserInfo,
11455
+ });
11456
+ }
11433
11457
  async sendPhoneCode(phoneNumber) {
11434
11458
  return smartlinks__namespace.authKit.sendPhoneCode(this.clientId, phoneNumber);
11435
11459
  }
@@ -11478,6 +11502,17 @@ class AuthAPI {
11478
11502
  throw error;
11479
11503
  }
11480
11504
  }
11505
+ /**
11506
+ * Complete a password reset OR accept an invite by setting the first password.
11507
+ *
11508
+ * The backend distinguishes the two flows via the token's `metadata.invitedBy`:
11509
+ * - Plain password reset → returns `{ success, message }` only.
11510
+ * - Invite acceptance under `verify-auto-login` → returns a full session
11511
+ * (`token`, `user`, `accountData`, and on native: refresh-token fields)
11512
+ * so the kit can log the user straight in without bouncing them to /login.
11513
+ *
11514
+ * See: SDK_AUTHKIT_REFRESH_TOKENS / "Invite Auto-Login" spec.
11515
+ */
11481
11516
  async completePasswordReset(token, newPassword) {
11482
11517
  return smartlinks__namespace.authKit.completePasswordReset(this.clientId, token, newPassword);
11483
11518
  }
@@ -11960,6 +11995,24 @@ async function getStorage() {
11960
11995
  storageInstance = await storageInitPromise;
11961
11996
  return storageInstance;
11962
11997
  }
11998
+ /**
11999
+ * Install a custom storage backend (e.g. Capacitor Keychain / Keystore).
12000
+ *
12001
+ * Must be called BEFORE `getStorage()` is invoked for the first time
12002
+ * (typically right after your app boots, before `<SmartlinksAuthUI />` mounts).
12003
+ * The supplied adapter then handles ALL token, user, and account persistence
12004
+ * — replacing the default IndexedDB → localStorage → in-memory chain.
12005
+ *
12006
+ * Intended for native shells (Capacitor / Capgo) that want tokens stored in
12007
+ * the OS secure enclave (Keychain on iOS, Keystore on Android) instead of the
12008
+ * WebView's IndexedDB. Implement the `PersistentStorage` interface against
12009
+ * your secure-storage plugin and pass the instance here.
12010
+ */
12011
+ function setStorageAdapter(adapter) {
12012
+ StorageFactory.reset();
12013
+ storageInstance = adapter;
12014
+ storageInitPromise = Promise.resolve(adapter);
12015
+ }
11963
12016
  /**
11964
12017
  * Listen for storage changes from other tabs
11965
12018
  */
@@ -11978,6 +12031,7 @@ function onStorageChange(callback) {
11978
12031
  }
11979
12032
 
11980
12033
  const TOKEN_KEY = 'token';
12034
+ const REFRESH_TOKEN_KEY = 'refresh_token';
11981
12035
  const USER_KEY = 'user';
11982
12036
  const ACCOUNT_DATA_KEY = 'account_data';
11983
12037
  const ACCOUNT_INFO_KEY = 'account_info';
@@ -12016,6 +12070,28 @@ const tokenStorage = {
12016
12070
  const storage = await getStorage();
12017
12071
  await storage.removeItem(TOKEN_KEY);
12018
12072
  },
12073
+ // ----- Refresh token (native / long-lived sessions) ------------------
12074
+ async saveRefreshToken(token, expiresAt) {
12075
+ const storage = await getStorage();
12076
+ const payload = { token, expiresAt };
12077
+ await storage.setItem(REFRESH_TOKEN_KEY, payload);
12078
+ },
12079
+ async getRefreshToken() {
12080
+ const storage = await getStorage();
12081
+ const rt = await storage.getItem(REFRESH_TOKEN_KEY);
12082
+ if (!rt)
12083
+ return null;
12084
+ if (rt.expiresAt && rt.expiresAt < Date.now()) {
12085
+ console.log('[TokenStorage] Refresh token expired - clearing');
12086
+ await this.clearRefreshToken();
12087
+ return null;
12088
+ }
12089
+ return rt;
12090
+ },
12091
+ async clearRefreshToken() {
12092
+ const storage = await getStorage();
12093
+ await storage.removeItem(REFRESH_TOKEN_KEY);
12094
+ },
12019
12095
  async saveUser(user) {
12020
12096
  const storage = await getStorage();
12021
12097
  await storage.setItem(USER_KEY, user);
@@ -12030,6 +12106,7 @@ const tokenStorage = {
12030
12106
  },
12031
12107
  async clearAll() {
12032
12108
  await this.clearToken();
12109
+ await this.clearRefreshToken();
12033
12110
  await this.clearUser();
12034
12111
  await this.clearAccountData();
12035
12112
  await this.clearAccountInfo();
@@ -12087,12 +12164,18 @@ const tokenStorage = {
12087
12164
 
12088
12165
  // Export context for optional usage (e.g., SmartlinksFrame can work without AuthProvider)
12089
12166
  const AuthContext = React.createContext(undefined);
12090
- const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false,
12167
+ const AuthProvider = ({ children, proxyMode = false, accountCacheTTL = 5 * 60 * 1000, preloadAccountInfo = false, clientId: clientIdProp,
12091
12168
  // Token refresh settings
12092
12169
  enableAutoRefresh = true, refreshThresholdPercent = 75, // Refresh when 75% of token lifetime has passed
12093
12170
  refreshCheckInterval = 60 * 1000, // Check every minute
12171
+ refreshOnResume,
12094
12172
  // Contact & Interaction features
12095
12173
  collectionId, enableContactSync, enableInteractionTracking, interactionAppId, interactionConfig, }) => {
12174
+ // Resolved client ID — explicit prop wins; otherwise fall back to the
12175
+ // collection's defaultAuthKitId (the standard pattern — see
12176
+ // mem://architecture/default-authkit-collection-property).
12177
+ const [resolvedClientId, setResolvedClientId] = React.useState(clientIdProp);
12178
+ const clientId = resolvedClientId;
12096
12179
  const [user, setUser] = React.useState(null);
12097
12180
  const [token, setToken] = React.useState(null);
12098
12181
  const [accountData, setAccountData] = React.useState(null);
@@ -12108,8 +12191,8 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12108
12191
  const pendingVerificationRef = React.useRef(false);
12109
12192
  const proxyRefreshInFlightRef = React.useRef(false);
12110
12193
  // Stable refs for callbacks to avoid useEffect dependency cycles
12111
- const syncContactRef = React.useRef();
12112
- const trackInteractionRef = React.useRef();
12194
+ const syncContactRef = React.useRef(undefined);
12195
+ const trackInteractionRef = React.useRef(undefined);
12113
12196
  // Default to enabled if collectionId is provided
12114
12197
  const shouldSyncContacts = enableContactSync ?? !!collectionId;
12115
12198
  const shouldTrackInteractions = enableInteractionTracking ?? !!collectionId;
@@ -12792,9 +12875,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12792
12875
  });
12793
12876
  });
12794
12877
  };
12795
- const login = React.useCallback(async (authToken, authUser, authAccountData, isNewUser, expiresAt
12796
- // Note: waitForParentAck removed - we ALWAYS wait for parent ack in iframe mode now
12797
- ) => {
12878
+ const login = React.useCallback(async (authToken, authUser, authAccountData, isNewUser, expiresAt, refreshTokenValue, refreshTokenExpiresAt) => {
12798
12879
  try {
12799
12880
  // Only persist to storage in standalone mode
12800
12881
  if (!proxyMode) {
@@ -12808,6 +12889,15 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12808
12889
  else {
12809
12890
  await tokenStorage.clearAccountData();
12810
12891
  }
12892
+ // Persist refresh token when the backend issued one (native clients).
12893
+ if (refreshTokenValue) {
12894
+ const rtExp = refreshTokenExpiresAt
12895
+ ?? Date.now() + 90 * 24 * 60 * 60 * 1000; // 90d fallback per spec
12896
+ await tokenStorage.saveRefreshToken(refreshTokenValue, rtExp);
12897
+ }
12898
+ else {
12899
+ await tokenStorage.clearRefreshToken();
12900
+ }
12811
12901
  smartlinks__namespace.auth.verifyToken(authToken).catch(() => { });
12812
12902
  }
12813
12903
  // Always update memory state (but NOT isVerified yet - wait for parent ack in iframe mode)
@@ -12871,6 +12961,17 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12871
12961
  }
12872
12962
  // Only clear persistent storage in standalone mode
12873
12963
  if (!proxyMode) {
12964
+ // Best-effort: revoke the refresh-token family server-side before
12965
+ // wiping local state, so a stolen copy is dead immediately.
12966
+ try {
12967
+ const storedRefresh = await tokenStorage.getRefreshToken();
12968
+ if (storedRefresh?.token && clientId) {
12969
+ await smartlinks__namespace.authKit.logout(clientId, storedRefresh.token);
12970
+ }
12971
+ }
12972
+ catch (err) {
12973
+ console.warn('[AuthContext] Refresh-token revoke failed (continuing):', err);
12974
+ }
12874
12975
  await tokenStorage.clearAll();
12875
12976
  smartlinks__namespace.auth.logout();
12876
12977
  }
@@ -12896,7 +12997,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12896
12997
  catch (error) {
12897
12998
  console.error('Failed to clear auth data from storage:', error);
12898
12999
  }
12899
- }, [proxyMode, notifyAuthStateChange, user, contactId, collectionId, shouldTrackInteractions, trackInteraction]);
13000
+ }, [proxyMode, clientId, notifyAuthStateChange, user, contactId, collectionId, shouldTrackInteractions, trackInteraction]);
12900
13001
  const getToken = React.useCallback(async () => {
12901
13002
  if (proxyMode) {
12902
13003
  return token;
@@ -12923,12 +13024,44 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12923
13024
  expiresIn: Math.max(0, storedToken.expiresAt - Date.now()),
12924
13025
  };
12925
13026
  }, [proxyMode, token]);
12926
- // Refresh token - validates current token and extends session if backend supports it
13027
+ // Refresh token - prefers the AuthKit refresh-token endpoint when a refresh
13028
+ // token is stored (native clients); otherwise falls back to verify-and-extend
13029
+ // for web clients whose backend still issues only short-lived JWTs.
12927
13030
  const refreshToken = React.useCallback(async () => {
12928
13031
  if (proxyMode) {
12929
13032
  throw new Error('Token refresh in proxy mode is handled by the parent application');
12930
13033
  }
12931
13034
  const storedToken = await tokenStorage.getToken();
13035
+ const storedRefresh = await tokenStorage.getRefreshToken();
13036
+ // -------- Path A: true refresh-token rotation (native) ---------------
13037
+ if (storedRefresh?.token && clientId) {
13038
+ try {
13039
+ const resp = await smartlinks__namespace.authKit.refreshToken(clientId, storedRefresh.token);
13040
+ await tokenStorage.saveToken(resp.token, resp.expiresAt);
13041
+ await tokenStorage.saveRefreshToken(resp.refreshToken, resp.refreshTokenExpiresAt);
13042
+ setToken(resp.token);
13043
+ setIsVerified(true);
13044
+ pendingVerificationRef.current = false;
13045
+ notifyAuthStateChange('TOKEN_REFRESH', user, resp.token, accountData, accountInfo, true, contact, contactId);
13046
+ return resp.token;
13047
+ }
13048
+ catch (error) {
13049
+ console.error('[AuthContext] Refresh-token rotation failed:', error);
13050
+ if (isNetworkError(error))
13051
+ throw error;
13052
+ // Reuse / invalid / expired → force re-login. The SDK surfaces these
13053
+ // as RefreshErrorCode (INVALID_REFRESH_TOKEN / REUSE_DETECTED / MISSING).
13054
+ const code = error?.errorCode ?? error?.code;
13055
+ if (code === 'INVALID_REFRESH_TOKEN' ||
13056
+ code === 'REFRESH_TOKEN_REUSE_DETECTED' ||
13057
+ code === 'MISSING_REFRESH_TOKEN') {
13058
+ await logout();
13059
+ throw new Error('Session expired. Please login again.');
13060
+ }
13061
+ // Fall through to verify-and-extend on unexpected errors.
13062
+ }
13063
+ }
13064
+ // -------- Path B: legacy verify-and-extend (web) ---------------------
12932
13065
  if (!storedToken?.token) {
12933
13066
  throw new Error('No token to refresh. Please login first.');
12934
13067
  }
@@ -12953,7 +13086,7 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
12953
13086
  await logout();
12954
13087
  throw new Error('Token refresh failed. Please login again.');
12955
13088
  }
12956
- }, [proxyMode, user, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
13089
+ }, [proxyMode, clientId, user, accountData, accountInfo, contact, contactId, notifyAuthStateChange, isNetworkError, logout]);
12957
13090
  const getAccount = React.useCallback(async (forceRefresh = false) => {
12958
13091
  try {
12959
13092
  if (proxyMode) {
@@ -13117,6 +13250,105 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
13117
13250
  clearInterval(intervalId);
13118
13251
  };
13119
13252
  }, [proxyMode, enableAutoRefresh, refreshCheckInterval, refreshThresholdPercent, token, user, refreshToken]);
13253
+ // Resolve clientId from the collection's defaultAuthKitId when not provided
13254
+ // explicitly. Most apps only pass `collectionId` — this means refresh-token
13255
+ // rotation and server-side logout revocation still work end-to-end.
13256
+ React.useEffect(() => {
13257
+ if (clientIdProp) {
13258
+ setResolvedClientId(clientIdProp);
13259
+ return;
13260
+ }
13261
+ if (!collectionId)
13262
+ return;
13263
+ let cancelled = false;
13264
+ (async () => {
13265
+ try {
13266
+ const { getDefaultAuthKitId } = await Promise.resolve().then(function () { return defaultAuthKit; });
13267
+ const id = await getDefaultAuthKitId(collectionId);
13268
+ if (!cancelled && id)
13269
+ setResolvedClientId(id);
13270
+ }
13271
+ catch (err) {
13272
+ console.warn('[AuthContext] Could not resolve default authKitId from collection:', err);
13273
+ }
13274
+ })();
13275
+ return () => { cancelled = true; };
13276
+ }, [clientIdProp, collectionId]);
13277
+ // Refresh-on-resume: fires when the app returns to the foreground.
13278
+ // - Capacitor: `App.addListener('appStateChange')` via dynamic import (no
13279
+ // runtime dep on web).
13280
+ // - Universal fallback: `document.visibilitychange` — also catches PWAs /
13281
+ // web tabs that have been backgrounded for hours.
13282
+ React.useEffect(() => {
13283
+ if (proxyMode || !enableAutoRefresh || !token || !user)
13284
+ return;
13285
+ // Default ON when we detect a native shell; OFF otherwise.
13286
+ const isNative = (() => {
13287
+ try {
13288
+ const cap = window.Capacitor;
13289
+ if (cap?.isNativePlatform?.() === true)
13290
+ return true;
13291
+ if (window.AuthKit?.isNative === true)
13292
+ return true;
13293
+ }
13294
+ catch { /* noop */ }
13295
+ return false;
13296
+ })();
13297
+ const enabled = refreshOnResume ?? isNative;
13298
+ if (!enabled)
13299
+ return;
13300
+ const maybeRefresh = async () => {
13301
+ try {
13302
+ const storedToken = await tokenStorage.getToken();
13303
+ if (!storedToken?.expiresAt)
13304
+ return;
13305
+ const remainingMs = storedToken.expiresAt - Date.now();
13306
+ const totalLifetimeMs = 7 * 24 * 60 * 60 * 1000;
13307
+ const percentUsed = ((totalLifetimeMs - remainingMs) / totalLifetimeMs) * 100;
13308
+ if (percentUsed < refreshThresholdPercent)
13309
+ return;
13310
+ await refreshToken().catch(() => { });
13311
+ }
13312
+ catch (err) {
13313
+ console.error('[AuthContext] Resume refresh check failed:', err);
13314
+ }
13315
+ };
13316
+ // Universal visibility listener
13317
+ const onVisibility = () => {
13318
+ if (document.visibilityState === 'visible')
13319
+ maybeRefresh();
13320
+ };
13321
+ document.addEventListener('visibilitychange', onVisibility);
13322
+ // Capacitor listener (optional)
13323
+ let capRemove;
13324
+ (async () => {
13325
+ try {
13326
+ // Only attempt to load @capacitor/app in an actual native shell.
13327
+ // The specifier is obfuscated so bundlers (Vite/webpack) don't try
13328
+ // to statically resolve an optional peer dependency at build time.
13329
+ const isNative = typeof window !== 'undefined' &&
13330
+ (window.Capacitor?.isNativePlatform?.() === true ||
13331
+ window.AuthKit?.isNative === true);
13332
+ if (!isNative)
13333
+ return;
13334
+ const specifier = ['@capacitor', 'app'].join('/');
13335
+ const dynamicImport = new Function('s', 'return import(s)');
13336
+ const mod = await dynamicImport(specifier);
13337
+ const handle = await mod.App.addListener('appStateChange', (state) => {
13338
+ if (state.isActive)
13339
+ maybeRefresh();
13340
+ });
13341
+ capRemove = () => handle?.remove?.();
13342
+ }
13343
+ catch {
13344
+ // @capacitor/app not installed or unavailable — visibilitychange handles it.
13345
+ }
13346
+ })();
13347
+ return () => {
13348
+ document.removeEventListener('visibilitychange', onVisibility);
13349
+ capRemove?.();
13350
+ };
13351
+ }, [proxyMode, enableAutoRefresh, refreshOnResume, refreshThresholdPercent, token, user, refreshToken]);
13120
13352
  const value = {
13121
13353
  user,
13122
13354
  token,
@@ -13378,8 +13610,22 @@ const loadGoogleIdentityServices = () => {
13378
13610
  document.head.appendChild(script);
13379
13611
  });
13380
13612
  };
13613
+ // Helper to detect a Capacitor native runtime (iOS/Android shell, not the web)
13614
+ const isCapacitorNative = () => {
13615
+ const cap = window.Capacitor;
13616
+ if (!cap)
13617
+ return false;
13618
+ // isNativePlatform() is the modern API; fall back to platform string for older cores
13619
+ if (typeof cap.isNativePlatform === 'function')
13620
+ return cap.isNativePlatform();
13621
+ return cap.platform === 'ios' || cap.platform === 'android';
13622
+ };
13381
13623
  // Helper to detect WebView environments (Android/iOS)
13382
13624
  const detectWebView = () => {
13625
+ // Capacitor apps run inside a WebView (WKWebView/Android WebView) but don't
13626
+ // always set the `wv` UA token — treat them as WebView explicitly.
13627
+ if (isCapacitorNative())
13628
+ return true;
13383
13629
  const ua = navigator.userAgent;
13384
13630
  // Android WebView detection
13385
13631
  if (/Android/i.test(ua)) {
@@ -13487,7 +13733,7 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
13487
13733
  });
13488
13734
  };
13489
13735
  // getFriendlyErrorMessage is now imported from ../utils/errorHandling
13490
- const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, onRedirect, enabledProviders = ['email', 'google', 'phone'], initialMode, signupProminence, redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, collectionId, disableConfigCache = false, enableSilentGoogleSignIn = false, whatsappReply, whatsappPrefillMessage, }) => {
13736
+ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, onRedirect, enabledProviders = ['email', 'google', 'phone'], initialMode, signupProminence, redirectUrl, theme = 'light', className, customization, skipConfigFetch = false, minimal = false, logger, proxyMode = false, collectionId, disableConfigCache = false, enableSilentGoogleSignIn = false, nativeAuth, enableSilentNativeSignIn = false, whatsappReply, whatsappPrefillMessage, }) => {
13491
13737
  // Resolve signup prominence from props, customization, config, or default
13492
13738
  const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
13493
13739
  // Determine initial mode based on signupProminence setting
@@ -13787,23 +14033,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13787
14033
  };
13788
14034
  fetchSchema();
13789
14035
  }, [collectionId, sdkReady]);
13790
- // Silent Google Sign-In check on mount (for native Android apps)
13791
- // When enabled, checks if user is already signed in via Google on the native side
14036
+ // Silent Sign-In check on mount (for native apps).
14037
+ // Prefers the injected `nativeAuth.checkSignIn` adapter (Capacitor) when
14038
+ // `enableSilentNativeSignIn` is set; otherwise falls back to the legacy
14039
+ // `window.AuthKit` bridge when `enableSilentGoogleSignIn` is set.
14040
+ const wantsSilentNative = enableSilentNativeSignIn && !!nativeAuth?.checkSignIn;
13792
14041
  React.useEffect(() => {
13793
- if (!enableSilentGoogleSignIn || silentSignInChecked || !sdkReady || auth.isAuthenticated) {
14042
+ if ((!enableSilentGoogleSignIn && !wantsSilentNative) || silentSignInChecked || !sdkReady || auth.isAuthenticated) {
13794
14043
  return;
13795
14044
  }
13796
14045
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
13797
14046
  const performSilentSignIn = async () => {
13798
14047
  try {
13799
- const result = await checkSilentGoogleSignIn(clientId, googleClientId);
14048
+ const result = wantsSilentNative
14049
+ ? await nativeAuth.checkSignIn({ serverClientId: googleClientId })
14050
+ : await checkSilentGoogleSignIn(clientId, googleClientId);
13800
14051
  setSilentSignInChecked(true);
13801
14052
  if (result?.isSignedIn && result.idToken) {
13802
14053
  setLoading(true);
13803
14054
  try {
13804
14055
  const authResponse = await api.loginWithGoogle(result.idToken);
13805
14056
  if (authResponse.token) {
13806
- await auth.login(authResponse.token, authResponse.user, authResponse.accountData, false, getExpirationFromResponse(authResponse));
14057
+ await auth.login(authResponse.token, authResponse.user, authResponse.accountData, false, getExpirationFromResponse(authResponse), authResponse.refreshToken, authResponse.refreshTokenExpiresAt);
13807
14058
  setAuthSuccess(true);
13808
14059
  setSuccessMessage('Signed in automatically with Google!');
13809
14060
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -13822,7 +14073,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13822
14073
  }
13823
14074
  };
13824
14075
  performSilentSignIn();
13825
- }, [enableSilentGoogleSignIn, silentSignInChecked, sdkReady, auth.isAuthenticated, clientId, config?.googleClientId, api, auth, onAuthSuccess]);
14076
+ }, [enableSilentGoogleSignIn, wantsSilentNative, nativeAuth, silentSignInChecked, sdkReady, auth.isAuthenticated, clientId, config?.googleClientId, api, auth, onAuthSuccess]);
13826
14077
  // Reset showEmailForm when mode changes away from login/register
13827
14078
  React.useEffect(() => {
13828
14079
  if (mode !== 'login' && mode !== 'register') {
@@ -13898,7 +14149,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13898
14149
  if ((verificationMode === 'verify-auto-login' || verificationMode === 'immediate') && response.token) {
13899
14150
  // Auto-login modes: Log the user in immediately if token is provided
13900
14151
  // Always await - auth.login now waits for parent ack automatically in iframe mode
13901
- await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response));
14152
+ await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response), response.refreshToken, response.refreshTokenExpiresAt);
13902
14153
  setUrlAuthProcessing(false); // Clear processing state before redirect/success
13903
14154
  if (redirectUrl) {
13904
14155
  // Redirect to clean URL and resume flow
@@ -13958,7 +14209,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
13958
14209
  // Auto-login with magic link if token is provided
13959
14210
  if (response.token) {
13960
14211
  // Always await - auth.login now waits for parent ack automatically in iframe mode
13961
- await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response));
14212
+ await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response), response.refreshToken, response.refreshTokenExpiresAt);
13962
14213
  setUrlAuthProcessing(false); // Clear processing state before redirect/success
13963
14214
  if (redirectUrl) {
13964
14215
  // Redirect to clean URL and resume flow
@@ -14033,7 +14284,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14033
14284
  log.log('Google OAuth code exchange response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
14034
14285
  if (response.token) {
14035
14286
  // Await login to ensure token is persisted before any navigation
14036
- await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
14287
+ await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response), response.refreshToken, response.refreshTokenExpiresAt);
14037
14288
  setAuthSuccess(true);
14038
14289
  setSuccessMessage('Google login successful!');
14039
14290
  onAuthSuccess(response.token, response.user, response.accountData);
@@ -14087,7 +14338,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14087
14338
  // Handle different verification modes
14088
14339
  if (verificationMode === 'immediate' && response.token) {
14089
14340
  // Immediate mode: Log in right away if token is provided (isNewUser=true for registration)
14090
- await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response));
14341
+ await auth.login(response.token, response.user, response.accountData, true, getExpirationFromResponse(response), response.refreshToken, response.refreshTokenExpiresAt);
14091
14342
  setAuthSuccess(true);
14092
14343
  const deadline = response.emailVerificationDeadline
14093
14344
  ? new Date(response.emailVerificationDeadline).toLocaleString()
@@ -14149,7 +14400,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14149
14400
  setLoading(false);
14150
14401
  return;
14151
14402
  }
14152
- await auth.login(response.token, response.user, response.accountData, false, getExpirationFromResponse(response));
14403
+ await auth.login(response.token, response.user, response.accountData, false, getExpirationFromResponse(response), response.refreshToken, response.refreshTokenExpiresAt);
14153
14404
  setAuthSuccess(true);
14154
14405
  setSuccessMessage('Login successful!');
14155
14406
  onAuthSuccess(response.token, response.user, response.accountData);
@@ -14279,6 +14530,55 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14279
14530
  }
14280
14531
  });
14281
14532
  };
14533
+ // Finish a login once a backend AuthResponse has been obtained (shared by the
14534
+ // native adapter paths for Google and Apple).
14535
+ const completeNativeLogin = async (authResponse, successLabel) => {
14536
+ if (!authResponse.token) {
14537
+ throw new Error('Authentication failed - no token received');
14538
+ }
14539
+ await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
14540
+ setAuthSuccess(true);
14541
+ setSuccessMessage(successLabel);
14542
+ onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
14543
+ };
14544
+ // Sign in with Apple. Apple is native-only in this kit today: it requires an
14545
+ // injected `nativeAuth` adapter (e.g. a Capacitor plugin) that resolves an
14546
+ // Apple identity token. There is no in-browser fallback yet.
14547
+ const handleAppleLogin = async () => {
14548
+ if (!nativeAuth?.signInWithApple) {
14549
+ log.warn('Apple sign-in requested but no nativeAuth.signInWithApple adapter is configured');
14550
+ setError('Apple Sign-In is not available in this environment.');
14551
+ onAuthError?.(new Error('No nativeAuth.signInWithApple adapter configured'));
14552
+ return;
14553
+ }
14554
+ setLoading(true);
14555
+ setError(undefined);
14556
+ try {
14557
+ log.log('Using native adapter for Apple Sign-In');
14558
+ const result = await nativeAuth.signInWithApple({
14559
+ clientId: config?.appleClientId,
14560
+ scopes: ['name', 'email'],
14561
+ });
14562
+ if (!result?.idToken) {
14563
+ throw new Error('Apple Sign-In did not return an identity token');
14564
+ }
14565
+ const authResponse = await api.loginWithApple(result.idToken, {
14566
+ authorizationCode: result.authorizationCode,
14567
+ nonce: result.nonce,
14568
+ appleUserInfo: result.email || result.name ? { email: result.email, name: result.name } : undefined,
14569
+ });
14570
+ log.log('Native Apple login response:', { hasToken: !!authResponse.token, isNewUser: authResponse.isNewUser });
14571
+ await completeNativeLogin(authResponse, 'Apple login successful!');
14572
+ }
14573
+ catch (err) {
14574
+ log.error('Apple Sign-In failed:', err);
14575
+ setError(getFriendlyErrorMessage(err));
14576
+ onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
14577
+ }
14578
+ finally {
14579
+ setLoading(false);
14580
+ }
14581
+ };
14282
14582
  const handleGoogleLogin = async () => {
14283
14583
  const hasCustomGoogleClientId = !!config?.googleClientId;
14284
14584
  const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
@@ -14308,6 +14608,32 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14308
14608
  setLoading(true);
14309
14609
  setError(undefined);
14310
14610
  try {
14611
+ // Priority 0: injected Promise-based native adapter (Capacitor, etc.).
14612
+ // This is the clean path — await the plugin directly, no callback bridge.
14613
+ if (nativeAuth?.signInWithGoogle) {
14614
+ log.log('Using injected nativeAuth adapter for Google Sign-In');
14615
+ try {
14616
+ const result = await nativeAuth.signInWithGoogle({
14617
+ serverClientId: googleClientId,
14618
+ scopes: ['email', 'profile'],
14619
+ });
14620
+ if (!result?.idToken) {
14621
+ throw new Error('Google Sign-In did not return an ID token');
14622
+ }
14623
+ const authResponse = await api.loginWithGoogle(result.idToken);
14624
+ log.log('Native (adapter) Google login response:', { hasToken: !!authResponse.token, isNewUser: authResponse.isNewUser });
14625
+ await completeNativeLogin(authResponse, 'Google login successful!');
14626
+ }
14627
+ catch (err) {
14628
+ log.error('Adapter Google Sign-In failed:', err);
14629
+ setError(getFriendlyErrorMessage(err));
14630
+ onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
14631
+ }
14632
+ finally {
14633
+ setLoading(false);
14634
+ }
14635
+ return;
14636
+ }
14311
14637
  if (nativeBridge) {
14312
14638
  log.log('Using native bridge for Google Sign-In');
14313
14639
  const callbackId = `google_auth_${Date.now()}`;
@@ -14326,7 +14652,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14326
14652
  const authResponse = await api.loginWithGoogle(result.idToken);
14327
14653
  log.log('Native Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
14328
14654
  if (authResponse.token) {
14329
- await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
14655
+ await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse), authResponse.refreshToken, authResponse.refreshTokenExpiresAt);
14330
14656
  setAuthSuccess(true);
14331
14657
  setSuccessMessage('Google login successful!');
14332
14658
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -14546,7 +14872,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14546
14872
  log.log('Popup Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
14547
14873
  if (authResponse.token) {
14548
14874
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
14549
- await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
14875
+ await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse), authResponse.refreshToken, authResponse.refreshTokenExpiresAt);
14550
14876
  setAuthSuccess(true);
14551
14877
  setSuccessMessage('Google login successful!');
14552
14878
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -14596,7 +14922,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14596
14922
  log.log('OneTap Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
14597
14923
  if (authResponse.token) {
14598
14924
  // Google OAuth can be login or signup - use isNewUser flag from backend if available
14599
- await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
14925
+ await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse), authResponse.refreshToken, authResponse.refreshTokenExpiresAt);
14600
14926
  setAuthSuccess(true);
14601
14927
  setSuccessMessage('Google login successful!');
14602
14928
  onAuthSuccess(authResponse.token, authResponse.user, authResponse.accountData);
@@ -14678,7 +15004,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14678
15004
  // Phone auth is an INTERACTIVE flow (user entered OTP in UI)
14679
15005
  // Unlike deep-link flows (email verification, magic link), there's no URL token to clean up
14680
15006
  // Do NOT auto-redirect - let the parent app control the next step (profile completion, etc.)
14681
- await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
15007
+ await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response), response.refreshToken, response.refreshTokenExpiresAt);
14682
15008
  setAuthSuccess(true);
14683
15009
  setSuccessMessage('Phone verified! You are now logged in.');
14684
15010
  onAuthSuccess(response.token, response.user, response.accountData);
@@ -14709,9 +15035,22 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14709
15035
  const effectiveRedirectUrl = getRedirectUrl();
14710
15036
  try {
14711
15037
  if (resetToken && confirmPassword) {
14712
- // Complete password reset with token
14713
- await api.completePasswordReset(resetToken, emailOrPassword);
14714
- // Auto-login with the new password if we have the email
15038
+ // Complete password reset (or invite acceptance) with token
15039
+ const completeResponse = await api.completePasswordReset(resetToken, emailOrPassword);
15040
+ // Invite acceptance under verify-auto-login: backend returns a full session.
15041
+ // Adopt it directly — same pattern as verifyEmail / verifyMagicLink — and skip /login.
15042
+ if (completeResponse?.token && completeResponse.user) {
15043
+ log.log('complete-reset returned a session (invite auto-login), adopting it');
15044
+ await auth.login(completeResponse.token, completeResponse.user, completeResponse.accountData, true, getExpirationFromResponse(completeResponse), completeResponse.refreshToken, completeResponse.refreshTokenExpiresAt);
15045
+ setAuthSuccess(true);
15046
+ setSuccessMessage('Welcome! Your account is ready.');
15047
+ onAuthSuccess(completeResponse.token, completeResponse.user, completeResponse.accountData);
15048
+ setResetToken(undefined);
15049
+ setResetEmail(undefined);
15050
+ return;
15051
+ }
15052
+ // Plain password reset: no session returned. Try auto-login with the new password
15053
+ // if we have the email on hand.
14715
15054
  if (resetEmail) {
14716
15055
  try {
14717
15056
  log.log('Auto-login after password reset for:', resetEmail);
@@ -14968,7 +15307,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14968
15307
  throw Object.assign(new Error(resultErrorMessage), session);
14969
15308
  }
14970
15309
  if (session?.token && session.user) {
14971
- await auth.login(session.token, session.user, session.accountData, true, getExpirationFromResponse(session));
15310
+ await auth.login(session.token, session.user, session.accountData, true, getExpirationFromResponse(session), session.refreshToken, session.refreshTokenExpiresAt);
14972
15311
  if (!proxyMode) {
14973
15312
  onAuthSuccess(session.token, session.user, session.accountData);
14974
15313
  }
@@ -15298,11 +15637,11 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
15298
15637
  const hasEmailProvider = actualProviders.includes('email');
15299
15638
  // If email provider is not enabled, only show provider buttons (no email/password form)
15300
15639
  if (!hasEmailProvider) {
15301
- return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), onWhatsAppLogin: () => setMode('whatsapp'), loading: loading }));
15640
+ return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onAppleLogin: handleAppleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), onWhatsAppLogin: () => setMode('whatsapp'), loading: loading }));
15302
15641
  }
15303
15642
  // Button mode: show provider selection first, then email form if email is selected
15304
15643
  if (emailDisplayMode === 'button' && !showEmailForm) {
15305
- return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), onWhatsAppLogin: () => setMode('whatsapp'), loading: loading }));
15644
+ return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onAppleLogin: handleAppleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), onWhatsAppLogin: () => setMode('whatsapp'), loading: loading }));
15306
15645
  }
15307
15646
  // Form mode or email button was clicked: show email form with other providers
15308
15647
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsxRuntime.jsx("button", { onClick: () => setShowEmailForm(false), style: {
@@ -15321,7 +15660,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
15321
15660
  setShowResendVerification(false);
15322
15661
  setShowRequestNewReset(false);
15323
15662
  setError(undefined);
15324
- }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, signupRedirectUrl: config?.signupRedirectUrl || customization?.signupRedirectUrl, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), onWhatsAppLogin: () => setMode('whatsapp'), loading: loading }))] }));
15663
+ }, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, signupRedirectUrl: config?.signupRedirectUrl || customization?.signupRedirectUrl, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onAppleLogin: handleAppleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), onWhatsAppLogin: () => setMode('whatsapp'), loading: loading }))] }));
15325
15664
  })()] })) })) : null }));
15326
15665
  };
15327
15666
 
@@ -16462,6 +16801,12 @@ async function setDefaultAuthKitId(collectionId, authKitId) {
16462
16801
  });
16463
16802
  }
16464
16803
 
16804
+ var defaultAuthKit = /*#__PURE__*/Object.freeze({
16805
+ __proto__: null,
16806
+ getDefaultAuthKitId: getDefaultAuthKitId,
16807
+ setDefaultAuthKitId: setDefaultAuthKitId
16808
+ });
16809
+
16465
16810
  exports.AccountManagement = AccountManagement;
16466
16811
  exports.AuthProvider = AuthProvider;
16467
16812
  exports.AuthUIPreview = AuthUIPreview;
@@ -16485,6 +16830,7 @@ exports.isRateLimitError = isRateLimitError;
16485
16830
  exports.isServerError = isServerError;
16486
16831
  exports.resolveFields = resolveFields;
16487
16832
  exports.setDefaultAuthKitId = setDefaultAuthKitId;
16833
+ exports.setStorageAdapter = setStorageAdapter;
16488
16834
  exports.sortFieldsByPlacement = sortFieldsByPlacement;
16489
16835
  exports.tokenStorage = tokenStorage;
16490
16836
  exports.useAdminDetection = useAdminDetection;