@proveanything/smartlinks-auth-ui 0.5.1 → 0.5.4

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
@@ -11002,9 +11002,9 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
11002
11002
  const awaitingNameInput = collectName && !sent;
11003
11003
  const awaitingAutoSend = !collectName && !sent;
11004
11004
  return (jsxRuntime.jsxs("form", { className: "auth-form", onSubmit: awaitingNameInput ? handleNameSubmit : (e) => e.preventDefault(), children: [jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: "Continue with WhatsApp" }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: sent
11005
- ? 'Send the pre-filled WhatsApp message to verify yourself. We\'ll log you in automatically.'
11005
+ ? "Tap below to open WhatsApp and send us the pre-filled message that's how we confirm your number. We'll log you in the moment we receive it."
11006
11006
  : awaitingNameInput
11007
- ? 'Tell us your name, then send us a WhatsApp message — no phone number needed.'
11007
+ ? "We'll open WhatsApp with a pre-filled message. Just hit send to confirm your number — no SMS code, no typing."
11008
11008
  : 'Preparing your WhatsApp link…' })] }), displayedError && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", 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" }) }), displayedError] })), awaitingNameInput && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "waName", className: "auth-label", children: "Name" }), jsxRuntime.jsx("input", { id: "waName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), className: "auth-input", placeholder: "John Smith", disabled: loading, autoComplete: "name", autoFocus: true })] }), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? jsxRuntime.jsx("span", { className: "auth-spinner" }) : 'Continue' })] })), awaitingAutoSend && (jsxRuntime.jsx("div", { style: { textAlign: 'center', padding: '1.5rem' }, children: jsxRuntime.jsx("span", { className: "auth-spinner" }) })), sent && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!showOnDesktop && (jsxRuntime.jsxs("a", { href: sent.waLink, target: "_blank", rel: "noopener noreferrer", className: "auth-button auth-button-primary", style: {
11009
11009
  display: 'flex',
11010
11010
  alignItems: 'center',
@@ -11013,7 +11013,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
11013
11013
  background: '#25D366',
11014
11014
  color: '#fff',
11015
11015
  textDecoration: 'none',
11016
- }, children: [jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.247-.694.247-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.464 3.488" }) }), "Open WhatsApp"] })), showOnDesktop && qrDataUrl && (jsxRuntime.jsxs("div", { style: { textAlign: 'center', margin: '0.75rem 0' }, children: [jsxRuntime.jsx("div", { style: {
11016
+ }, children: [jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.247-.694.247-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.464 3.488" }) }), "Open WhatsApp & send message"] })), showOnDesktop && qrDataUrl && (jsxRuntime.jsxs("div", { style: { textAlign: 'center', margin: '0.75rem 0' }, children: [jsxRuntime.jsx("div", { style: {
11017
11017
  display: 'inline-block',
11018
11018
  padding: '0.75rem',
11019
11019
  background: '#fff',
@@ -11023,7 +11023,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
11023
11023
  fontSize: '0.8125rem',
11024
11024
  color: '#6B7280',
11025
11025
  marginTop: '0.5rem',
11026
- }, children: "Scan with your phone \u2014 your WhatsApp will open with a pre-filled message. Just hit send." })] })), showOnDesktop && (jsxRuntime.jsx("a", { href: sent.waLink, target: "_blank", rel: "noopener noreferrer", className: "auth-button auth-button-secondary", style: { display: 'block', textAlign: 'center', textDecoration: 'none' }, children: "Or open WhatsApp on this device" })), jsxRuntime.jsxs("div", { style: {
11026
+ }, children: "Scan with your phone \u2014 your WhatsApp will open with a pre-filled message. Just hit send." })] })), showOnDesktop && (jsxRuntime.jsx("a", { href: sent.waLink, target: "_blank", rel: "noopener noreferrer", className: "auth-button auth-button-secondary", style: { display: 'block', textAlign: 'center', textDecoration: 'none' }, children: "Or send the message from this device" })), jsxRuntime.jsxs("div", { style: {
11027
11027
  marginTop: '1rem',
11028
11028
  padding: '0.75rem',
11029
11029
  borderRadius: '0.5rem',
@@ -11032,7 +11032,7 @@ const WhatsAppAuthForm = ({ onSend, onPollStatus, onVerified, onBack, loading =
11032
11032
  fontSize: '0.8125rem',
11033
11033
  color: '#374151',
11034
11034
  textAlign: 'center',
11035
- }, children: [jsxRuntime.jsx("span", { className: "auth-spinner", style: { marginRight: '0.5rem' } }), "Waiting for your WhatsApp message\u2026", jsxRuntime.jsxs("div", { style: { marginTop: '0.25rem', fontSize: '0.75rem', opacity: 0.7 }, children: ["Code: ", jsxRuntime.jsx("code", { children: sent.code })] })] }), jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: handleSendNew, disabled: loading, style: {
11035
+ }, children: [jsxRuntime.jsx("span", { className: "auth-spinner", style: { marginRight: '0.5rem' } }), "Waiting for your message \u2014 we'll log you in automatically\u2026", jsxRuntime.jsxs("div", { style: { marginTop: '0.25rem', fontSize: '0.75rem', opacity: 0.7 }, children: ["Code: ", jsxRuntime.jsx("code", { children: sent.code })] })] }), jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: handleSendNew, disabled: loading, style: {
11036
11036
  marginTop: '0.75rem',
11037
11037
  background: 'none',
11038
11038
  border: 'none',
@@ -11509,10 +11509,19 @@ class AuthAPI {
11509
11509
  return smartlinks__namespace.authKit.verifyMagicLink(this.clientId, token);
11510
11510
  }
11511
11511
  // ============= WhatsApp =============
11512
- async sendWhatsApp(redirectUrl, phoneNumber) {
11512
+ async sendWhatsApp(redirectUrl, phoneNumber, reply, prefillMessage) {
11513
11513
  // phoneNumber is optional — backend extracts it from the inbound WhatsApp webhook.
11514
- // Cast keeps us forward-compatible with the relaxed backend schema.
11515
- return smartlinks__namespace.authKit.sendWhatsApp(this.clientId, { phoneNumber: phoneNumber ?? '', redirectUrl });
11514
+ // `reply`, when provided, is sent back to the user via WhatsApp on successful verification.
11515
+ // `prefillMessage` customizes the message body pre-filled in the wa.me link.
11516
+ return smartlinks__namespace.authKit.sendWhatsApp(this.clientId, {
11517
+ phoneNumber: phoneNumber ?? '',
11518
+ redirectUrl,
11519
+ ...(reply ? { reply } : {}),
11520
+ ...(prefillMessage ? { prefillMessage } : {}),
11521
+ });
11522
+ }
11523
+ async exchangeWhatsAppSession(token, sessionKey) {
11524
+ return smartlinks__namespace.authKit.exchangeWhatsAppSession(this.clientId, token, sessionKey);
11516
11525
  }
11517
11526
  async getWhatsAppStatus(token) {
11518
11527
  return smartlinks__namespace.authKit.getWhatsAppStatus(this.clientId, token);
@@ -12843,6 +12852,31 @@ const getActionResultErrorMessage = (result) => {
12843
12852
  details: candidate.details,
12844
12853
  });
12845
12854
  };
12855
+ const interpolateReply = (input, vars) => {
12856
+ if (!input)
12857
+ return input;
12858
+ return input
12859
+ .replace(/\{\{\s*name\s*\}\}/gi, vars.name?.trim() || 'there')
12860
+ .replace(/\{\{\s*clientName\s*\}\}/gi, vars.clientName?.trim() || '')
12861
+ .replace(/\{\{\s*phoneNumber\s*\}\}/gi, vars.phoneNumber?.trim() || '');
12862
+ };
12863
+ const buildWhatsAppReply = (cfg, vars) => {
12864
+ if (!cfg)
12865
+ return undefined;
12866
+ if (cfg.enabled === false)
12867
+ return undefined;
12868
+ const text = interpolateReply(cfg.text, vars);
12869
+ const cta = cfg.cta && cfg.cta.buttonUrl && cfg.cta.buttonLabel
12870
+ ? {
12871
+ body: interpolateReply(cfg.cta.body, vars) || '',
12872
+ buttonLabel: interpolateReply(cfg.cta.buttonLabel, vars) || cfg.cta.buttonLabel,
12873
+ buttonUrl: interpolateReply(cfg.cta.buttonUrl, vars) || cfg.cta.buttonUrl,
12874
+ }
12875
+ : undefined;
12876
+ if (!text && !cta)
12877
+ return undefined;
12878
+ return { ...(text ? { text } : {}), ...(cta ? { cta } : {}) };
12879
+ };
12846
12880
  // Default Smartlinks Google OAuth Client ID (public - safe to expose)
12847
12881
  const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
12848
12882
  // Default Google OAuth proxy URL (hosted on our whitelisted domain)
@@ -13020,7 +13054,7 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
13020
13054
  });
