@proveanything/smartlinks-auth-ui 0.4.0 → 0.4.3
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/components/SchemaFieldRenderer.d.ts +6 -3
- package/dist/components/SchemaFieldRenderer.d.ts.map +1 -1
- package/dist/components/SmartlinksAuthUI.d.ts.map +1 -1
- package/dist/context/AuthContext.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +278 -68
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +279 -71
- package/dist/index.js.map +1 -1
- package/dist/utils/errorHandling.d.ts +5 -4
- package/dist/utils/errorHandling.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -79,6 +79,52 @@ const AuthContainer = ({ children, theme = 'light', className = '', config, mini
|
|
|
79
79
|
return (jsxRuntime.jsx("div", { className: containerClass, children: jsxRuntime.jsxs("div", { className: cardClass, style: !minimal && config?.branding?.buttonStyle === 'square' ? { borderRadius: '4px' } : undefined, children: [(logoUrl || title || subtitle) && (jsxRuntime.jsxs("div", { className: "auth-header", children: [logoUrl && (jsxRuntime.jsx("div", { className: "auth-logo", children: jsxRuntime.jsx("img", { src: logoUrl, alt: "Logo", style: { maxWidth: '200px', height: 'auto', objectFit: 'contain' } }) })), title && jsxRuntime.jsx("h1", { className: "auth-title", children: title }), subtitle && jsxRuntime.jsx("p", { className: "auth-subtitle", children: subtitle })] })), jsxRuntime.jsx("div", { className: "auth-content", children: children }), (config?.branding?.termsUrl || config?.branding?.privacyUrl) && (jsxRuntime.jsxs("div", { className: "auth-footer", children: [config.branding.termsUrl && jsxRuntime.jsx("a", { href: config.branding.termsUrl, target: "_blank", rel: "noopener noreferrer", children: "Terms" }), config.branding.termsUrl && config.branding.privacyUrl && jsxRuntime.jsx("span", { children: "\u2022" }), config.branding.privacyUrl && jsxRuntime.jsx("a", { href: config.branding.privacyUrl, target: "_blank", rel: "noopener noreferrer", children: "Privacy" })] }))] }) }));
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Evaluate whether a field's conditions are satisfied given current form values.
|
|
84
|
+
* Local implementation matching SDK's evaluateConditions — avoids Vite ESM export issues.
|
|
85
|
+
*/
|
|
86
|
+
const evaluateConditions = (conditions, showWhen, fieldValues) => {
|
|
87
|
+
if (!conditions || conditions.length === 0)
|
|
88
|
+
return true;
|
|
89
|
+
const results = conditions.map(condition => {
|
|
90
|
+
const value = fieldValues[condition.targetFieldId];
|
|
91
|
+
switch (condition.operator) {
|
|
92
|
+
case 'is_empty':
|
|
93
|
+
return value == null || value === '' || (Array.isArray(value) && value.length === 0);
|
|
94
|
+
case 'is_not_empty':
|
|
95
|
+
return value != null && value !== '' && !(Array.isArray(value) && value.length === 0);
|
|
96
|
+
case 'is_true':
|
|
97
|
+
return value === true;
|
|
98
|
+
case 'is_false':
|
|
99
|
+
return value === false;
|
|
100
|
+
case 'equals':
|
|
101
|
+
return value === condition.value;
|
|
102
|
+
case 'not_equals':
|
|
103
|
+
return value !== condition.value;
|
|
104
|
+
case 'contains':
|
|
105
|
+
return Array.isArray(value)
|
|
106
|
+
? value.includes(condition.value)
|
|
107
|
+
: typeof value === 'string' && value.includes(String(condition.value));
|
|
108
|
+
case 'not_contains':
|
|
109
|
+
return Array.isArray(value)
|
|
110
|
+
? !value.includes(condition.value)
|
|
111
|
+
: typeof value === 'string' && !value.includes(String(condition.value));
|
|
112
|
+
case 'greater_than':
|
|
113
|
+
return typeof value === 'number' && typeof condition.value === 'number'
|
|
114
|
+
? value > condition.value
|
|
115
|
+
: String(value) > String(condition.value);
|
|
116
|
+
case 'less_than':
|
|
117
|
+
return typeof value === 'number' && typeof condition.value === 'number'
|
|
118
|
+
? value < condition.value
|
|
119
|
+
: String(value) < String(condition.value);
|
|
120
|
+
default:
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return (showWhen ?? 'all') === 'any'
|
|
125
|
+
? results.some(Boolean)
|
|
126
|
+
: results.every(Boolean);
|
|
127
|
+
};
|
|
82
128
|
/**
|
|
83
129
|
* Renders a form field based on a ContactSchemaResponse property + uiSchema entry.
|
|
84
130
|
*/
|
|
@@ -149,19 +195,22 @@ const SchemaFieldRenderer = ({ field, value, onChange, disabled = false, error,
|
|
|
149
195
|
const resolveFields = (schema, formValues) => {
|
|
150
196
|
if (!schema)
|
|
151
197
|
return [];
|
|
152
|
-
const
|
|
153
|
-
|
|
198
|
+
const fieldOrder = schema.fieldOrder ?? [];
|
|
199
|
+
const properties = schema.schema?.properties ?? {};
|
|
200
|
+
const uiSchema = schema.uiSchema ?? {};
|
|
201
|
+
const requiredSet = new Set(schema.schema?.required || []);
|
|
202
|
+
return fieldOrder
|
|
154
203
|
.filter(fieldId => {
|
|
155
|
-
const prop =
|
|
204
|
+
const prop = properties[fieldId];
|
|
156
205
|
if (!prop)
|
|
157
206
|
return false;
|
|
158
207
|
// Evaluate conditional visibility
|
|
159
|
-
return
|
|
208
|
+
return evaluateConditions(prop.conditions, prop.showWhen, formValues || {});
|
|
160
209
|
})
|
|
161
210
|
.map(fieldId => ({
|
|
162
211
|
key: fieldId,
|
|
163
|
-
property:
|
|
164
|
-
ui:
|
|
212
|
+
property: properties[fieldId],
|
|
213
|
+
ui: uiSchema[fieldId] || {},
|
|
165
214
|
required: requiredSet.has(fieldId),
|
|
166
215
|
}));
|
|
167
216
|
};
|
|
@@ -178,23 +227,26 @@ const getRegistrationFields = (schema, registrationConfig, formValues) => {
|
|
|
178
227
|
if (!schema || !registrationConfig.length)
|
|
179
228
|
return [];
|
|
180
229
|
const configMap = new Map(registrationConfig.map(c => [c.key, c]));
|
|
181
|
-
const
|
|
182
|
-
|
|
230
|
+
const fieldOrder = schema.fieldOrder ?? [];
|
|
231
|
+
const properties = schema.schema?.properties ?? {};
|
|
232
|
+
const uiSchema = schema.uiSchema ?? {};
|
|
233
|
+
const requiredSet = new Set(schema.schema?.required || []);
|
|
234
|
+
return fieldOrder
|
|
183
235
|
.filter(fieldId => {
|
|
184
236
|
const config = configMap.get(fieldId);
|
|
185
237
|
if (!config?.showDuringRegistration)
|
|
186
238
|
return false;
|
|
187
|
-
const prop =
|
|
239
|
+
const prop = properties[fieldId];
|
|
188
240
|
if (!prop)
|
|
189
241
|
return false;
|
|
190
|
-
return
|
|
242
|
+
return evaluateConditions(prop.conditions, prop.showWhen, formValues || {});
|
|
191
243
|
})
|
|
192
244
|
.map(fieldId => {
|
|
193
245
|
const config = configMap.get(fieldId);
|
|
194
246
|
return {
|
|
195
247
|
key: fieldId,
|
|
196
|
-
property:
|
|
197
|
-
ui:
|
|
248
|
+
property: properties[fieldId],
|
|
249
|
+
ui: uiSchema[fieldId] || {},
|
|
198
250
|
required: config.required ?? requiredSet.has(fieldId),
|
|
199
251
|
};
|
|
200
252
|
});
|
|
@@ -282,7 +334,7 @@ const EmailAuthForm = ({ mode, onSubmit, onModeSwitch, onForgotPassword, loading
|
|
|
282
334
|
if (newMode !== mode) {
|
|
283
335
|
onModeSwitch();
|
|
284
336
|
}
|
|
285
|
-
}, disabled: loading })), jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: title }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John
|
|
337
|
+
}, disabled: loading })), jsxRuntime.jsxs("div", { className: "auth-form-header", children: [jsxRuntime.jsx("h2", { className: "auth-form-title", children: title }), jsxRuntime.jsx("p", { className: "auth-form-subtitle", children: subtitle })] }), error && (jsxRuntime.jsxs("div", { className: "auth-error", role: "alert", children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z" }) }), error] })), mode === 'register' && (jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "displayName", className: "auth-label", children: "Full Name" }), jsxRuntime.jsx("input", { type: "text", id: "displayName", className: "auth-input", value: formData.displayName || '', onChange: (e) => handleChange('displayName', e.target.value), required: mode === 'register', disabled: loading, placeholder: "John Smith" })] })), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "email", className: "auth-label", children: "Email address" }), jsxRuntime.jsx("input", { type: "email", id: "email", className: "auth-input", value: formData.email || '', onChange: (e) => handleChange('email', e.target.value), required: true, disabled: loading, placeholder: "you@example.com", autoComplete: "email" })] }), jsxRuntime.jsxs("div", { className: "auth-form-group", children: [jsxRuntime.jsx("label", { htmlFor: "password", className: "auth-label", children: "Password" }), jsxRuntime.jsx("input", { type: "password", id: "password", className: "auth-input", value: formData.password || '', onChange: (e) => handleChange('password', e.target.value), required: true, disabled: loading, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === 'login' ? 'current-password' : 'new-password', minLength: 6 })] }), mode === 'register' && hasSchemaFields && schemaFields.inline.map(renderSchemaField), mode === 'register' && hasLegacyFields && additionalFields.map(renderLegacyField), mode === 'register' && hasSchemaFields && schemaFields.postCredentials.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "auth-divider", style: { margin: '16px 0' }, children: jsxRuntime.jsx("span", { children: "Additional Information" }) }), schemaFields.postCredentials.map(renderSchemaField)] })), mode === 'login' && (jsxRuntime.jsx("div", { className: "auth-form-footer", children: jsxRuntime.jsx("button", { type: "button", className: "auth-link", onClick: onForgotPassword, disabled: loading, children: "Forgot password?" }) })), jsxRuntime.jsx("button", { type: "submit", className: "auth-button auth-button-primary", disabled: loading, children: loading ? (jsxRuntime.jsx("span", { className: "auth-spinner" })) : mode === 'login' ? ('Sign in') : ('Create account') }), signupProminence !== 'balanced' && (jsxRuntime.jsxs("div", { className: "auth-divider", children: [jsxRuntime.jsx("span", { children: mode === 'login' ? "Don't have an account?" : 'Already have an account?' }), jsxRuntime.jsx("button", { type: "button", className: "auth-link auth-link-bold", onClick: onModeSwitch, disabled: loading, children: mode === 'login' ? 'Sign up' : 'Sign in' })] }))] }));
|
|
286
338
|
};
|
|
287
339
|
|
|
288
340
|
const ProviderButtons = ({ enabledProviders, providerOrder, onEmailLogin, onGoogleLogin, onPhoneLogin, onMagicLinkLogin, loading, }) => {
|
|
@@ -11700,15 +11752,27 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11700
11752
|
const headers = http.getApiHeaders();
|
|
11701
11753
|
const hasBearer = !!headers['Authorization'];
|
|
11702
11754
|
const hasSdkProxy = http.isProxyEnabled();
|
|
11755
|
+
console.log('[AuthContext] 🔍 Proxy mode init:', {
|
|
11756
|
+
hasBearer,
|
|
11757
|
+
hasSdkProxy,
|
|
11758
|
+
authHeader: headers['Authorization'] ? `${headers['Authorization'].substring(0, 20)}...` : '(none)',
|
|
11759
|
+
willCallGetAccount: hasBearer || hasSdkProxy,
|
|
11760
|
+
});
|
|
11703
11761
|
if (!hasBearer && !hasSdkProxy) {
|
|
11704
|
-
console.
|
|
11762
|
+
console.log('[AuthContext] ⏭️ Skipping getAccount - no bearer token and SDK proxy not enabled');
|
|
11705
11763
|
// Fall through to "no valid session" state
|
|
11706
11764
|
}
|
|
11707
11765
|
else {
|
|
11766
|
+
console.log('[AuthContext] 📡 Calling auth.getAccount() via', hasSdkProxy ? 'proxy' : 'bearer');
|
|
11708
11767
|
try {
|
|
11709
11768
|
const accountResponse = await smartlinks__namespace.auth.getAccount();
|
|
11710
11769
|
const accountAny = accountResponse;
|
|
11711
11770
|
const hasValidSession = accountAny?.uid && accountAny.uid.length > 0;
|
|
11771
|
+
console.log('[AuthContext] 📋 getAccount response:', {
|
|
11772
|
+
hasValidSession,
|
|
11773
|
+
uid: accountAny?.uid || '(none)',
|
|
11774
|
+
email: accountAny?.email || '(none)',
|
|
11775
|
+
});
|
|
11712
11776
|
if (hasValidSession && isMounted) {
|
|
11713
11777
|
const userFromAccount = {
|
|
11714
11778
|
uid: accountAny.uid,
|
|
@@ -11725,10 +11789,11 @@ collectionId, enableContactSync, enableInteractionTracking, interactionAppId, in
|
|
|
11725
11789
|
syncContactRef.current?.(userFromAccount, accountResponse);
|
|
11726
11790
|
}
|
|
11727
11791
|
else if (isMounted) {
|
|
11728
|
-
|
|
11792
|
+
console.log('[AuthContext] ℹ️ No valid session found via proxy');
|
|
11729
11793
|
}
|
|
11730
11794
|
}
|
|
11731
11795
|
catch (error) {
|
|
11796
|
+
console.warn('[AuthContext] ❌ getAccount() failed:', error);
|
|
11732
11797
|
// auth.getAccount() failed, awaiting login
|
|
11733
11798
|
}
|
|
11734
11799
|
} // end else (has credentials)
|
|
@@ -12237,7 +12302,6 @@ const useAuth = () => {
|
|
|
12237
12302
|
|
|
12238
12303
|
/**
|
|
12239
12304
|
* Friendly error messages for common HTTP status codes.
|
|
12240
|
-
* Maps status codes to context-specific user-friendly messages.
|
|
12241
12305
|
*/
|
|
12242
12306
|
const STATUS_MESSAGES = {
|
|
12243
12307
|
400: 'Invalid request. Please check your input and try again.',
|
|
@@ -12249,41 +12313,103 @@ const STATUS_MESSAGES = {
|
|
|
12249
12313
|
};
|
|
12250
12314
|
/**
|
|
12251
12315
|
* Context-specific error messages for different auth operations.
|
|
12252
|
-
* Use `errorCode` from the SDK when available, otherwise fall back to status code.
|
|
12253
12316
|
*/
|
|
12254
12317
|
const ERROR_CODE_MESSAGES = {
|
|
12255
|
-
//
|
|
12318
|
+
// 400 - Validation errors
|
|
12319
|
+
'MISSING_FIELDS': 'Email and password are required.',
|
|
12320
|
+
'MISSING_EMAIL': 'Email is required.',
|
|
12321
|
+
'MISSING_PASSWORD': 'Password is required.',
|
|
12322
|
+
'MISSING_TOKEN': 'Token is required.',
|
|
12323
|
+
'MISSING_PHONE_NUMBER': 'Phone number is required.',
|
|
12324
|
+
'MISSING_VERIFICATION_CODE': 'Phone number and verification code are required.',
|
|
12325
|
+
'MISSING_REDIRECT_URL': 'Redirect URL is required.',
|
|
12326
|
+
'MISSING_GOOGLE_TOKEN': 'Google token is required.',
|
|
12327
|
+
'INVALID_CLIENT_ID': 'Invalid client configuration.',
|
|
12328
|
+
'INVALID_REDIRECT_URL': 'Invalid redirect URL.',
|
|
12329
|
+
'INVALID_PHONE_NUMBER': 'Invalid phone number. Please check the format and try again.',
|
|
12330
|
+
'INVALID_GOOGLE_TOKEN': 'Invalid Google sign-in token.',
|
|
12331
|
+
'PASSWORD_TOO_SHORT': 'Password must be at least 8 characters long.',
|
|
12332
|
+
'PASSWORD_REQUIREMENTS_NOT_MET': 'New password must be at least 6 characters.',
|
|
12333
|
+
'EMAIL_ALREADY_VERIFIED': 'Your email is already verified.',
|
|
12334
|
+
'INVALID_CONFIRMATION': 'Please type DELETE to confirm account deletion.',
|
|
12335
|
+
// 401 - Authentication errors
|
|
12256
12336
|
'INVALID_CREDENTIALS': 'Invalid email or password.',
|
|
12257
|
-
'
|
|
12337
|
+
'INCORRECT_PASSWORD': 'Current password is incorrect.',
|
|
12338
|
+
'INVALID_VERIFICATION_CODE': 'Invalid or expired verification code. Please try again.',
|
|
12339
|
+
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
12340
|
+
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
12341
|
+
'TOKEN_ALREADY_USED': 'This link has already been used. Please request a new one.',
|
|
12342
|
+
'UNAUTHORIZED': 'You must be logged in to perform this action.',
|
|
12343
|
+
'GOOGLE_TOKEN_AUDIENCE_MISMATCH': 'Google sign-in failed. Token was not issued for this application.',
|
|
12344
|
+
// 403 - Forbidden / verification required
|
|
12258
12345
|
'EMAIL_NOT_VERIFIED': 'Please verify your email before signing in.',
|
|
12259
|
-
'ACCOUNT_LOCKED': 'Your account has been locked. Please
|
|
12346
|
+
'ACCOUNT_LOCKED': 'Your account has been locked. Please verify your email to unlock.',
|
|
12347
|
+
'EMAIL_VERIFICATION_EXPIRED': 'Your verification deadline has passed and your account is locked. Please contact support.',
|
|
12348
|
+
// 404
|
|
12349
|
+
'USER_NOT_FOUND': 'Account not found. Please check your email or create a new account.',
|
|
12350
|
+
// 409 - Conflicts
|
|
12351
|
+
'EMAIL_ALREADY_EXISTS': 'This email is already registered.',
|
|
12352
|
+
'EMAIL_IN_USE': 'This email is already in use.',
|
|
12353
|
+
// 429 - Rate limiting
|
|
12354
|
+
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
|
|
12355
|
+
'TOO_MANY_MAGIC_LINKS': 'Too many magic link requests. Please try again later.',
|
|
12356
|
+
'TOO_MANY_VERIFICATION_ATTEMPTS': 'Too many verification attempts. Please wait and try again.',
|
|
12357
|
+
'MAX_VERIFICATION_ATTEMPTS': 'Maximum verification attempts reached. Please try again later.',
|
|
12358
|
+
// 500 - Server errors
|
|
12359
|
+
'LOGIN_FAILED': 'Login failed. Please try again later.',
|
|
12360
|
+
'REGISTRATION_FAILED': 'Registration failed. Please try again later.',
|
|
12361
|
+
'GOOGLE_AUTH_NOT_CONFIGURED': 'Google sign-in is not available for this application.',
|
|
12362
|
+
'GOOGLE_AUTH_FAILED': 'Google sign-in failed. Please try again.',
|
|
12363
|
+
'GOOGLE_USERINFO_FAILED': 'Failed to retrieve your Google account information. Please try again.',
|
|
12364
|
+
'PHONE_VERIFICATION_FAILED': 'Phone verification failed. Please try again.',
|
|
12365
|
+
'SEND_VERIFICATION_CODE_FAILED': 'Failed to send verification code. Please try again.',
|
|
12366
|
+
'MAGIC_LINK_SEND_FAILED': 'Failed to send magic link. Please try again.',
|
|
12367
|
+
'MAGIC_LINK_VERIFICATION_FAILED': 'Magic link verification failed. Please try again.',
|
|
12368
|
+
'PASSWORD_RESET_FAILED': 'Failed to process password reset. Please try again.',
|
|
12369
|
+
'PASSWORD_RESET_COMPLETE_FAILED': 'Failed to reset password. Please try again.',
|
|
12370
|
+
'EMAIL_VERIFICATION_SEND_FAILED': 'Failed to send verification email. Please try again.',
|
|
12371
|
+
'EMAIL_VERIFICATION_FAILED': 'Email verification failed. Please try again.',
|
|
12372
|
+
// Account management 500s
|
|
12373
|
+
'UPDATE_PROFILE_FAILED': 'Failed to update profile. Please try again.',
|
|
12374
|
+
'CHANGE_PASSWORD_FAILED': 'Failed to change password. Please try again.',
|
|
12375
|
+
'CHANGE_EMAIL_FAILED': 'Failed to change email. Please try again.',
|
|
12376
|
+
'UPDATE_PHONE_FAILED': 'Failed to update phone number. Please try again.',
|
|
12377
|
+
'DELETE_ACCOUNT_FAILED': 'Failed to delete account. Please try again.',
|
|
12378
|
+
'CONFIG_FETCH_FAILED': 'Failed to load configuration. Please try again.',
|
|
12379
|
+
'INTERNAL_ERROR': 'An unexpected error occurred. Please try again.',
|
|
12380
|
+
// Legacy aliases (kept for backward compatibility)
|
|
12260
12381
|
'INVALID_CODE': 'Invalid verification code. Please check and try again.',
|
|
12261
12382
|
'CODE_EXPIRED': 'This code has expired. Please request a new one.',
|
|
12262
|
-
'INVALID_TOKEN': 'This link has expired or is invalid. Please request a new one.',
|
|
12263
|
-
'TOKEN_EXPIRED': 'This link has expired. Please request a new one.',
|
|
12264
12383
|
'PHONE_NOT_SUPPORTED': 'This phone number is not supported. Please try a different number.',
|
|
12265
12384
|
'INVALID_PHONE': 'Invalid phone number. Please check the format and try again.',
|
|
12266
12385
|
'PASSWORD_TOO_WEAK': 'Password is too weak. Please use at least 8 characters with a mix of letters and numbers.',
|
|
12267
12386
|
'RATE_LIMITED': 'Too many attempts. Please wait a moment and try again.',
|
|
12268
12387
|
};
|
|
12388
|
+
function isApiErrorLike(error) {
|
|
12389
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12390
|
+
return true;
|
|
12391
|
+
if (error && typeof error === 'object' && 'statusCode' in error && 'message' in error) {
|
|
12392
|
+
const e = error;
|
|
12393
|
+
return typeof e.statusCode === 'number' && typeof e.message === 'string';
|
|
12394
|
+
}
|
|
12395
|
+
return false;
|
|
12396
|
+
}
|
|
12269
12397
|
/**
|
|
12270
12398
|
* Extracts a user-friendly error message from an error.
|
|
12271
12399
|
*
|
|
12272
12400
|
* Handles:
|
|
12273
|
-
* - SmartlinksApiError
|
|
12401
|
+
* - SmartlinksApiError (and duck-typed equivalents from proxy mode)
|
|
12274
12402
|
* - Standard Error: Uses message property
|
|
12275
12403
|
* - String: Passes through directly (for native bridge errors)
|
|
12276
12404
|
* - Unknown: Returns generic message
|
|
12277
|
-
*
|
|
12278
|
-
* @param error - The caught error (SmartlinksApiError, Error, string, or unknown)
|
|
12279
|
-
* @returns A user-friendly error message suitable for display
|
|
12280
12405
|
*/
|
|
12281
12406
|
function getFriendlyErrorMessage(error) {
|
|
12282
|
-
// Handle SmartlinksApiError
|
|
12283
|
-
if (error
|
|
12407
|
+
// Handle SmartlinksApiError or duck-typed API errors (proxy mode)
|
|
12408
|
+
if (isApiErrorLike(error)) {
|
|
12284
12409
|
// First, check for specific error code (most precise)
|
|
12285
|
-
|
|
12286
|
-
|
|
12410
|
+
const errorCode = error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12411
|
+
if (errorCode && ERROR_CODE_MESSAGES[errorCode]) {
|
|
12412
|
+
return ERROR_CODE_MESSAGES[errorCode];
|
|
12287
12413
|
}
|
|
12288
12414
|
// Then, check status code for general category messages
|
|
12289
12415
|
if (error.statusCode >= 500) {
|
|
@@ -12295,8 +12421,12 @@ function getFriendlyErrorMessage(error) {
|
|
|
12295
12421
|
// Fall back to the server's message (already human-readable from backend)
|
|
12296
12422
|
return error.message;
|
|
12297
12423
|
}
|
|
12298
|
-
// Handle standard Error objects
|
|
12424
|
+
// Handle standard Error objects — check message for known patterns
|
|
12299
12425
|
if (error instanceof Error) {
|
|
12426
|
+
// Check if the message itself contains a known API error pattern
|
|
12427
|
+
if (/already (registered|exists)/i.test(error.message)) {
|
|
12428
|
+
return 'This email is already registered.';
|
|
12429
|
+
}
|
|
12300
12430
|
return error.message;
|
|
12301
12431
|
}
|
|
12302
12432
|
// Handle plain strings (e.g., from native bridge callbacks)
|
|
@@ -12310,41 +12440,57 @@ function getFriendlyErrorMessage(error) {
|
|
|
12310
12440
|
* Checks if an error represents a conflict (409) - typically duplicate registration.
|
|
12311
12441
|
*/
|
|
12312
12442
|
function isConflictError(error) {
|
|
12313
|
-
|
|
12443
|
+
if (isApiErrorLike(error))
|
|
12444
|
+
return error.statusCode === 409;
|
|
12445
|
+
// Also check error message for keyword match (resilient fallback)
|
|
12446
|
+
if (error instanceof Error && /already (registered|exists)/i.test(error.message))
|
|
12447
|
+
return true;
|
|
12448
|
+
return false;
|
|
12314
12449
|
}
|
|
12315
12450
|
/**
|
|
12316
12451
|
* Checks if an error represents invalid credentials (401).
|
|
12317
12452
|
*/
|
|
12318
12453
|
function isAuthError(error) {
|
|
12319
|
-
|
|
12454
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12455
|
+
return error.isAuthError();
|
|
12456
|
+
if (isApiErrorLike(error))
|
|
12457
|
+
return error.statusCode === 401 || error.statusCode === 403;
|
|
12458
|
+
return false;
|
|
12320
12459
|
}
|
|
12321
12460
|
/**
|
|
12322
12461
|
* Checks if an error represents rate limiting (429).
|
|
12323
12462
|
*/
|
|
12324
12463
|
function isRateLimitError(error) {
|
|
12325
|
-
|
|
12464
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12465
|
+
return error.isRateLimited();
|
|
12466
|
+
if (isApiErrorLike(error))
|
|
12467
|
+
return error.statusCode === 429;
|
|
12468
|
+
return false;
|
|
12326
12469
|
}
|
|
12327
12470
|
/**
|
|
12328
12471
|
* Checks if an error represents a server error (5xx).
|
|
12329
12472
|
*/
|
|
12330
12473
|
function isServerError(error) {
|
|
12331
|
-
|
|
12474
|
+
if (error instanceof smartlinks.SmartlinksApiError)
|
|
12475
|
+
return error.isServerError();
|
|
12476
|
+
if (isApiErrorLike(error))
|
|
12477
|
+
return error.statusCode >= 500;
|
|
12478
|
+
return false;
|
|
12332
12479
|
}
|
|
12333
12480
|
/**
|
|
12334
12481
|
* Gets the HTTP status code from an error, if available.
|
|
12335
12482
|
*/
|
|
12336
12483
|
function getErrorStatusCode(error) {
|
|
12337
|
-
if (error
|
|
12484
|
+
if (isApiErrorLike(error))
|
|
12338
12485
|
return error.statusCode;
|
|
12339
|
-
}
|
|
12340
12486
|
return undefined;
|
|
12341
12487
|
}
|
|
12342
12488
|
/**
|
|
12343
12489
|
* Gets the server-specific error code from an error, if available.
|
|
12344
12490
|
*/
|
|
12345
12491
|
function getErrorCode(error) {
|
|
12346
|
-
if (error
|
|
12347
|
-
return error.errorCode;
|
|
12492
|
+
if (isApiErrorLike(error)) {
|
|
12493
|
+
return error.errorCode || error.details?.errorCode || error.details?.error;
|
|
12348
12494
|
}
|
|
12349
12495
|
return undefined;
|
|
12350
12496
|
}
|
|
@@ -12574,6 +12720,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
12574
12720
|
const [sdkReady, setSdkReady] = React.useState(false); // Track SDK initialization state
|
|
12575
12721
|
const [contactSchema, setContactSchema] = React.useState(null); // Schema for registration fields
|
|
12576
12722
|
const [silentSignInChecked, setSilentSignInChecked] = React.useState(false); // Track if silent sign-in has been checked
|
|
12723
|
+
const [googleFallbackToPopup, setGoogleFallbackToPopup] = React.useState(false); // Show popup fallback when FedCM is blocked/dismissed
|
|
12577
12724
|
const log = React.useMemo(() => createLoggerWrapper(logger), [logger]);
|
|
12578
12725
|
const api = new AuthAPI(apiEndpoint, clientId, clientName, logger);
|
|
12579
12726
|
const auth = useAuth();
|
|
@@ -13166,6 +13313,22 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13166
13313
|
setLoading(false);
|
|
13167
13314
|
}
|
|
13168
13315
|
};
|
|
13316
|
+
// Retry Google login using popup flow (fallback when FedCM/OneTap is blocked)
|
|
13317
|
+
const handleGooglePopupFallback = () => {
|
|
13318
|
+
setGoogleFallbackToPopup(false);
|
|
13319
|
+
setError(undefined);
|
|
13320
|
+
// Temporarily override the flow to popup and call handleGoogleLogin
|
|
13321
|
+
const originalFlow = config?.googleOAuthFlow;
|
|
13322
|
+
if (config) {
|
|
13323
|
+
config.googleOAuthFlow = 'popup';
|
|
13324
|
+
}
|
|
13325
|
+
handleGoogleLogin().finally(() => {
|
|
13326
|
+
// Restore original flow setting
|
|
13327
|
+
if (config) {
|
|
13328
|
+
config.googleOAuthFlow = originalFlow;
|
|
13329
|
+
}
|
|
13330
|
+
});
|
|
13331
|
+
};
|
|
13169
13332
|
const handleGoogleLogin = async () => {
|
|
13170
13333
|
const hasCustomGoogleClientId = !!config?.googleClientId;
|
|
13171
13334
|
const googleClientId = config?.googleClientId || DEFAULT_GOOGLE_CLIENT_ID;
|
|
@@ -13181,6 +13344,7 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13181
13344
|
const isWhitelisted = isWhitelistedGoogleDomain(config?.whitelistedGoogleDomains);
|
|
13182
13345
|
const googleProxyUrl = config?.googleOAuthProxyUrl
|
|
13183
13346
|
|| (!hasCustomGoogleClientId && !isWhitelisted ? DEFAULT_GOOGLE_PROXY_URL : undefined);
|
|
13347
|
+
setGoogleFallbackToPopup(false);
|
|
13184
13348
|
log.log('Google Auth initiated:', {
|
|
13185
13349
|
configuredFlow,
|
|
13186
13350
|
effectiveFlow: oauthFlow,
|
|
@@ -13491,14 +13655,26 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13491
13655
|
cancel_on_tap_outside: true,
|
|
13492
13656
|
use_fedcm_for_prompt: true, // Enable FedCM for future browser compatibility
|
|
13493
13657
|
});
|
|
13494
|
-
// Use timeout fallback
|
|
13495
|
-
// (isNotDisplayed/isSkippedMoment will stop working when FedCM becomes mandatory)
|
|
13658
|
+
// Use timeout fallback — if no prompt interaction after 5s, assume FedCM was blocked
|
|
13496
13659
|
const promptTimeout = setTimeout(() => {
|
|
13660
|
+
log.log('Google OneTap prompt timed out — FedCM may be blocked or unavailable');
|
|
13661
|
+
setGoogleFallbackToPopup(true);
|
|
13497
13662
|
setLoading(false);
|
|
13498
13663
|
}, 5000);
|
|
13499
|
-
google.accounts.id.prompt(() => {
|
|
13500
|
-
// Clear timeout if prompt interaction occurs
|
|
13664
|
+
google.accounts.id.prompt((notification) => {
|
|
13501
13665
|
clearTimeout(promptTimeout);
|
|
13666
|
+
// Check for FedCM/OneTap dismissal or blocking
|
|
13667
|
+
// notification may have getMomentType(), getDismissedReason(), getSkippedReason()
|
|
13668
|
+
const momentType = notification?.getMomentType?.();
|
|
13669
|
+
const dismissedReason = notification?.getDismissedReason?.();
|
|
13670
|
+
const skippedReason = notification?.getSkippedReason?.();
|
|
13671
|
+
log.log('Google OneTap prompt notification:', { momentType, dismissedReason, skippedReason });
|
|
13672
|
+
if (momentType === 'skipped' || momentType === 'dismissed') {
|
|
13673
|
+
// User dismissed the prompt, or browser blocked it (FedCM disabled)
|
|
13674
|
+
// Offer popup flow as alternative
|
|
13675
|
+
log.log('Google OneTap was dismissed/skipped, offering popup fallback');
|
|
13676
|
+
setGoogleFallbackToPopup(true);
|
|
13677
|
+
}
|
|
13502
13678
|
setLoading(false);
|
|
13503
13679
|
});
|
|
13504
13680
|
}
|
|
@@ -13821,33 +13997,68 @@ const SmartlinksAuthUI = ({ apiEndpoint, clientId, clientName, accountData, onAu
|
|
|
13821
13997
|
fontSize: '0.875rem',
|
|
13822
13998
|
fontWeight: 500,
|
|
13823
13999
|
opacity: loading ? 0.6 : 1
|
|
13824
|
-
}, children: "Cancel" })] })] })) : (jsxRuntime.
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
color: 'var(--auth-text-color, #6B7280)',
|
|
13839
|
-
cursor: 'pointer',
|
|
14000
|
+
}, children: "Cancel" })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [googleFallbackToPopup && (jsxRuntime.jsxs("div", { style: {
|
|
14001
|
+
marginBottom: '1rem',
|
|
14002
|
+
padding: '0.75rem 1rem',
|
|
14003
|
+
borderRadius: '0.5rem',
|
|
14004
|
+
backgroundColor: resolvedTheme === 'dark' ? 'rgba(59, 130, 246, 0.1)' : 'rgba(59, 130, 246, 0.05)',
|
|
14005
|
+
border: `1px solid ${resolvedTheme === 'dark' ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.2)'}`,
|
|
14006
|
+
}, children: [jsxRuntime.jsx("p", { style: {
|
|
14007
|
+
fontSize: '0.8125rem',
|
|
14008
|
+
color: resolvedTheme === 'dark' ? '#93c5fd' : '#1e40af',
|
|
14009
|
+
marginBottom: '0.5rem',
|
|
14010
|
+
lineHeight: 1.4,
|
|
14011
|
+
}, children: "Google sign-in was blocked by your browser. You can try signing in via a popup window instead." }), jsxRuntime.jsxs("button", { onClick: handleGooglePopupFallback, disabled: loading, style: {
|
|
14012
|
+
width: '100%',
|
|
14013
|
+
padding: '0.5rem 1rem',
|
|
13840
14014
|
fontSize: '0.875rem',
|
|
14015
|
+
fontWeight: 500,
|
|
14016
|
+
color: '#fff',
|
|
14017
|
+
backgroundColor: '#4285F4',
|
|
14018
|
+
border: 'none',
|
|
14019
|
+
borderRadius: '0.375rem',
|
|
14020
|
+
cursor: loading ? 'not-allowed' : 'pointer',
|
|
14021
|
+
opacity: loading ? 0.6 : 1,
|
|
13841
14022
|
display: 'flex',
|
|
13842
14023
|
alignItems: 'center',
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
14024
|
+
justifyContent: 'center',
|
|
14025
|
+
gap: '0.5rem',
|
|
14026
|
+
}, children: [jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", xmlns: "http://www.w3.org/2000/svg", children: [jsxRuntime.jsx("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615Z", fill: "#4285F4" }), jsxRuntime.jsx("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18Z", fill: "#34A853" }), jsxRuntime.jsx("path", { d: "M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.997 8.997 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332Z", fill: "#FBBC05" }), jsxRuntime.jsx("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58Z", fill: "#EA4335" })] }), "Sign in with Google"] }), jsxRuntime.jsx("button", { onClick: () => setGoogleFallbackToPopup(false), style: {
|
|
14027
|
+
marginTop: '0.375rem',
|
|
14028
|
+
width: '100%',
|
|
14029
|
+
padding: '0.25rem',
|
|
14030
|
+
fontSize: '0.75rem',
|
|
14031
|
+
color: resolvedTheme === 'dark' ? '#64748b' : '#9ca3af',
|
|
14032
|
+
backgroundColor: 'transparent',
|
|
14033
|
+
border: 'none',
|
|
14034
|
+
cursor: 'pointer',
|
|
14035
|
+
}, children: "Dismiss" })] })), (() => {
|
|
14036
|
+
const emailDisplayMode = config?.emailDisplayMode || 'form';
|
|
14037
|
+
const providerOrder = config?.providerOrder || (config?.enabledProviders || enabledProviders);
|
|
14038
|
+
const actualProviders = config?.enabledProviders || enabledProviders;
|
|
14039
|
+
// Button mode: show provider selection first, then email form if email is selected
|
|
14040
|
+
if (emailDisplayMode === 'button' && !showEmailForm) {
|
|
14041
|
+
return (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders, providerOrder: providerOrder, onEmailLogin: () => setShowEmailForm(true), onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }));
|
|
14042
|
+
}
|
|
14043
|
+
// Form mode or email button was clicked: show email form with other providers
|
|
14044
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [emailDisplayMode === 'button' && showEmailForm && (jsxRuntime.jsx("button", { onClick: () => setShowEmailForm(false), style: {
|
|
14045
|
+
marginBottom: '1rem',
|
|
14046
|
+
padding: '0.5rem',
|
|
14047
|
+
background: 'none',
|
|
14048
|
+
border: 'none',
|
|
14049
|
+
color: 'var(--auth-text-color, #6B7280)',
|
|
14050
|
+
cursor: 'pointer',
|
|
14051
|
+
fontSize: '0.875rem',
|
|
14052
|
+
display: 'flex',
|
|
14053
|
+
alignItems: 'center',
|
|
14054
|
+
gap: '0.25rem'
|
|
14055
|
+
}, children: "\u2190 Back to options" })), jsxRuntime.jsx(EmailAuthForm, { mode: mode, onSubmit: handleEmailAuth, onModeSwitch: () => {
|
|
14056
|
+
setMode(mode === 'login' ? 'register' : 'login');
|
|
14057
|
+
setShowResendVerification(false);
|
|
14058
|
+
setShowRequestNewReset(false);
|
|
14059
|
+
setError(undefined);
|
|
14060
|
+
}, onForgotPassword: () => setMode('reset-password'), loading: loading, error: error, signupProminence: resolvedSignupProminence, schema: contactSchema, registrationFieldsConfig: config?.registrationFields, additionalFields: config?.signupAdditionalFields }), emailDisplayMode === 'form' && actualProviders.length > 1 && (jsxRuntime.jsx(ProviderButtons, { enabledProviders: actualProviders.filter((p) => p !== 'email'), providerOrder: providerOrder, onGoogleLogin: handleGoogleLogin, onPhoneLogin: () => setMode('phone'), onMagicLinkLogin: () => setMode('magic-link'), loading: loading }))] }));
|
|
14061
|
+
})()] })) })) : null }));
|
|
13851
14062
|
};
|
|
13852
14063
|
|
|
13853
14064
|
var SmartlinksAuthUI$1 = /*#__PURE__*/Object.freeze({
|
|
@@ -14916,10 +15127,6 @@ async function setDefaultAuthKitId(collectionId, authKitId) {
|
|
|
14916
15127
|
});
|
|
14917
15128
|
}
|
|
14918
15129
|
|
|
14919
|
-
Object.defineProperty(exports, "evaluateConditions", {
|
|
14920
|
-
enumerable: true,
|
|
14921
|
-
get: function () { return smartlinks.evaluateConditions; }
|
|
14922
|
-
});
|
|
14923
15130
|
exports.AccountManagement = AccountManagement;
|
|
14924
15131
|
exports.AuthProvider = AuthProvider;
|
|
14925
15132
|
exports.AuthUIPreview = AuthUIPreview;
|
|
@@ -14929,6 +15136,7 @@ exports.SchemaFieldRenderer = SchemaFieldRenderer;
|
|
|
14929
15136
|
exports.SmartlinksAuthUI = SmartlinksAuthUI;
|
|
14930
15137
|
exports.SmartlinksFrame = SmartlinksFrame;
|
|
14931
15138
|
exports.buildIframeSrc = buildIframeSrc;
|
|
15139
|
+
exports.evaluateConditions = evaluateConditions;
|
|
14932
15140
|
exports.getDefaultAuthKitId = getDefaultAuthKitId;
|
|
14933
15141
|
exports.getEditableFields = getEditableFields;
|
|
14934
15142
|
exports.getErrorCode = getErrorCode;
|