@arcblock/did-connect-core 4.1.51 → 4.1.53
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/ui/design-tokens.js +4 -4
- package/dist/ui/i18n.d.ts +3 -1
- package/dist/ui/i18n.d.ts.map +1 -1
- package/dist/ui/i18n.js +92 -32
- package/dist/ui/i18n.js.map +1 -1
- package/dist/ui/index.bundle.js +804 -96
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/login-dialog.d.ts +26 -0
- package/dist/ui/login-dialog.d.ts.map +1 -0
- package/dist/ui/login-dialog.js +206 -0
- package/dist/ui/login-dialog.js.map +1 -0
- package/dist/ui/login-page.d.ts +73 -2
- package/dist/ui/login-page.d.ts.map +1 -1
- package/dist/ui/login-page.js +383 -46
- package/dist/ui/login-page.js.map +1 -1
- package/dist/ui/qr-renderer.d.ts.map +1 -1
- package/dist/ui/qr-renderer.js +19 -3
- package/dist/ui/qr-renderer.js.map +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/styles.js +84 -8
- package/dist/ui/styles.js.map +1 -1
- package/dist/ui/webauthn-browser.d.ts +1 -0
- package/dist/ui/webauthn-browser.d.ts.map +1 -1
- package/dist/ui/webauthn-browser.js +19 -9
- package/dist/ui/webauthn-browser.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/index.bundle.js
CHANGED
|
@@ -100,7 +100,7 @@ var lightTokens = {
|
|
|
100
100
|
var DESIGN_TOKENS_CSS = (
|
|
101
101
|
/* css */
|
|
102
102
|
`
|
|
103
|
-
:root {
|
|
103
|
+
:root, :host {
|
|
104
104
|
/* Backgrounds \u2014 layered depth */
|
|
105
105
|
--bg-root: ${tokens.bg.root};
|
|
106
106
|
--bg-surface: ${tokens.bg.surface};
|
|
@@ -159,7 +159,7 @@ var DESIGN_TOKENS_CSS = (
|
|
|
159
159
|
var DESIGN_TOKENS_LIGHT_CSS = (
|
|
160
160
|
/* css */
|
|
161
161
|
`
|
|
162
|
-
[data-theme="light"] {
|
|
162
|
+
[data-theme="light"], :host([data-theme="light"]) {
|
|
163
163
|
--bg-root: ${lightTokens.bg.root};
|
|
164
164
|
--bg-surface: ${lightTokens.bg.surface};
|
|
165
165
|
--bg-card: ${lightTokens.bg.card};
|
|
@@ -190,8 +190,8 @@ var DESIGN_RESET_CSS = (
|
|
|
190
190
|
/* css */
|
|
191
191
|
`
|
|
192
192
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
193
|
-
html { color-scheme: dark; }
|
|
194
|
-
html[data-theme="light"] { color-scheme: light; }
|
|
193
|
+
html, :host { color-scheme: dark; }
|
|
194
|
+
html[data-theme="light"], :host([data-theme="light"]) { color-scheme: light; }
|
|
195
195
|
body {
|
|
196
196
|
font-family: ${tokens.font.family};
|
|
197
197
|
background: var(--bg-root);
|
|
@@ -378,18 +378,32 @@ if (typeof customElements !== "undefined" && !customElements.get("did-address"))
|
|
|
378
378
|
// dist/ui/i18n.js
|
|
379
379
|
var messages = {
|
|
380
380
|
en: {
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
emailExplanation: "We will send you a verification code. No password needed.",
|
|
382
|
+
otherMethods: "Other ways to sign in",
|
|
383
|
+
passkeyCreateAccount: "Create an account with Passkey",
|
|
384
|
+
lastUsed: "Last used: {method}",
|
|
385
|
+
passkeyExplanation: "Create a new account using your fingerprint, face, or device unlock. Your device will ask you to save a Passkey. Already have an account? Go back to sign in.",
|
|
386
|
+
continueWith: "Continue with {provider}",
|
|
387
|
+
oauthWaiting: "Continue in the {provider} window",
|
|
388
|
+
oauthIncomplete: "Sign-in is not complete. Reopen the window or choose another method.",
|
|
389
|
+
oauthReopen: "Reopen window",
|
|
390
|
+
oauthFullPage: "Continue on this page",
|
|
391
|
+
emailEdit: "Change email",
|
|
392
|
+
emailCooldown: "Resend in {seconds}s",
|
|
393
|
+
walletRetry: "Wallet did not open? Try again or scan the QR code.",
|
|
394
|
+
tryAgain: "Try again",
|
|
395
|
+
signIn: "Sign in or create an account",
|
|
396
|
+
signInTo: "Sign in or join {appName}",
|
|
383
397
|
passkeyButton: "Continue with Passkey",
|
|
384
398
|
passkeySignIn: "Sign in with Passkey",
|
|
385
399
|
passkeyCreate: "Create Passkey",
|
|
386
|
-
passkeyFirstTime: "First time? Create
|
|
400
|
+
passkeyFirstTime: "First time? Create an account",
|
|
387
401
|
passkeyBackToSignIn: "Back to sign in",
|
|
388
402
|
passkeyWaiting: "Waiting for passkey\u2026",
|
|
389
403
|
passkeyCreating: "Creating your passkey\u2026",
|
|
390
|
-
passkeyNoLocal: "
|
|
404
|
+
passkeyNoLocal: "Passkey verification was not completed. Retry, use another device, or choose another method.",
|
|
391
405
|
passkeyUseAnotherDevice: "Use another device",
|
|
392
|
-
passkeyCreateInstead: "
|
|
406
|
+
passkeyCreateInstead: "First time? Create an account",
|
|
393
407
|
didWalletButton: "Continue with DID Wallet",
|
|
394
408
|
didWalletButtonShort: "DID Wallet",
|
|
395
409
|
scanWithWallet: "Scan with your DID Wallet",
|
|
@@ -422,26 +436,40 @@ var messages = {
|
|
|
422
436
|
emailTitle: "Continue with Email",
|
|
423
437
|
emailSubmit: "Send Code",
|
|
424
438
|
emailSending: "Sending code...",
|
|
425
|
-
emailSent: "We sent
|
|
426
|
-
emailCodePlaceholder: "
|
|
439
|
+
emailSent: "We sent an 8-digit code to {email}",
|
|
440
|
+
emailCodePlaceholder: "8-digit code",
|
|
427
441
|
emailVerify: "Verify",
|
|
428
442
|
emailResend: "Resend code",
|
|
429
443
|
emailInvalid: "Please enter a valid email address.",
|
|
430
|
-
emailCodeInvalid: "Please enter the
|
|
444
|
+
emailCodeInvalid: "Please enter the 8-digit code."
|
|
431
445
|
},
|
|
432
446
|
zh: {
|
|
433
|
-
|
|
434
|
-
|
|
447
|
+
emailExplanation: "\u6211\u4EEC\u4F1A\u53D1\u9001\u9A8C\u8BC1\u7801\uFF0C\u65E0\u9700\u5BC6\u7801\u3002",
|
|
448
|
+
otherMethods: "\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F",
|
|
449
|
+
passkeyCreateAccount: "\u4F7F\u7528 Passkey \u521B\u5EFA\u8D26\u6237",
|
|
450
|
+
lastUsed: "\u4E0A\u6B21\u4F7F\u7528\uFF1A{method}",
|
|
451
|
+
passkeyExplanation: "\u4F7F\u7528\u6307\u7EB9\u3001\u9762\u5BB9\u6216\u8BBE\u5907\u89E3\u9501\u65B9\u5F0F\u521B\u5EFA\u65B0\u8D26\u6237\uFF0C\u65E0\u9700\u5BC6\u7801\u3002\u8BBE\u5907\u4F1A\u63D0\u793A\u4FDD\u5B58 Passkey\u3002\u5DF2\u6709\u8D26\u6237\uFF1F\u8BF7\u8FD4\u56DE\u767B\u5F55\u3002",
|
|
452
|
+
continueWith: "\u4F7F\u7528 {provider} \u7EE7\u7EED",
|
|
453
|
+
oauthWaiting: "\u8BF7\u5728 {provider} \u7A97\u53E3\u4E2D\u7EE7\u7EED",
|
|
454
|
+
oauthIncomplete: "\u767B\u5F55\u5C1A\u672A\u5B8C\u6210\u3002\u60A8\u53EF\u4EE5\u91CD\u65B0\u6253\u5F00\u7A97\u53E3\u6216\u9009\u62E9\u5176\u4ED6\u65B9\u5F0F\u3002",
|
|
455
|
+
oauthReopen: "\u91CD\u65B0\u6253\u5F00\u7A97\u53E3",
|
|
456
|
+
oauthFullPage: "\u5728\u5F53\u524D\u9875\u9762\u7EE7\u7EED",
|
|
457
|
+
emailEdit: "\u4FEE\u6539\u90AE\u7BB1",
|
|
458
|
+
emailCooldown: "{seconds} \u79D2\u540E\u91CD\u53D1",
|
|
459
|
+
walletRetry: "\u94B1\u5305\u672A\u6253\u5F00\uFF1F\u8BF7\u91CD\u8BD5\u6216\u626B\u63CF\u4E8C\u7EF4\u7801\u3002",
|
|
460
|
+
tryAgain: "\u91CD\u8BD5",
|
|
461
|
+
signIn: "\u767B\u5F55\u6216\u521B\u5EFA\u8D26\u6237",
|
|
462
|
+
signInTo: "\u767B\u5F55\u6216\u52A0\u5165 {appName}",
|
|
435
463
|
passkeyButton: "\u4F7F\u7528 Passkey \u7EE7\u7EED",
|
|
436
464
|
passkeySignIn: "\u4F7F\u7528 Passkey \u767B\u5F55",
|
|
437
465
|
passkeyCreate: "\u521B\u5EFA Passkey",
|
|
438
|
-
passkeyFirstTime: "\
|
|
466
|
+
passkeyFirstTime: "\u9996\u6B21\u4F7F\u7528\uFF1F\u521B\u5EFA\u8D26\u6237",
|
|
439
467
|
passkeyBackToSignIn: "\u8FD4\u56DE\u767B\u5F55",
|
|
440
468
|
passkeyWaiting: "\u6B63\u5728\u7B49\u5F85 Passkey\u2026",
|
|
441
469
|
passkeyCreating: "\u6B63\u5728\u521B\u5EFA Passkey\u2026",
|
|
442
|
-
passkeyNoLocal: "\
|
|
470
|
+
passkeyNoLocal: "\u8FD9\u6B21\u672A\u5B8C\u6210 Passkey \u9A8C\u8BC1\u3002\u8BF7\u91CD\u8BD5\u3001\u4F7F\u7528\u5176\u4ED6\u8BBE\u5907\uFF0C\u6216\u9009\u62E9\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F\u3002",
|
|
443
471
|
passkeyUseAnotherDevice: "\u7528\u5176\u4ED6\u8BBE\u5907\u767B\u5F55",
|
|
444
|
-
passkeyCreateInstead: "\
|
|
472
|
+
passkeyCreateInstead: "\u9996\u6B21\u4F7F\u7528\uFF1F\u521B\u5EFA\u8D26\u6237",
|
|
445
473
|
didWalletButton: "\u4F7F\u7528 DID Wallet \u7EE7\u7EED",
|
|
446
474
|
didWalletButtonShort: "DID Wallet",
|
|
447
475
|
scanWithWallet: "\u8BF7\u4F7F\u7528 DID Wallet \u626B\u7801",
|
|
@@ -475,25 +503,39 @@ var messages = {
|
|
|
475
503
|
emailSubmit: "\u53D1\u9001\u9A8C\u8BC1\u7801",
|
|
476
504
|
emailSending: "\u6B63\u5728\u53D1\u9001\u9A8C\u8BC1\u7801...",
|
|
477
505
|
emailSent: "\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001\u81F3 {email}",
|
|
478
|
-
emailCodePlaceholder: "
|
|
506
|
+
emailCodePlaceholder: "8 \u4F4D\u9A8C\u8BC1\u7801",
|
|
479
507
|
emailVerify: "\u9A8C\u8BC1",
|
|
480
508
|
emailResend: "\u91CD\u65B0\u53D1\u9001",
|
|
481
509
|
emailInvalid: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740\u3002",
|
|
482
|
-
emailCodeInvalid: "\u8BF7\u8F93\u5165
|
|
510
|
+
emailCodeInvalid: "\u8BF7\u8F93\u5165 8 \u4F4D\u9A8C\u8BC1\u7801\u3002"
|
|
483
511
|
},
|
|
484
512
|
"zh-TW": {
|
|
485
|
-
|
|
486
|
-
|
|
513
|
+
emailExplanation: "\u6211\u5011\u6703\u50B3\u9001\u9A57\u8B49\u78BC\uFF0C\u7121\u9700\u5BC6\u78BC\u3002",
|
|
514
|
+
otherMethods: "\u5176\u4ED6\u767B\u5165\u65B9\u5F0F",
|
|
515
|
+
passkeyCreateAccount: "\u4F7F\u7528 Passkey \u5EFA\u7ACB\u5E33\u6236",
|
|
516
|
+
lastUsed: "\u4E0A\u6B21\u4F7F\u7528\uFF1A{method}",
|
|
517
|
+
passkeyExplanation: "\u4F7F\u7528\u6307\u7D0B\u3001\u9762\u5BB9\u6216\u88DD\u7F6E\u89E3\u9396\u65B9\u5F0F\u5EFA\u7ACB\u65B0\u5E33\u6236\uFF0C\u7121\u9700\u5BC6\u78BC\u3002\u88DD\u7F6E\u6703\u63D0\u793A\u5132\u5B58 Passkey\u3002\u5DF2\u6709\u5E33\u6236\uFF1F\u8ACB\u8FD4\u56DE\u767B\u5165\u3002",
|
|
518
|
+
continueWith: "\u4F7F\u7528 {provider} \u7E7C\u7E8C",
|
|
519
|
+
oauthWaiting: "\u8ACB\u5728 {provider} \u8996\u7A97\u4E2D\u7E7C\u7E8C",
|
|
520
|
+
oauthIncomplete: "\u767B\u5165\u5C1A\u672A\u5B8C\u6210\u3002\u60A8\u53EF\u4EE5\u91CD\u65B0\u958B\u555F\u8996\u7A97\u6216\u9078\u64C7\u5176\u4ED6\u65B9\u5F0F\u3002",
|
|
521
|
+
oauthReopen: "\u91CD\u65B0\u958B\u555F\u8996\u7A97",
|
|
522
|
+
oauthFullPage: "\u5728\u76EE\u524D\u9801\u9762\u7E7C\u7E8C",
|
|
523
|
+
emailEdit: "\u4FEE\u6539\u96FB\u5B50\u90F5\u4EF6",
|
|
524
|
+
emailCooldown: "{seconds} \u79D2\u5F8C\u91CD\u65B0\u50B3\u9001",
|
|
525
|
+
walletRetry: "\u9322\u5305\u672A\u958B\u555F\uFF1F\u8ACB\u91CD\u8A66\u6216\u6383\u78BC\u3002",
|
|
526
|
+
tryAgain: "\u91CD\u8A66",
|
|
527
|
+
signIn: "\u767B\u5165\u6216\u5EFA\u7ACB\u5E33\u6236",
|
|
528
|
+
signInTo: "\u767B\u5165\u6216\u52A0\u5165 {appName}",
|
|
487
529
|
passkeyButton: "\u4F7F\u7528 Passkey \u7E7C\u7E8C",
|
|
488
530
|
passkeySignIn: "\u4F7F\u7528 Passkey \u767B\u5165",
|
|
489
531
|
passkeyCreate: "\u5EFA\u7ACB Passkey",
|
|
490
|
-
passkeyFirstTime: "\u7B2C\u4E00\u6B21\u4F7F\u7528\uFF1F\u5EFA\u7ACB
|
|
532
|
+
passkeyFirstTime: "\u7B2C\u4E00\u6B21\u4F7F\u7528\uFF1F\u5EFA\u7ACB\u5E33\u6236",
|
|
491
533
|
passkeyBackToSignIn: "\u8FD4\u56DE\u767B\u5165",
|
|
492
534
|
passkeyWaiting: "\u6B63\u5728\u7B49\u5F85 Passkey\u2026",
|
|
493
535
|
passkeyCreating: "\u6B63\u5728\u5EFA\u7ACB Passkey\u2026",
|
|
494
|
-
passkeyNoLocal: "\
|
|
536
|
+
passkeyNoLocal: "\u9019\u6B21\u672A\u5B8C\u6210 Passkey \u9A57\u8B49\u3002\u8ACB\u91CD\u8A66\u3001\u4F7F\u7528\u5176\u4ED6\u88DD\u7F6E\uFF0C\u6216\u9078\u64C7\u5176\u4ED6\u767B\u5165\u65B9\u5F0F\u3002",
|
|
495
537
|
passkeyUseAnotherDevice: "\u7528\u5176\u4ED6\u88DD\u7F6E\u767B\u5165",
|
|
496
|
-
passkeyCreateInstead: "\
|
|
538
|
+
passkeyCreateInstead: "\u7B2C\u4E00\u6B21\u4F7F\u7528\uFF1F\u5EFA\u7ACB\u5E33\u6236",
|
|
497
539
|
didWalletButton: "\u4F7F\u7528 DID Wallet \u7E7C\u7E8C",
|
|
498
540
|
didWalletButtonShort: "DID Wallet",
|
|
499
541
|
scanWithWallet: "\u8ACB\u4F7F\u7528 DID Wallet \u6383\u78BC",
|
|
@@ -527,25 +569,39 @@ var messages = {
|
|
|
527
569
|
emailSubmit: "\u50B3\u9001\u9A57\u8B49\u78BC",
|
|
528
570
|
emailSending: "\u6B63\u5728\u50B3\u9001\u9A57\u8B49\u78BC...",
|
|
529
571
|
emailSent: "\u9A57\u8B49\u78BC\u5DF2\u50B3\u9001\u81F3 {email}",
|
|
530
|
-
emailCodePlaceholder: "
|
|
572
|
+
emailCodePlaceholder: "8 \u4F4D\u6578\u9A57\u8B49\u78BC",
|
|
531
573
|
emailVerify: "\u9A57\u8B49",
|
|
532
574
|
emailResend: "\u91CD\u65B0\u50B3\u9001",
|
|
533
575
|
emailInvalid: "\u8ACB\u8F38\u5165\u6709\u6548\u7684\u96FB\u5B50\u90F5\u4EF6\u5730\u5740\u3002",
|
|
534
|
-
emailCodeInvalid: "\u8ACB\u8F38\u5165
|
|
576
|
+
emailCodeInvalid: "\u8ACB\u8F38\u5165 8 \u4F4D\u6578\u9A57\u8B49\u78BC\u3002"
|
|
535
577
|
},
|
|
536
578
|
ja: {
|
|
537
|
-
|
|
538
|
-
|
|
579
|
+
emailExplanation: "\u78BA\u8A8D\u30B3\u30FC\u30C9\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u4E0D\u8981\u3067\u3059\u3002",
|
|
580
|
+
otherMethods: "\u4ED6\u306E\u30ED\u30B0\u30A4\u30F3\u65B9\u6CD5",
|
|
581
|
+
passkeyCreateAccount: "Passkey \u3067\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
|
|
582
|
+
lastUsed: "\u524D\u56DE\u306E\u65B9\u6CD5\uFF1A{method}",
|
|
583
|
+
passkeyExplanation: "\u6307\u7D0B\u3001\u9854\u8A8D\u8A3C\u3001\u307E\u305F\u306F\u30C7\u30D0\u30A4\u30B9\u306E\u30ED\u30C3\u30AF\u89E3\u9664\u3067\u65B0\u3057\u3044\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210\u3057\u307E\u3059\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u4E0D\u8981\u3067\u3059\u3002\u30C7\u30D0\u30A4\u30B9\u304C Passkey \u306E\u4FDD\u5B58\u3092\u6C42\u3081\u307E\u3059\u3002\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u304A\u6301\u3061\u3067\u3059\u304B\uFF1F\u30ED\u30B0\u30A4\u30F3\u306B\u623B\u3063\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
584
|
+
continueWith: "{provider} \u3067\u7D9A\u3051\u308B",
|
|
585
|
+
oauthWaiting: "{provider} \u306E\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u7D9A\u3051\u3066\u304F\u3060\u3055\u3044",
|
|
586
|
+
oauthIncomplete: "\u30ED\u30B0\u30A4\u30F3\u306F\u307E\u3060\u5B8C\u4E86\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3059\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
|
|
587
|
+
oauthReopen: "\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3059",
|
|
588
|
+
oauthFullPage: "\u3053\u306E\u30DA\u30FC\u30B8\u3067\u7D9A\u3051\u308B",
|
|
589
|
+
emailEdit: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5909\u66F4",
|
|
590
|
+
emailCooldown: "{seconds} \u79D2\u5F8C\u306B\u518D\u9001\u4FE1",
|
|
591
|
+
walletRetry: "\u30A6\u30A9\u30EC\u30C3\u30C8\u304C\u958B\u304B\u306A\u3044\u5834\u5408\u306F\u3001\u518D\u8A66\u884C\u3059\u308B\u304B QR \u30B3\u30FC\u30C9\u3092\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
592
|
+
tryAgain: "\u518D\u8A66\u884C",
|
|
593
|
+
signIn: "\u30ED\u30B0\u30A4\u30F3\u307E\u305F\u306F\u30A2\u30AB\u30A6\u30F3\u30C8\u4F5C\u6210",
|
|
594
|
+
signInTo: "{appName} \u306B\u30ED\u30B0\u30A4\u30F3\u307E\u305F\u306F\u53C2\u52A0",
|
|
539
595
|
passkeyButton: "Passkey \u3067\u7D9A\u3051\u308B",
|
|
540
596
|
passkeySignIn: "Passkey \u3067\u30ED\u30B0\u30A4\u30F3",
|
|
541
597
|
passkeyCreate: "Passkey \u3092\u4F5C\u6210",
|
|
542
|
-
passkeyFirstTime: "\u521D\u3081\u3066\u3067\u3059\u304B\
|
|
598
|
+
passkeyFirstTime: "\u521D\u3081\u3066\u3067\u3059\u304B\uFF1F\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
|
|
543
599
|
passkeyBackToSignIn: "\u30ED\u30B0\u30A4\u30F3\u306B\u623B\u308B",
|
|
544
600
|
passkeyWaiting: "Passkey \u3092\u5F85\u3063\u3066\u3044\u307E\u3059\u2026",
|
|
545
601
|
passkeyCreating: "Passkey \u3092\u4F5C\u6210\u3057\u3066\u3044\u307E\u3059\u2026",
|
|
546
|
-
passkeyNoLocal: "\
|
|
602
|
+
passkeyNoLocal: "\u4ECA\u56DE\u306F Passkey \u306E\u78BA\u8A8D\u304C\u5B8C\u4E86\u3057\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u304B\u3001\u5225\u306E\u30C7\u30D0\u30A4\u30B9\u3092\u4F7F\u3046\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
|
|
547
603
|
passkeyUseAnotherDevice: "\u5225\u306E\u30C7\u30D0\u30A4\u30B9\u3092\u4F7F\u7528",
|
|
548
|
-
passkeyCreateInstead: "\
|
|
604
|
+
passkeyCreateInstead: "\u521D\u3081\u3066\u3067\u3059\u304B\uFF1F\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
|
|
549
605
|
didWalletButton: "DID Wallet \u3067\u7D9A\u3051\u308B",
|
|
550
606
|
didWalletButtonShort: "DID Wallet",
|
|
551
607
|
scanWithWallet: "DID Wallet \u3067\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
@@ -578,12 +634,12 @@ var messages = {
|
|
|
578
634
|
emailTitle: "\u30E1\u30FC\u30EB\u3067\u7D9A\u3051\u308B",
|
|
579
635
|
emailSubmit: "\u30B3\u30FC\u30C9\u3092\u9001\u4FE1",
|
|
580
636
|
emailSending: "\u30B3\u30FC\u30C9\u3092\u9001\u4FE1\u3057\u3066\u3044\u307E\u3059...",
|
|
581
|
-
emailSent: "{email} \u306B
|
|
582
|
-
emailCodePlaceholder: "
|
|
637
|
+
emailSent: "{email} \u306B 8 \u6841\u306E\u30B3\u30FC\u30C9\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F",
|
|
638
|
+
emailCodePlaceholder: "8 \u6841\u306E\u30B3\u30FC\u30C9",
|
|
583
639
|
emailVerify: "\u78BA\u8A8D",
|
|
584
640
|
emailResend: "\u30B3\u30FC\u30C9\u3092\u518D\u9001\u4FE1",
|
|
585
641
|
emailInvalid: "\u6709\u52B9\u306A\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
586
|
-
emailCodeInvalid: "
|
|
642
|
+
emailCodeInvalid: "8 \u6841\u306E\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
587
643
|
}
|
|
588
644
|
};
|
|
589
645
|
var SUPPORTED_LOCALES = [
|
|
@@ -592,7 +648,7 @@ var SUPPORTED_LOCALES = [
|
|
|
592
648
|
];
|
|
593
649
|
function t(key, locale, params) {
|
|
594
650
|
const lang = locale && messages[locale] ? locale : "en";
|
|
595
|
-
let msg = messages[lang][key] ?? key;
|
|
651
|
+
let msg = messages[lang][key] ?? messages.en[key] ?? key;
|
|
596
652
|
if (params) {
|
|
597
653
|
for (const [k, v] of Object.entries(params)) {
|
|
598
654
|
msg = msg.replace(new RegExp(`\\{${k}\\}`, "g"), v);
|
|
@@ -2258,8 +2314,17 @@ var qrcode_default = qrcode;
|
|
|
2258
2314
|
var stringToBytes = qrcode.stringToBytes;
|
|
2259
2315
|
|
|
2260
2316
|
// dist/ui/qr-renderer.js
|
|
2317
|
+
var QR_PLATE_DARK = "#e8e8e8";
|
|
2318
|
+
var QR_PLATE_LIGHT = "#ffffff";
|
|
2319
|
+
function plateColor(container) {
|
|
2320
|
+
const root = container.getRootNode();
|
|
2321
|
+
const themeEl = root instanceof ShadowRoot ? root.host : document.documentElement;
|
|
2322
|
+
const theme = themeEl instanceof Element ? themeEl.getAttribute("data-theme") : null;
|
|
2323
|
+
return theme === "light" ? QR_PLATE_LIGHT : QR_PLATE_DARK;
|
|
2324
|
+
}
|
|
2261
2325
|
function renderQR(container, url, size = 200) {
|
|
2262
2326
|
clearQR(container);
|
|
2327
|
+
container.dataset.qrUrl = url;
|
|
2263
2328
|
const canvas = document.createElement("canvas");
|
|
2264
2329
|
const ctx = canvas.getContext?.("2d") ?? null;
|
|
2265
2330
|
if (!ctx) {
|
|
@@ -2283,7 +2348,7 @@ function renderQR(container, url, size = 200) {
|
|
|
2283
2348
|
canvas.style.width = `${size}px`;
|
|
2284
2349
|
canvas.style.height = `${size}px`;
|
|
2285
2350
|
canvas.style.imageRendering = "pixelated";
|
|
2286
|
-
const bgColor =
|
|
2351
|
+
const bgColor = plateColor(container);
|
|
2287
2352
|
ctx.fillStyle = bgColor;
|
|
2288
2353
|
ctx.fillRect(0, 0, actualSize, actualSize);
|
|
2289
2354
|
ctx.fillStyle = "#000000";
|
|
@@ -2295,12 +2360,18 @@ function renderQR(container, url, size = 200) {
|
|
|
2295
2360
|
}
|
|
2296
2361
|
}
|
|
2297
2362
|
const wrapper = document.createElement("div");
|
|
2298
|
-
wrapper.
|
|
2363
|
+
wrapper.className = "qr-plate";
|
|
2364
|
+
wrapper.style.display = "inline-block";
|
|
2365
|
+
wrapper.style.padding = "10px";
|
|
2366
|
+
wrapper.style.backgroundColor = bgColor;
|
|
2367
|
+
wrapper.style.borderRadius = "8px";
|
|
2368
|
+
wrapper.style.lineHeight = "0";
|
|
2299
2369
|
wrapper.appendChild(canvas);
|
|
2300
2370
|
container.appendChild(wrapper);
|
|
2301
2371
|
}
|
|
2302
2372
|
function clearQR(container) {
|
|
2303
2373
|
container.innerHTML = "";
|
|
2374
|
+
delete container.dataset.qrUrl;
|
|
2304
2375
|
}
|
|
2305
2376
|
|
|
2306
2377
|
// dist/ui/styles.js
|
|
@@ -2317,6 +2388,14 @@ body {
|
|
|
2317
2388
|
justify-content: center;
|
|
2318
2389
|
background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.08) 0%, transparent 60%);
|
|
2319
2390
|
}
|
|
2391
|
+
/* The body rule does not apply inside a shadow tree. The host page must
|
|
2392
|
+
give the LoginPage container a height for this centering to show. */
|
|
2393
|
+
:host {
|
|
2394
|
+
display: flex;
|
|
2395
|
+
align-items: center;
|
|
2396
|
+
justify-content: center;
|
|
2397
|
+
min-height: 100%;
|
|
2398
|
+
}
|
|
2320
2399
|
|
|
2321
2400
|
/* Card \u2014 fixed width prevents shrinking when views switch */
|
|
2322
2401
|
.card {
|
|
@@ -2614,19 +2693,25 @@ h1 {
|
|
|
2614
2693
|
/* Email section (methods view) */
|
|
2615
2694
|
.email-section { margin-top: 8px; }
|
|
2616
2695
|
|
|
2617
|
-
/* Back button
|
|
2696
|
+
/* Back button \u2014 same top row as Cancel and the locale/theme tools.
|
|
2697
|
+
A float sat inside the 48px content padding, so Back landed below the
|
|
2698
|
+
absolute locale pill. */
|
|
2618
2699
|
.back-btn {
|
|
2700
|
+
position: absolute;
|
|
2701
|
+
top: 16px;
|
|
2702
|
+
left: 40px;
|
|
2619
2703
|
display: inline-flex;
|
|
2620
2704
|
align-items: center;
|
|
2621
2705
|
gap: 4px;
|
|
2706
|
+
height: 28px;
|
|
2622
2707
|
background: none;
|
|
2623
2708
|
border: none;
|
|
2624
2709
|
color: var(--text-secondary);
|
|
2625
2710
|
font-size: 14px;
|
|
2711
|
+
line-height: 1;
|
|
2626
2712
|
cursor: pointer;
|
|
2627
2713
|
padding: 0;
|
|
2628
|
-
margin
|
|
2629
|
-
float: left;
|
|
2714
|
+
margin: 0;
|
|
2630
2715
|
}
|
|
2631
2716
|
.back-btn:hover { color: var(--text); }
|
|
2632
2717
|
.back-btn svg { width: 18px; height: 18px; }
|
|
@@ -2651,6 +2736,7 @@ h1 {
|
|
|
2651
2736
|
line-height: 1;
|
|
2652
2737
|
text-decoration: none;
|
|
2653
2738
|
cursor: pointer;
|
|
2739
|
+
height: 28px;
|
|
2654
2740
|
padding: 2px 4px 2px 0;
|
|
2655
2741
|
transition: color 0.15s ease;
|
|
2656
2742
|
}
|
|
@@ -2663,7 +2749,6 @@ h1 {
|
|
|
2663
2749
|
font-weight: 600;
|
|
2664
2750
|
color: var(--text-white);
|
|
2665
2751
|
margin-bottom: 8px;
|
|
2666
|
-
clear: both;
|
|
2667
2752
|
padding-top: 8px;
|
|
2668
2753
|
}
|
|
2669
2754
|
|
|
@@ -2837,23 +2922,32 @@ h1 {
|
|
|
2837
2922
|
[data-theme="light"] body {
|
|
2838
2923
|
background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.05) 0%, transparent 60%);
|
|
2839
2924
|
}
|
|
2840
|
-
[data-theme="light"] .card
|
|
2925
|
+
[data-theme="light"] .card,
|
|
2926
|
+
:host([data-theme="light"]) .card {
|
|
2841
2927
|
box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.08);
|
|
2842
2928
|
}
|
|
2843
|
-
[data-theme="light"] .btn:hover
|
|
2929
|
+
[data-theme="light"] .btn:hover,
|
|
2930
|
+
:host([data-theme="light"]) .btn:hover {
|
|
2844
2931
|
box-shadow: 0 2px 8px rgba(108,71,255,0.15);
|
|
2845
2932
|
}
|
|
2846
2933
|
|
|
2847
|
-
/* Language
|
|
2848
|
-
.
|
|
2934
|
+
/* Language sits on the same top row as Back / Cancel. */
|
|
2935
|
+
.card-tools {
|
|
2849
2936
|
position: absolute;
|
|
2850
2937
|
top: 16px;
|
|
2851
2938
|
right: 16px;
|
|
2939
|
+
z-index: 2;
|
|
2940
|
+
display: flex;
|
|
2941
|
+
align-items: flex-start;
|
|
2942
|
+
}
|
|
2943
|
+
.locale-switcher {
|
|
2944
|
+
position: relative;
|
|
2852
2945
|
}
|
|
2853
2946
|
.locale-btn {
|
|
2854
2947
|
display: inline-flex;
|
|
2855
2948
|
align-items: center;
|
|
2856
2949
|
gap: 4px;
|
|
2950
|
+
height: 28px;
|
|
2857
2951
|
padding: 4px 10px;
|
|
2858
2952
|
font-size: 12px;
|
|
2859
2953
|
color: var(--text-secondary);
|
|
@@ -2872,6 +2966,7 @@ h1 {
|
|
|
2872
2966
|
display: inline-flex;
|
|
2873
2967
|
align-items: center;
|
|
2874
2968
|
gap: 4px;
|
|
2969
|
+
height: 28px;
|
|
2875
2970
|
padding: 4px 10px;
|
|
2876
2971
|
font-size: 12px;
|
|
2877
2972
|
color: var(--text-secondary);
|
|
@@ -2901,6 +2996,58 @@ h1 {
|
|
|
2901
2996
|
}
|
|
2902
2997
|
.locale-check { font-size: 11px; line-height: 1; }
|
|
2903
2998
|
.card { position: relative; }
|
|
2999
|
+
|
|
3000
|
+
/* Long localized method labels remain readable on narrow screens. */
|
|
3001
|
+
.methods-grid { grid-template-columns: 1fr; }
|
|
3002
|
+
.method-btn { min-height: 48px; white-space: normal; }
|
|
3003
|
+
.passkey-alt { min-height: 44px; font-weight: 600; }
|
|
3004
|
+
.oauth-recovery { font-size: 14px; line-height: 1.5; margin: 12px 0; }
|
|
3005
|
+
@media (max-width: 480px) { .card { padding: 24px 20px; width: 100%; box-sizing: border-box; } h1 { font-size: 24px; } }
|
|
3006
|
+
|
|
3007
|
+
.passkey-create-entry { display: flex; justify-content: center; align-items: center; width: 100%; box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-surface); color: var(--text); padding: 10px 14px; }
|
|
3008
|
+
.passkey-create-entry:hover { background: var(--bg-hover); text-decoration: none; }
|
|
3009
|
+
.status:empty { min-height: 0; padding: 0; margin: 0; }
|
|
3010
|
+
.passkey-section .subtitle { margin: 8px 0 16px; font-size: 14px; line-height: 1.5; }
|
|
3011
|
+
@media (max-width: 480px) { .card { padding: 20px 18px; } .brand-header { margin-bottom: 20px; } }
|
|
3012
|
+
|
|
3013
|
+
.card-modal .card-tools { left: 20px; right: 80px; top: 12px; }
|
|
3014
|
+
.card-modal .locale-switcher { min-width: 0; max-width: 100%; }
|
|
3015
|
+
.card-modal .locale-trigger { max-width: 100%; height: 36px; }
|
|
3016
|
+
.locale-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3017
|
+
.card-modal .locale-menu { position: absolute; top: calc(100% + 8px); left: 0; z-index: 4; margin-top: 0;
|
|
3018
|
+
width: max-content; max-width: min(260px,calc(100vw - 80px)); padding: 6px;
|
|
3019
|
+
background: var(--bg-card); border: 1px solid var(--border-strong); border-radius: 10px; box-shadow: var(--shadow-lg); }
|
|
3020
|
+
.card-modal .locale-menu .locale-btn { min-height: 40px; height: auto; text-align: left; border-radius: 6px; }
|
|
3021
|
+
.card-modal .locale-menu .locale-btn span { overflow-wrap: anywhere; }
|
|
3022
|
+
|
|
3023
|
+
.card { padding: 52px 24px 24px; box-sizing: border-box; }
|
|
3024
|
+
.card-modal { width: 100%; }
|
|
3025
|
+
.card-modal { padding-top: 68px; border: 0; border-radius: 0; }
|
|
3026
|
+
/* The dialog frame owns the shadow in both palettes. */
|
|
3027
|
+
.card.card-modal, :host([data-theme="light"]) .card.card-modal { box-shadow: none; }
|
|
3028
|
+
/* A translateY entrance expands an auto scroller by 4px until it finishes. */
|
|
3029
|
+
.card-modal[data-view] { animation: none; }
|
|
3030
|
+
.card-modal .back-btn { position: static; display: flex; width: max-content; max-width: 100%; margin: 0 0 16px; }
|
|
3031
|
+
.card-modal h1 { display: block; overflow: visible; -webkit-line-clamp: unset; line-clamp: unset; line-height: 1.4; padding-block: 2px; }
|
|
3032
|
+
.card-modal h1[tabindex="-1"]:focus { outline: none; }
|
|
3033
|
+
.card-modal h1, .card-modal h2, .card-modal .subtitle, .card-modal .status { overflow-wrap: anywhere; }
|
|
3034
|
+
.card-modal .btn { height: auto; min-height: 44px; line-height: 1.4; padding-block: 10px; white-space: normal; }
|
|
3035
|
+
.card-modal button, .card-modal a { overflow-wrap: anywhere; }
|
|
3036
|
+
.social-first .divider { margin: 14px 0; }
|
|
3037
|
+
.social-first .app-logo { width: 32px; height: 32px; margin-bottom: 12px; }
|
|
3038
|
+
.social-first .login-footer { margin-top: 16px; padding-top: 12px; }
|
|
3039
|
+
.other-methods { margin-top: 8px; color: var(--text-secondary); font-size: 14px; }
|
|
3040
|
+
.other-methods summary { min-height: 44px; padding: 12px 0; cursor: pointer; }
|
|
3041
|
+
.other-methods summary:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
3042
|
+
@media (max-width:480px) { .card { padding: 52px 18px 24px; } }
|
|
3043
|
+
@media (max-width:480px) { .card-modal { padding-top: 68px; } .card-modal .card-tools { left: 18px; } }
|
|
3044
|
+
@media (prefers-reduced-motion: reduce) { .card[data-view], .qr-view { animation: none; } }
|
|
3045
|
+
|
|
3046
|
+
.passkey-creating > .methods-grid, .passkey-creating > .divider, .passkey-creating > .other-methods { display: none; }
|
|
3047
|
+
|
|
3048
|
+
.input-label { display: block; text-align: left; margin: 8px 0; color: var(--text); font-size: 14px; font-weight: 500; }
|
|
3049
|
+
.input-label[hidden], .email-help[hidden] { display: none; }
|
|
3050
|
+
.email-help { text-align: left; font-size: 13px; line-height: 1.5; color: var(--text-secondary); margin: 0 0 12px; }
|
|
2904
3051
|
`;
|
|
2905
3052
|
|
|
2906
3053
|
// dist/ui/webauthn-browser.js
|
|
@@ -2962,7 +3109,7 @@ async function runPasskeyAuth(options) {
|
|
|
2962
3109
|
const txtCreating = texts.creating || t("passkeyCreating", locale);
|
|
2963
3110
|
const txtVerifying = texts.verifying || t("verifying", locale);
|
|
2964
3111
|
async function fetchAuthChallenge() {
|
|
2965
|
-
const res = await fetch(`${prefix}/auth`, { method: "GET" });
|
|
3112
|
+
const res = await fetch(`${prefix}/auth`, { method: "GET", signal: options.signal });
|
|
2966
3113
|
if (!res.ok)
|
|
2967
3114
|
throw new Error(t("authFailed", locale));
|
|
2968
3115
|
return res.json();
|
|
@@ -2974,7 +3121,7 @@ async function runPasskeyAuth(options) {
|
|
|
2974
3121
|
if (invitationId)
|
|
2975
3122
|
params.push(`invitationId=${encodeURIComponent(invitationId)}`);
|
|
2976
3123
|
const qs = params.length ? `?${params.join("&")}` : "";
|
|
2977
|
-
const res = await fetch(`${prefix}/register${qs}`, { method: "GET" });
|
|
3124
|
+
const res = await fetch(`${prefix}/register${qs}`, { method: "GET", signal: options.signal });
|
|
2978
3125
|
if (!res.ok)
|
|
2979
3126
|
throw new Error(t("registrationFailed", locale));
|
|
2980
3127
|
return res.json();
|
|
@@ -2988,6 +3135,7 @@ async function runPasskeyAuth(options) {
|
|
|
2988
3135
|
async function verifyAuth(challengeId, credential) {
|
|
2989
3136
|
const res = await fetch(`${prefix}/auth`, {
|
|
2990
3137
|
method: "POST",
|
|
3138
|
+
signal: options.signal,
|
|
2991
3139
|
headers: { "Content-Type": "application/json" },
|
|
2992
3140
|
body: JSON.stringify({ challengeId, credential })
|
|
2993
3141
|
});
|
|
@@ -3000,6 +3148,7 @@ async function runPasskeyAuth(options) {
|
|
|
3000
3148
|
async function verifyRegister(challengeId, credential, name) {
|
|
3001
3149
|
const res = await fetch(`${prefix}/register`, {
|
|
3002
3150
|
method: "POST",
|
|
3151
|
+
signal: options.signal,
|
|
3003
3152
|
headers: { "Content-Type": "application/json" },
|
|
3004
3153
|
body: JSON.stringify({ challengeId, credential, name: name || void 0 })
|
|
3005
3154
|
});
|
|
@@ -3016,6 +3165,7 @@ async function runPasskeyAuth(options) {
|
|
|
3016
3165
|
const { challengeId } = challengeResult;
|
|
3017
3166
|
const authentication = challengeResult.authentication || challengeResult;
|
|
3018
3167
|
const authOpts = {
|
|
3168
|
+
signal: options.signal,
|
|
3019
3169
|
publicKey: {
|
|
3020
3170
|
...authentication,
|
|
3021
3171
|
challenge: b64urlDecode(authentication.challenge),
|
|
@@ -3033,6 +3183,10 @@ async function runPasskeyAuth(options) {
|
|
|
3033
3183
|
return { success: false, error: "no_passkey" };
|
|
3034
3184
|
}
|
|
3035
3185
|
}
|
|
3186
|
+
if (!raw && authOnly)
|
|
3187
|
+
return { success: false, error: "no_passkey" };
|
|
3188
|
+
if (options.signal?.aborted)
|
|
3189
|
+
return { success: false, error: "cancelled" };
|
|
3036
3190
|
if (raw) {
|
|
3037
3191
|
const credential = serializeAuthentication(raw);
|
|
3038
3192
|
onStatus(txtVerifying, false);
|
|
@@ -3046,15 +3200,16 @@ async function runPasskeyAuth(options) {
|
|
|
3046
3200
|
return { success: false, error: errMsg, code: errCode };
|
|
3047
3201
|
}
|
|
3048
3202
|
}
|
|
3049
|
-
const challengeData = await fetchRegisterChallenge(options.name || "");
|
|
3050
|
-
if (challengeData.registrationAllowed === false) {
|
|
3051
|
-
onStatus(t("regClosed", locale), true);
|
|
3052
|
-
return { success: false, error: "registration_closed" };
|
|
3053
|
-
}
|
|
3054
|
-
onStatus(txtCreating, false);
|
|
3055
3203
|
try {
|
|
3204
|
+
const challengeData = await fetchRegisterChallenge(options.name || "");
|
|
3205
|
+
if (challengeData.registrationAllowed === false) {
|
|
3206
|
+
onStatus(t("regClosed", locale), true);
|
|
3207
|
+
return { success: false, error: "registration_closed" };
|
|
3208
|
+
}
|
|
3209
|
+
onStatus(txtCreating, false);
|
|
3056
3210
|
const registration = challengeData.registration || challengeData;
|
|
3057
3211
|
const regOpts = {
|
|
3212
|
+
signal: options.signal,
|
|
3058
3213
|
publicKey: {
|
|
3059
3214
|
...registration,
|
|
3060
3215
|
challenge: b64urlDecode(registration.challenge),
|
|
@@ -3069,12 +3224,14 @@ async function runPasskeyAuth(options) {
|
|
|
3069
3224
|
}
|
|
3070
3225
|
};
|
|
3071
3226
|
const raw = await navigator.credentials.create(regOpts);
|
|
3227
|
+
if (!raw || options.signal?.aborted)
|
|
3228
|
+
return { success: false, error: t("passkeyNoLocal", locale) };
|
|
3072
3229
|
const credential = serializeRegistration(raw);
|
|
3073
3230
|
onStatus(txtVerifying, false);
|
|
3074
3231
|
await verifyRegister(challengeData.challengeId, credential, options.name || "");
|
|
3075
3232
|
return { success: true };
|
|
3076
3233
|
} catch (regErr) {
|
|
3077
|
-
const errMsg = regErr instanceof Error ? regErr.message : t("passkeySetupFailed", locale);
|
|
3234
|
+
const errMsg = regErr instanceof Error && (regErr.name === "NotAllowedError" || regErr.name === "AbortError") ? t("passkeyNoLocal", locale) : regErr instanceof Error ? regErr.message : t("passkeySetupFailed", locale);
|
|
3078
3235
|
const errCode = regErr instanceof Error ? regErr.code : void 0;
|
|
3079
3236
|
onStatus(errMsg, true);
|
|
3080
3237
|
return { success: false, error: errMsg, code: errCode };
|
|
@@ -3113,15 +3270,42 @@ var LoginPage = class {
|
|
|
3113
3270
|
tokenSession = null;
|
|
3114
3271
|
styleEl = null;
|
|
3115
3272
|
mounted = false;
|
|
3273
|
+
completed = false;
|
|
3274
|
+
generation = 0;
|
|
3275
|
+
activeMethod;
|
|
3276
|
+
activeProvider;
|
|
3277
|
+
oauthCleanup;
|
|
3278
|
+
emailTimer;
|
|
3279
|
+
emailDraft = "";
|
|
3280
|
+
abort;
|
|
3281
|
+
emit(name) {
|
|
3282
|
+
try {
|
|
3283
|
+
this.options.onEvent?.({ name, method: this.activeMethod, mode: this.options.mode ?? "page" });
|
|
3284
|
+
} catch {
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
select(method) {
|
|
3288
|
+
this.generation++;
|
|
3289
|
+
this.oauthCleanup?.();
|
|
3290
|
+
this.abort?.abort();
|
|
3291
|
+
this.abort = new AbortController();
|
|
3292
|
+
this.activeMethod = method;
|
|
3293
|
+
this.activeProvider = void 0;
|
|
3294
|
+
this.emit("method_selected");
|
|
3295
|
+
}
|
|
3116
3296
|
currentView = "methods";
|
|
3117
3297
|
cardEl = null;
|
|
3118
3298
|
mediaQuery = null;
|
|
3119
3299
|
mediaListener = null;
|
|
3120
3300
|
localeOutsideClick = null;
|
|
3301
|
+
/** Open shadow root when isolate is set. Reused across destroy/mount. */
|
|
3302
|
+
shadow = null;
|
|
3303
|
+
isolate;
|
|
3121
3304
|
constructor(container, config, http, options) {
|
|
3122
3305
|
this.container = container;
|
|
3123
3306
|
this.config = config;
|
|
3124
3307
|
this.http = http;
|
|
3308
|
+
this.isolate = options?.isolate === true;
|
|
3125
3309
|
this.options = {
|
|
3126
3310
|
methods: ["passkey", "did-connect"],
|
|
3127
3311
|
locale: "en",
|
|
@@ -3142,32 +3326,50 @@ var LoginPage = class {
|
|
|
3142
3326
|
if (this.mounted)
|
|
3143
3327
|
return;
|
|
3144
3328
|
this.mounted = true;
|
|
3329
|
+
this.completed = false;
|
|
3330
|
+
this.emit("view");
|
|
3145
3331
|
this.styleEl = document.createElement("style");
|
|
3146
3332
|
this.styleEl.textContent = LOGIN_PAGE_STYLES;
|
|
3147
|
-
this.
|
|
3333
|
+
this.mountRoot().appendChild(this.styleEl);
|
|
3334
|
+
this.adoptStoredTheme();
|
|
3148
3335
|
this.applyTheme();
|
|
3149
3336
|
const available = this.filterMethods(this.options.methods);
|
|
3150
3337
|
this.renderCard(available);
|
|
3151
3338
|
}
|
|
3152
3339
|
/** Destroy the login UI, clean up timers and listeners. Idempotent. */
|
|
3153
3340
|
destroy() {
|
|
3341
|
+
this.generation++;
|
|
3342
|
+
this.oauthCleanup?.();
|
|
3343
|
+
this.abort?.abort();
|
|
3344
|
+
clearInterval(this.emailTimer);
|
|
3154
3345
|
this.detachLocaleMenu();
|
|
3155
3346
|
if (this.tokenSession) {
|
|
3156
3347
|
this.tokenSession.destroy();
|
|
3157
3348
|
this.tokenSession = null;
|
|
3158
3349
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
this.
|
|
3162
|
-
|
|
3350
|
+
this.detachMedia();
|
|
3351
|
+
if (this.isolate) {
|
|
3352
|
+
const shadow = this.shadow ?? this.container.shadowRoot;
|
|
3353
|
+
if (shadow)
|
|
3354
|
+
shadow.innerHTML = "";
|
|
3355
|
+
this.container.removeAttribute("data-theme");
|
|
3356
|
+
} else {
|
|
3357
|
+
document.documentElement.removeAttribute("data-theme");
|
|
3358
|
+
this.container.innerHTML = "";
|
|
3163
3359
|
}
|
|
3164
|
-
document.documentElement.removeAttribute("data-theme");
|
|
3165
|
-
this.container.innerHTML = "";
|
|
3166
3360
|
this.styleEl = null;
|
|
3167
3361
|
this.cardEl = null;
|
|
3168
3362
|
this.currentView = "methods";
|
|
3169
3363
|
this.mounted = false;
|
|
3170
3364
|
}
|
|
3365
|
+
/**
|
|
3366
|
+
* Apply a light or dark theme without remounting.
|
|
3367
|
+
* The host writes `web-mode` itself. Isolate updates the shadow host only.
|
|
3368
|
+
*/
|
|
3369
|
+
setTheme(theme) {
|
|
3370
|
+
this.options.theme = theme;
|
|
3371
|
+
this.applyTheme();
|
|
3372
|
+
}
|
|
3171
3373
|
/** Update options and re-render. */
|
|
3172
3374
|
update(newOptions) {
|
|
3173
3375
|
Object.assign(this.options, newOptions);
|
|
@@ -3177,24 +3379,82 @@ var LoginPage = class {
|
|
|
3177
3379
|
}
|
|
3178
3380
|
}
|
|
3179
3381
|
// ─── Private: theme ────────────────────────────────────────────────
|
|
3382
|
+
/** Light DOM themes <html>. Isolate themes the shadow host only. */
|
|
3383
|
+
themeTarget() {
|
|
3384
|
+
return this.isolate ? this.container : document.documentElement;
|
|
3385
|
+
}
|
|
3386
|
+
/** Shadow root created once. attachShadow throws if called again. */
|
|
3387
|
+
mountRoot() {
|
|
3388
|
+
if (!this.isolate)
|
|
3389
|
+
return this.container;
|
|
3390
|
+
if (this.shadow)
|
|
3391
|
+
return this.shadow;
|
|
3392
|
+
this.shadow = this.container.shadowRoot ?? this.container.attachShadow({ mode: "open" });
|
|
3393
|
+
return this.shadow;
|
|
3394
|
+
}
|
|
3395
|
+
detachMedia() {
|
|
3396
|
+
if (this.mediaQuery && this.mediaListener) {
|
|
3397
|
+
this.mediaQuery.removeEventListener("change", this.mediaListener);
|
|
3398
|
+
}
|
|
3399
|
+
this.mediaQuery = null;
|
|
3400
|
+
this.mediaListener = null;
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Same order as theme/init.js: `?theme=` is a preview and wins;
|
|
3404
|
+
* otherwise `localStorage["web-mode"]`. Neither write happens here.
|
|
3405
|
+
*/
|
|
3406
|
+
adoptStoredTheme() {
|
|
3407
|
+
if (this.options.mode === "dialog")
|
|
3408
|
+
return;
|
|
3409
|
+
if (typeof window === "undefined")
|
|
3410
|
+
return;
|
|
3411
|
+
const urlTheme = new URLSearchParams(window.location.search).get("theme");
|
|
3412
|
+
if (urlTheme === "light" || urlTheme === "dark" || urlTheme === "auto") {
|
|
3413
|
+
this.options.theme = urlTheme;
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
try {
|
|
3417
|
+
const stored = localStorage.getItem("web-mode");
|
|
3418
|
+
if (stored === "light" || stored === "dark" || stored === "auto")
|
|
3419
|
+
this.options.theme = stored;
|
|
3420
|
+
} catch {
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
/** light sets data-theme. dark removes it. auto follows the OS. */
|
|
3180
3424
|
applyTheme() {
|
|
3425
|
+
this.detachMedia();
|
|
3181
3426
|
const theme = this.options.theme ?? "dark";
|
|
3427
|
+
const target = this.themeTarget();
|
|
3182
3428
|
if (theme === "light") {
|
|
3183
|
-
|
|
3429
|
+
target.dataset.theme = "light";
|
|
3184
3430
|
} else if (theme === "auto" && typeof window.matchMedia === "function") {
|
|
3185
3431
|
const mq = window.matchMedia("(prefers-color-scheme: light)");
|
|
3186
3432
|
this.mediaQuery = mq;
|
|
3187
3433
|
const apply = (light) => {
|
|
3188
|
-
if (light)
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3434
|
+
if (light)
|
|
3435
|
+
target.dataset.theme = "light";
|
|
3436
|
+
else
|
|
3437
|
+
target.removeAttribute("data-theme");
|
|
3438
|
+
this.repaintQRPlate();
|
|
3193
3439
|
};
|
|
3194
3440
|
apply(mq.matches);
|
|
3195
3441
|
this.mediaListener = (e) => apply(e.matches);
|
|
3196
3442
|
mq.addEventListener("change", this.mediaListener);
|
|
3443
|
+
return;
|
|
3444
|
+
} else {
|
|
3445
|
+
target.removeAttribute("data-theme");
|
|
3197
3446
|
}
|
|
3447
|
+
this.repaintQRPlate();
|
|
3448
|
+
}
|
|
3449
|
+
/** Redraw a visible QR so its plate matches the theme. Leaves a timeout overlay alone. */
|
|
3450
|
+
repaintQRPlate() {
|
|
3451
|
+
const qr = this.mountRoot().querySelector("#did-connect-qr");
|
|
3452
|
+
if (!(qr instanceof HTMLElement))
|
|
3453
|
+
return;
|
|
3454
|
+
const url = qr.dataset.qrUrl;
|
|
3455
|
+
if (!url || !qr.querySelector("canvas") || qr.querySelector(".qr-timeout-overlay"))
|
|
3456
|
+
return;
|
|
3457
|
+
renderQR(qr, url);
|
|
3198
3458
|
}
|
|
3199
3459
|
// ─── Private: rendering ──────────────────────────────────────────────
|
|
3200
3460
|
filterMethods(methods) {
|
|
@@ -3207,11 +3467,18 @@ var LoginPage = class {
|
|
|
3207
3467
|
}
|
|
3208
3468
|
renderCard(methods) {
|
|
3209
3469
|
const card = document.createElement("div");
|
|
3210
|
-
card.className = "card";
|
|
3470
|
+
card.className = this.options.mode === "dialog" ? "card card-modal" : "card";
|
|
3211
3471
|
this.cardEl = card;
|
|
3212
|
-
this.
|
|
3472
|
+
this.mountRoot().appendChild(card);
|
|
3213
3473
|
this.switchView("methods", methods);
|
|
3214
3474
|
}
|
|
3475
|
+
/** Locale sits on the card's top-right row, level with Back / Cancel. */
|
|
3476
|
+
renderCardTools(card) {
|
|
3477
|
+
const tools = document.createElement("div");
|
|
3478
|
+
tools.className = "card-tools";
|
|
3479
|
+
this.renderLocaleSwitcher(tools);
|
|
3480
|
+
card.appendChild(tools);
|
|
3481
|
+
}
|
|
3215
3482
|
renderLocaleSwitcher(card) {
|
|
3216
3483
|
const passed = this.options.locales;
|
|
3217
3484
|
if (passed) {
|
|
@@ -3250,7 +3517,7 @@ var LoginPage = class {
|
|
|
3250
3517
|
trigger.className = "locale-trigger";
|
|
3251
3518
|
trigger.setAttribute("aria-haspopup", "menu");
|
|
3252
3519
|
trigger.setAttribute("aria-expanded", "false");
|
|
3253
|
-
trigger.innerHTML = `${ICON_GLOBE}
|
|
3520
|
+
trigger.innerHTML = `${ICON_GLOBE} <span class="locale-label">${escapeHtml(current.label)}</span>`;
|
|
3254
3521
|
trigger.title = current.label;
|
|
3255
3522
|
const menu = document.createElement("div");
|
|
3256
3523
|
menu.className = "locale-menu";
|
|
@@ -3291,8 +3558,12 @@ var LoginPage = class {
|
|
|
3291
3558
|
menu.appendChild(btn);
|
|
3292
3559
|
}
|
|
3293
3560
|
const onOutsideClick = (event) => {
|
|
3294
|
-
|
|
3295
|
-
|
|
3561
|
+
const path = event.composedPath();
|
|
3562
|
+
if (path.includes(wrapper))
|
|
3563
|
+
return;
|
|
3564
|
+
if (this.isolate && path.includes(this.container))
|
|
3565
|
+
return;
|
|
3566
|
+
setOpen(false);
|
|
3296
3567
|
};
|
|
3297
3568
|
document.addEventListener("click", onOutsideClick);
|
|
3298
3569
|
this.localeOutsideClick = onOutsideClick;
|
|
@@ -3316,10 +3587,14 @@ var LoginPage = class {
|
|
|
3316
3587
|
this.tokenSession = null;
|
|
3317
3588
|
}
|
|
3318
3589
|
}
|
|
3590
|
+
this.generation++;
|
|
3591
|
+
this.oauthCleanup?.();
|
|
3592
|
+
this.abort?.abort();
|
|
3593
|
+
clearInterval(this.emailTimer);
|
|
3319
3594
|
this.currentView = view;
|
|
3320
3595
|
this.cardEl.innerHTML = "";
|
|
3321
3596
|
this.cardEl.dataset.view = view;
|
|
3322
|
-
this.
|
|
3597
|
+
this.renderCardTools(this.cardEl);
|
|
3323
3598
|
switch (view) {
|
|
3324
3599
|
case "methods":
|
|
3325
3600
|
this.renderMethodsView(this.cardEl, locale, methods ?? this.filterMethods(this.options.methods));
|
|
@@ -3333,8 +3608,24 @@ var LoginPage = class {
|
|
|
3333
3608
|
}
|
|
3334
3609
|
}
|
|
3335
3610
|
renderMethodsView(card, locale, methods) {
|
|
3336
|
-
this.
|
|
3611
|
+
if (this.options.mode !== "dialog")
|
|
3612
|
+
this.renderHomeLink(card, locale);
|
|
3337
3613
|
this.renderBrandHeader(card, locale);
|
|
3614
|
+
if (this.options.preferenceKey) {
|
|
3615
|
+
try {
|
|
3616
|
+
const last = localStorage.getItem(`did-login-method:${this.options.preferenceKey}`);
|
|
3617
|
+
const labels = { passkey: "Passkey", oauth: "OAuth", email: t("emailButton", locale), "did-connect": "DID Wallet" };
|
|
3618
|
+
const provider = last?.startsWith("oauth:") ? this.options.oauthProviders?.find((p) => p.enabled && `oauth:${p.id}` === last) : void 0;
|
|
3619
|
+
const label = provider?.name ?? (last ? labels[last] : void 0);
|
|
3620
|
+
if (last && label && (methods.includes(last) || provider && methods.includes("oauth"))) {
|
|
3621
|
+
const hint = document.createElement("p");
|
|
3622
|
+
hint.className = "subtitle";
|
|
3623
|
+
hint.textContent = t("lastUsed", locale, { method: label });
|
|
3624
|
+
card.appendChild(hint);
|
|
3625
|
+
}
|
|
3626
|
+
} catch {
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3338
3629
|
const requestedPasskey = this.options.methods.includes("passkey");
|
|
3339
3630
|
if (requestedPasskey && !methods.includes("passkey")) {
|
|
3340
3631
|
const warn = document.createElement("div");
|
|
@@ -3342,6 +3633,12 @@ var LoginPage = class {
|
|
|
3342
3633
|
warn.textContent = t("passkeyUnsupported", locale);
|
|
3343
3634
|
card.appendChild(warn);
|
|
3344
3635
|
}
|
|
3636
|
+
const socialFirst = this.options.preferredLayout !== "passkey-first" && methods.includes("oauth") && this.options.oauthProviders?.some((p) => p.enabled && ["google", "apple"].includes(p.id));
|
|
3637
|
+
card.classList.toggle("social-first", !!socialFirst);
|
|
3638
|
+
if (socialFirst) {
|
|
3639
|
+
this.renderSecondaryGrid(card, locale, ["oauth"], "primary");
|
|
3640
|
+
this.renderDivider(card, locale);
|
|
3641
|
+
}
|
|
3345
3642
|
const hasPrimary = methods.includes("passkey");
|
|
3346
3643
|
if (hasPrimary) {
|
|
3347
3644
|
this.renderPasskeySection(card, locale);
|
|
@@ -3351,7 +3648,20 @@ var LoginPage = class {
|
|
|
3351
3648
|
this.renderDivider(card, locale);
|
|
3352
3649
|
}
|
|
3353
3650
|
if (secondary.length > 0) {
|
|
3354
|
-
|
|
3651
|
+
if (socialFirst) {
|
|
3652
|
+
if (methods.includes("email"))
|
|
3653
|
+
this.renderSecondaryGrid(card, locale, ["email"]);
|
|
3654
|
+
const details = document.createElement("details");
|
|
3655
|
+
details.className = "other-methods";
|
|
3656
|
+
const summary = document.createElement("summary");
|
|
3657
|
+
summary.textContent = t("otherMethods", locale);
|
|
3658
|
+
details.appendChild(summary);
|
|
3659
|
+
this.renderSecondaryGrid(details, locale, methods.filter((m) => m !== "email"), "secondary");
|
|
3660
|
+
if (details.querySelector("button"))
|
|
3661
|
+
card.appendChild(details);
|
|
3662
|
+
} else {
|
|
3663
|
+
this.renderSecondaryGrid(card, locale, methods);
|
|
3664
|
+
}
|
|
3355
3665
|
}
|
|
3356
3666
|
if (methods.length === 0 && !requestedPasskey) {
|
|
3357
3667
|
const msg = document.createElement("p");
|
|
@@ -3378,10 +3688,14 @@ var LoginPage = class {
|
|
|
3378
3688
|
walletBtn.id = "did-wallet-open-btn";
|
|
3379
3689
|
walletBtn.textContent = t("openInWallet", locale);
|
|
3380
3690
|
walletBtn.disabled = true;
|
|
3381
|
-
|
|
3691
|
+
if (isMobile())
|
|
3692
|
+
card.insertBefore(walletBtn, qrContainer);
|
|
3693
|
+
else
|
|
3694
|
+
card.appendChild(walletBtn);
|
|
3382
3695
|
const statusEl = document.createElement("div");
|
|
3383
3696
|
statusEl.id = "did-connect-status";
|
|
3384
3697
|
statusEl.className = "status";
|
|
3698
|
+
statusEl.setAttribute("role", "status");
|
|
3385
3699
|
card.appendChild(statusEl);
|
|
3386
3700
|
this.renderTrustFooter(card, locale);
|
|
3387
3701
|
this.startDIDConnectInView(statusEl, qrContainer, walletBtn, locale);
|
|
@@ -3393,9 +3707,26 @@ var LoginPage = class {
|
|
|
3393
3707
|
title.className = "view-title";
|
|
3394
3708
|
title.textContent = t("emailTitle", locale);
|
|
3395
3709
|
card.appendChild(title);
|
|
3710
|
+
const emailLabel = document.createElement("label");
|
|
3711
|
+
emailLabel.className = "input-label";
|
|
3712
|
+
emailLabel.htmlFor = "login-email-address";
|
|
3713
|
+
emailLabel.textContent = t("emailPlaceholder", locale);
|
|
3714
|
+
card.appendChild(emailLabel);
|
|
3715
|
+
const emailHelp = document.createElement("p");
|
|
3716
|
+
emailHelp.className = "email-help";
|
|
3717
|
+
emailHelp.textContent = t("emailExplanation", locale);
|
|
3718
|
+
card.appendChild(emailHelp);
|
|
3396
3719
|
const emailInput = document.createElement("input");
|
|
3397
3720
|
emailInput.className = "input";
|
|
3721
|
+
emailInput.id = "login-email-address";
|
|
3398
3722
|
emailInput.type = "email";
|
|
3723
|
+
emailInput.required = true;
|
|
3724
|
+
emailInput.autocomplete = "email";
|
|
3725
|
+
emailInput.value = this.emailDraft;
|
|
3726
|
+
emailInput.setAttribute("aria-label", t("emailPlaceholder", locale));
|
|
3727
|
+
emailInput.addEventListener("input", () => {
|
|
3728
|
+
this.emailDraft = emailInput.value;
|
|
3729
|
+
});
|
|
3399
3730
|
emailInput.placeholder = t("emailPlaceholder", locale);
|
|
3400
3731
|
card.appendChild(emailInput);
|
|
3401
3732
|
const sendBtn = document.createElement("button");
|
|
@@ -3404,15 +3735,23 @@ var LoginPage = class {
|
|
|
3404
3735
|
card.appendChild(sendBtn);
|
|
3405
3736
|
const status = document.createElement("div");
|
|
3406
3737
|
status.className = "status";
|
|
3738
|
+
status.setAttribute("role", "status");
|
|
3407
3739
|
card.appendChild(status);
|
|
3408
3740
|
const codeSection = document.createElement("div");
|
|
3409
3741
|
codeSection.className = "email-code-section";
|
|
3410
3742
|
codeSection.style.display = "none";
|
|
3743
|
+
const codeLabel = document.createElement("label");
|
|
3744
|
+
codeLabel.className = "input-label";
|
|
3745
|
+
codeLabel.htmlFor = "login-email-code";
|
|
3746
|
+
codeLabel.textContent = t("emailCodePlaceholder", locale);
|
|
3747
|
+
codeSection.appendChild(codeLabel);
|
|
3411
3748
|
const codeInput = document.createElement("input");
|
|
3749
|
+
codeInput.id = "login-email-code";
|
|
3412
3750
|
codeInput.className = "input";
|
|
3413
3751
|
codeInput.type = "text";
|
|
3414
3752
|
codeInput.inputMode = "numeric";
|
|
3415
3753
|
codeInput.maxLength = 8;
|
|
3754
|
+
codeInput.setAttribute("aria-label", t("emailCodePlaceholder", locale));
|
|
3416
3755
|
codeInput.autocomplete = "one-time-code";
|
|
3417
3756
|
codeInput.placeholder = t("emailCodePlaceholder", locale);
|
|
3418
3757
|
codeSection.appendChild(codeInput);
|
|
@@ -3427,6 +3766,27 @@ var LoginPage = class {
|
|
|
3427
3766
|
codeSection.appendChild(resendBtn);
|
|
3428
3767
|
card.insertBefore(codeSection, status);
|
|
3429
3768
|
this.renderTrustFooter(card, locale);
|
|
3769
|
+
const epoch = this.generation;
|
|
3770
|
+
const isCurrent = () => this.mounted && epoch === this.generation;
|
|
3771
|
+
const editBtn = document.createElement("button");
|
|
3772
|
+
editBtn.className = "passkey-alt";
|
|
3773
|
+
editBtn.textContent = t("emailEdit", locale);
|
|
3774
|
+
codeSection.appendChild(editBtn);
|
|
3775
|
+
editBtn.addEventListener("click", () => this.switchView("email"));
|
|
3776
|
+
const cooldown = () => {
|
|
3777
|
+
let remaining = 60;
|
|
3778
|
+
clearInterval(this.emailTimer);
|
|
3779
|
+
resendBtn.disabled = true;
|
|
3780
|
+
resendBtn.textContent = t("emailCooldown", locale, { seconds: String(remaining) });
|
|
3781
|
+
this.emailTimer = setInterval(() => {
|
|
3782
|
+
remaining--;
|
|
3783
|
+
resendBtn.textContent = remaining > 0 ? t("emailCooldown", locale, { seconds: String(remaining) }) : t("emailResend", locale);
|
|
3784
|
+
if (remaining <= 0) {
|
|
3785
|
+
clearInterval(this.emailTimer);
|
|
3786
|
+
resendBtn.disabled = false;
|
|
3787
|
+
}
|
|
3788
|
+
}, 1e3);
|
|
3789
|
+
};
|
|
3430
3790
|
let currentEmail = "";
|
|
3431
3791
|
let currentCodeId;
|
|
3432
3792
|
const servicePrefix = this.config.servicePrefix ?? "/.well-known/service";
|
|
@@ -3434,7 +3794,7 @@ var LoginPage = class {
|
|
|
3434
3794
|
if (sendBtn.disabled)
|
|
3435
3795
|
return;
|
|
3436
3796
|
const email = emailInput.value.trim();
|
|
3437
|
-
if (!
|
|
3797
|
+
if (!emailInput.checkValidity()) {
|
|
3438
3798
|
status.textContent = t("emailInvalid", locale);
|
|
3439
3799
|
status.className = "status error";
|
|
3440
3800
|
return;
|
|
@@ -3442,11 +3802,17 @@ var LoginPage = class {
|
|
|
3442
3802
|
sendBtn.disabled = true;
|
|
3443
3803
|
status.textContent = t("emailSending", locale);
|
|
3444
3804
|
status.className = "status";
|
|
3805
|
+
status.setAttribute("role", "status");
|
|
3445
3806
|
try {
|
|
3446
3807
|
const sendResult = await this.http.post(`${servicePrefix}/api/email/sendCode`, { email, locale });
|
|
3808
|
+
if (!isCurrent())
|
|
3809
|
+
return;
|
|
3810
|
+
cooldown();
|
|
3447
3811
|
currentEmail = email;
|
|
3448
3812
|
currentCodeId = sendResult?.id;
|
|
3449
3813
|
emailInput.style.display = "none";
|
|
3814
|
+
emailLabel.hidden = true;
|
|
3815
|
+
emailHelp.hidden = true;
|
|
3450
3816
|
sendBtn.style.display = "none";
|
|
3451
3817
|
codeSection.style.display = "";
|
|
3452
3818
|
title.textContent = t("emailVerify", locale);
|
|
@@ -3454,6 +3820,8 @@ var LoginPage = class {
|
|
|
3454
3820
|
status.className = "status";
|
|
3455
3821
|
codeInput.focus();
|
|
3456
3822
|
} catch (err) {
|
|
3823
|
+
if (!isCurrent())
|
|
3824
|
+
return;
|
|
3457
3825
|
const msg = err instanceof Error ? err.message : "Failed to send email";
|
|
3458
3826
|
status.textContent = msg;
|
|
3459
3827
|
status.className = "status error";
|
|
@@ -3464,7 +3832,7 @@ var LoginPage = class {
|
|
|
3464
3832
|
if (verifyBtn.disabled)
|
|
3465
3833
|
return;
|
|
3466
3834
|
const code = codeInput.value.trim();
|
|
3467
|
-
if (
|
|
3835
|
+
if (!/^\d{8}$/.test(code)) {
|
|
3468
3836
|
status.textContent = t("emailCodeInvalid", locale);
|
|
3469
3837
|
status.className = "status error";
|
|
3470
3838
|
return;
|
|
@@ -3474,10 +3842,14 @@ var LoginPage = class {
|
|
|
3474
3842
|
status.className = "status";
|
|
3475
3843
|
try {
|
|
3476
3844
|
await this.http.post(`${servicePrefix}/api/email/login`, { code, id: currentCodeId });
|
|
3845
|
+
if (!isCurrent())
|
|
3846
|
+
return;
|
|
3477
3847
|
status.textContent = t("success", locale);
|
|
3478
3848
|
status.className = "status";
|
|
3479
3849
|
this.handleSuccess();
|
|
3480
3850
|
} catch (err) {
|
|
3851
|
+
if (!isCurrent())
|
|
3852
|
+
return;
|
|
3481
3853
|
if (err instanceof Error && "code" in err && err.code === "FORBIDDEN") {
|
|
3482
3854
|
this.showFullError(friendlyError(err, locale));
|
|
3483
3855
|
} else {
|
|
@@ -3500,20 +3872,27 @@ var LoginPage = class {
|
|
|
3500
3872
|
verifyCode();
|
|
3501
3873
|
});
|
|
3502
3874
|
resendBtn.addEventListener("click", async () => {
|
|
3875
|
+
if (resendBtn.disabled)
|
|
3876
|
+
return;
|
|
3503
3877
|
resendBtn.disabled = true;
|
|
3504
3878
|
status.textContent = t("emailSending", locale);
|
|
3505
3879
|
status.className = "status";
|
|
3506
3880
|
try {
|
|
3507
3881
|
const resendResult = await this.http.post(`${servicePrefix}/api/email/sendCode`, { email: currentEmail, locale });
|
|
3882
|
+
if (!isCurrent())
|
|
3883
|
+
return;
|
|
3884
|
+
cooldown();
|
|
3508
3885
|
currentCodeId = resendResult?.id;
|
|
3509
3886
|
status.textContent = t("emailSent", locale, { email: currentEmail });
|
|
3510
3887
|
status.className = "status";
|
|
3511
3888
|
} catch (err) {
|
|
3889
|
+
if (!isCurrent())
|
|
3890
|
+
return;
|
|
3891
|
+
resendBtn.disabled = false;
|
|
3512
3892
|
const msg = err instanceof Error ? err.message : "Failed to resend";
|
|
3513
3893
|
status.textContent = msg;
|
|
3514
3894
|
status.className = "status error";
|
|
3515
3895
|
}
|
|
3516
|
-
resendBtn.disabled = false;
|
|
3517
3896
|
});
|
|
3518
3897
|
}
|
|
3519
3898
|
renderBackButton(parent, locale) {
|
|
@@ -3535,6 +3914,8 @@ var LoginPage = class {
|
|
|
3535
3914
|
* sub-view back buttons that mean "return to the method list".
|
|
3536
3915
|
*/
|
|
3537
3916
|
renderHomeLink(parent, locale) {
|
|
3917
|
+
if (this.options.dismissible === false)
|
|
3918
|
+
return;
|
|
3538
3919
|
const { homeUrl, onCancel } = this.options;
|
|
3539
3920
|
if (!onCancel && !homeUrl)
|
|
3540
3921
|
return;
|
|
@@ -3544,7 +3925,10 @@ var LoginPage = class {
|
|
|
3544
3925
|
btn.type = "button";
|
|
3545
3926
|
btn.className = "home-link";
|
|
3546
3927
|
btn.innerHTML = label;
|
|
3547
|
-
btn.addEventListener("click", () =>
|
|
3928
|
+
btn.addEventListener("click", () => {
|
|
3929
|
+
this.emit("cancel");
|
|
3930
|
+
this.options.onCancel?.();
|
|
3931
|
+
});
|
|
3548
3932
|
parent.appendChild(btn);
|
|
3549
3933
|
} else {
|
|
3550
3934
|
const link = document.createElement("a");
|
|
@@ -3572,7 +3956,16 @@ var LoginPage = class {
|
|
|
3572
3956
|
} else {
|
|
3573
3957
|
h1.textContent = t("signIn", locale);
|
|
3574
3958
|
}
|
|
3959
|
+
if (this.options.purpose)
|
|
3960
|
+
h1.textContent = this.options.purpose.title;
|
|
3961
|
+
h1.id = "login-title";
|
|
3575
3962
|
header.appendChild(h1);
|
|
3963
|
+
if (this.options.purpose?.description) {
|
|
3964
|
+
const description = document.createElement("p");
|
|
3965
|
+
description.className = "subtitle";
|
|
3966
|
+
description.textContent = this.options.purpose.description;
|
|
3967
|
+
header.appendChild(description);
|
|
3968
|
+
}
|
|
3576
3969
|
if (this.options.masterAppName) {
|
|
3577
3970
|
const sub = document.createElement("p");
|
|
3578
3971
|
sub.className = "federated-hint";
|
|
@@ -3613,6 +4006,12 @@ var LoginPage = class {
|
|
|
3613
4006
|
}
|
|
3614
4007
|
footer.appendChild(links);
|
|
3615
4008
|
}
|
|
4009
|
+
if (this.currentView !== "methods" && this.options.mode !== "dialog") {
|
|
4010
|
+
this.renderHomeLink(footer, locale);
|
|
4011
|
+
const cancel = footer.querySelector(".home-link");
|
|
4012
|
+
if (cancel)
|
|
4013
|
+
cancel.style.cssText = "position:static;display:inline-flex;margin-top:12px";
|
|
4014
|
+
}
|
|
3616
4015
|
parent.appendChild(footer);
|
|
3617
4016
|
}
|
|
3618
4017
|
renderDivider(parent, locale) {
|
|
@@ -3673,14 +4072,24 @@ var LoginPage = class {
|
|
|
3673
4072
|
retryBtn.addEventListener("click", () => doSignIn());
|
|
3674
4073
|
const createLink = document.createElement("button");
|
|
3675
4074
|
createLink.type = "button";
|
|
3676
|
-
createLink.className = "passkey-alt";
|
|
4075
|
+
createLink.className = "passkey-alt passkey-create-entry";
|
|
3677
4076
|
createLink.textContent = t("passkeyCreateInstead", locale);
|
|
3678
4077
|
section.appendChild(createLink);
|
|
3679
4078
|
createLink.addEventListener("click", () => renderCreateState());
|
|
3680
4079
|
};
|
|
3681
4080
|
const renderCreateState = () => {
|
|
4081
|
+
this.cardEl?.classList.add("passkey-creating");
|
|
4082
|
+
this.abort?.abort();
|
|
4083
|
+
this.generation++;
|
|
3682
4084
|
section.innerHTML = "";
|
|
3683
4085
|
clearStatus();
|
|
4086
|
+
const heading = this.cardEl?.querySelector("h1");
|
|
4087
|
+
if (heading)
|
|
4088
|
+
heading.textContent = t("passkeyCreateAccount", locale);
|
|
4089
|
+
const explanation = document.createElement("p");
|
|
4090
|
+
explanation.className = "subtitle";
|
|
4091
|
+
explanation.textContent = t("passkeyExplanation", locale);
|
|
4092
|
+
section.appendChild(explanation);
|
|
3684
4093
|
const nameRow = document.createElement("div");
|
|
3685
4094
|
nameRow.className = "name-row visible";
|
|
3686
4095
|
nameRow.id = "passkey-name-row";
|
|
@@ -3688,6 +4097,7 @@ var LoginPage = class {
|
|
|
3688
4097
|
nameInput.className = "input";
|
|
3689
4098
|
nameInput.id = "passkey-name-input";
|
|
3690
4099
|
nameInput.type = "text";
|
|
4100
|
+
nameInput.setAttribute("aria-label", t("namePlaceholder", locale));
|
|
3691
4101
|
nameInput.placeholder = t("namePlaceholder", locale);
|
|
3692
4102
|
nameInput.autocomplete = "name";
|
|
3693
4103
|
nameRow.appendChild(nameInput);
|
|
@@ -3706,8 +4116,11 @@ var LoginPage = class {
|
|
|
3706
4116
|
if (createBtn.disabled)
|
|
3707
4117
|
return;
|
|
3708
4118
|
createBtn.disabled = true;
|
|
4119
|
+
this.select("passkey");
|
|
4120
|
+
const epoch = this.generation;
|
|
3709
4121
|
const result = await runPasskeyAuth({
|
|
3710
4122
|
registerOnly: true,
|
|
4123
|
+
signal: this.abort?.signal,
|
|
3711
4124
|
name: nameInput.value.trim(),
|
|
3712
4125
|
onStatus: setStatus,
|
|
3713
4126
|
texts: statusTexts,
|
|
@@ -3715,6 +4128,8 @@ var LoginPage = class {
|
|
|
3715
4128
|
prefix: this.options.passkeyPrefix,
|
|
3716
4129
|
invitationId: this.options.invitationId
|
|
3717
4130
|
});
|
|
4131
|
+
if (!this.mounted || epoch !== this.generation || this.abort?.signal.aborted)
|
|
4132
|
+
return;
|
|
3718
4133
|
if (handleResult(result))
|
|
3719
4134
|
return;
|
|
3720
4135
|
if (result.error)
|
|
@@ -3727,20 +4142,27 @@ var LoginPage = class {
|
|
|
3727
4142
|
doCreate();
|
|
3728
4143
|
});
|
|
3729
4144
|
backLink.addEventListener("click", () => renderSignInState());
|
|
3730
|
-
|
|
4145
|
+
createBtn.focus();
|
|
3731
4146
|
};
|
|
3732
4147
|
const doSignIn = async () => {
|
|
3733
4148
|
const btn = section.querySelector("#passkey-btn");
|
|
4149
|
+
if (btn?.disabled)
|
|
4150
|
+
return;
|
|
3734
4151
|
if (btn)
|
|
3735
4152
|
btn.disabled = true;
|
|
4153
|
+
this.select("passkey");
|
|
4154
|
+
const epoch = this.generation;
|
|
3736
4155
|
const result = await runPasskeyAuth({
|
|
3737
4156
|
authOnly: true,
|
|
4157
|
+
signal: this.abort?.signal,
|
|
3738
4158
|
onStatus: setStatus,
|
|
3739
4159
|
texts: statusTexts,
|
|
3740
4160
|
locale,
|
|
3741
4161
|
prefix: this.options.passkeyPrefix,
|
|
3742
4162
|
invitationId: this.options.invitationId
|
|
3743
4163
|
});
|
|
4164
|
+
if (!this.mounted || epoch !== this.generation || this.abort?.signal.aborted)
|
|
4165
|
+
return;
|
|
3744
4166
|
if (handleResult(result))
|
|
3745
4167
|
return;
|
|
3746
4168
|
if (result.error && result.error !== "no_passkey") {
|
|
@@ -3749,20 +4171,27 @@ var LoginPage = class {
|
|
|
3749
4171
|
btn.disabled = false;
|
|
3750
4172
|
return;
|
|
3751
4173
|
}
|
|
4174
|
+
this.emit("incomplete");
|
|
3752
4175
|
renderCrossDeviceState();
|
|
3753
4176
|
};
|
|
3754
4177
|
const renderSignInState = () => {
|
|
4178
|
+
this.cardEl?.classList.remove("passkey-creating");
|
|
4179
|
+
this.abort?.abort();
|
|
4180
|
+
this.generation++;
|
|
4181
|
+
const heading = this.cardEl?.querySelector("h1");
|
|
4182
|
+
if (heading)
|
|
4183
|
+
heading.textContent = this.options.purpose?.title ?? (this.options.appName ? t("signInTo", locale, { appName: this.options.appName }) : t("signIn", locale));
|
|
3755
4184
|
section.innerHTML = "";
|
|
3756
4185
|
clearStatus();
|
|
3757
4186
|
const signInBtn = document.createElement("button");
|
|
3758
|
-
signInBtn.className = "btn";
|
|
4187
|
+
signInBtn.className = this.cardEl?.classList.contains("social-first") ? "btn btn-secondary" : "btn";
|
|
3759
4188
|
signInBtn.id = "passkey-btn";
|
|
3760
4189
|
signInBtn.innerHTML = `${ICON_PASSKEY} ${escapeHtml(t("passkeySignIn", locale))}`;
|
|
3761
4190
|
section.appendChild(signInBtn);
|
|
3762
4191
|
signInBtn.addEventListener("click", () => doSignIn());
|
|
3763
4192
|
const createLink = document.createElement("button");
|
|
3764
4193
|
createLink.type = "button";
|
|
3765
|
-
createLink.className = "passkey-alt";
|
|
4194
|
+
createLink.className = "passkey-alt passkey-create-entry";
|
|
3766
4195
|
createLink.textContent = t("passkeyFirstTime", locale);
|
|
3767
4196
|
section.appendChild(createLink);
|
|
3768
4197
|
createLink.addEventListener("click", () => renderCreateState());
|
|
@@ -3777,6 +4206,8 @@ var LoginPage = class {
|
|
|
3777
4206
|
// methods now render through renderSecondaryGrid)
|
|
3778
4207
|
// ─── Private: OAuth popup flow ──────────────────────────────────────
|
|
3779
4208
|
startOAuthPopup(providerId, btn) {
|
|
4209
|
+
this.oauthCleanup?.();
|
|
4210
|
+
const epoch = this.generation;
|
|
3780
4211
|
const servicePrefix = this.config.servicePrefix ?? "/.well-known/service";
|
|
3781
4212
|
const oauthBase = this.options.masterOAuthOrigin ? `${this.options.masterOAuthOrigin}${servicePrefix}` : servicePrefix;
|
|
3782
4213
|
const oauthOrigin = new URL(oauthBase, window.location.href).origin;
|
|
@@ -3787,14 +4218,32 @@ var LoginPage = class {
|
|
|
3787
4218
|
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
3788
4219
|
const popup = window.open(loginUrl, "oauth-login:popup", `left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1,popup`);
|
|
3789
4220
|
if (!popup) {
|
|
4221
|
+
this.options.onBeforeRedirect?.();
|
|
3790
4222
|
window.location.href = loginUrl;
|
|
3791
4223
|
return;
|
|
3792
4224
|
}
|
|
3793
4225
|
const originalHTML = btn.innerHTML;
|
|
3794
|
-
btn.
|
|
4226
|
+
btn.textContent = t("oauthWaiting", this.options.locale, { provider: this.options.oauthProviders?.find((p) => p.id === providerId)?.name ?? providerId });
|
|
3795
4227
|
btn.disabled = true;
|
|
3796
4228
|
btn.classList.add("oauth-btn-loading");
|
|
4229
|
+
const recovery = document.createElement("div");
|
|
4230
|
+
recovery.className = "oauth-recovery";
|
|
4231
|
+
recovery.setAttribute("role", "status");
|
|
4232
|
+
const hint = document.createElement("p");
|
|
4233
|
+
hint.textContent = t("oauthIncomplete", this.options.locale);
|
|
4234
|
+
const fullPage = document.createElement("a");
|
|
4235
|
+
fullPage.href = loginUrl;
|
|
4236
|
+
fullPage.textContent = t("oauthFullPage", this.options.locale);
|
|
4237
|
+
fullPage.addEventListener("click", () => {
|
|
4238
|
+
this.options.onBeforeRedirect?.();
|
|
4239
|
+
this.oauthCleanup?.();
|
|
4240
|
+
});
|
|
4241
|
+
recovery.append(hint, fullPage);
|
|
4242
|
+
recovery.hidden = true;
|
|
4243
|
+
btn.parentElement?.after(recovery);
|
|
3797
4244
|
const restore = () => {
|
|
4245
|
+
recovery.hidden = false;
|
|
4246
|
+
this.emit("incomplete");
|
|
3798
4247
|
btn.innerHTML = originalHTML;
|
|
3799
4248
|
btn.disabled = false;
|
|
3800
4249
|
btn.classList.remove("oauth-btn-loading");
|
|
@@ -3802,19 +4251,19 @@ var LoginPage = class {
|
|
|
3802
4251
|
let settled = false;
|
|
3803
4252
|
let sessionPollTimer;
|
|
3804
4253
|
const succeed = () => {
|
|
3805
|
-
if (settled)
|
|
4254
|
+
if (settled || !this.mounted || epoch !== this.generation)
|
|
3806
4255
|
return;
|
|
3807
4256
|
settled = true;
|
|
3808
4257
|
cleanup();
|
|
3809
|
-
btn.
|
|
4258
|
+
btn.textContent = t("success", this.options.locale);
|
|
3810
4259
|
this.handleSuccess();
|
|
3811
4260
|
};
|
|
3812
4261
|
const onMessage = async (event) => {
|
|
3813
|
-
if (event.origin !== oauthOrigin)
|
|
4262
|
+
if (event.origin !== oauthOrigin || event.source !== popup)
|
|
3814
4263
|
return;
|
|
3815
4264
|
if (!event.data || event.data.type !== "authorization_response")
|
|
3816
4265
|
return;
|
|
3817
|
-
if (settled)
|
|
4266
|
+
if (settled || !this.mounted || epoch !== this.generation)
|
|
3818
4267
|
return;
|
|
3819
4268
|
if (pollTimer !== void 0) {
|
|
3820
4269
|
clearInterval(pollTimer);
|
|
@@ -3835,7 +4284,7 @@ var LoginPage = class {
|
|
|
3835
4284
|
cleanup();
|
|
3836
4285
|
return;
|
|
3837
4286
|
}
|
|
3838
|
-
btn.
|
|
4287
|
+
btn.textContent = t("verifying", this.options.locale);
|
|
3839
4288
|
btn.disabled = true;
|
|
3840
4289
|
try {
|
|
3841
4290
|
const oauthBody = { provider: providerId, code, state };
|
|
@@ -3844,6 +4293,8 @@ var LoginPage = class {
|
|
|
3844
4293
|
await this.http.post(`${servicePrefix}/api/oauth/login`, oauthBody);
|
|
3845
4294
|
succeed();
|
|
3846
4295
|
} catch (oauthErr) {
|
|
4296
|
+
if (settled || !this.mounted || epoch !== this.generation)
|
|
4297
|
+
return;
|
|
3847
4298
|
const errMsg = oauthErr instanceof Error ? oauthErr.message : "";
|
|
3848
4299
|
const errCode = oauthErr instanceof Error && "code" in oauthErr ? oauthErr.code : "";
|
|
3849
4300
|
if (errCode === "FORBIDDEN" || errMsg === "registration_closed" || errCode === "registration_closed") {
|
|
@@ -3886,6 +4337,10 @@ var LoginPage = class {
|
|
|
3886
4337
|
restore();
|
|
3887
4338
|
}, 5 * 60 * 1e3);
|
|
3888
4339
|
const cleanup = () => {
|
|
4340
|
+
settled = true;
|
|
4341
|
+
btn.innerHTML = originalHTML;
|
|
4342
|
+
btn.disabled = false;
|
|
4343
|
+
btn.classList.remove("oauth-btn-loading");
|
|
3889
4344
|
if (pollTimer !== void 0) {
|
|
3890
4345
|
clearInterval(pollTimer);
|
|
3891
4346
|
pollTimer = void 0;
|
|
@@ -3897,6 +4352,14 @@ var LoginPage = class {
|
|
|
3897
4352
|
clearTimeout(abandonTimer);
|
|
3898
4353
|
window.removeEventListener("message", onMessage);
|
|
3899
4354
|
};
|
|
4355
|
+
this.oauthCleanup = () => {
|
|
4356
|
+
cleanup();
|
|
4357
|
+
recovery.remove();
|
|
4358
|
+
try {
|
|
4359
|
+
popup.close();
|
|
4360
|
+
} catch {
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
3900
4363
|
window.addEventListener("message", onMessage);
|
|
3901
4364
|
}
|
|
3902
4365
|
// (removed dead code: renderEmailButton — email now renders through renderSecondaryGrid)
|
|
@@ -3906,19 +4369,24 @@ var LoginPage = class {
|
|
|
3906
4369
|
* Even count: all items in 2 columns (each half width).
|
|
3907
4370
|
* Odd count: DID Wallet spans full width at the end.
|
|
3908
4371
|
*/
|
|
3909
|
-
renderSecondaryGrid(parent, locale, methods) {
|
|
4372
|
+
renderSecondaryGrid(parent, locale, methods, group) {
|
|
3910
4373
|
const grid = document.createElement("div");
|
|
3911
4374
|
grid.className = "methods-grid";
|
|
3912
4375
|
if (methods.includes("oauth") && this.options.oauthProviders?.length) {
|
|
3913
|
-
const
|
|
4376
|
+
const order = this.options.oauthProviderOrder ?? ["google", "apple", "github"];
|
|
4377
|
+
const rank = (id) => order.includes(id) ? order.indexOf(id) : order.length;
|
|
4378
|
+
const explicitOrder = !!this.options.oauthProviderOrder || this.options.oauthProviders?.some((p) => p.order !== void 0 && p.order !== 999);
|
|
4379
|
+
const enabledProviders = (this.options.oauthProviders ?? []).filter((p) => p.enabled && (!group || (explicitOrder || ["google", "apple"].includes(p.id)) === (group === "primary"))).sort((a, b) => this.options.oauthProviderOrder ? rank(a.id) - rank(b.id) : (a.order ?? 999) - (b.order ?? 999) || rank(a.id) - rank(b.id));
|
|
3914
4380
|
for (const provider of enabledProviders) {
|
|
3915
4381
|
const btn = document.createElement("button");
|
|
3916
4382
|
btn.className = "method-btn";
|
|
3917
4383
|
btn.dataset.provider = provider.id;
|
|
3918
4384
|
const icon = getOAuthIcon(provider.id);
|
|
3919
|
-
btn.innerHTML = `${icon} ${escapeHtml(provider.name)}`;
|
|
4385
|
+
btn.innerHTML = `${icon} ${escapeHtml(t("continueWith", locale, { provider: provider.name }))}`;
|
|
3920
4386
|
grid.appendChild(btn);
|
|
3921
4387
|
btn.addEventListener("click", () => {
|
|
4388
|
+
this.select("oauth");
|
|
4389
|
+
this.activeProvider = provider.id;
|
|
3922
4390
|
this.startOAuthPopup(provider.id, btn);
|
|
3923
4391
|
});
|
|
3924
4392
|
}
|
|
@@ -3930,6 +4398,7 @@ var LoginPage = class {
|
|
|
3930
4398
|
btn.innerHTML = `${ICON_EMAIL} ${escapeHtml(t("emailButton", locale))}`;
|
|
3931
4399
|
grid.appendChild(btn);
|
|
3932
4400
|
btn.addEventListener("click", () => {
|
|
4401
|
+
this.select("email");
|
|
3933
4402
|
this.switchView("email");
|
|
3934
4403
|
});
|
|
3935
4404
|
}
|
|
@@ -3941,6 +4410,7 @@ var LoginPage = class {
|
|
|
3941
4410
|
btn.innerHTML = `${ICON_DID_WALLET} ${escapeHtml(walletLabel)}`;
|
|
3942
4411
|
grid.appendChild(btn);
|
|
3943
4412
|
btn.addEventListener("click", () => {
|
|
4413
|
+
this.select("did-connect");
|
|
3944
4414
|
this.switchView("qr");
|
|
3945
4415
|
});
|
|
3946
4416
|
}
|
|
@@ -3949,19 +4419,27 @@ var LoginPage = class {
|
|
|
3949
4419
|
// ─── Private: DID Connect flow (QR view) ───────────────────────────
|
|
3950
4420
|
async startDIDConnectInView(statusEl, qrContainer, walletBtn, locale) {
|
|
3951
4421
|
statusEl.textContent = t("connecting", locale);
|
|
4422
|
+
const epoch = this.generation;
|
|
4423
|
+
const isCurrent = () => this.mounted && epoch === this.generation;
|
|
3952
4424
|
try {
|
|
3953
4425
|
this.tokenSession?.destroy();
|
|
3954
4426
|
this.tokenSession = new TokenSession(this.config, this.http);
|
|
3955
4427
|
this.tokenSession.on("succeed", async () => {
|
|
4428
|
+
if (!isCurrent())
|
|
4429
|
+
return;
|
|
3956
4430
|
this.showConnectResult(qrContainer, walletBtn, statusEl, t("verifying", locale));
|
|
3957
4431
|
try {
|
|
3958
4432
|
const servicePrefix = this.config.servicePrefix ?? "/.well-known/service";
|
|
3959
4433
|
await this.http.post(`${servicePrefix}/api/did/connect/complete`, {
|
|
3960
4434
|
token: this.tokenSession.state.token
|
|
3961
4435
|
});
|
|
4436
|
+
if (!isCurrent())
|
|
4437
|
+
return;
|
|
3962
4438
|
this.showConnectSuccess(qrContainer, walletBtn, statusEl, locale);
|
|
3963
4439
|
this.handleSuccess();
|
|
3964
4440
|
} catch (err) {
|
|
4441
|
+
if (!isCurrent())
|
|
4442
|
+
return;
|
|
3965
4443
|
this.showConnectResult(qrContainer, walletBtn, statusEl, friendlyError(err, locale), true);
|
|
3966
4444
|
}
|
|
3967
4445
|
});
|
|
@@ -3992,17 +4470,21 @@ var LoginPage = class {
|
|
|
3992
4470
|
}
|
|
3993
4471
|
});
|
|
3994
4472
|
const state = await this.tokenSession.create({ locale });
|
|
4473
|
+
if (!isCurrent())
|
|
4474
|
+
return;
|
|
3995
4475
|
if (state.url) {
|
|
3996
4476
|
renderQR(qrContainer, state.url);
|
|
3997
4477
|
walletBtn.disabled = false;
|
|
3998
4478
|
walletBtn.addEventListener("click", () => {
|
|
3999
|
-
|
|
4479
|
+
statusEl.textContent = t("walletRetry", locale);
|
|
4000
4480
|
this.openInWallet(state.url, locale);
|
|
4001
4481
|
});
|
|
4002
4482
|
}
|
|
4003
4483
|
statusEl.textContent = t("scanWithWallet", locale);
|
|
4004
4484
|
this.tokenSession.startPolling();
|
|
4005
4485
|
} catch (err) {
|
|
4486
|
+
if (!isCurrent())
|
|
4487
|
+
return;
|
|
4006
4488
|
statusEl.textContent = friendlyError(err, locale);
|
|
4007
4489
|
clearQR(qrContainer);
|
|
4008
4490
|
}
|
|
@@ -4022,7 +4504,7 @@ var LoginPage = class {
|
|
|
4022
4504
|
}
|
|
4023
4505
|
this.cardEl.innerHTML = "";
|
|
4024
4506
|
this.cardEl.dataset.view = "error";
|
|
4025
|
-
this.
|
|
4507
|
+
this.renderCardTools(this.cardEl);
|
|
4026
4508
|
const resultContainer = document.createElement("div");
|
|
4027
4509
|
resultContainer.className = "connect-result";
|
|
4028
4510
|
resultContainer.innerHTML = `<div class="result-icon result-error">\u2715</div>`;
|
|
@@ -4161,13 +4643,238 @@ var LoginPage = class {
|
|
|
4161
4643
|
}
|
|
4162
4644
|
// ─── Private: success handler ────────────────────────────────────────
|
|
4163
4645
|
handleSuccess() {
|
|
4646
|
+
if (!this.mounted || this.completed)
|
|
4647
|
+
return;
|
|
4648
|
+
this.completed = true;
|
|
4649
|
+
this.emit("success");
|
|
4650
|
+
if (this.options.preferenceKey && this.activeMethod) {
|
|
4651
|
+
try {
|
|
4652
|
+
localStorage.setItem(`did-login-method:${this.options.preferenceKey}`, this.activeMethod === "oauth" && this.activeProvider ? `oauth:${this.activeProvider}` : this.activeMethod);
|
|
4653
|
+
} catch {
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4164
4656
|
if (this.options.onSuccess) {
|
|
4165
|
-
this.
|
|
4657
|
+
const epoch = this.generation;
|
|
4658
|
+
Promise.resolve().then(() => {
|
|
4659
|
+
if (this.mounted && epoch === this.generation)
|
|
4660
|
+
return this.options.onSuccess?.();
|
|
4661
|
+
}).catch(() => {
|
|
4662
|
+
if (!this.mounted || epoch !== this.generation)
|
|
4663
|
+
return;
|
|
4664
|
+
this.completed = false;
|
|
4665
|
+
this.showFullError(t("authFailed", this.options.locale));
|
|
4666
|
+
});
|
|
4166
4667
|
} else {
|
|
4167
4668
|
setTimeout(() => location.reload(), 300);
|
|
4168
4669
|
}
|
|
4169
4670
|
}
|
|
4170
4671
|
};
|
|
4672
|
+
|
|
4673
|
+
// dist/ui/login-dialog.js
|
|
4674
|
+
var DIALOG_STYLES = `${DESIGN_TOKENS_CSS}${DESIGN_TOKENS_LIGHT_CSS}
|
|
4675
|
+
:host { color-scheme: dark; }
|
|
4676
|
+
:host([data-theme="light"]) { color-scheme: light; }
|
|
4677
|
+
dialog { box-sizing:border-box; position:fixed; inset:0; margin:auto; padding:0;
|
|
4678
|
+
width:min(420px,calc(100vw - 32px)); max-width:none; max-height:calc(100dvh - 32px);
|
|
4679
|
+
border:1px solid var(--border); border-radius:16px; background:var(--bg-card);
|
|
4680
|
+
color:var(--text); color-scheme:inherit; overflow:hidden; box-shadow:var(--shadow-lg); }
|
|
4681
|
+
dialog::backdrop { background:rgba(8,10,18,.56); }
|
|
4682
|
+
:host(:not([data-theme="light"])) dialog::backdrop { background:rgba(0,0,0,.68); }
|
|
4683
|
+
[data-dialog-scroll] { max-height:calc(100dvh - 34px); overflow:auto; overscroll-behavior:contain; }
|
|
4684
|
+
.close { position:absolute; top:8px; right:8px; z-index:3; display:grid; place-items:center;
|
|
4685
|
+
box-sizing:border-box; width:44px; height:44px; padding:0; border:1px solid transparent;
|
|
4686
|
+
border-radius:10px; background:var(--bg-card); color:var(--text-secondary);
|
|
4687
|
+
font:400 24px/1 system-ui,sans-serif; cursor:pointer; }
|
|
4688
|
+
.close:hover { color:var(--text); background:var(--bg-hover); }
|
|
4689
|
+
.close:focus-visible { outline:2px solid var(--blue); outline-offset:-3px; }
|
|
4690
|
+
.host-error { padding:16px 20px; font:14px/1.5 system-ui,sans-serif; overflow-wrap:anywhere; }
|
|
4691
|
+
.retry { padding:10px 16px; margin:0 20px 20px; color:var(--text); background:var(--bg-elevated);
|
|
4692
|
+
border:1px solid var(--border); border-radius:8px; font:inherit; cursor:pointer; }
|
|
4693
|
+
`;
|
|
4694
|
+
var LoginDialog = class {
|
|
4695
|
+
config;
|
|
4696
|
+
http;
|
|
4697
|
+
options;
|
|
4698
|
+
host;
|
|
4699
|
+
dialog;
|
|
4700
|
+
page;
|
|
4701
|
+
previous;
|
|
4702
|
+
themeObserver;
|
|
4703
|
+
systemTheme;
|
|
4704
|
+
syncTheme = () => this.applyTheme(this.hostTheme());
|
|
4705
|
+
constructor(config, http, options = {}) {
|
|
4706
|
+
this.config = config;
|
|
4707
|
+
this.http = http;
|
|
4708
|
+
this.options = options;
|
|
4709
|
+
}
|
|
4710
|
+
mount() {
|
|
4711
|
+
if (this.dialog)
|
|
4712
|
+
return;
|
|
4713
|
+
let focused = document.activeElement;
|
|
4714
|
+
while (focused?.shadowRoot?.activeElement)
|
|
4715
|
+
focused = focused.shadowRoot.activeElement;
|
|
4716
|
+
this.previous = this.options.returnFocus ?? (focused instanceof HTMLElement ? focused : void 0);
|
|
4717
|
+
const host = document.createElement("div");
|
|
4718
|
+
host.dataset.didLoginDialogHost = "";
|
|
4719
|
+
host.style.cssText = "position:fixed;width:0;height:0;";
|
|
4720
|
+
this.host = host;
|
|
4721
|
+
const root = host.attachShadow({ mode: "open" });
|
|
4722
|
+
const style = document.createElement("style");
|
|
4723
|
+
style.textContent = DIALOG_STYLES;
|
|
4724
|
+
root.append(style);
|
|
4725
|
+
const dialog = document.createElement("dialog");
|
|
4726
|
+
this.dialog = dialog;
|
|
4727
|
+
dialog.setAttribute("aria-label", this.options.purpose?.title ?? t("signIn", this.options.locale));
|
|
4728
|
+
root.append(dialog);
|
|
4729
|
+
const close = document.createElement("button");
|
|
4730
|
+
close.type = "button";
|
|
4731
|
+
close.className = "close";
|
|
4732
|
+
close.textContent = "\xD7";
|
|
4733
|
+
close.setAttribute("aria-label", t("cancel", this.options.locale));
|
|
4734
|
+
const cancel = () => {
|
|
4735
|
+
if (this.options.dismissible === false || this.dialog !== dialog)
|
|
4736
|
+
return;
|
|
4737
|
+
try {
|
|
4738
|
+
this.options.onEvent?.({ name: "cancel", mode: "dialog" });
|
|
4739
|
+
} catch {
|
|
4740
|
+
}
|
|
4741
|
+
this.destroy();
|
|
4742
|
+
this.options.onCancel?.();
|
|
4743
|
+
};
|
|
4744
|
+
close.addEventListener("click", cancel);
|
|
4745
|
+
if (this.options.dismissible !== false)
|
|
4746
|
+
dialog.append(close);
|
|
4747
|
+
const scroller = document.createElement("div");
|
|
4748
|
+
scroller.dataset.dialogScroll = "";
|
|
4749
|
+
dialog.append(scroller);
|
|
4750
|
+
const container = document.createElement("div");
|
|
4751
|
+
container.dataset.loginDialogMount = "";
|
|
4752
|
+
scroller.append(container);
|
|
4753
|
+
dialog.addEventListener("cancel", (event) => {
|
|
4754
|
+
event.preventDefault();
|
|
4755
|
+
cancel();
|
|
4756
|
+
});
|
|
4757
|
+
document.body.append(host);
|
|
4758
|
+
const theme = this.hostTheme();
|
|
4759
|
+
host.dataset.theme = theme;
|
|
4760
|
+
this.page = new LoginPage(container, this.config, this.http, {
|
|
4761
|
+
...this.options,
|
|
4762
|
+
theme,
|
|
4763
|
+
isolate: true,
|
|
4764
|
+
mode: "dialog",
|
|
4765
|
+
onCancel: cancel,
|
|
4766
|
+
onLocaleChange: (locale) => {
|
|
4767
|
+
this.options.locale = locale;
|
|
4768
|
+
close.setAttribute("aria-label", t("cancel", locale));
|
|
4769
|
+
dialog.setAttribute("aria-label", this.options.purpose?.title ?? t("signIn", locale));
|
|
4770
|
+
this.options.onLocaleChange?.(locale);
|
|
4771
|
+
},
|
|
4772
|
+
onSuccess: async () => {
|
|
4773
|
+
const showFailure = () => {
|
|
4774
|
+
if (!dialog.isConnected || this.dialog !== dialog)
|
|
4775
|
+
return;
|
|
4776
|
+
for (const node of scroller.querySelectorAll(".host-error, .retry"))
|
|
4777
|
+
node.remove();
|
|
4778
|
+
const status = document.createElement("p");
|
|
4779
|
+
status.className = "host-error";
|
|
4780
|
+
status.setAttribute("role", "alert");
|
|
4781
|
+
status.textContent = t("authFailed", this.options.locale);
|
|
4782
|
+
const retry = document.createElement("button");
|
|
4783
|
+
retry.type = "button";
|
|
4784
|
+
retry.className = "retry";
|
|
4785
|
+
retry.textContent = t("tryAgain", this.options.locale);
|
|
4786
|
+
retry.onclick = async () => {
|
|
4787
|
+
retry.disabled = true;
|
|
4788
|
+
try {
|
|
4789
|
+
await this.options.onSuccess?.();
|
|
4790
|
+
if (this.dialog === dialog)
|
|
4791
|
+
this.destroy();
|
|
4792
|
+
} catch {
|
|
4793
|
+
showFailure();
|
|
4794
|
+
}
|
|
4795
|
+
};
|
|
4796
|
+
scroller.append(status, retry);
|
|
4797
|
+
};
|
|
4798
|
+
try {
|
|
4799
|
+
await this.options.onSuccess?.();
|
|
4800
|
+
if (this.dialog === dialog)
|
|
4801
|
+
this.destroy();
|
|
4802
|
+
} catch {
|
|
4803
|
+
showFailure();
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
});
|
|
4807
|
+
this.page.mount();
|
|
4808
|
+
this.themeObserver = new MutationObserver(this.syncTheme);
|
|
4809
|
+
for (const target of [document.documentElement, document.body]) {
|
|
4810
|
+
this.themeObserver.observe(target, { attributes: true, attributeFilter: ["data-theme", "data-mode", "class", "style"] });
|
|
4811
|
+
}
|
|
4812
|
+
if (typeof window.matchMedia === "function") {
|
|
4813
|
+
this.systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
|
|
4814
|
+
this.systemTheme.addEventListener("change", this.syncTheme);
|
|
4815
|
+
}
|
|
4816
|
+
dialog.showModal();
|
|
4817
|
+
const title = container.shadowRoot?.querySelector("h1, h2");
|
|
4818
|
+
if (title) {
|
|
4819
|
+
title.tabIndex = -1;
|
|
4820
|
+
title.focus({ preventScroll: true });
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
hostTheme() {
|
|
4824
|
+
for (const element of [document.documentElement, document.body]) {
|
|
4825
|
+
for (const name of ["data-mode", "data-theme"]) {
|
|
4826
|
+
const value = element.getAttribute(name);
|
|
4827
|
+
if (value === "light" || value === "dark")
|
|
4828
|
+
return value;
|
|
4829
|
+
if (value === "system" || value === "auto")
|
|
4830
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
for (const element of [document.documentElement, document.body]) {
|
|
4834
|
+
if (element.classList.contains("dark"))
|
|
4835
|
+
return "dark";
|
|
4836
|
+
if (element.classList.contains("light"))
|
|
4837
|
+
return "light";
|
|
4838
|
+
}
|
|
4839
|
+
for (const element of [document.body, document.documentElement]) {
|
|
4840
|
+
const schemes = getComputedStyle(element).colorScheme.split(/\s+/);
|
|
4841
|
+
if (schemes.includes("light") && schemes.includes("dark")) {
|
|
4842
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
4843
|
+
}
|
|
4844
|
+
if (schemes.includes("light"))
|
|
4845
|
+
return "light";
|
|
4846
|
+
if (schemes.includes("dark"))
|
|
4847
|
+
return "dark";
|
|
4848
|
+
}
|
|
4849
|
+
if (this.options.theme === "light" || this.options.theme === "dark")
|
|
4850
|
+
return this.options.theme;
|
|
4851
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
4852
|
+
}
|
|
4853
|
+
setTheme(theme) {
|
|
4854
|
+
this.options.theme = theme;
|
|
4855
|
+
this.applyTheme(theme);
|
|
4856
|
+
}
|
|
4857
|
+
applyTheme(theme) {
|
|
4858
|
+
if (this.host)
|
|
4859
|
+
this.host.dataset.theme = theme;
|
|
4860
|
+
this.page?.setTheme(theme);
|
|
4861
|
+
}
|
|
4862
|
+
destroy() {
|
|
4863
|
+
this.themeObserver?.disconnect();
|
|
4864
|
+
this.themeObserver = void 0;
|
|
4865
|
+
this.systemTheme?.removeEventListener("change", this.syncTheme);
|
|
4866
|
+
this.systemTheme = void 0;
|
|
4867
|
+
this.page?.destroy();
|
|
4868
|
+
this.page = void 0;
|
|
4869
|
+
this.dialog?.close();
|
|
4870
|
+
this.dialog?.remove();
|
|
4871
|
+
this.dialog = void 0;
|
|
4872
|
+
this.host?.remove();
|
|
4873
|
+
this.host = void 0;
|
|
4874
|
+
if (this.previous?.isConnected)
|
|
4875
|
+
this.previous.focus({ preventScroll: true });
|
|
4876
|
+
}
|
|
4877
|
+
};
|
|
4171
4878
|
export {
|
|
4172
4879
|
DESIGN_RESET_CSS,
|
|
4173
4880
|
DESIGN_TOKENS_CSS,
|
|
@@ -4177,6 +4884,7 @@ export {
|
|
|
4177
4884
|
ICON_EMAIL,
|
|
4178
4885
|
ICON_PASSKEY,
|
|
4179
4886
|
LOGIN_PAGE_STYLES,
|
|
4887
|
+
LoginDialog,
|
|
4180
4888
|
LoginPage,
|
|
4181
4889
|
OAUTH_ICONS,
|
|
4182
4890
|
SUPPORTED_LOCALES,
|