13021
13055
  };
13022
13056
  // getFriendlyErrorMessage is now imported from ../utils/errorHandling
13023
- 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, }) => {
13057
+ 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, }) => {
13024
13058
  // Resolve signup prominence from props, customization, config, or default
13025
13059
  const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
13026
13060
  // Determine initial mode based on signupProminence setting
@@ -14237,15 +14271,24 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14237
14271
  setLoading(false);
14238
14272
  }
14239
14273
  };
14274
+ // Track the most recent WhatsApp send so we can exchange its sessionKey for a real login session.
14275
+ const whatsappSendRef = React.useRef(null);
14240
14276
  const handleWhatsAppSend = async (displayName) => {
14241
14277
  setLoading(true);
14242
14278
  setError(undefined);
14243
14279
  try {
14244
14280
  // No ensureAccount and no phone number — the backend creates/links the
14245
14281
  // account from the inbound WhatsApp webhook (sender's number is the proof).
14246
- // displayName is reserved for a future "name on first contact" hook.
14247
- void displayName;
14248
- const result = await api.sendWhatsApp(getRedirectUrl());
14282
+ // Resolve reply config: per-call prop wins, otherwise fall back to AuthKit default.
14283
+ const replyConfig = whatsappReply ?? config?.whatsappReply;
14284
+ const reply = buildWhatsAppReply(replyConfig, {
14285
+ name: displayName,
14286
+ clientName,
14287
+ });
14288
+ // Resolve outbound prefill message: per-call prop wins, then AuthKit default.
14289
+ const prefillMessage = whatsappPrefillMessage ?? config?.whatsappPrefillMessage;
14290
+ const result = await api.sendWhatsApp(getRedirectUrl(), undefined, reply, prefillMessage);
14291
+ whatsappSendRef.current = { token: result.token, sessionKey: result.sessionKey };
14249
14292
  return result;
14250
14293
  }
14251
14294
  catch (err) {
@@ -14260,12 +14303,28 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
14260
14303
  const handleWhatsAppPoll = async (token) => {
14261
14304
  return api.getWhatsAppStatus(token);
14262
14305
  };
14263
- const handleWhatsAppVerified = (status) => {
14264
- // Backend returns a redirectUrl containing a magic-link-style token that
14265
- // completes the login on landing. Treat the same as magic-link verification.
14306
+ const handleWhatsAppVerified = async (status) => {
14266
14307
  const target = status.redirectUrl || getRedirectUrl();
14267
14308
  setAuthSuccess(true);
14268
14309
  setSuccessMessage('WhatsApp verified! Signing you in…');
14310
+ // Exchange the verified WhatsApp proof for a real Auth Kit session token,
14311
+ // so the user is fully logged in (not just verified) and any same-tab
14312
+ // continuation flows (claim, bid, etc.) pick up an authenticated session.
14313
+ const send = whatsappSendRef.current;
14314
+ if (send?.sessionKey) {
14315
+ try {
14316
+ const session = await api.exchangeWhatsAppSession(send.token, send.sessionKey);
14317
+ if (session?.token && session.user) {
14318
+ await auth.login(session.token, session.user, session.accountData, true, getExpirationFromResponse(session));
14319
+ if (!proxyMode) {
14320
+ onAuthSuccess(session.token, session.user, session.accountData);
14321
+ }
14322
+ }
14323
+ }
14324
+ catch (err) {
14325
+ log.warn('WhatsApp session exchange failed, falling back to redirect-only flow:', err);
14326
+ }
14327
+ }
14269
14328
  performRedirect(target, 'magic-link');
14270
14329
  };
14271
14330
  // Show processing state for URL-based auth (verification, magic link, password reset)