@abstraxn/signer-react 2.1.5 → 2.2.0
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/CHANGELOG.md +11 -0
- package/dist/src/WalletModal.css +0 -1
- package/dist/src/WalletModal.js +10 -4
- package/dist/src/WalletModal.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +557 -81
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.d.ts +2 -1
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js +72 -8
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +16 -11
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/utils.d.ts +5 -0
- package/dist/src/components/AbstraxnProvider/utils.js +9 -0
- package/dist/src/components/AbstraxnProvider/utils.js.map +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUI.css +186 -6
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js +18 -4
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +21 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +528 -20
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.js +2 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/MfaForm.d.ts +17 -0
- package/dist/src/components/OnboardingUI/components/MfaForm.js +81 -0
- package/dist/src/components/OnboardingUI/components/MfaForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/index.d.ts +2 -0
- package/dist/src/components/OnboardingUI/components/index.js +1 -0
- package/dist/src/components/OnboardingUI/components/index.js.map +1 -1
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +3 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -1
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +4 -1
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +65 -2
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -1
- package/dist/src/components/WalletModal/components/ManageWalletModal.css +1443 -2
- package/dist/src/components/WalletModal/components/ManageWalletModal.js +737 -23
- package/dist/src/components/WalletModal/components/ManageWalletModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/ReceiveModal.css +0 -1
- package/dist/src/components/WalletModal/hooks/useSendTransaction.js +67 -12
- package/dist/src/components/WalletModal/hooks/useSendTransaction.js.map +1 -1
- package/dist/src/types.d.ts +16 -0
- package/dist/src/wagmiConfig.js +1 -0
- package/dist/src/wagmiConfig.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
|
@@ -346,30 +346,62 @@ const ONBOARDING_UI_STYLES = `
|
|
|
346
346
|
right: 8px;
|
|
347
347
|
top: 50%;
|
|
348
348
|
transform: translateY(-50%);
|
|
349
|
+
width: 32px;
|
|
350
|
+
height: 32px;
|
|
349
351
|
background: none;
|
|
350
352
|
border: none;
|
|
351
353
|
cursor: pointer;
|
|
352
|
-
padding:
|
|
354
|
+
padding: 0;
|
|
353
355
|
display: flex;
|
|
354
356
|
align-items: center;
|
|
355
357
|
justify-content: center;
|
|
356
358
|
z-index: 2;
|
|
357
|
-
transition:
|
|
358
|
-
border-radius:
|
|
359
|
+
transition: background-color 0.2s ease, color 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
|
|
360
|
+
border-radius: 6px;
|
|
359
361
|
}
|
|
360
362
|
|
|
361
363
|
/* Light theme arrow button */
|
|
362
364
|
.onboarding-theme-light .onboarding-input-arrow-button {
|
|
363
|
-
color: #
|
|
365
|
+
color: #6b7280;
|
|
364
366
|
}
|
|
365
367
|
|
|
366
|
-
.onboarding-theme-light .onboarding-input-arrow-button:hover {
|
|
367
|
-
background-color: rgba(
|
|
368
|
+
.onboarding-theme-light .onboarding-input-arrow-button:hover:not(:disabled) {
|
|
369
|
+
background-color: rgba(139, 152, 202, 0.35);
|
|
368
370
|
color: #1f2937;
|
|
371
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.onboarding-theme-light .onboarding-input-arrow-button:active:not(:disabled) {
|
|
375
|
+
transform: translateY(-50%) scale(0.94);
|
|
376
|
+
background-color: rgba(139, 152, 202, 0.5);
|
|
377
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.onboarding-theme-light .onboarding-input-arrow-button:focus-visible {
|
|
381
|
+
outline: none;
|
|
382
|
+
box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px rgba(139, 152, 202, 0.5);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.onboarding-theme-light .onboarding-input-arrow-button-filled {
|
|
386
|
+
background-color: rgba(139, 152, 202, 0.45);
|
|
387
|
+
color: #1f2937;
|
|
388
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.onboarding-theme-light .onboarding-input-arrow-button-filled:hover:not(:disabled) {
|
|
392
|
+
background-color: rgba(139, 152, 202, 0.6);
|
|
393
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
369
394
|
}
|
|
370
395
|
|
|
371
|
-
.onboarding-theme-light .onboarding-input-arrow-button:active {
|
|
372
|
-
|
|
396
|
+
.onboarding-theme-light .onboarding-input-arrow-button-filled:active:not(:disabled) {
|
|
397
|
+
transform: translateY(-50%) scale(0.94);
|
|
398
|
+
background-color: rgba(139, 152, 202, 0.7);
|
|
399
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.onboarding-theme-light .onboarding-input-arrow-button-filled:focus-visible {
|
|
403
|
+
outline: none;
|
|
404
|
+
box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px rgba(139, 152, 202, 0.55);
|
|
373
405
|
}
|
|
374
406
|
|
|
375
407
|
/* Dark theme arrow button */
|
|
@@ -377,17 +409,37 @@ const ONBOARDING_UI_STYLES = `
|
|
|
377
409
|
color: #9ca3af;
|
|
378
410
|
}
|
|
379
411
|
|
|
380
|
-
.onboarding-theme-dark .onboarding-input-arrow-button:hover {
|
|
381
|
-
|
|
382
|
-
color:
|
|
412
|
+
.onboarding-theme-dark .onboarding-input-arrow-button:hover:not(:disabled) {
|
|
413
|
+
color: #ffffff;
|
|
414
|
+
background-color: rgba(255, 255, 255, 0.12);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.onboarding-theme-dark .onboarding-input-arrow-button:active:not(:disabled) {
|
|
418
|
+
transform: translateY(-50%) scale(0.94);
|
|
419
|
+
background-color: rgba(255, 255, 255, 0.18);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.onboarding-theme-dark .onboarding-input-arrow-button:focus-visible {
|
|
423
|
+
outline: none;
|
|
424
|
+
box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px rgba(156, 163, 175, 0.4);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.onboarding-theme-dark .onboarding-input-arrow-button-filled {
|
|
428
|
+
color: #ffffff;
|
|
429
|
+
background-color: rgba(255, 255, 255, 0.12);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.onboarding-theme-dark .onboarding-input-arrow-button-filled:hover:not(:disabled) {
|
|
433
|
+
background-color: rgba(255, 255, 255, 0.18);
|
|
383
434
|
}
|
|
384
435
|
|
|
385
|
-
.onboarding-theme-dark .onboarding-input-arrow-button:active {
|
|
386
|
-
|
|
436
|
+
.onboarding-theme-dark .onboarding-input-arrow-button-filled:active:not(:disabled) {
|
|
437
|
+
transform: translateY(-50%) scale(0.94);
|
|
438
|
+
background-color: rgba(255, 255, 255, 0.22);
|
|
387
439
|
}
|
|
388
440
|
|
|
389
441
|
.onboarding-input-arrow-button:disabled {
|
|
390
|
-
opacity: 0.
|
|
442
|
+
opacity: 0.35;
|
|
391
443
|
cursor: not-allowed;
|
|
392
444
|
}
|
|
393
445
|
|
|
@@ -823,6 +875,12 @@ const ONBOARDING_UI_STYLES = `
|
|
|
823
875
|
justify-content: center;
|
|
824
876
|
margin-bottom: 24px;
|
|
825
877
|
width: 100%;
|
|
878
|
+
padding: 0 10px;
|
|
879
|
+
box-sizing: border-box;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.onboarding-otp-inputs-single {
|
|
883
|
+
padding: 0;
|
|
826
884
|
}
|
|
827
885
|
|
|
828
886
|
.onboarding-otp-input {
|
|
@@ -869,6 +927,54 @@ const ONBOARDING_UI_STYLES = `
|
|
|
869
927
|
color: #ffffff;
|
|
870
928
|
}
|
|
871
929
|
|
|
930
|
+
.onboarding-mfa-backup-field {
|
|
931
|
+
width: 100%;
|
|
932
|
+
max-width: 320px;
|
|
933
|
+
height: 52px;
|
|
934
|
+
border: 1.5px solid #e5e7eb;
|
|
935
|
+
border-radius: 8px;
|
|
936
|
+
background-color: inherit;
|
|
937
|
+
color: inherit;
|
|
938
|
+
text-align: center;
|
|
939
|
+
font-size: 18px;
|
|
940
|
+
font-weight: 600;
|
|
941
|
+
letter-spacing: 0.16em;
|
|
942
|
+
padding: 0 14px;
|
|
943
|
+
box-sizing: border-box;
|
|
944
|
+
text-transform: uppercase;
|
|
945
|
+
transition: all 0.15s ease;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.onboarding-theme-light .onboarding-mfa-backup-field {
|
|
949
|
+
background-color: #ffffff;
|
|
950
|
+
border-color: #e5e7eb;
|
|
951
|
+
color: #000000;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.onboarding-theme-light .onboarding-mfa-backup-field:focus {
|
|
955
|
+
outline: none;
|
|
956
|
+
border-color: #111827;
|
|
957
|
+
box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.1);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.onboarding-theme-dark .onboarding-mfa-backup-field {
|
|
961
|
+
border-color: #4b5563;
|
|
962
|
+
background-color: #374151;
|
|
963
|
+
color: #ffffff;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.onboarding-theme-dark .onboarding-mfa-backup-field:focus {
|
|
967
|
+
outline: none;
|
|
968
|
+
border-color: #9ca3af;
|
|
969
|
+
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
|
970
|
+
background-color: #4b5563;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.onboarding-mfa-backup-field:disabled {
|
|
974
|
+
opacity: 0.6;
|
|
975
|
+
cursor: not-allowed;
|
|
976
|
+
}
|
|
977
|
+
|
|
872
978
|
.onboarding-otp-resend {
|
|
873
979
|
text-align: center;
|
|
874
980
|
margin-top: 20px;
|
|
@@ -1005,6 +1111,11 @@ export class OnboardingUIWeb {
|
|
|
1005
1111
|
divider = null;
|
|
1006
1112
|
footer = null;
|
|
1007
1113
|
verifyButton = null;
|
|
1114
|
+
mfaVerificationScreen = null;
|
|
1115
|
+
mfaCodeInputs = [];
|
|
1116
|
+
mfaVerifyButton = null;
|
|
1117
|
+
mfaCode = "";
|
|
1118
|
+
mfaUseBackupCode = false;
|
|
1008
1119
|
email = "";
|
|
1009
1120
|
otp = "";
|
|
1010
1121
|
otpSent = false;
|
|
@@ -1092,13 +1203,15 @@ export class OnboardingUIWeb {
|
|
|
1092
1203
|
detectedProvider === "twitter" ||
|
|
1093
1204
|
detectedProvider === "x");
|
|
1094
1205
|
const hasOAuthParams = isOAuthCallback || (success === "true" && !!accessToken);
|
|
1095
|
-
// If success=true and it's an OAuth callback (Google, Discord, Twitter), show loading modal
|
|
1206
|
+
// If success=true and it's an OAuth callback (Google, Discord, Twitter), show loading modal unless MFA is required
|
|
1096
1207
|
if (success === "true" && hasOAuthParams) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1208
|
+
const mfaRequired = params.get("mfaRequired") === "true";
|
|
1209
|
+
if (mfaRequired) {
|
|
1210
|
+
// Don't show "Verifying login..." - provider will show MFA screen instead; avoid it appearing after MFA modal
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1099
1213
|
requestAnimationFrame(() => {
|
|
1100
1214
|
setTimeout(() => {
|
|
1101
|
-
// Directly show loading modal - it will create its own overlay with header and footer
|
|
1102
1215
|
this.showLoadingModal();
|
|
1103
1216
|
}, 100);
|
|
1104
1217
|
});
|
|
@@ -2439,7 +2552,11 @@ export class OnboardingUIWeb {
|
|
|
2439
2552
|
if (this.config.onEmailOtpVerify) {
|
|
2440
2553
|
const result = await this.config.onEmailOtpVerify(this.email, this.otp);
|
|
2441
2554
|
if (result.success) {
|
|
2442
|
-
|
|
2555
|
+
if (result.mfaRequired === true) {
|
|
2556
|
+
this.showMfaVerificationScreen();
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
this.config.onLoginSuccess?.({ token: result.token, user: result.user });
|
|
2443
2560
|
}
|
|
2444
2561
|
else {
|
|
2445
2562
|
throw new Error("Invalid OTP");
|
|
@@ -2976,6 +3093,10 @@ export class OnboardingUIWeb {
|
|
|
2976
3093
|
if (this.config.onEmailOtpVerify) {
|
|
2977
3094
|
const result = await this.config.onEmailOtpVerify(this.email, this.otp);
|
|
2978
3095
|
if (result.success) {
|
|
3096
|
+
if (result.mfaRequired === true) {
|
|
3097
|
+
this.showMfaVerificationScreen();
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
2979
3100
|
this.config.onLoginSuccess?.(result);
|
|
2980
3101
|
}
|
|
2981
3102
|
else {
|
|
@@ -3044,6 +3165,377 @@ export class OnboardingUIWeb {
|
|
|
3044
3165
|
this.setLoading(false);
|
|
3045
3166
|
}
|
|
3046
3167
|
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Show MFA verification screen for OAuth callback when mfaRequired is true.
|
|
3170
|
+
* Public method: hides loading modal, then shows 6-digit MFA form with Cancel (no Back to OTP).
|
|
3171
|
+
*/
|
|
3172
|
+
showMfaVerificationScreenForOAuth() {
|
|
3173
|
+
this.hideLoadingModal();
|
|
3174
|
+
this.showMfaVerificationScreen({ fromOAuth: true });
|
|
3175
|
+
}
|
|
3176
|
+
/**
|
|
3177
|
+
* Show MFA verification screen (auth code / TOTP) after email OTP when mfaRequired is true
|
|
3178
|
+
* @param options.fromOAuth - when true, show Cancel instead of Back and do not assume OTP screen exists
|
|
3179
|
+
*/
|
|
3180
|
+
showMfaVerificationScreen(options) {
|
|
3181
|
+
const fromOAuth = options?.fromOAuth === true;
|
|
3182
|
+
if (!this.rootElement)
|
|
3183
|
+
return;
|
|
3184
|
+
const card = this.rootElement.querySelector(".onboarding-card");
|
|
3185
|
+
if (!card)
|
|
3186
|
+
return;
|
|
3187
|
+
if (!this.config.onMfaVerify) {
|
|
3188
|
+
this.setError("MFA verification is required but not configured");
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
// Remove existing MFA screen if any
|
|
3192
|
+
if (this.mfaVerificationScreen) {
|
|
3193
|
+
this.mfaVerificationScreen.remove();
|
|
3194
|
+
this.mfaVerificationScreen = null;
|
|
3195
|
+
this.mfaVerifyButton = null;
|
|
3196
|
+
this.mfaCodeInputs = [];
|
|
3197
|
+
}
|
|
3198
|
+
card.classList.add("onboarding-mfa-active");
|
|
3199
|
+
// Hide external wallet CTA ("Continue with wallet") and its divider on all MFA screens
|
|
3200
|
+
if (this.externalWalletContainer) {
|
|
3201
|
+
this.externalWalletContainer.style.display = "none";
|
|
3202
|
+
}
|
|
3203
|
+
if (this.externalWalletDivider) {
|
|
3204
|
+
this.externalWalletDivider.style.display = "none";
|
|
3205
|
+
}
|
|
3206
|
+
// Hide "Powered by Abstraxn" footer on all MFA screens
|
|
3207
|
+
if (this.footer) {
|
|
3208
|
+
this.footer.style.display = "none";
|
|
3209
|
+
}
|
|
3210
|
+
if (fromOAuth) {
|
|
3211
|
+
// Hide email form and social sections so only MFA is visible (OAuth flow)
|
|
3212
|
+
if (this.emailForm)
|
|
3213
|
+
this.emailForm.style.display = "none";
|
|
3214
|
+
const header = this.rootElement.querySelector(".onboarding-header");
|
|
3215
|
+
if (header)
|
|
3216
|
+
header.style.display = "none";
|
|
3217
|
+
if (this.divider)
|
|
3218
|
+
this.divider.style.display = "none";
|
|
3219
|
+
if (this.googleButton)
|
|
3220
|
+
this.googleButton.style.display = "none";
|
|
3221
|
+
const twitterBtn = this.rootElement.querySelector(".onboarding-button-twitter");
|
|
3222
|
+
if (twitterBtn)
|
|
3223
|
+
twitterBtn.style.display = "none";
|
|
3224
|
+
const discordBtn = this.rootElement.querySelector(".onboarding-button-discord");
|
|
3225
|
+
if (discordBtn)
|
|
3226
|
+
discordBtn.style.display = "none";
|
|
3227
|
+
const passkeyBtn = this.rootElement.querySelector(".onboarding-button-passkey");
|
|
3228
|
+
if (passkeyBtn)
|
|
3229
|
+
passkeyBtn.style.display = "none";
|
|
3230
|
+
const passkeyLink = this.rootElement.querySelector(".onboarding-passkey-signup-link");
|
|
3231
|
+
if (passkeyLink)
|
|
3232
|
+
passkeyLink.style.display = "none";
|
|
3233
|
+
const socialGrid = this.rootElement.querySelector(".onboarding-social-grid");
|
|
3234
|
+
if (socialGrid)
|
|
3235
|
+
socialGrid.style.display = "none";
|
|
3236
|
+
}
|
|
3237
|
+
else {
|
|
3238
|
+
// Remove OTP screen from DOM so only MFA screen is visible (separate step)
|
|
3239
|
+
if (this.otpVerificationScreen && this.otpVerificationScreen.parentNode === card) {
|
|
3240
|
+
card.removeChild(this.otpVerificationScreen);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
this.mfaVerificationScreen = this.createElement("div", {
|
|
3244
|
+
className: "onboarding-mfa-verification onboarding-otp-verification",
|
|
3245
|
+
});
|
|
3246
|
+
if (fromOAuth) {
|
|
3247
|
+
const cancelButton = this.createElement("button", {
|
|
3248
|
+
type: "button",
|
|
3249
|
+
className: "onboarding-otp-back-button",
|
|
3250
|
+
"aria-label": "Cancel",
|
|
3251
|
+
});
|
|
3252
|
+
cancelButton.innerHTML = `
|
|
3253
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3254
|
+
<path d="M19 12H5M12 19l-7-7 7-7" />
|
|
3255
|
+
</svg>
|
|
3256
|
+
`;
|
|
3257
|
+
cancelButton.addEventListener("click", () => {
|
|
3258
|
+
if (this.loading)
|
|
3259
|
+
return;
|
|
3260
|
+
if (this.mfaUseBackupCode) {
|
|
3261
|
+
this.mfaUseBackupCode = false;
|
|
3262
|
+
this.mfaCode = "";
|
|
3263
|
+
this.showMfaVerificationScreen(options);
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
this.close();
|
|
3267
|
+
});
|
|
3268
|
+
this.mfaVerificationScreen.appendChild(cancelButton);
|
|
3269
|
+
}
|
|
3270
|
+
else {
|
|
3271
|
+
const backButton = this.createElement("button", {
|
|
3272
|
+
type: "button",
|
|
3273
|
+
className: "onboarding-otp-back-button",
|
|
3274
|
+
"aria-label": "Go back to OTP",
|
|
3275
|
+
});
|
|
3276
|
+
backButton.innerHTML = `
|
|
3277
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3278
|
+
<path d="M19 12H5M12 19l-7-7 7-7" />
|
|
3279
|
+
</svg>
|
|
3280
|
+
`;
|
|
3281
|
+
backButton.addEventListener("click", () => {
|
|
3282
|
+
if (this.loading)
|
|
3283
|
+
return;
|
|
3284
|
+
if (this.mfaUseBackupCode) {
|
|
3285
|
+
this.mfaUseBackupCode = false;
|
|
3286
|
+
this.mfaCode = "";
|
|
3287
|
+
this.showMfaVerificationScreen(options);
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
if (!this.loading && this.mfaVerificationScreen && this.otpVerificationScreen && card) {
|
|
3291
|
+
this.mfaVerificationScreen.remove();
|
|
3292
|
+
this.mfaVerificationScreen = null;
|
|
3293
|
+
this.mfaVerifyButton = null;
|
|
3294
|
+
this.mfaCodeInputs = [];
|
|
3295
|
+
this.mfaCode = "";
|
|
3296
|
+
this.mfaUseBackupCode = false;
|
|
3297
|
+
card.appendChild(this.otpVerificationScreen);
|
|
3298
|
+
card.classList.remove("onboarding-mfa-active");
|
|
3299
|
+
// Restore external wallet CTA and footer when returning to OTP screen
|
|
3300
|
+
if (this.externalWalletContainer) {
|
|
3301
|
+
this.externalWalletContainer.style.display = this.externalWalletsEnabled ? "" : "none";
|
|
3302
|
+
}
|
|
3303
|
+
if (this.externalWalletDivider) {
|
|
3304
|
+
this.externalWalletDivider.style.display = this.externalWalletsEnabled ? "" : "none";
|
|
3305
|
+
}
|
|
3306
|
+
if (this.footer && this.config.showFooter) {
|
|
3307
|
+
this.footer.style.display = "";
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
});
|
|
3311
|
+
this.mfaVerificationScreen.appendChild(backButton);
|
|
3312
|
+
}
|
|
3313
|
+
const iconContainer = this.createElement("div", {
|
|
3314
|
+
className: "onboarding-otp-icon-container",
|
|
3315
|
+
});
|
|
3316
|
+
const iconInner = this.createElement("div", { className: "onboarding-otp-icon-inner" });
|
|
3317
|
+
const lockIcon = this.createElement("span", {
|
|
3318
|
+
innerHTML: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
|
|
3319
|
+
});
|
|
3320
|
+
iconInner.appendChild(lockIcon);
|
|
3321
|
+
iconContainer.appendChild(iconInner);
|
|
3322
|
+
this.mfaVerificationScreen.appendChild(iconContainer);
|
|
3323
|
+
const title = this.createElement("h1", {
|
|
3324
|
+
className: "onboarding-otp-title",
|
|
3325
|
+
textContent: this.mfaUseBackupCode
|
|
3326
|
+
? "Enter backup code"
|
|
3327
|
+
: this.config.labels?.mfaVerifyTitle ?? "Enter authentication code",
|
|
3328
|
+
});
|
|
3329
|
+
this.mfaVerificationScreen.appendChild(title);
|
|
3330
|
+
const instruction = this.createElement("p", {
|
|
3331
|
+
className: "onboarding-otp-instruction",
|
|
3332
|
+
textContent: this.mfaUseBackupCode
|
|
3333
|
+
? "Enter the 8-character backup code to continue"
|
|
3334
|
+
: this.config.labels?.mfaVerifyInstruction ?? "Enter the 6-digit code from your authenticator app",
|
|
3335
|
+
});
|
|
3336
|
+
this.mfaVerificationScreen.appendChild(instruction);
|
|
3337
|
+
const inputsContainer = this.createElement("div", {
|
|
3338
|
+
className: `onboarding-otp-inputs-container${this.mfaUseBackupCode ? " onboarding-otp-inputs-single" : ""}`,
|
|
3339
|
+
});
|
|
3340
|
+
const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
|
|
3341
|
+
this.mfaCodeInputs = [];
|
|
3342
|
+
if (this.mfaUseBackupCode) {
|
|
3343
|
+
const input = this.createElement("input", {
|
|
3344
|
+
type: "text",
|
|
3345
|
+
className: "onboarding-mfa-backup-field",
|
|
3346
|
+
maxLength: 8,
|
|
3347
|
+
inputMode: "text",
|
|
3348
|
+
pattern: "[A-Za-z0-9]*",
|
|
3349
|
+
// placeholder: "Enter backup code",
|
|
3350
|
+
});
|
|
3351
|
+
input.addEventListener("input", (e) => {
|
|
3352
|
+
const target = e.target;
|
|
3353
|
+
target.value = target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8);
|
|
3354
|
+
this.updateMfaCodeValue();
|
|
3355
|
+
});
|
|
3356
|
+
input.addEventListener("paste", (e) => {
|
|
3357
|
+
e.preventDefault();
|
|
3358
|
+
const pasted = (e.clipboardData?.getData("text") || "")
|
|
3359
|
+
.toUpperCase()
|
|
3360
|
+
.replace(/[^A-Z0-9]/g, "")
|
|
3361
|
+
.slice(0, 8);
|
|
3362
|
+
input.value = pasted;
|
|
3363
|
+
this.updateMfaCodeValue();
|
|
3364
|
+
});
|
|
3365
|
+
this.mfaCodeInputs.push(input);
|
|
3366
|
+
inputsContainer.appendChild(input);
|
|
3367
|
+
}
|
|
3368
|
+
else {
|
|
3369
|
+
for (let i = 0; i < mfaCodeLength; i++) {
|
|
3370
|
+
const input = this.createElement("input", {
|
|
3371
|
+
type: "text",
|
|
3372
|
+
className: "onboarding-otp-input",
|
|
3373
|
+
maxLength: 1,
|
|
3374
|
+
inputMode: "numeric",
|
|
3375
|
+
pattern: "[0-9]*",
|
|
3376
|
+
});
|
|
3377
|
+
input.addEventListener("input", (e) => this.handleMfaInput(e, i));
|
|
3378
|
+
input.addEventListener("keydown", (e) => this.handleMfaKeydown(e, i));
|
|
3379
|
+
input.addEventListener("paste", (e) => this.handleMfaPaste(e));
|
|
3380
|
+
this.mfaCodeInputs.push(input);
|
|
3381
|
+
inputsContainer.appendChild(input);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
this.mfaVerificationScreen.appendChild(inputsContainer);
|
|
3385
|
+
if (!this.mfaUseBackupCode) {
|
|
3386
|
+
const backupLinkText = this.createElement("p", {
|
|
3387
|
+
className: "onboarding-mfa-trouble-text",
|
|
3388
|
+
});
|
|
3389
|
+
const backupLinkButton = this.createElement("button", {
|
|
3390
|
+
type: "button",
|
|
3391
|
+
className: "onboarding-mfa-backup-link",
|
|
3392
|
+
textContent: "Use a backup code",
|
|
3393
|
+
});
|
|
3394
|
+
backupLinkButton.addEventListener("click", () => {
|
|
3395
|
+
if (this.loading)
|
|
3396
|
+
return;
|
|
3397
|
+
this.mfaUseBackupCode = true;
|
|
3398
|
+
this.mfaCode = "";
|
|
3399
|
+
this.showMfaVerificationScreen(options);
|
|
3400
|
+
});
|
|
3401
|
+
backupLinkText.append("Having trouble? ", backupLinkButton);
|
|
3402
|
+
this.mfaVerificationScreen.appendChild(backupLinkText);
|
|
3403
|
+
}
|
|
3404
|
+
const mfaErrorElement = this.createElement("div", {
|
|
3405
|
+
className: "onboarding-error",
|
|
3406
|
+
style: "display: none;",
|
|
3407
|
+
});
|
|
3408
|
+
this.mfaVerificationScreen.appendChild(mfaErrorElement);
|
|
3409
|
+
this.mfaVerificationScreen.errorElement = mfaErrorElement;
|
|
3410
|
+
this.mfaVerifyButton = this.createElement("button", {
|
|
3411
|
+
type: "button",
|
|
3412
|
+
className: "onboarding-button onboarding-button-primary",
|
|
3413
|
+
textContent: "Verify",
|
|
3414
|
+
});
|
|
3415
|
+
this.mfaVerifyButton.disabled = true;
|
|
3416
|
+
this.mfaVerifyButton.addEventListener("click", () => this.handleMfaVerify());
|
|
3417
|
+
this.mfaVerificationScreen.appendChild(this.mfaVerifyButton);
|
|
3418
|
+
card.appendChild(this.mfaVerificationScreen);
|
|
3419
|
+
this.setError(null);
|
|
3420
|
+
if (this.mfaCodeInputs[0]) {
|
|
3421
|
+
this.mfaCodeInputs[0].focus();
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
updateMfaCodeValue() {
|
|
3425
|
+
this.mfaCode = this.mfaCodeInputs.map((inp) => inp.value).join("");
|
|
3426
|
+
const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
|
|
3427
|
+
if (this.mfaVerifyButton) {
|
|
3428
|
+
this.mfaVerifyButton.disabled = this.loading || this.mfaCode.length !== mfaCodeLength;
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
handleMfaInput(e, i) {
|
|
3432
|
+
const input = e.target;
|
|
3433
|
+
if (this.mfaUseBackupCode) {
|
|
3434
|
+
input.value = input.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 1);
|
|
3435
|
+
}
|
|
3436
|
+
else {
|
|
3437
|
+
input.value = input.value.replace(/\D/g, "").slice(0, 1);
|
|
3438
|
+
}
|
|
3439
|
+
this.updateMfaCodeValue();
|
|
3440
|
+
const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
|
|
3441
|
+
// Auto-focus next input when user types a character
|
|
3442
|
+
if (this.mfaCodeInputs[i]?.value && i < mfaCodeLength - 1 && this.mfaCodeInputs[i + 1]) {
|
|
3443
|
+
this.mfaCodeInputs[i + 1].focus();
|
|
3444
|
+
}
|
|
3445
|
+
// Auto-verify only for 6-digit authenticator flow.
|
|
3446
|
+
if (!this.mfaUseBackupCode && this.mfaCode.length === 6 && !this.loading) {
|
|
3447
|
+
this.handleMfaVerify();
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
handleMfaKeydown(e, i) {
|
|
3451
|
+
if (e.key === "Backspace" && !this.mfaCodeInputs[i].value && i > 0 && this.mfaCodeInputs[i - 1]) {
|
|
3452
|
+
this.mfaCodeInputs[i - 1].focus();
|
|
3453
|
+
}
|
|
3454
|
+
this.updateMfaCodeValue();
|
|
3455
|
+
}
|
|
3456
|
+
handleMfaPaste(e) {
|
|
3457
|
+
e.preventDefault();
|
|
3458
|
+
const mfaCodeLength = this.mfaUseBackupCode ? 8 : 6;
|
|
3459
|
+
const pasted = this.mfaUseBackupCode
|
|
3460
|
+
? (e.clipboardData?.getData("text") || "").toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8)
|
|
3461
|
+
: (e.clipboardData?.getData("text") || "").replace(/\D/g, "").slice(0, 6);
|
|
3462
|
+
for (let i = 0; i < mfaCodeLength; i++) {
|
|
3463
|
+
if (this.mfaCodeInputs[i]) {
|
|
3464
|
+
this.mfaCodeInputs[i].value = pasted[i] ?? "";
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
this.updateMfaCodeValue();
|
|
3468
|
+
const focusIndex = Math.min(Math.max(pasted.length - 1, 0), mfaCodeLength - 1);
|
|
3469
|
+
if (pasted.length > 0 && this.mfaCodeInputs[focusIndex]) {
|
|
3470
|
+
this.mfaCodeInputs[focusIndex].focus();
|
|
3471
|
+
}
|
|
3472
|
+
// Auto-verify only for 6-digit authenticator flow.
|
|
3473
|
+
if (!this.mfaUseBackupCode && this.mfaCode.length === 6 && !this.loading) {
|
|
3474
|
+
this.handleMfaVerify();
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
async handleMfaVerify() {
|
|
3478
|
+
this.updateMfaCodeValue();
|
|
3479
|
+
const normalizedCode = this.mfaCode.trim().toUpperCase();
|
|
3480
|
+
const isTotpCode = /^\d{6}$/.test(normalizedCode);
|
|
3481
|
+
const isBackupCode = /^[A-Z0-9]{8}$/.test(normalizedCode);
|
|
3482
|
+
if (!isTotpCode && !isBackupCode) {
|
|
3483
|
+
this.setMfaError("Please enter a valid 6-digit or 8-character backup code");
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
const onMfaVerify = this.config.onMfaVerify;
|
|
3487
|
+
if (!onMfaVerify) {
|
|
3488
|
+
this.setMfaError("MFA verification is not configured");
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
this.mfaCodeInputs.forEach((input) => { input.disabled = true; });
|
|
3492
|
+
this.loading = true;
|
|
3493
|
+
if (this.mfaVerifyButton) {
|
|
3494
|
+
this.mfaVerifyButton.disabled = true;
|
|
3495
|
+
this.mfaVerifyButton.textContent = "Verifying...";
|
|
3496
|
+
}
|
|
3497
|
+
this.setMfaError(null);
|
|
3498
|
+
try {
|
|
3499
|
+
const result = await onMfaVerify(normalizedCode);
|
|
3500
|
+
if (result.success) {
|
|
3501
|
+
this.config.onLoginSuccess?.({});
|
|
3502
|
+
}
|
|
3503
|
+
else {
|
|
3504
|
+
this.setMfaError(result.error ?? "Invalid code");
|
|
3505
|
+
this.mfaCodeInputs.forEach((input) => { input.value = ""; input.disabled = false; });
|
|
3506
|
+
this.mfaCode = "";
|
|
3507
|
+
this.updateMfaCodeValue();
|
|
3508
|
+
if (this.mfaCodeInputs[0])
|
|
3509
|
+
this.mfaCodeInputs[0].focus();
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
catch (err) {
|
|
3513
|
+
const msg = err instanceof Error ? err.message : "Verification failed";
|
|
3514
|
+
this.setMfaError(msg);
|
|
3515
|
+
this.mfaCodeInputs.forEach((input) => { input.value = ""; input.disabled = false; });
|
|
3516
|
+
this.mfaCode = "";
|
|
3517
|
+
this.updateMfaCodeValue();
|
|
3518
|
+
if (this.mfaCodeInputs[0])
|
|
3519
|
+
this.mfaCodeInputs[0].focus();
|
|
3520
|
+
}
|
|
3521
|
+
finally {
|
|
3522
|
+
this.loading = false;
|
|
3523
|
+
if (this.mfaVerifyButton) {
|
|
3524
|
+
this.mfaVerifyButton.textContent = "Verify";
|
|
3525
|
+
this.updateMfaCodeValue();
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
setMfaError(message) {
|
|
3530
|
+
const el = this.mfaVerificationScreen ? this.mfaVerificationScreen.errorElement : null;
|
|
3531
|
+
if (el) {
|
|
3532
|
+
el.textContent = message ?? "";
|
|
3533
|
+
el.style.display = message ? "block" : "none";
|
|
3534
|
+
}
|
|
3535
|
+
if (message) {
|
|
3536
|
+
this.config.onLoginError?.(new Error(message));
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3047
3539
|
/**
|
|
3048
3540
|
* Handle resend OTP
|
|
3049
3541
|
*/
|
|
@@ -3323,12 +3815,14 @@ export class OnboardingUIWeb {
|
|
|
3323
3815
|
*/
|
|
3324
3816
|
updateButtonState() {
|
|
3325
3817
|
const isValid = isValidEmail(this.email.trim());
|
|
3818
|
+
const hasEmailValue = this.email.trim().length > 0;
|
|
3326
3819
|
if (this.continueButton) {
|
|
3327
3820
|
this.continueButton.disabled = this.loading || !isValid;
|
|
3328
3821
|
}
|
|
3329
3822
|
// Update arrow button state (when all auth methods are enabled)
|
|
3330
3823
|
if (this.emailArrowButton) {
|
|
3331
3824
|
this.emailArrowButton.disabled = this.loading || !isValid;
|
|
3825
|
+
this.emailArrowButton.classList.toggle("onboarding-input-arrow-button-filled", hasEmailValue);
|
|
3332
3826
|
}
|
|
3333
3827
|
}
|
|
3334
3828
|
/**
|
|
@@ -3383,12 +3877,22 @@ export class OnboardingUIWeb {
|
|
|
3383
3877
|
this.otpVerificationScreen.remove();
|
|
3384
3878
|
this.otpVerificationScreen = null;
|
|
3385
3879
|
}
|
|
3386
|
-
//
|
|
3880
|
+
// Clear MFA verification screen
|
|
3881
|
+
if (this.mfaVerificationScreen) {
|
|
3882
|
+
this.mfaVerificationScreen.remove();
|
|
3883
|
+
this.mfaVerificationScreen = null;
|
|
3884
|
+
this.mfaVerifyButton = null;
|
|
3885
|
+
this.mfaCodeInputs = [];
|
|
3886
|
+
this.mfaCode = "";
|
|
3887
|
+
this.mfaUseBackupCode = false;
|
|
3888
|
+
}
|
|
3889
|
+
// Remove loading/error mode classes and OTP/MFA active class
|
|
3387
3890
|
const card = this.rootElement?.querySelector(".onboarding-card");
|
|
3388
3891
|
if (card) {
|
|
3389
3892
|
card.classList.remove("onboarding-mode-loading");
|
|
3390
3893
|
card.classList.remove("onboarding-mode-error");
|
|
3391
3894
|
card.classList.remove("onboarding-otp-active");
|
|
3895
|
+
card.classList.remove("onboarding-mfa-active");
|
|
3392
3896
|
}
|
|
3393
3897
|
// Hide loading modal if exists (for inline components)
|
|
3394
3898
|
this.hideLoadingModal();
|
|
@@ -3575,6 +4079,10 @@ export class OnboardingUIWeb {
|
|
|
3575
4079
|
this.errorElement = null;
|
|
3576
4080
|
this.otpGroup = null;
|
|
3577
4081
|
this.otpVerificationScreen = null; // Clear OTP screen reference
|
|
4082
|
+
this.mfaVerificationScreen = null;
|
|
4083
|
+
this.mfaVerifyButton = null;
|
|
4084
|
+
this.mfaCodeInputs = [];
|
|
4085
|
+
this.mfaUseBackupCode = false;
|
|
3578
4086
|
this.externalWalletContainer = null; // Clear external wallet container reference
|
|
3579
4087
|
this.externalWalletDivider = null; // Clear external wallet divider reference
|
|
3580
4088
|
}
|