signet-login 0.10.3 → 0.10.5
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/modal.js +57 -13
- package/dist/signet-login.iife.js +21 -20
- package/dist/types.d.ts +2 -2
- package/package.json +4 -2
package/dist/modal.js
CHANGED
|
@@ -18,6 +18,7 @@ const DEFAULT_PICKER_METHODS = ['nip07', 'amber', 'local-signet', 'remote-signet
|
|
|
18
18
|
const ALL_PICKER_METHODS = [...DEFAULT_PICKER_METHODS, 'redirect', 'qr'];
|
|
19
19
|
const DEFAULT_ADVANCED_METHODS = ['bunker', 'nostrconnect', 'nsec'];
|
|
20
20
|
const DEFAULT_NOSTR_CONNECT_PERMS = ['sign_event', 'nip44_encrypt', 'nip44_decrypt'];
|
|
21
|
+
const LARGE_QR_SIZE_PX = 360;
|
|
21
22
|
function escapeHtml(str) {
|
|
22
23
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
23
24
|
}
|
|
@@ -26,6 +27,13 @@ function generateChallenge() {
|
|
|
26
27
|
crypto.getRandomValues(bytes);
|
|
27
28
|
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
28
29
|
}
|
|
30
|
+
function keepQrCanvasResponsive(canvas, sizePx = LARGE_QR_SIZE_PX) {
|
|
31
|
+
// qrcode mutates canvas.style.width/height after rendering. Reset height so
|
|
32
|
+
// max-width scaling on narrow phones preserves the square QR aspect ratio.
|
|
33
|
+
canvas.style.width = `${sizePx}px`;
|
|
34
|
+
canvas.style.height = 'auto';
|
|
35
|
+
canvas.style.maxWidth = '100%';
|
|
36
|
+
}
|
|
29
37
|
function isDarkMode(theme) {
|
|
30
38
|
if (theme === 'dark')
|
|
31
39
|
return true;
|
|
@@ -441,7 +449,7 @@ async function runRedirectFlow(refs, opts, flowOpts = {}) {
|
|
|
441
449
|
<h2 style="margin:0 0 8px;font-size:1.2rem;">${sameDevice ? 'Open My Signet' : 'Sign in with Signet'}</h2>
|
|
442
450
|
<p style="margin:0 0 16px;color:${muted};font-size:0.85rem;">${sameDevice ? 'Approve in My Signet and keep that tab open so it can sign for this app.' : 'Open the link on your phone, or scan the QR if rendered.'}</p>
|
|
443
451
|
<div style="background:${dark ? '#0f0f1f' : '#f5f5f8'};border-radius:8px;padding:16px;margin-bottom:16px;">
|
|
444
|
-
<canvas id="signet-login-qr" width="
|
|
452
|
+
<canvas id="signet-login-qr" width="${LARGE_QR_SIZE_PX}" height="${LARGE_QR_SIZE_PX}" style="display:block;width:${LARGE_QR_SIZE_PX}px;height:auto;max-width:100%;margin:0 auto 12px;background:#ffffff;border-radius:6px;box-sizing:border-box;"></canvas>
|
|
445
453
|
<a id="signet-login-open-signet" href="${escapeHtml(authUrl)}" target="_blank" rel="noopener" style="${sameDevice ? buttonStyle(dark, true) + 'justify-content:center;text-align:center;text-decoration:none;' : 'display:block;color:#5b6dff;font-size:0.75rem;word-break:break-all;text-decoration:none;'}">${sameDevice ? 'Open My Signet' : `${escapeHtml(authUrl.slice(0, 80))}…`}</a>
|
|
446
454
|
</div>
|
|
447
455
|
<p id="signet-login-status" style="margin:0 0 12px;color:${muted};font-size:0.85rem;">${sameDevice ? 'Waiting for My Signet approval…' : 'Waiting for approval…'}</p>
|
|
@@ -459,11 +467,11 @@ async function runRedirectFlow(refs, opts, flowOpts = {}) {
|
|
|
459
467
|
const qrCanvas = refs.dialog.querySelector('#signet-login-qr');
|
|
460
468
|
if (qrCanvas) {
|
|
461
469
|
void QRCode.toCanvas(qrCanvas, authUrl, {
|
|
462
|
-
width:
|
|
470
|
+
width: LARGE_QR_SIZE_PX,
|
|
463
471
|
margin: 1,
|
|
464
472
|
errorCorrectionLevel: 'H',
|
|
465
473
|
color: { dark: '#0a0418', light: '#ffffff' },
|
|
466
|
-
}).catch(() => {
|
|
474
|
+
}).then(() => { keepQrCanvasResponsive(qrCanvas); }).catch(() => {
|
|
467
475
|
// Encoding failure (URL too long for QR L-Q levels, canvas inaccessible)
|
|
468
476
|
// — the visible link below the canvas still gets the user across.
|
|
469
477
|
});
|
|
@@ -708,9 +716,10 @@ async function runNostrConnectFlow(refs, opts) {
|
|
|
708
716
|
});
|
|
709
717
|
refs.dialog.innerHTML = `
|
|
710
718
|
<h2 style="margin:0 0 8px;font-size:1.2rem;">Connect a Nostr signer</h2>
|
|
711
|
-
<p style="margin:0 0 16px;color:${muted};font-size:0.85rem;">Scan
|
|
719
|
+
<p style="margin:0 0 16px;color:${muted};font-size:0.85rem;">Scan this with your signer (nsec.app, Amber, Keychat...), or copy/paste the URI below. The connection happens over your configured relay${opts.relayUrls.length > 1 ? 's' : ''}.</p>
|
|
712
720
|
<div style="background:${dark ? '#0f0f1f' : '#f5f5f8'};border-radius:8px;padding:16px;margin-bottom:16px;">
|
|
713
|
-
<canvas id="signet-login-nc-qr" width="
|
|
721
|
+
<canvas id="signet-login-nc-qr" width="${LARGE_QR_SIZE_PX}" height="${LARGE_QR_SIZE_PX}" style="display:block;width:${LARGE_QR_SIZE_PX}px;height:auto;max-width:100%;margin:0 auto 12px;background:#ffffff;border-radius:6px;box-sizing:border-box;"></canvas>
|
|
722
|
+
<textarea id="signet-login-nc-uri" readonly rows="4" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" aria-label="NostrConnect URI" style="width:100%;background:${dark ? '#050510' : '#ffffff'};color:${dark ? '#e7e7f0' : '#141427'};border:1px solid ${dark ? '#34344d' : '#d8dae3'};border-radius:6px;padding:8px;font-size:0.7rem;line-height:1.35;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;box-sizing:border-box;resize:vertical;margin:0 0 10px;overflow-wrap:anywhere;">${escapeHtml(uri)}</textarea>
|
|
714
723
|
<button data-action="copy" style="${buttonStyle(dark)}width:auto;font-size:0.75rem;padding:6px 10px;margin:0 auto;display:block;">Copy URI</button>
|
|
715
724
|
</div>
|
|
716
725
|
<p id="signet-login-nc-status" style="margin:0 0 12px;color:${muted};font-size:0.85rem;">Waiting for signer to connect…</p>
|
|
@@ -722,19 +731,49 @@ async function runNostrConnectFlow(refs, opts) {
|
|
|
722
731
|
const qrCanvas = refs.dialog.querySelector('#signet-login-nc-qr');
|
|
723
732
|
if (qrCanvas) {
|
|
724
733
|
void QRCode.toCanvas(qrCanvas, uri, {
|
|
725
|
-
width:
|
|
734
|
+
width: LARGE_QR_SIZE_PX, margin: 2, errorCorrectionLevel: 'L',
|
|
726
735
|
color: { dark: '#0a0418', light: '#ffffff' },
|
|
727
|
-
}).catch(() => { });
|
|
736
|
+
}).then(() => { keepQrCanvasResponsive(qrCanvas); }).catch(() => { });
|
|
728
737
|
}
|
|
738
|
+
const status = refs.dialog.querySelector('#signet-login-nc-status');
|
|
739
|
+
const uriText = refs.dialog.querySelector('#signet-login-nc-uri');
|
|
740
|
+
const selectUriText = () => {
|
|
741
|
+
if (!uriText)
|
|
742
|
+
return;
|
|
743
|
+
uriText.focus();
|
|
744
|
+
uriText.select();
|
|
745
|
+
uriText.setSelectionRange(0, uriText.value.length);
|
|
746
|
+
};
|
|
747
|
+
uriText?.addEventListener('focus', selectUriText);
|
|
748
|
+
uriText?.addEventListener('click', selectUriText);
|
|
729
749
|
const copyBtn = refs.dialog.querySelector('[data-action="copy"]');
|
|
730
750
|
copyBtn?.addEventListener('click', () => {
|
|
731
|
-
void
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
751
|
+
void (async () => {
|
|
752
|
+
try {
|
|
753
|
+
if (navigator.clipboard?.writeText) {
|
|
754
|
+
await navigator.clipboard.writeText(uri);
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
selectUriText();
|
|
758
|
+
if (typeof document.execCommand !== 'function' || !document.execCommand('copy')) {
|
|
759
|
+
throw new Error('clipboard-unavailable');
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
copyBtn.textContent = 'Copied ✓';
|
|
763
|
+
window.setTimeout(() => { copyBtn.textContent = 'Copy URI'; }, 1500);
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
selectUriText();
|
|
767
|
+
copyBtn.textContent = 'URI selected';
|
|
768
|
+
if (status) {
|
|
769
|
+
status.textContent = 'URI selected. Copy it manually if needed.';
|
|
770
|
+
status.style.color = '';
|
|
771
|
+
}
|
|
772
|
+
window.setTimeout(() => { copyBtn.textContent = 'Copy URI'; }, 2000);
|
|
773
|
+
}
|
|
774
|
+
})();
|
|
735
775
|
});
|
|
736
776
|
const ac = new AbortController();
|
|
737
|
-
const status = refs.dialog.querySelector('#signet-login-nc-status');
|
|
738
777
|
return new Promise(resolve => {
|
|
739
778
|
let settled = false;
|
|
740
779
|
const settle = (v) => {
|
|
@@ -849,6 +888,10 @@ function resolveRelayUrls(opts) {
|
|
|
849
888
|
const cleanRelayUrls = relayUrls.map(relay => relay.trim()).filter(Boolean);
|
|
850
889
|
return cleanRelayUrls.length > 0 ? cleanRelayUrls : [DEFAULTS.relayUrl];
|
|
851
890
|
}
|
|
891
|
+
function resolvePrimaryRelayUrl(opts, relayUrls) {
|
|
892
|
+
const relayUrl = opts.relayUrl?.trim();
|
|
893
|
+
return relayUrl || relayUrls[0] || DEFAULTS.relayUrl;
|
|
894
|
+
}
|
|
852
895
|
function resolveOptions(opts) {
|
|
853
896
|
const challenge = opts.challenge ?? generateChallenge();
|
|
854
897
|
if (!/^[0-9a-f]{64}$/i.test(challenge))
|
|
@@ -856,6 +899,7 @@ function resolveOptions(opts) {
|
|
|
856
899
|
const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost';
|
|
857
900
|
const timeout = Math.max(5000, Math.min(opts.timeout ?? DEFAULTS.timeout, 600000));
|
|
858
901
|
const relayUrls = resolveRelayUrls(opts);
|
|
902
|
+
const relayUrl = resolvePrimaryRelayUrl(opts, relayUrls);
|
|
859
903
|
const methodConfig = resolveMethodConfig(opts);
|
|
860
904
|
const result = {
|
|
861
905
|
appName: opts.appName,
|
|
@@ -863,7 +907,7 @@ function resolveOptions(opts) {
|
|
|
863
907
|
origin,
|
|
864
908
|
methods: methodConfig.methods,
|
|
865
909
|
advancedMethods: methodConfig.advancedMethods,
|
|
866
|
-
relayUrl
|
|
910
|
+
relayUrl,
|
|
867
911
|
relayUrls,
|
|
868
912
|
nostrConnectPerms: opts.nostrConnectPerms ?? DEFAULT_NOSTR_CONNECT_PERMS,
|
|
869
913
|
theme: opts.theme ?? DEFAULTS.theme,
|