@proveanything/smartlinks-auth-ui 0.4.7 → 0.4.9
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 +10 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/components/MagicLinkForm.d.ts +2 -1
- package/dist/components/MagicLinkForm.d.ts.map +1 -1
- package/dist/components/PhoneAuthForm.d.ts +2 -1
- package/dist/components/PhoneAuthForm.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/index.esm.js +296 -258
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +295 -257
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10779,7 +10779,8 @@ const PhoneInput = ({ value, onChange, disabled = false, }) => {
|
|
|
10779
10779
|
return (jsxRuntime.jsx(PhoneInputComponent, { international: true, defaultCountry: defaultCountry, value: value, onChange: (value) => onChange(value || ''), disabled: disabled, className: "phone-input-wrapper" }));
|
|
10780
10780
|
};
|
|
10781
10781
|
|
|
10782
|
-
const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
10782
|
+
const PhoneAuthForm = ({ onSubmit, onBack, loading, error, collectName = false, }) => {
|
|
10783
|
+
const [displayName, setDisplayName] = React.useState('');
|
|
10783
10784
|
const [phoneNumber, setPhoneNumber] = React.useState('');
|
|
10784
10785
|
const [verificationCode, setVerificationCode] = React.useState('');
|
|
10785
10786
|
const [codeSent, setCodeSent] = React.useState(false);
|
|
@@ -10796,7 +10797,8 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10796
10797
|
const handleSendCode = async (e) => {
|
|
10797
10798
|
e.preventDefault();
|
|
10798
10799
|
try {
|
|
10799
|
-
|
|
10800
|
+
const normalizedDisplayName = displayName.trim();
|
|
10801
|
+
await onSubmit(phoneNumber, undefined, collectName ? normalizedDisplayName || undefined : undefined);
|
|
10800
10802
|
// Only transition to code entry UI AFTER successful send
|
|
10801
10803
|
setCodeSent(true);
|
|
10802
10804
|
setResendCooldown(60); // 60 second cooldown
|
|
@@ -10823,7 +10825,7 @@ const PhoneAuthForm = ({ onSubmit, onBack, loading, error, }) => {
|
|
|
10823
10825
|
};
|
|
10824
10826
|
return (jsxRuntime.jsxs("form", { className: "auth-form", onSubmit: codeSent ? handleVerifyCode : handleSendCode, children: [jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: "Phone Authentication" }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: codeSent
|
|
10825
10827
|
? 'Enter the verification code sent to your phone.'
|
|
10826
|
-
: 'Enter your phone number to receive a verification code.' })] }), 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] })), !codeSent ? (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneNumber", className: "auth-label", children: "Phone Number" }), jsxRuntime.jsx(PhoneInput, { value: phoneNumber, onChange: setPhoneNumber, disabled: loading })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsxRuntime.jsx(OTPInput, { length: 6, value: verificationCode, onChange: setVerificationCode, disabled: loading })] }), jsxRuntime.jsx("div", { style: { marginTop: '0.5rem', textAlign: 'center' }, children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: handleResendCode, disabled: loading || resendCooldown > 0, style: {
|
|
10828
|
+
: 'Enter your phone number to receive a verification code.' })] }), 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] })), !codeSent ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [collectName && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneName", className: "auth-label", children: "Name" }), jsxRuntime.jsx("input", { id: "phoneName", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), className: "auth-input", placeholder: "John Smith", disabled: loading, autoComplete: "name" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "phoneNumber", className: "auth-label", children: "Phone Number" }), jsxRuntime.jsx(PhoneInput, { value: phoneNumber, onChange: setPhoneNumber, disabled: loading })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "verificationCode", className: "auth-label", children: "Verification Code" }), jsxRuntime.jsx(OTPInput, { length: 6, value: verificationCode, onChange: setVerificationCode, disabled: loading })] }), jsxRuntime.jsx("div", { style: { marginTop: '0.5rem', textAlign: 'center' }, children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: handleResendCode, disabled: loading || resendCooldown > 0, style: {
|
|
10827
10829
|
cursor: resendCooldown > 0 ? 'not-allowed' : 'pointer',
|
|
10828
10830
|
opacity: resendCooldown > 0 ? 0.5 : 1,
|
|
10829
10831
|
}, children: resendCooldown > 0
|
|
@@ -10866,13 +10868,15 @@ const PasswordResetForm = ({ onSubmit, onBack, loading, error, success, successM
|
|
|
10866
10868
|
: "Enter your email address and we'll send you instructions to reset your password." })] }), (error || passwordError) && (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 || passwordError] })), token ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [resetEmail && (jsxRuntime.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" })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "New Password" }), jsxRuntime.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 })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "confirmPassword", className: "auth-label", children: "Confirm Password" }), jsxRuntime.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 })] })] })) : (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: email, onChange: (e) => setEmail(e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : token ? ('Reset password') : ('Send reset instructions') }), jsxRuntime.jsx("div", { className: "auth-divider", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onBack, disabled: loading, children: "\u2190 Back to Sign in" }) })] }));
|
|
10867
10869
|
};
|
|
10868
10870
|
|
|
10869
|
-
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, }) => {
|
|
10871
|
+
const MagicLinkForm = ({ onSubmit, onCancel, loading = false, error, collectName = false, }) => {
|
|
10870
10872
|
const [email, setEmail] = React.useState('');
|
|
10873
|
+
const [displayName, setDisplayName] = React.useState('');
|
|
10871
10874
|
const handleSubmit = async (e) => {
|
|
10872
10875
|
e.preventDefault();
|
|
10873
|
-
|
|
10876
|
+
const normalizedDisplayName = displayName.trim();
|
|
10877
|
+
await onSubmit(email, collectName ? normalizedDisplayName || undefined : undefined);
|
|
10874
10878
|
};
|
|
10875
|
-
return (jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "auth-form", children: [jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "magic-link-email", className: "auth-label", children: "Email Address" }), jsxRuntime.jsx("input", { id: "magic-link-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "auth-input", placeholder: "you@example.com", required: true, disabled: loading })] }), error && (jsxRuntime.jsx("div", { className: "auth-error-message", children: error })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading || !email, children: loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "auth-spinner" }), "Sending..."] })) : ('Send Magic Link') }), jsxRuntime.jsx("button", { type: "button", onClick: onCancel, className: "auth-button auth-button-secondary", disabled: loading, children: "Cancel" })] }));
|
|
10879
|
+
return (jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "auth-form", children: [collectName && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "magic-link-name", className: "auth-label", children: "Name" }), jsxRuntime.jsx("input", { id: "magic-link-name", type: "text", value: displayName, onChange: (e) => setDisplayName(e.target.value), className: "auth-input", placeholder: "John Smith", disabled: loading, autoComplete: "name" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "magic-link-email", className: "auth-label", children: "Email Address" }), jsxRuntime.jsx("input", { id: "magic-link-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "auth-input", placeholder: "you@example.com", required: true, disabled: loading })] }), error && (jsxRuntime.jsx("div", { className: "auth-error-message", children: error })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading || !email, children: loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "auth-spinner" }), "Sending..."] })) : ('Send Magic Link') }), jsxRuntime.jsx("button", { type: "button", onClick: onCancel, className: "auth-button auth-button-secondary", disabled: loading, children: "Cancel" })] }));
|
|
10876
10880
|
};
|
|
10877
10881
|
|
|
10878
10882
|
/**
|
|
@@ -10909,6 +10913,254 @@ function createLoggerWrapper(logger) {
|
|
|
10909
10913
|
};
|
|
10910
10914
|
}
|
|
10911
10915
|
|
|
10916
|
+
/**
|
|
10917
|
+
* Friendly error messages for common HTTP status codes.
|
|
10918
|
+
*/
|
|
10919
|
+
const STATUS_MESSAGES = {
|
|
10920
|
+
400: 'Invalid request. Please check your input and try again.',
|
|
10921
|
+
401: 'Invalid credentials. Please check your email and password.',
|
|
10922
|
+
403: 'Access denied. You do not have permission to perform this action.',
|
|
10923
|
+
404: 'Account not found. Please check your email or create a new account.',
|
|
10924
|
+
409: 'This email is already registered.',
|
|
10925
|
+
429: 'Too many attempts. Please wait a moment and try again.',
|
|
10926
|
+
};
|
|
10927
|
+
/**
|
|
10928
|
+
* Context-specific error messages for different auth operations.
|
|
10929
|
+
*/
|
|
10930
|
+
const ERROR_CODE_MESSAGES = {
|
|
10931
|
+
// 400 - Validation errors
|
|
10932
|
+
'MISSING_FIELDS': 'Email and password are required.',
|
|
10933
|
+
'MISSING_EMAIL': 'Email is required.',
|
|
10934
|
+
'MISSING_PASSWORD': 'Password is required.',
|
|
10935
|
+
'MISSING_TOKEN': 'Token is required.',
|
|
10936
|
+
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
10937
|
+
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
10938
|
+
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
10939
|
+
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
10940
|
+
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
10941
|
+
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
10942
|
+
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
10943
|
+
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
10944
|
+
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
10945
|
+
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
10946
|
+
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
10947
|
+
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
10948
|
+
// 401 - Authentication errors
|
|
10949
|
+
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
10950
|
+
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
10951
|
+
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
10952
|
+
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
10953
|
+
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
10954
|
+
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
10955
|
+
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
10956
|
+
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
10957
|
+
// 403 - Forbidden / verification required
|
|
10958
|
+
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
10959
|
+
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
10960
|
+
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
10961
|
+
// 404
|
|
10962
|
+
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
10963
|
+
// 409 - Conflicts
|
|
10964
|
+
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
10965
|
+
'EMAIL_IN_USE': 'This email is already in use.',
|
|
10966
|
+
// 429 - Rate limiting
|
|
10967
|
+
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
10968
|
+
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
10969
|
+
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
10970
|
+
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
10971
|
+
// 500 - Server errors
|
|
10972
|
+
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
10973
|
+
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
10974
|
+
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
10975
|
+
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
10976
|
+
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
10977
|
+
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
10978
|
+
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
10979
|
+
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
10980
|
+
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
10981
|
+
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
10982
|
+
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
10983
|
+
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
10984
|
+
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
10985
|
+
// Account management 500s
|
|
10986
|
+
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
10987
|
+
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
10988
|
+
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
10989
|
+
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
10990
|
+
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
10991
|
+
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
10992
|
+
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
10993
|
+
// Legacy aliases (kept for backward compatibility)
|
|
10994
|
+
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
10995
|
+
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
10996
|
+
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
10997
|
+
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
10998
|
+
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
10999
|
+
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
11000
|
+
};
|
|
11001
|
+
function isApiErrorLike(error) {
|
|
11002
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11003
|
+
return true;
|
|
11004
|
+
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
11005
|
+
const e = error;
|
|
11006
|
+
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
11007
|
+
}
|
|
11008
|
+
return false;
|
|
11009
|
+
}
|
|
11010
|
+
/**
|
|
11011
|
+
* Extracts a user-friendly error message from an error.
|
|
11012
|
+
*
|
|
11013
|
+
* Handles:
|
|
11014
|
+
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
11015
|
+
* - Standard Error: Uses message property
|
|
11016
|
+
* - String: Passes through directly (for native bridge errors)
|
|
11017
|
+
* - Unknown: Returns generic message
|
|
11018
|
+
*/
|
|
11019
|
+
function getFriendlyErrorMessage(error) {
|
|
11020
|
+
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
11021
|
+
if (isApiErrorLike(error)) {
|
|
11022
|
+
// First, check for specific error code (most precise)
|
|
11023
|
+
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
11024
|
+
// For rate-limit errors, prefer the backend's message as it includes specific wait times
|
|
11025
|
+
if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
|
|
11026
|
+
return error.message;
|
|
11027
|
+
}
|
|
11028
|
+
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
11029
|
+
return ERROR_CODE_MESSAGES[errorCode];
|
|
11030
|
+
}
|
|
11031
|
+
// Then, check status code for general category messages
|
|
11032
|
+
if (error.statusCode >= 500) {
|
|
11033
|
+
return 'Server error. Please try again later.';
|
|
11034
|
+
}
|
|
11035
|
+
// For 429 status, prefer backend message (may include wait time)
|
|
11036
|
+
if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
|
|
11037
|
+
return error.message;
|
|
11038
|
+
}
|
|
11039
|
+
if (STATUS_MESSAGES[error.statusCode]) {
|
|
11040
|
+
return STATUS_MESSAGES[error.statusCode];
|
|
11041
|
+
}
|
|
11042
|
+
// Fall back to the server's message (already human-readable from backend)
|
|
11043
|
+
return error.message;
|
|
11044
|
+
}
|
|
11045
|
+
// Handle standard Error objects
|
|
11046
|
+
if (error instanceof Error) {
|
|
11047
|
+
// SDK bug workaround: SDK may do `throw new Error(responseBodyObject)` which produces
|
|
11048
|
+
// message "[object Object]". Check for API error properties attached to the Error instance.
|
|
11049
|
+
const errAny = error;
|
|
11050
|
+
// Check if the Error has API error properties directly attached (e.g., error.statusCode, error.errorCode)
|
|
11051
|
+
if (typeof errAny.statusCode === 'number' || errAny.errorCode || errAny.response) {
|
|
11052
|
+
// Try to extract from attached properties
|
|
11053
|
+
const apiLike = errAny.response || errAny;
|
|
11054
|
+
if (isApiErrorLike(apiLike)) {
|
|
11055
|
+
return getFriendlyErrorMessage(apiLike);
|
|
11056
|
+
}
|
|
11057
|
+
}
|
|
11058
|
+
// Check if the Error has a `cause` with API error details (modern Error cause pattern)
|
|
11059
|
+
if (errAny.cause && typeof errAny.cause === 'object') {
|
|
11060
|
+
if (isApiErrorLike(errAny.cause)) {
|
|
11061
|
+
return getFriendlyErrorMessage(errAny.cause);
|
|
11062
|
+
}
|
|
11063
|
+
}
|
|
11064
|
+
// If the message is "[object Object]", the error was constructed from a plain object
|
|
11065
|
+
// This is useless - return a generic message instead
|
|
11066
|
+
if (error.message === '[object Object]') {
|
|
11067
|
+
// Log the actual error for debugging
|
|
11068
|
+
console.warn('[AuthKit] Error with [object Object] message. Raw error:', JSON.stringify(errAny, Object.getOwnPropertyNames(errAny)));
|
|
11069
|
+
return 'An unexpected error occurred. Please try again.';
|
|
11070
|
+
}
|
|
11071
|
+
// Check if the message itself contains a known API error pattern
|
|
11072
|
+
if (/already (registered|exists)/i.test(error.message)) {
|
|
11073
|
+
return 'This email is already registered.';
|
|
11074
|
+
}
|
|
11075
|
+
return error.message;
|
|
11076
|
+
}
|
|
11077
|
+
// Handle plain strings (e.g., from native bridge callbacks)
|
|
11078
|
+
if (typeof error === 'string') {
|
|
11079
|
+
return error;
|
|
11080
|
+
}
|
|
11081
|
+
// Unknown error type
|
|
11082
|
+
return 'An unexpected error occurred. Please try again.';
|
|
11083
|
+
}
|
|
11084
|
+
/**
|
|
11085
|
+
* Checks if an error represents a conflict (409) - typically duplicate registration.
|
|
11086
|
+
*/
|
|
11087
|
+
function isConflictError(error) {
|
|
11088
|
+
if (isApiErrorLike(error))
|
|
11089
|
+
return error.statusCode === 409;
|
|
11090
|
+
// Also check error message for keyword match (resilient fallback)
|
|
11091
|
+
if (error instanceof Error && /already (registered|exists)/i.test(error.message))
|
|
11092
|
+
return true;
|
|
11093
|
+
return false;
|
|
11094
|
+
}
|
|
11095
|
+
/**
|
|
11096
|
+
* Checks if an error represents invalid credentials (401).
|
|
11097
|
+
*/
|
|
11098
|
+
function isAuthError(error) {
|
|
11099
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11100
|
+
return error.isAuthError();
|
|
11101
|
+
if (isApiErrorLike(error))
|
|
11102
|
+
return error.statusCode === 401 || error.statusCode === 403;
|
|
11103
|
+
return false;
|
|
11104
|
+
}
|
|
11105
|
+
/**
|
|
11106
|
+
* Checks if an error represents rate limiting (429).
|
|
11107
|
+
*/
|
|
11108
|
+
function isRateLimitError(error) {
|
|
11109
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11110
|
+
return error.isRateLimited();
|
|
11111
|
+
if (isApiErrorLike(error))
|
|
11112
|
+
return error.statusCode === 429;
|
|
11113
|
+
return false;
|
|
11114
|
+
}
|
|
11115
|
+
/**
|
|
11116
|
+
* Checks if an error represents a server error (5xx).
|
|
11117
|
+
*/
|
|
11118
|
+
function isServerError(error) {
|
|
11119
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
11120
|
+
return error.isServerError();
|
|
11121
|
+
if (isApiErrorLike(error))
|
|
11122
|
+
return error.statusCode >= 500;
|
|
11123
|
+
return false;
|
|
11124
|
+
}
|
|
11125
|
+
/**
|
|
11126
|
+
* Gets the HTTP status code from an error, if available.
|
|
11127
|
+
*/
|
|
11128
|
+
function getErrorStatusCode(error) {
|
|
11129
|
+
if (isApiErrorLike(error))
|
|
11130
|
+
return error.statusCode;
|
|
11131
|
+
return undefined;
|
|
11132
|
+
}
|
|
11133
|
+
/**
|
|
11134
|
+
* Gets the server-specific error code from an error, if available.
|
|
11135
|
+
*/
|
|
11136
|
+
function getErrorCode(error) {
|
|
11137
|
+
if (isApiErrorLike(error)) {
|
|
11138
|
+
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
11139
|
+
}
|
|
11140
|
+
return undefined;
|
|
11141
|
+
}
|
|
11142
|
+
/**
|
|
11143
|
+
* Error codes that indicate the user needs to verify their email.
|
|
11144
|
+
*/
|
|
11145
|
+
const EMAIL_VERIFICATION_ERROR_CODES = new Set([
|
|
11146
|
+
'EMAIL_NOT_VERIFIED',
|
|
11147
|
+
'ACCOUNT_LOCKED',
|
|
11148
|
+
'EMAIL_VERIFICATION_EXPIRED',
|
|
11149
|
+
]);
|
|
11150
|
+
/**
|
|
11151
|
+
* Checks if an error requires email verification action from the user.
|
|
11152
|
+
*/
|
|
11153
|
+
function requiresEmailVerification(error) {
|
|
11154
|
+
const code = getErrorCode(error);
|
|
11155
|
+
if (code && EMAIL_VERIFICATION_ERROR_CODES.has(code))
|
|
11156
|
+
return true;
|
|
11157
|
+
// Also check the flag from the response body
|
|
11158
|
+
if (error && typeof error === 'object' && 'requiresEmailVerification' in error) {
|
|
11159
|
+
return error.requiresEmailVerification === true;
|
|
11160
|
+
}
|
|
11161
|
+
return false;
|
|
11162
|
+
}
|
|
11163
|
+
|
|
10912
11164
|
/**
|
|
10913
11165
|
* AuthAPI - Thin wrapper around Smartlinks SDK authKit namespace
|
|
10914
11166
|
* All authentication operations now use the global Smartlinks SDK
|
|
@@ -11054,6 +11306,36 @@ class AuthAPI {
|
|
|
11054
11306
|
};
|
|
11055
11307
|
}
|
|
11056
11308
|
}
|
|
11309
|
+
/**
|
|
11310
|
+
* Ensure an account exists for the given email (or phone).
|
|
11311
|
+
* Calls register and silently handles 409 (account already exists).
|
|
11312
|
+
* This enables passwordless flows (magic link, phone) to work for new users.
|
|
11313
|
+
*/
|
|
11314
|
+
async ensureAccount(data) {
|
|
11315
|
+
try {
|
|
11316
|
+
// Generate a random password since passwordless users won't use it
|
|
11317
|
+
const randomPassword = crypto.randomUUID?.() || Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
11318
|
+
// Backend requires displayName to be a valid string
|
|
11319
|
+
// Fall back to email local part if no name provided
|
|
11320
|
+
const normalizedDisplayName = typeof data.displayName === 'string' ? data.displayName.trim() : '';
|
|
11321
|
+
const fallbackName = normalizedDisplayName || (data.email ? data.email.split('@')[0] : 'User');
|
|
11322
|
+
await this.register({
|
|
11323
|
+
email: data.email,
|
|
11324
|
+
password: randomPassword,
|
|
11325
|
+
displayName: fallbackName,
|
|
11326
|
+
});
|
|
11327
|
+
this.log.log('ensureAccount: new account created for', data.email || data.phoneNumber);
|
|
11328
|
+
}
|
|
11329
|
+
catch (err) {
|
|
11330
|
+
// 409 = account already exists, which is fine
|
|
11331
|
+
if (isConflictError(err)) {
|
|
11332
|
+
this.log.log('ensureAccount: account already exists for', data.email || data.phoneNumber);
|
|
11333
|
+
return;
|
|
11334
|
+
}
|
|
11335
|
+
// Re-throw any other error
|
|
11336
|
+
throw err;
|
|
11337
|
+
}
|
|
11338
|
+
}
|
|
11057
11339
|
async sendMagicLink(email, redirectUrl) {
|
|
11058
11340
|
return smartlinks__namespace.authKit.sendMagicLink(this.clientId, {
|
|
11059
11341
|
email,
|
|
@@ -12331,254 +12613,6 @@ const useAuth = () => {
|
|
|
12331
12613
|
return context;
|
|
12332
12614
|
};
|
|
12333
12615
|
|
|
12334
|
-
/**
|
|
12335
|
-
* Friendly error messages for common HTTP status codes.
|
|
12336
|
-
*/
|
|
12337
|
-
const STATUS_MESSAGES = {
|
|
12338
|
-
400: 'Invalid request. Please check your input and try again.',
|
|
12339
|
-
401: 'Invalid credentials. Please check your email and password.',
|
|
12340
|
-
403: 'Access denied. You do not have permission to perform this action.',
|
|
12341
|
-
404: 'Account not found. Please check your email or create a new account.',
|
|
12342
|
-
409: 'This email is already registered.',
|
|
12343
|
-
429: 'Too many attempts. Please wait a moment and try again.',
|
|
12344
|
-
};
|
|
12345
|
-
/**
|
|
12346
|
-
* Context-specific error messages for different auth operations.
|
|
12347
|
-
*/
|
|
12348
|
-
const ERROR_CODE_MESSAGES = {
|
|
12349
|
-
// 400 - Validation errors
|
|
12350
|
-
'MISSING_FIELDS': 'Email and password are required.',
|
|
12351
|
-
'MISSING_EMAIL': 'Email is required.',
|
|
12352
|
-
'MISSING_PASSWORD': 'Password is required.',
|
|
12353
|
-
'MISSING_TOKEN': 'Token is required.',
|
|
12354
|
-
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
12355
|
-
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
12356
|
-
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
12357
|
-
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
12358
|
-
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
12359
|
-
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
12360
|
-
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
12361
|
-
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
12362
|
-
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
12363
|
-
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
12364
|
-
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
12365
|
-
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
12366
|
-
// 401 - Authentication errors
|
|
12367
|
-
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
12368
|
-
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
12369
|
-
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
12370
|
-
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
12371
|
-
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
12372
|
-
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
12373
|
-
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
12374
|
-
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
12375
|
-
// 403 - Forbidden / verification required
|
|
12376
|
-
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
12377
|
-
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
12378
|
-
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
12379
|
-
// 404
|
|
12380
|
-
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
12381
|
-
// 409 - Conflicts
|
|
12382
|
-
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
12383
|
-
'EMAIL_IN_USE': 'This email is already in use.',
|
|
12384
|
-
// 429 - Rate limiting
|
|
12385
|
-
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
12386
|
-
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
12387
|
-
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
12388
|
-
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
12389
|
-
// 500 - Server errors
|
|
12390
|
-
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
12391
|
-
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
12392
|
-
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
12393
|
-
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
12394
|
-
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
12395
|
-
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
12396
|
-
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
12397
|
-
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
12398
|
-
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
12399
|
-
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
12400
|
-
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
12401
|
-
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
12402
|
-
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
12403
|
-
// Account management 500s
|
|
12404
|
-
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
12405
|
-
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
12406
|
-
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
12407
|
-
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
12408
|
-
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
12409
|
-
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
12410
|
-
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
12411
|
-
// Legacy aliases (kept for backward compatibility)
|
|
12412
|
-
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
12413
|
-
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
12414
|
-
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
12415
|
-
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
12416
|
-
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
12417
|
-
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
12418
|
-
};
|
|
12419
|
-
function isApiErrorLike(error) {
|
|
12420
|
-
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12421
|
-
return true;
|
|
12422
|
-
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
12423
|
-
const e = error;
|
|
12424
|
-
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
12425
|
-
}
|
|
12426
|
-
return false;
|
|
12427
|
-
}
|
|
12428
|
-
/**
|
|
12429
|
-
* Extracts a user-friendly error message from an error.
|
|
12430
|
-
*
|
|
12431
|
-
* Handles:
|
|
12432
|
-
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
12433
|
-
* - Standard Error: Uses message property
|
|
12434
|
-
* - String: Passes through directly (for native bridge errors)
|
|
12435
|
-
* - Unknown: Returns generic message
|
|
12436
|
-
*/
|
|
12437
|
-
function getFriendlyErrorMessage(error) {
|
|
12438
|
-
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
12439
|
-
if (isApiErrorLike(error)) {
|
|
12440
|
-
// First, check for specific error code (most precise)
|
|
12441
|
-
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12442
|
-
// For rate-limit errors, prefer the backend's message as it includes specific wait times
|
|
12443
|
-
if (errorCode === 'RATE_LIMIT_EXCEEDED' && error.message && error.message !== '[object Object]') {
|
|
12444
|
-
return error.message;
|
|
12445
|
-
}
|
|
12446
|
-
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
12447
|
-
return ERROR_CODE_MESSAGES[errorCode];
|
|
12448
|
-
}
|
|
12449
|
-
// Then, check status code for general category messages
|
|
12450
|
-
if (error.statusCode >= 500) {
|
|
12451
|
-
return 'Server error. Please try again later.';
|
|
12452
|
-
}
|
|
12453
|
-
// For 429 status, prefer backend message (may include wait time)
|
|
12454
|
-
if (error.statusCode === 429 && error.message && error.message !== '[object Object]') {
|
|
12455
|
-
return error.message;
|
|
12456
|
-
}
|
|
12457
|
-
if (STATUS_MESSAGES[error.statusCode]) {
|
|
12458
|
-
return STATUS_MESSAGES[error.statusCode];
|
|
12459
|
-
}
|
|
12460
|
-
// Fall back to the server's message (already human-readable from backend)
|
|
12461
|
-
return error.message;
|
|
12462
|
-
}
|
|
12463
|
-
// Handle standard Error objects
|
|
12464
|
-
if (error instanceof Error) {
|
|
12465
|
-
// SDK bug workaround: SDK may do `throw new Error(responseBodyObject)` which produces
|
|
12466
|
-
// message "[object Object]". Check for API error properties attached to the Error instance.
|
|
12467
|
-
const errAny = error;
|
|
12468
|
-
// Check if the Error has API error properties directly attached (e.g., error.statusCode, error.errorCode)
|
|
12469
|
-
if (typeof errAny.statusCode === 'number' || errAny.errorCode || errAny.response) {
|
|
12470
|
-
// Try to extract from attached properties
|
|
12471
|
-
const apiLike = errAny.response || errAny;
|
|
12472
|
-
if (isApiErrorLike(apiLike)) {
|
|
12473
|
-
return getFriendlyErrorMessage(apiLike);
|
|
12474
|
-
}
|
|
12475
|
-
}
|
|
12476
|
-
// Check if the Error has a `cause` with API error details (modern Error cause pattern)
|
|
12477
|
-
if (errAny.cause && typeof errAny.cause === 'object') {
|
|
12478
|
-
if (isApiErrorLike(errAny.cause)) {
|
|
12479
|
-
return getFriendlyErrorMessage(errAny.cause);
|
|
12480
|
-
}
|
|
12481
|
-
}
|
|
12482
|
-
// If the message is "[object Object]", the error was constructed from a plain object
|
|
12483
|
-
// This is useless - return a generic message instead
|
|
12484
|
-
if (error.message === '[object Object]') {
|
|
12485
|
-
// Log the actual error for debugging
|
|
12486
|
-
console.warn('[AuthKit] Error with [object Object] message. Raw error:', JSON.stringify(errAny, Object.getOwnPropertyNames(errAny)));
|
|
12487
|
-
return 'An unexpected error occurred. Please try again.';
|
|
12488
|
-
}
|
|
12489
|
-
// Check if the message itself contains a known API error pattern
|
|
12490
|
-
if (/already (registered|exists)/i.test(error.message)) {
|
|
12491
|
-
return 'This email is already registered.';
|
|
12492
|
-
}
|
|
12493
|
-
return error.message;
|
|
12494
|
-
}
|
|
12495
|
-
// Handle plain strings (e.g., from native bridge callbacks)
|
|
12496
|
-
if (typeof error === 'string') {
|
|
12497
|
-
return error;
|
|
12498
|
-
}
|
|
12499
|
-
// Unknown error type
|
|
12500
|
-
return 'An unexpected error occurred. Please try again.';
|
|
12501
|
-
}
|
|
12502
|
-
/**
|
|
12503
|
-
* Checks if an error represents a conflict (409) - typically duplicate registration.
|
|
12504
|
-
*/
|
|
12505
|
-
function isConflictError(error) {
|
|
12506
|
-
if (isApiErrorLike(error))
|
|
12507
|
-
return error.statusCode === 409;
|
|
12508
|
-
// Also check error message for keyword match (resilient fallback)
|
|
12509
|
-
if (error instanceof Error && /already (registered|exists)/i.test(error.message))
|
|
12510
|
-
return true;
|
|
12511
|
-
return false;
|
|
12512
|
-
}
|
|
12513
|
-
/**
|
|
12514
|
-
* Checks if an error represents invalid credentials (401).
|
|
12515
|
-
*/
|
|
12516
|
-
function isAuthError(error) {
|
|
12517
|
-
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12518
|
-
return error.isAuthError();
|
|
12519
|
-
if (isApiErrorLike(error))
|
|
12520
|
-
return error.statusCode === 401 || error.statusCode === 403;
|
|
12521
|
-
return false;
|
|
12522
|
-
}
|
|
12523
|
-
/**
|
|
12524
|
-
* Checks if an error represents rate limiting (429).
|
|
12525
|
-
*/
|
|
12526
|
-
function isRateLimitError(error) {
|
|
12527
|
-
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12528
|
-
return error.isRateLimited();
|
|
12529
|
-
if (isApiErrorLike(error))
|
|
12530
|
-
return error.statusCode === 429;
|
|
12531
|
-
return false;
|
|
12532
|
-
}
|
|
12533
|
-
/**
|
|
12534
|
-
* Checks if an error represents a server error (5xx).
|
|
12535
|
-
*/
|
|
12536
|
-
function isServerError(error) {
|
|
12537
|
-
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12538
|
-
return error.isServerError();
|
|
12539
|
-
if (isApiErrorLike(error))
|
|
12540
|
-
return error.statusCode >= 500;
|
|
12541
|
-
return false;
|
|
12542
|
-
}
|
|
12543
|
-
/**
|
|
12544
|
-
* Gets the HTTP status code from an error, if available.
|
|
12545
|
-
*/
|
|
12546
|
-
function getErrorStatusCode(error) {
|
|
12547
|
-
if (isApiErrorLike(error))
|
|
12548
|
-
return error.statusCode;
|
|
12549
|
-
return undefined;
|
|
12550
|
-
}
|
|
12551
|
-
/**
|
|
12552
|
-
* Gets the server-specific error code from an error, if available.
|
|
12553
|
-
*/
|
|
12554
|
-
function getErrorCode(error) {
|
|
12555
|
-
if (isApiErrorLike(error)) {
|
|
12556
|
-
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12557
|
-
}
|
|
12558
|
-
return undefined;
|
|
12559
|
-
}
|
|
12560
|
-
/**
|
|
12561
|
-
* Error codes that indicate the user needs to verify their email.
|
|
12562
|
-
*/
|
|
12563
|
-
const EMAIL_VERIFICATION_ERROR_CODES = new Set([
|
|
12564
|
-
'EMAIL_NOT_VERIFIED',
|
|
12565
|
-
'ACCOUNT_LOCKED',
|
|
12566
|
-
'EMAIL_VERIFICATION_EXPIRED',
|
|
12567
|
-
]);
|
|
12568
|
-
/**
|
|
12569
|
-
* Checks if an error requires email verification action from the user.
|
|
12570
|
-
*/
|
|
12571
|
-
function requiresEmailVerification(error) {
|
|
12572
|
-
const code = getErrorCode(error);
|
|
12573
|
-
if (code && EMAIL_VERIFICATION_ERROR_CODES.has(code))
|
|
12574
|
-
return true;
|
|
12575
|
-
// Also check the flag from the response body
|
|
12576
|
-
if (error && typeof error === 'object' && 'requiresEmailVerification' in error) {
|
|
12577
|
-
return error.requiresEmailVerification === true;
|
|
12578
|
-
}
|
|
12579
|
-
return false;
|
|
12580
|
-
}
|
|
12581
|
-
|
|
12582
12616
|
// VERSION: Update this when making changes to help identify which version is running
|
|
12583
12617
|
const AUTH_UI_VERSION = '46';
|
|
12584
12618
|
const LOG_PREFIX = `[SmartlinksAuthUI:v${AUTH_UI_VERSION}]`;
|
|
@@ -13872,11 +13906,13 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13872
13906
|
setLoading(false);
|
|
13873
13907
|
}
|
|
13874
13908
|
};
|
|
13875
|
-
const handlePhoneAuth = async (phoneNumber, verificationCode) => {
|
|
13909
|
+
const handlePhoneAuth = async (phoneNumber, verificationCode, displayName) => {
|
|
13876
13910
|
setLoading(true);
|
|
13877
13911
|
setError(undefined);
|
|
13878
13912
|
try {
|
|
13879
13913
|
if (!verificationCode) {
|
|
13914
|
+
// Phone verify endpoint handles account creation on the backend side,
|
|
13915
|
+
// so no need for ensureAccount here (register requires email which phone users may not have)
|
|
13880
13916
|
// Send verification code via Twilio Verify Service
|
|
13881
13917
|
await api.sendPhoneCode(phoneNumber);
|
|
13882
13918
|
// Twilio Verify Service tracks the verification by phone number
|
|
@@ -13995,10 +14031,12 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13995
14031
|
setLoading(false);
|
|
13996
14032
|
}
|
|
13997
14033
|
};
|
|
13998
|
-
const handleMagicLink = async (email) => {
|
|
14034
|
+
const handleMagicLink = async (email, displayName) => {
|
|
13999
14035
|
setLoading(true);
|
|
14000
14036
|
setError(undefined);
|
|
14001
14037
|
try {
|
|
14038
|
+
// Ensure account exists before sending magic link (creates if new, no-op if exists)
|
|
14039
|
+
await api.ensureAccount({ email, displayName });
|
|
14002
14040
|
await api.sendMagicLink(email, getRedirectUrl());
|
|
14003
14041
|
setAuthSuccess(true);
|
|
14004
14042
|
setSuccessMessage('Magic link sent! Check your email to log in.');
|
|
@@ -14045,7 +14083,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
14045
14083
|
? 'hsl(var(--muted-foreground, 215 15% 45%))'
|
|
14046
14084
|
: (resolvedTheme === 'dark' ? '#94a3b8' : '#6B7280'),
|
|
14047
14085
|
fontSize: '0.875rem'
|
|
14048
|
-
}, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
14086
|
+
}, children: successMessage })] })) : mode === 'magic-link' ? (jsxRuntime.jsx(MagicLinkForm, { onSubmit: handleMagicLink, onCancel: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'phone' ? (jsxRuntime.jsx(PhoneAuthForm, { onSubmit: handlePhoneAuth, onBack: () => setMode('login'), loading: loading, error: error, collectName: config?.collectNameOnPasswordlessSignup })) : mode === 'reset-password' ? (jsxRuntime.jsx(PasswordResetForm, { onSubmit: handlePasswordReset, onBack: () => {
|
|
14049
14087
|
setMode('login');
|
|
14050
14088
|
setResetSuccess(false);
|
|
14051
14089
|
setResetToken(undefined); // Clear token when going back
|