@proveanything/smartlinks-auth-ui 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts.map +1 -1
- package/dist/components/EmailAuthForm.d.ts +1 -0
- package/dist/components/EmailAuthForm.d.ts.map +1 -1
- package/dist/components/PasswordResetForm.d.ts +1 -0
- package/dist/components/PasswordResetForm.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/index.esm.js +277 -80
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +277 -80
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/errorHandling.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -258,7 +258,7 @@ const AuthModeToggle = ({ mode, onModeChange, disabled = false, }) => {
|
|
|
258
258
|
return (jsxs("div", { className: "auth-mode-toggle", role: "tablist", "aria-label": "Authentication mode", children: [jsx("button", { type: "button", role: "tab", "aria-selected": mode === 'login', className: `auth-mode-toggle-button ${mode === 'login' ? 'auth-mode-toggle-button--active' : ''}`, onClick: () => onModeChange('login'), disabled: disabled, children: "Sign In" }), jsx("button", { type: "button", role: "tab", "aria-selected": mode === 'register', className: `auth-mode-toggle-button ${mode === 'register' ? 'auth-mode-toggle-button--active' : ''}`, onClick: () => onModeChange('register'), disabled: disabled, children: "Create Account" })] }));
|
|
259
259
|
};
|
|
260
260
|
|
|
261
|
-
const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', schema, registrationFieldsConfig = [], additionalFields = [], }) => {
|
|
261
|
+
const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading, error, signupProminence = 'minimal', signupRedirectUrl, schema, registrationFieldsConfig = [], additionalFields = [], }) => {
|
|
262
262
|
const [formData, setFormData] = useState({
|
|
263
263
|
email: '',
|
|
264
264
|
password: '',
|
|
@@ -314,7 +314,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
|
|
|
314
314
|
if (newMode !== mode) {
|
|
315
315
|
onModeSwitch();
|
|
316
316
|
}
|
|
317
|
-
}, disabled: loading })), jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: title }), jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: 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' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), 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" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), 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" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), 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 && (jsxs(Fragment, { children: [jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && (jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] }))] }));
|
|
317
|
+
}, disabled: loading })), jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: title }), jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: 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' && (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), 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" })] })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), 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" })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), 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 && (jsxs(Fragment, { children: [jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsx("div", { className: "auth-form-footer", children: jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && signupProminence !== 'none' && (jsxs("div", { className: "auth-divider", children: [jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), signupRedirectUrl && mode === 'login' ? (jsx("a", { href: signupRedirectUrl, target: "_top", className: "auth-link auth-link-bold", children: "Sign up" })) : (jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' }))] }))] }));
|
|
318
318
|
};
|
|
319
319
|
|
|
320
320
|
const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
|
|
@@ -10811,7 +10811,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10811
10811
|
: 'Resend code' }) })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : codeSent ? ('Verify Code') : ('Send Code') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to login" }) })] }));
|
|
10812
10812
|
};
|
|
10813
10813
|
|
|
10814
|
-
const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, }) => {
|
|
10814
|
+
const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successMessage, token, resetEmail, }) => {
|
|
10815
10815
|
const [email, setEmail] = useState('');
|
|
10816
10816
|
const [password, setPassword] = useState('');
|
|
10817
10817
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
@@ -10836,14 +10836,14 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successM
|
|
|
10836
10836
|
await onSubmit(email);
|
|
10837
10837
|
}
|
|
10838
10838
|
};
|
|
10839
|
-
if (success) {
|
|
10839
|
+
if (success && !error) {
|
|
10840
10840
|
return (jsxs("div", { className: "auth-form", children: [jsxs("div", { className: "auth-form-header", children: [jsx("div", { style: { textAlign: 'center', marginBottom: '1rem' }, children: jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", style: { margin: '0 auto' }, children: [jsx("circle", { cx: "32", cy: "32", r: "32", fill: "#10b981", fillOpacity: "0.1" }), jsx("path", { d: "M20 32l8 8 16-16", stroke: "#10b981", strokeWidth: "4", strokeLinecap: "round", strokeLinejoin: "round" })] }) }), jsx("h2", { className: "auth-form-title", children: token ? 'Password reset!' : 'Check your email' }), jsx("p", { className: "auth-form-subtitle", children: token
|
|
10841
10841
|
? (successMessage || 'Your password has been successfully reset. You can now sign in with your new password.')
|
|
10842
10842
|
: (successMessage || `We've sent password reset instructions to ${email}`) })] }), jsx("button", { type: "button", className: "auth-button auth-button-primary", onClick: onBack, children: "Back to Sign in" })] }));
|
|
10843
10843
|
}
|
|
10844
10844
|
return (jsxs("form", { className: "auth-form", onSubmit: handleSubmit, children: [jsxs("div", { className: "auth-form-header", children: [jsx("h2", { className: "auth-form-title", children: token ? 'Set new password' : 'Reset your password' }), jsx("p", { className: "auth-form-subtitle", children: token
|
|
10845
10845
|
? 'Enter your new password below.'
|
|
10846
|
-
: "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: 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 || passwordError] })), token ? (jsxs(Fragment, { children: [jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: loading, placeholder: "Enter new password", autoComplete: "new-password", minLength: 6 })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsx("input", { type: "password", id: "confirmPassword", className: "auth-input", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, placeholder: "Confirm new password", autoComplete: "new-password", minLength: 6 })] })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
|
|
10846
|
+
: "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (jsxs("div", { className: "auth-error", role: "alert", children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: 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 || passwordError] })), token ? (jsxs(Fragment, { children: [resetEmail && (jsx("input", { type: "email", value: resetEmail, readOnly: true, autoComplete: "username", style: { position: 'absolute', opacity: 0, height: 0, width: 0, overflow: 'hidden', pointerEvents: 'none' }, tabIndex: -1, "aria-hidden": "true" })), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsx("input", { type: "password", id: "password", className: "auth-input", value: password, onChange: (e) => setPassword(e.target.value), required: true, disabled: loading, placeholder: "Enter new password", autoComplete: "new-password", minLength: 6 })] }), jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsx("input", { type: "password", id: "confirmPassword", className: "auth-input", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, placeholder: "Confirm new password", autoComplete: "new-password", minLength: 6 })] })] })) : (jsxs("div", { className: "auth-form-group", children: [jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsx("input", { type: "email", id: "email", className: "auth-input", value: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsx("div", { className: "auth-divider", children: jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
|
|
10847
10847
|
};
|
|
10848
10848
|
|
|
10849
10849
|
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
|
|
@@ -10949,14 +10949,46 @@ class AuthAPI {
|
|
|
10949
10949
|
return smartlinks.authKit.verifyPhoneCode(this.clientId, phoneNumber, code);
|
|
10950
10950
|
}
|
|
10951
10951
|
async requestPasswordReset(email, redirectUrl) {
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10952
|
+
console.log('[AuthKit:API] requestPasswordReset called', { clientId: this.clientId, email, redirectUrl });
|
|
10953
|
+
try {
|
|
10954
|
+
const result = await smartlinks.authKit.requestPasswordReset(this.clientId, {
|
|
10955
|
+
email,
|
|
10956
|
+
redirectUrl,
|
|
10957
|
+
clientName: this.clientName
|
|
10958
|
+
});
|
|
10959
|
+
console.log('[AuthKit:API] requestPasswordReset resolved:', JSON.stringify(result));
|
|
10960
|
+
return result;
|
|
10961
|
+
}
|
|
10962
|
+
catch (error) {
|
|
10963
|
+
const err = error;
|
|
10964
|
+
console.error('[AuthKit:API] requestPasswordReset threw:', {
|
|
10965
|
+
type: typeof error,
|
|
10966
|
+
message: err?.message,
|
|
10967
|
+
statusCode: err?.statusCode ?? err?.response?.status,
|
|
10968
|
+
errorCode: err?.errorCode ?? err?.details?.errorCode,
|
|
10969
|
+
details: err?.details ?? err?.response?.data,
|
|
10970
|
+
keys: err ? Object.keys(err) : [],
|
|
10971
|
+
});
|
|
10972
|
+
throw error;
|
|
10973
|
+
}
|
|
10957
10974
|
}
|
|
10958
10975
|
async verifyResetToken(token) {
|
|
10959
|
-
|
|
10976
|
+
console.log('[AuthKit:API] verifyResetToken called');
|
|
10977
|
+
try {
|
|
10978
|
+
const result = await smartlinks.authKit.verifyResetToken(this.clientId, token);
|
|
10979
|
+
console.log('[AuthKit:API] verifyResetToken resolved:', JSON.stringify(result));
|
|
10980
|
+
return result;
|
|
10981
|
+
}
|
|
10982
|
+
catch (error) {
|
|
10983
|
+
const err = error;
|
|
10984
|
+
console.error('[AuthKit:API] verifyResetToken threw:', {
|
|
10985
|
+
type: typeof error,
|
|
10986
|
+
message: err?.message,
|
|
10987
|
+
statusCode: err?.statusCode ?? err?.response?.status,
|
|
10988
|
+
errorCode: err?.errorCode ?? err?.details?.errorCode,
|
|
10989
|
+
});
|
|
10990
|
+
throw error;
|
|
10991
|
+
}
|
|
10960
10992
|
}
|
|
10961
10993
|
async completePasswordReset(token, newPassword) {
|
|
10962
10994
|
return smartlinks.authKit.completePasswordReset(this.clientId, token, newPassword);
|
|
@@ -10992,7 +11024,6 @@ class AuthAPI {
|
|
|
10992
11024
|
this.log.warn('Failed to fetch UI config, using defaults:', error);
|
|
10993
11025
|
return {
|
|
10994
11026
|
branding: {
|
|
10995
|
-
title: 'Smartlinks Auth',
|
|
10996
11027
|
subtitle: 'Sign in to your account',
|
|
10997
11028
|
primaryColor: '#3B82F6',
|
|
10998
11029
|
secondaryColor: '#1D4ED8',
|
|
@@ -12388,6 +12419,10 @@ function getFriendlyErrorMessage(error) {
|
|
|
12388
12419
|
if (isApiErrorLike(error)) {
|
|
12389
12420
|
// First, check for specific error code (most precise)
|
|
12390
12421
|
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12422
|
+
// For rate-limit errors, prefer the backend's message as it includes specific wait times
|
|
12423
|
+
if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
|
|
12424
|
+
return error.message;
|
|
12425
|
+
}
|
|
12391
12426
|
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
12392
12427
|
return ERROR_CODE_MESSAGES[errorCode];
|
|
12393
12428
|
}
|
|
@@ -12395,6 +12430,10 @@ function getFriendlyErrorMessage(error) {
|
|
|
12395
12430
|
if (error.statusCode >= 500) {
|
|
12396
12431
|
return 'Server error. Please try again later.';
|
|
12397
12432
|
}
|
|
12433
|
+
// For 429 status, prefer backend message (may include wait time)
|
|
12434
|
+
if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
|
|
12435
|
+
return error.message;
|
|
12436
|
+
}
|
|
12398
12437
|
if (STATUS_MESSAGES[error.statusCode]) {
|
|
12399
12438
|
return STATUS_MESSAGES[error.statusCode];
|
|
12400
12439
|
}
|
|
@@ -12521,7 +12560,7 @@ function requiresEmailVerification(error) {
|
|
|
12521
12560
|
}
|
|
12522
12561
|
|
|
12523
12562
|
// VERSION: Update this when making changes to help identify which version is running
|
|
12524
|
-
const AUTH_UI_VERSION = '
|
|
12563
|
+
const AUTH_UI_VERSION = '46';
|
|
12525
12564
|
const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
|
|
12526
12565
|
// Helper to check for URL auth params synchronously (runs during initialization)
|
|
12527
12566
|
// This prevents the form from flashing before detecting deep-link flows
|
|
@@ -12545,6 +12584,24 @@ const getExpirationFromResponse = (response) => {
|
|
|
12545
12584
|
return Date.now() + response.expiresIn;
|
|
12546
12585
|
return undefined; // Will use 7-day default in tokenStorage
|
|
12547
12586
|
};
|
|
12587
|
+
const getActionResultErrorMessage = (result) => {
|
|
12588
|
+
if (!result || typeof result !== 'object')
|
|
12589
|
+
return null;
|
|
12590
|
+
const candidate = result;
|
|
12591
|
+
const numericStatusCode = typeof candidate.statusCode === 'string'
|
|
12592
|
+
? Number(candidate.statusCode)
|
|
12593
|
+
: candidate.statusCode;
|
|
12594
|
+
const hasFailureStatus = typeof numericStatusCode === 'number' && !Number.isNaN(numericStatusCode) && numericStatusCode >= 400;
|
|
12595
|
+
const isFailure = candidate.success === false || candidate.ok === false || !!candidate.errorCode || hasFailureStatus;
|
|
12596
|
+
if (!isFailure)
|
|
12597
|
+
return null;
|
|
12598
|
+
return getFriendlyErrorMessage({
|
|
12599
|
+
statusCode: hasFailureStatus ? numericStatusCode : 400,
|
|
12600
|
+
errorCode: candidate.errorCode,
|
|
12601
|
+
message: candidate.message || 'Request failed. Please try again.',
|
|
12602
|
+
details: candidate.details,
|
|
12603
|
+
});
|
|
12604
|
+
};
|
|
12548
12605
|
// Default Smartlinks Google OAuth Client ID (public - safe to expose)
|
|
12549
12606
|
const DEFAULT_GOOGLE_CLIENT_ID = '696509063554-jdlbjl8vsjt7cr0vgkjkjf3ffnvi3a70.apps.googleusercontent.com';
|
|
12550
12607
|
// Default Google OAuth proxy URL (hosted on our whitelisted domain)
|
|
@@ -12636,6 +12693,43 @@ const getNativeBridge = () => {
|
|
|
12636
12693
|
return native;
|
|
12637
12694
|
return null;
|
|
12638
12695
|
};
|
|
12696
|
+
// Helper to register an AuthKit.onAuthResult handler for a specific callbackId
|
|
12697
|
+
// The Android native app calls: window.AuthKit.onAuthResult(resultJson)
|
|
12698
|
+
const registerAuthKitCallback = (callbackId, onResult, timeoutMs = 5000, onTimeout) => {
|
|
12699
|
+
const bridge = window.AuthKit;
|
|
12700
|
+
if (!bridge) {
|
|
12701
|
+
console.warn('[AuthKit] No AuthKit bridge found, cannot register callback');
|
|
12702
|
+
onTimeout?.();
|
|
12703
|
+
return () => { };
|
|
12704
|
+
}
|
|
12705
|
+
// Store the previous handler so we can restore on cleanup
|
|
12706
|
+
const previousHandler = bridge.onAuthResult;
|
|
12707
|
+
const timeout = setTimeout(() => {
|
|
12708
|
+
// Restore previous handler on timeout
|
|
12709
|
+
bridge.onAuthResult = previousHandler;
|
|
12710
|
+
onTimeout?.();
|
|
12711
|
+
}, timeoutMs);
|
|
12712
|
+
bridge.onAuthResult = (resultOrJson) => {
|
|
12713
|
+
const result = typeof resultOrJson === 'string' ? JSON.parse(resultOrJson) : resultOrJson;
|
|
12714
|
+
// Match by callbackId if present, otherwise accept any result
|
|
12715
|
+
if (result.callbackId && result.callbackId !== callbackId) {
|
|
12716
|
+
// Not for us — pass through to previous handler if any
|
|
12717
|
+
if (typeof previousHandler === 'function') {
|
|
12718
|
+
previousHandler(resultOrJson);
|
|
12719
|
+
}
|
|
12720
|
+
return;
|
|
12721
|
+
}
|
|
12722
|
+
clearTimeout(timeout);
|
|
12723
|
+
// Restore previous handler
|
|
12724
|
+
bridge.onAuthResult = previousHandler;
|
|
12725
|
+
onResult(result);
|
|
12726
|
+
};
|
|
12727
|
+
// Return cleanup function
|
|
12728
|
+
return () => {
|
|
12729
|
+
clearTimeout(timeout);
|
|
12730
|
+
bridge.onAuthResult = previousHandler;
|
|
12731
|
+
};
|
|
12732
|
+
};
|
|
12639
12733
|
// Sign out from Google on the native side (clears cached Google account)
|
|
12640
12734
|
// This is fire-and-forget with a timeout - gracefully degrades if not supported
|
|
12641
12735
|
const signOutGoogleNative = async () => {
|
|
@@ -12644,21 +12738,7 @@ const signOutGoogleNative = async () => {
|
|
|
12644
12738
|
return;
|
|
12645
12739
|
const callbackId = `google_signout_${Date.now()}`;
|
|
12646
12740
|
return new Promise((resolve) => {
|
|
12647
|
-
|
|
12648
|
-
// Store original callback to restore later
|
|
12649
|
-
const originalCallback = window.smartlinksNativeCallback;
|
|
12650
|
-
window.smartlinksNativeCallback = (result) => {
|
|
12651
|
-
if (result.callbackId === callbackId) {
|
|
12652
|
-
clearTimeout(timeout);
|
|
12653
|
-
// Restore original callback
|
|
12654
|
-
window.smartlinksNativeCallback = originalCallback;
|
|
12655
|
-
resolve();
|
|
12656
|
-
}
|
|
12657
|
-
else if (originalCallback) {
|
|
12658
|
-
// Pass through to original callback for other messages
|
|
12659
|
-
originalCallback(result);
|
|
12660
|
-
}
|
|
12661
|
-
};
|
|
12741
|
+
registerAuthKitCallback(callbackId, () => resolve(), 3000, () => resolve());
|
|
12662
12742
|
const payload = JSON.stringify({
|
|
12663
12743
|
type: 'GOOGLE_SIGN_OUT',
|
|
12664
12744
|
callbackId,
|
|
@@ -12672,44 +12752,32 @@ const checkSilentGoogleSignIn = async (clientId, googleClientId) => {
|
|
|
12672
12752
|
return null;
|
|
12673
12753
|
const callbackId = `google_check_${Date.now()}`;
|
|
12674
12754
|
return new Promise((resolve) => {
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
resolve({
|
|
12685
|
-
isSignedIn: result.isSignedIn || false,
|
|
12686
|
-
idToken: result.idToken,
|
|
12687
|
-
email: result.email,
|
|
12688
|
-
name: result.name,
|
|
12689
|
-
picture: result.picture,
|
|
12690
|
-
});
|
|
12691
|
-
}
|
|
12692
|
-
else {
|
|
12693
|
-
resolve(null);
|
|
12694
|
-
}
|
|
12755
|
+
registerAuthKitCallback(callbackId, (result) => {
|
|
12756
|
+
if (result.success) {
|
|
12757
|
+
resolve({
|
|
12758
|
+
isSignedIn: result.isSignedIn || false,
|
|
12759
|
+
idToken: result.idToken,
|
|
12760
|
+
email: result.email,
|
|
12761
|
+
name: result.name,
|
|
12762
|
+
picture: result.picture,
|
|
12763
|
+
});
|
|
12695
12764
|
}
|
|
12696
|
-
else
|
|
12697
|
-
|
|
12698
|
-
originalCallback(result);
|
|
12765
|
+
else {
|
|
12766
|
+
resolve(null);
|
|
12699
12767
|
}
|
|
12700
|
-
};
|
|
12768
|
+
}, 5000, () => resolve(null));
|
|
12701
12769
|
const payload = JSON.stringify({
|
|
12702
12770
|
type: 'GOOGLE_CHECK_SIGN_IN',
|
|
12703
12771
|
clientId,
|
|
12704
12772
|
googleClientId,
|
|
12705
|
-
serverClientId: googleClientId,
|
|
12773
|
+
serverClientId: googleClientId,
|
|
12706
12774
|
callbackId,
|
|
12707
12775
|
});
|
|
12708
12776
|
nativeBridge.checkGoogleSignIn(payload);
|
|
12709
12777
|
});
|
|
12710
12778
|
};
|
|
12711
12779
|
// getFriendlyErrorMessage is now imported from ../utils/errorHandling
|
|
12712
|
-
const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAuthSuccess, onAuthError, onRedirect, enabledProviders = ['email', 'google', 'phone'], initialMode, signupProminence, redirectUrl, theme = '
|
|
12780
|
+
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, }) => {
|
|
12713
12781
|
// Resolve signup prominence from props, customization, config, or default
|
|
12714
12782
|
const resolvedSignupProminence = signupProminence || customization?.signupProminence || 'minimal';
|
|
12715
12783
|
// Determine initial mode based on signupProminence setting
|
|
@@ -12746,6 +12814,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12746
12814
|
const [contactSchema, setContactSchema] = useState(null); // Schema for registration fields
|
|
12747
12815
|
const [silentSignInChecked, setSilentSignInChecked] = useState(false); // Track if silent sign-in has been checked
|
|
12748
12816
|
const [googleFallbackToPopup, setGoogleFallbackToPopup] = useState(false); // Show popup fallback when FedCM is blocked/dismissed
|
|
12817
|
+
const [googleNativeTimedOut, setGoogleNativeTimedOut] = useState(false); // Native bridge callback timed out
|
|
12749
12818
|
const log = useMemo(() => createLoggerWrapper(logger), [logger]);
|
|
12750
12819
|
const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
|
|
12751
12820
|
const auth = useAuth();
|
|
@@ -13022,6 +13091,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13022
13091
|
if (urlMode === 'verifyEmail') {
|
|
13023
13092
|
log.log('Verifying email with token:', token);
|
|
13024
13093
|
const response = await api.verifyEmailWithToken(token);
|
|
13094
|
+
log.log('Email verification response:', { hasToken: !!response.token, hasUser: !!response.user, emailVerificationMode: response.emailVerificationMode, isNewUser: response.isNewUser });
|
|
13025
13095
|
// Get email verification mode from response or config
|
|
13026
13096
|
const verificationMode = response.emailVerificationMode || config?.emailVerification?.mode || 'verify-auto-login';
|
|
13027
13097
|
if ((verificationMode === 'verify-auto-login' || verificationMode === 'immediate') && response.token) {
|
|
@@ -13066,9 +13136,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13066
13136
|
log.log('Verifying reset token:', token);
|
|
13067
13137
|
// Verify token is valid, then show password reset form
|
|
13068
13138
|
const verifyResult = await api.verifyResetToken(token);
|
|
13069
|
-
//
|
|
13070
|
-
if (!verifyResult.valid) {
|
|
13071
|
-
throw new Error(verifyResult
|
|
13139
|
+
// Guard against undefined result (proxy mode may resolve without data)
|
|
13140
|
+
if (!verifyResult || !verifyResult.valid) {
|
|
13141
|
+
throw new Error(verifyResult?.message || 'Invalid or expired token');
|
|
13072
13142
|
}
|
|
13073
13143
|
setUrlAuthProcessing(false); // Clear processing state - showing reset form
|
|
13074
13144
|
setResetToken(token); // Store token for use in password reset
|
|
@@ -13083,6 +13153,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13083
13153
|
else if (urlMode === 'magicLink') {
|
|
13084
13154
|
log.log('Verifying magic link token:', token);
|
|
13085
13155
|
const response = await api.verifyMagicLink(token);
|
|
13156
|
+
log.log('Magic link verification response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
|
|
13086
13157
|
// Auto-login with magic link if token is provided
|
|
13087
13158
|
if (response.token) {
|
|
13088
13159
|
// Always await - auth.login now waits for parent ack automatically in iframe mode
|
|
@@ -13158,6 +13229,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13158
13229
|
const redirectUri = state.redirectUri || window.location.origin + window.location.pathname;
|
|
13159
13230
|
// Exchange authorization code for tokens
|
|
13160
13231
|
const response = await api.loginWithGoogleCode(code, redirectUri);
|
|
13232
|
+
log.log('Google OAuth code exchange response:', { hasToken: !!response.token, hasUser: !!response.user, isNewUser: response.isNewUser });
|
|
13161
13233
|
if (response.token) {
|
|
13162
13234
|
// Await login to ensure token is persisted before any navigation
|
|
13163
13235
|
await auth.login(response.token, response.user, response.accountData, response.isNewUser, getExpirationFromResponse(response));
|
|
@@ -13188,13 +13260,15 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13188
13260
|
setError(undefined);
|
|
13189
13261
|
setAuthSuccess(false);
|
|
13190
13262
|
try {
|
|
13263
|
+
log.log('Submitting email auth:', { mode, email: data.email });
|
|
13191
13264
|
const response = mode === 'login'
|
|
13192
13265
|
? await api.login(data.email, data.password)
|
|
13193
13266
|
: await api.register({
|
|
13194
13267
|
...data,
|
|
13195
13268
|
accountData: mode === 'register' ? accountData : undefined,
|
|
13196
|
-
redirectUrl: getRedirectUrl(),
|
|
13269
|
+
redirectUrl: getRedirectUrl(),
|
|
13197
13270
|
});
|
|
13271
|
+
log.log('Email auth response:', { mode, hasToken: !!response?.token, hasUser: !!response?.user, isNewUser: response?.isNewUser, requiresEmailVerification: response?.requiresEmailVerification, accountLocked: response?.accountLocked, emailVerificationMode: response?.emailVerificationMode });
|
|
13198
13272
|
// Defensive check: validate response before accessing properties
|
|
13199
13273
|
// SDK should throw on 401/error responses, but handle edge cases gracefully
|
|
13200
13274
|
if (!response) {
|
|
@@ -13342,17 +13416,46 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13342
13416
|
return;
|
|
13343
13417
|
setLoading(true);
|
|
13344
13418
|
setError(undefined);
|
|
13419
|
+
setAuthSuccess(false);
|
|
13420
|
+
setResetSuccess(false);
|
|
13421
|
+
setSuccessMessage(undefined);
|
|
13422
|
+
setShowResendVerification(false);
|
|
13345
13423
|
try {
|
|
13346
13424
|
const result = await api.requestPasswordReset(resetRequestEmail, getRedirectUrl());
|
|
13347
|
-
|
|
13425
|
+
const resultError = getActionResultErrorMessage(result);
|
|
13426
|
+
if (resultError) {
|
|
13427
|
+
setAuthSuccess(false);
|
|
13428
|
+
setResetSuccess(false);
|
|
13429
|
+
setSuccessMessage(undefined);
|
|
13430
|
+
setError(resultError);
|
|
13431
|
+
setShowRequestNewReset(true);
|
|
13432
|
+
return;
|
|
13433
|
+
}
|
|
13434
|
+
const resultAny = result;
|
|
13435
|
+
const isConfirmedSuccess = result && (resultAny.success === true ||
|
|
13436
|
+
resultAny.ok === true ||
|
|
13437
|
+
(resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
|
|
13438
|
+
if (!isConfirmedSuccess) {
|
|
13439
|
+
setAuthSuccess(false);
|
|
13440
|
+
setResetSuccess(false);
|
|
13441
|
+
setSuccessMessage(undefined);
|
|
13442
|
+
setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
|
|
13443
|
+
setShowRequestNewReset(true);
|
|
13444
|
+
return;
|
|
13445
|
+
}
|
|
13446
|
+
setAuthSuccess(false);
|
|
13447
|
+
setMode('reset-password');
|
|
13348
13448
|
setResetSuccess(true);
|
|
13349
|
-
// Use backend message if available, otherwise default
|
|
13350
13449
|
setSuccessMessage(result?.message || 'Password reset email sent! Please check your inbox.');
|
|
13351
13450
|
setShowRequestNewReset(false);
|
|
13352
13451
|
setResetRequestEmail('');
|
|
13353
13452
|
}
|
|
13354
13453
|
catch (err) {
|
|
13454
|
+
setAuthSuccess(false);
|
|
13455
|
+
setResetSuccess(false);
|
|
13456
|
+
setSuccessMessage(undefined);
|
|
13355
13457
|
setError(getFriendlyErrorMessage(err));
|
|
13458
|
+
setShowRequestNewReset(true);
|
|
13356
13459
|
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13357
13460
|
}
|
|
13358
13461
|
finally {
|
|
@@ -13407,21 +13510,20 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13407
13510
|
if (nativeBridge) {
|
|
13408
13511
|
log.log('Using native bridge for Google Sign-In');
|
|
13409
13512
|
const callbackId = `google_auth_${Date.now()}`;
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
log.log('Ignoring stale native callback:', result.callbackId);
|
|
13414
|
-
return;
|
|
13415
|
-
}
|
|
13416
|
-
log.log('Native callback received:', {
|
|
13513
|
+
// Register callback via AuthKit.onAuthResult (the native app calls this)
|
|
13514
|
+
const cleanup = registerAuthKitCallback(callbackId, async (result) => {
|
|
13515
|
+
log.log('Native Google auth result received:', {
|
|
13417
13516
|
success: result.success,
|
|
13418
13517
|
hasIdToken: !!result.idToken,
|
|
13419
13518
|
email: result.email,
|
|
13420
13519
|
error: result.error,
|
|
13520
|
+
callbackId: result.callbackId,
|
|
13421
13521
|
});
|
|
13522
|
+
setGoogleNativeTimedOut(false);
|
|
13422
13523
|
try {
|
|
13423
13524
|
if (result.success && result.idToken) {
|
|
13424
13525
|
const authResponse = await api.loginWithGoogle(result.idToken);
|
|
13526
|
+
log.log('Native Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
|
|
13425
13527
|
if (authResponse.token) {
|
|
13426
13528
|
await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
|
|
13427
13529
|
setAuthSuccess(true);
|
|
@@ -13444,7 +13546,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13444
13546
|
finally {
|
|
13445
13547
|
setLoading(false);
|
|
13446
13548
|
}
|
|
13447
|
-
}
|
|
13549
|
+
}, 30000, // 30s timeout for interactive Google Sign-In
|
|
13550
|
+
() => {
|
|
13551
|
+
log.log('Native Google Sign-In timed out after 30s');
|
|
13552
|
+
setLoading(false);
|
|
13553
|
+
setGoogleNativeTimedOut(true);
|
|
13554
|
+
});
|
|
13448
13555
|
const payloadObj = {
|
|
13449
13556
|
type: 'GOOGLE_SIGN_IN',
|
|
13450
13557
|
clientId,
|
|
@@ -13456,12 +13563,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13456
13563
|
requestServerAuthCode: false,
|
|
13457
13564
|
};
|
|
13458
13565
|
const payload = JSON.stringify(payloadObj);
|
|
13459
|
-
log.log('Invoking native signInWithGoogle');
|
|
13566
|
+
log.log('Invoking native signInWithGoogle with callbackId:', callbackId);
|
|
13460
13567
|
try {
|
|
13461
13568
|
nativeBridge.signInWithGoogle(payload);
|
|
13462
13569
|
}
|
|
13463
13570
|
catch (invokeError) {
|
|
13464
13571
|
console.error(`${LOG_PREFIX} Exception invoking signInWithGoogle:`, invokeError);
|
|
13572
|
+
cleanup(); // Clean up the callback registration
|
|
13465
13573
|
throw invokeError;
|
|
13466
13574
|
}
|
|
13467
13575
|
// Don't set loading to false - waiting for native callback
|
|
@@ -13506,6 +13614,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13506
13614
|
clearInterval(checkClosed);
|
|
13507
13615
|
try {
|
|
13508
13616
|
if (event.data.success && event.data.token) {
|
|
13617
|
+
log.log('Google proxy result received:', { success: true, hasUser: !!event.data.user, isNewUser: event.data.isNewUser });
|
|
13509
13618
|
const { token, user, accountData, isNewUser, expiresAt, expiresIn } = event.data;
|
|
13510
13619
|
const expiration = expiresAt || (expiresIn ? Date.now() + expiresIn : undefined);
|
|
13511
13620
|
await auth.login(token, user, accountData, isNewUser, expiration);
|
|
@@ -13633,6 +13742,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13633
13742
|
googleUserInfo: userInfo,
|
|
13634
13743
|
tokenType: 'access_token',
|
|
13635
13744
|
});
|
|
13745
|
+
log.log('Popup Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
|
|
13636
13746
|
if (authResponse.token) {
|
|
13637
13747
|
// Google OAuth can be login or signup - use isNewUser flag from backend if available
|
|
13638
13748
|
await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
|
|
@@ -13672,12 +13782,17 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13672
13782
|
client_id: googleClientId,
|
|
13673
13783
|
origin: window.location.origin,
|
|
13674
13784
|
});
|
|
13785
|
+
// Track whether the credential callback has fired (user selected an account)
|
|
13786
|
+
// so we don't show the "blocked" fallback when the prompt closes after a successful selection
|
|
13787
|
+
let credentialCallbackFired = false;
|
|
13675
13788
|
google.accounts.id.initialize({
|
|
13676
13789
|
client_id: googleClientId,
|
|
13677
13790
|
callback: async (response) => {
|
|
13791
|
+
credentialCallbackFired = true;
|
|
13678
13792
|
try {
|
|
13679
13793
|
const idToken = response.credential;
|
|
13680
13794
|
const authResponse = await api.loginWithGoogle(idToken);
|
|
13795
|
+
log.log('OneTap Google login response:', { hasToken: !!authResponse.token, hasUser: !!authResponse.user, isNewUser: authResponse.isNewUser });
|
|
13681
13796
|
if (authResponse.token) {
|
|
13682
13797
|
// Google OAuth can be login or signup - use isNewUser flag from backend if available
|
|
13683
13798
|
await auth.login(authResponse.token, authResponse.user, authResponse.accountData, authResponse.isNewUser, getExpirationFromResponse(authResponse));
|
|
@@ -13701,23 +13816,29 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13701
13816
|
cancel_on_tap_outside: true,
|
|
13702
13817
|
use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
|
|
13703
13818
|
});
|
|
13704
|
-
// Use timeout fallback — if no prompt interaction after
|
|
13819
|
+
// Use timeout fallback — if no prompt interaction after 15s, assume FedCM was blocked
|
|
13820
|
+
// (15s gives users enough time to choose a Google account from the picker)
|
|
13705
13821
|
const promptTimeout = setTimeout(() => {
|
|
13822
|
+
if (credentialCallbackFired)
|
|
13823
|
+
return; // Auth is already in progress, don't show fallback
|
|
13706
13824
|
log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
|
|
13707
13825
|
setGoogleFallbackToPopup(true);
|
|
13708
13826
|
setLoading(false);
|
|
13709
|
-
},
|
|
13827
|
+
}, 15000);
|
|
13710
13828
|
google.accounts.id.prompt((notification) => {
|
|
13711
13829
|
clearTimeout(promptTimeout);
|
|
13830
|
+
// If the credential callback already fired, the user successfully selected an account
|
|
13831
|
+
// The prompt closing with 'dismissed' is expected — don't show fallback
|
|
13832
|
+
if (credentialCallbackFired) {
|
|
13833
|
+
log.log('Google OneTap prompt closed after credential selection — ignoring');
|
|
13834
|
+
return;
|
|
13835
|
+
}
|
|
13712
13836
|
// Check for FedCM/OneTap dismissal or blocking
|
|
13713
|
-
// notification may have getMomentType(), getDismissedReason(), getSkippedReason()
|
|
13714
13837
|
const momentType = notification?.getMomentType?.();
|
|
13715
13838
|
const dismissedReason = notification?.getDismissedReason?.();
|
|
13716
13839
|
const skippedReason = notification?.getSkippedReason?.();
|
|
13717
13840
|
log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
|
|
13718
13841
|
if (momentType === 'skipped' || momentType === 'dismissed') {
|
|
13719
|
-
// User dismissed the prompt, or browser blocked it (FedCM disabled)
|
|
13720
|
-
// Offer popup flow as alternative
|
|
13721
13842
|
log.log('Google OneTap was dismissed/skipped, offering popup fallback');
|
|
13722
13843
|
setGoogleFallbackToPopup(true);
|
|
13723
13844
|
}
|
|
@@ -13744,6 +13865,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13744
13865
|
else {
|
|
13745
13866
|
// Verify code - Twilio identifies the verification by phone number
|
|
13746
13867
|
const response = await api.verifyPhoneCode(phoneNumber, verificationCode);
|
|
13868
|
+
log.log('Phone verification response:', { hasToken: !!response?.token, hasUser: !!response?.user, isNewUser: response?.isNewUser });
|
|
13747
13869
|
// Defensive validation: API may return undefined or error object on failure
|
|
13748
13870
|
if (!response) {
|
|
13749
13871
|
throw new Error('Verification failed - please check your code and try again');
|
|
@@ -13778,6 +13900,9 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13778
13900
|
const handlePasswordReset = async (emailOrPassword, confirmPassword) => {
|
|
13779
13901
|
setLoading(true);
|
|
13780
13902
|
setError(undefined);
|
|
13903
|
+
setAuthSuccess(false);
|
|
13904
|
+
setResetSuccess(false);
|
|
13905
|
+
setSuccessMessage(undefined);
|
|
13781
13906
|
const effectiveRedirectUrl = getRedirectUrl();
|
|
13782
13907
|
try {
|
|
13783
13908
|
if (resetToken && confirmPassword) {
|
|
@@ -13786,6 +13911,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13786
13911
|
// Auto-login with the new password if we have the email
|
|
13787
13912
|
if (resetEmail) {
|
|
13788
13913
|
try {
|
|
13914
|
+
log.log('Auto-login after password reset for:', resetEmail);
|
|
13789
13915
|
const loginResponse = await api.login(resetEmail, emailOrPassword);
|
|
13790
13916
|
if (loginResponse.token) {
|
|
13791
13917
|
await auth.login(loginResponse.token, loginResponse.user, loginResponse.accountData, false);
|
|
@@ -13798,9 +13924,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13798
13924
|
}
|
|
13799
13925
|
}
|
|
13800
13926
|
catch (loginErr) {
|
|
13801
|
-
|
|
13927
|
+
log.warn('Auto-login after password reset failed, requiring manual login:', loginErr);
|
|
13802
13928
|
}
|
|
13803
13929
|
}
|
|
13930
|
+
else {
|
|
13931
|
+
log.warn('No resetEmail available for auto-login after password reset');
|
|
13932
|
+
}
|
|
13804
13933
|
// Fallback: show success but require manual login
|
|
13805
13934
|
setResetSuccess(true);
|
|
13806
13935
|
setSuccessMessage('Password reset successful! Please sign in with your new password.');
|
|
@@ -13810,14 +13939,35 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13810
13939
|
else {
|
|
13811
13940
|
// Request password reset email
|
|
13812
13941
|
const result = await api.requestPasswordReset(emailOrPassword, effectiveRedirectUrl);
|
|
13942
|
+
const resultError = getActionResultErrorMessage(result);
|
|
13943
|
+
if (resultError) {
|
|
13944
|
+
setAuthSuccess(false);
|
|
13945
|
+
setResetSuccess(false);
|
|
13946
|
+
setSuccessMessage(undefined);
|
|
13947
|
+
setError(resultError);
|
|
13948
|
+
return;
|
|
13949
|
+
}
|
|
13950
|
+
const resultAny = result;
|
|
13951
|
+
const isConfirmedSuccess = result && (resultAny.success === true ||
|
|
13952
|
+
resultAny.ok === true ||
|
|
13953
|
+
(resultAny.message && !resultAny.errorCode && !resultAny.statusCode));
|
|
13954
|
+
if (!isConfirmedSuccess) {
|
|
13955
|
+
setAuthSuccess(false);
|
|
13956
|
+
setResetSuccess(false);
|
|
13957
|
+
setSuccessMessage(undefined);
|
|
13958
|
+
setError(resultAny?.message || 'Unable to send password reset email. Please try again.');
|
|
13959
|
+
return;
|
|
13960
|
+
}
|
|
13813
13961
|
setResetSuccess(true);
|
|
13814
|
-
// Use backend message if available
|
|
13815
13962
|
if (result?.message) {
|
|
13816
13963
|
setSuccessMessage(result.message);
|
|
13817
13964
|
}
|
|
13818
13965
|
}
|
|
13819
13966
|
}
|
|
13820
13967
|
catch (err) {
|
|
13968
|
+
setAuthSuccess(false);
|
|
13969
|
+
setResetSuccess(false);
|
|
13970
|
+
setSuccessMessage(undefined);
|
|
13821
13971
|
setError(getFriendlyErrorMessage(err));
|
|
13822
13972
|
onAuthError?.(err instanceof Error ? err : new Error(getFriendlyErrorMessage(err)));
|
|
13823
13973
|
}
|
|
@@ -13881,7 +14031,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13881
14031
|
setResetToken(undefined); // Clear token when going back
|
|
13882
14032
|
setResetEmail(undefined); // Clear email when going back
|
|
13883
14033
|
setSuccessMessage(undefined); // Clear success message
|
|
13884
|
-
}, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: {
|
|
14034
|
+
}, loading: loading, error: error, success: resetSuccess, successMessage: successMessage, token: resetToken, resetEmail: resetEmail })) : (mode === 'login' || mode === 'register') ? (jsx(Fragment, { children: showResendVerification ? (jsxs("div", { style: {
|
|
13885
14035
|
marginTop: '1rem',
|
|
13886
14036
|
padding: '1.5rem',
|
|
13887
14037
|
backgroundColor: config?.branding?.inheritHostStyles
|
|
@@ -13979,7 +14129,16 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13979
14129
|
color: config?.branding?.inheritHostStyles
|
|
13980
14130
|
? 'hsl(var(--foreground, 215 25% 15%))'
|
|
13981
14131
|
: (resolvedTheme === 'dark' ? '#f1f5f9' : '#374151')
|
|
13982
|
-
}, children: "Password Reset Link Expired" }), jsx("
|
|
14132
|
+
}, children: "Password Reset Link Expired" }), error && (jsx("div", { style: {
|
|
14133
|
+
marginBottom: '1rem',
|
|
14134
|
+
padding: '0.75rem',
|
|
14135
|
+
backgroundColor: resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(239, 68, 68, 0.1)',
|
|
14136
|
+
border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.2)'}`,
|
|
14137
|
+
borderRadius: '0.375rem',
|
|
14138
|
+
color: resolvedTheme === 'dark' ? '#fca5a5' : '#dc2626',
|
|
14139
|
+
fontSize: '0.875rem',
|
|
14140
|
+
lineHeight: '1.5',
|
|
14141
|
+
}, children: error })), jsx("p", { style: {
|
|
13983
14142
|
marginBottom: '1rem',
|
|
13984
14143
|
fontSize: '0.875rem',
|
|
13985
14144
|
color: config?.branding?.inheritHostStyles
|
|
@@ -14078,10 +14237,48 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14078
14237
|
backgroundColor: 'transparent',
|
|
14079
14238
|
border: 'none',
|
|
14080
14239
|
cursor: 'pointer',
|
|
14081
|
-
}, children: "Dismiss" })] })), ((
|
|
14240
|
+
}, children: "Dismiss" })] })), googleNativeTimedOut && (jsxs("div", { style: {
|
|
14241
|
+
marginBottom: '1rem',
|
|
14242
|
+
padding: '0.75rem 1rem',
|
|
14243
|
+
borderRadius: '0.5rem',
|
|
14244
|
+
backgroundColor: resolvedTheme === 'dark' ? 'rgba(245, 158, 11, 0.1)' : 'rgba(245, 158, 11, 0.05)',
|
|
14245
|
+
border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(245, 158, 11, 0.3)' : 'rgba(245, 158, 11, 0.2)'}`,
|
|
14246
|
+
}, children: [jsx("p", { style: {
|
|
14247
|
+
fontSize: '0.8125rem',
|
|
14248
|
+
color: resolvedTheme === 'dark' ? '#fbbf24' : '#92400e',
|
|
14249
|
+
marginBottom: '0.5rem',
|
|
14250
|
+
lineHeight: 1.4,
|
|
14251
|
+
}, children: 'Google sign-in didn\'t respond. You can try again or use another sign-in method.' }), jsx("button", { onClick: () => {
|
|
14252
|
+
setGoogleNativeTimedOut(false);
|
|
14253
|
+
handleGoogleLogin();
|
|
14254
|
+
}, style: {
|
|
14255
|
+
width: '100%',
|
|
14256
|
+
padding: '0.5rem 1rem',
|
|
14257
|
+
fontSize: '0.875rem',
|
|
14258
|
+
fontWeight: 500,
|
|
14259
|
+
color: '#fff',
|
|
14260
|
+
backgroundColor: '#4285F4',
|
|
14261
|
+
border: 'none',
|
|
14262
|
+
borderRadius: '0.375rem',
|
|
14263
|
+
cursor: 'pointer',
|
|
14264
|
+
}, children: 'Retry Google Sign-In' }), jsx("button", { onClick: () => setGoogleNativeTimedOut(false), style: {
|
|
14265
|
+
marginTop: '0.375rem',
|
|
14266
|
+
width: '100%',
|
|
14267
|
+
padding: '0.25rem',
|
|
14268
|
+
fontSize: '0.75rem',
|
|
14269
|
+
color: resolvedTheme === 'dark' ? '#64748b' : '#9ca3af',
|
|
14270
|
+
backgroundColor: 'transparent',
|
|
14271
|
+
border: 'none',
|
|
14272
|
+
cursor: 'pointer',
|
|
14273
|
+
}, children: 'Dismiss' })] })), (() => {
|
|
14082
14274
|
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
14083
14275
|
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
14084
14276
|
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
14277
|
+
const hasEmailProvider = actualProviders.includes('email');
|
|
14278
|
+
// If email provider is not enabled, only show provider buttons (no email/password form)
|
|
14279
|
+
if (!hasEmailProvider) {
|
|
14280
|
+
return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
14281
|
+
}
|
|
14085
14282
|
// Button mode: show provider selection first, then email form if email is selected
|
|
14086
14283
|
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
14087
14284
|
return (jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
@@ -14103,7 +14300,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14103
14300
|
setShowResendVerification(false);
|
|
14104
14301
|
setShowRequestNewReset(false);
|
|
14105
14302
|
setError(undefined);
|
|
14106
|
-
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
14303
|
+
}, 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 && (jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
14107
14304
|
})()] })) })) : null }));
|
|
14108
14305
|
};
|
|
14109
14306
|
|