Add wallet URI copy and shortlink trust toggle
This commit is contained in:
42
app.js
42
app.js
@@ -35,14 +35,17 @@
|
||||
const qrContainer = $('#qr');
|
||||
const uriBox = $('#uri');
|
||||
const openWalletBtn = $('#openWallet');
|
||||
const copyUriBtn = $('#copyUri');
|
||||
const copyAddrBtn = $('#copyAddr');
|
||||
const countdownEl = $('#countdown');
|
||||
const fiatHint = $('#fiatHint');
|
||||
const toast = $('#toast');
|
||||
const shareLinkInput = $('#shareLink');
|
||||
const copyShareLinkBtn = $('#copyShareLink');
|
||||
const useShortLinkCheckbox = $('#useShortLink');
|
||||
const newRequestBtn = $('#newRequest');
|
||||
const homeLink = $('#homeLink');
|
||||
let currentInvoiceHash = null;
|
||||
|
||||
// TX Proof DOM
|
||||
const proofToggle = $('#proofToggle');
|
||||
@@ -119,8 +122,12 @@
|
||||
amountInput.addEventListener('input', updateFiatHint);
|
||||
currencySelect.addEventListener('change', updateFiatHint);
|
||||
generateBtn.addEventListener('click', generate);
|
||||
copyUriBtn.addEventListener('click', () => copyToClipboard(uriBox.textContent));
|
||||
copyAddrBtn.addEventListener('click', () => copyToClipboard(addrInput.value.trim()));
|
||||
copyShareLinkBtn.addEventListener('click', () => copyToClipboard(shareLinkInput.value));
|
||||
useShortLinkCheckbox.addEventListener('change', function () {
|
||||
if (currentInvoiceHash) updateShareLink(currentInvoiceHash);
|
||||
});
|
||||
qrContainer.addEventListener('click', downloadQR);
|
||||
newRequestBtn.addEventListener('click', resetForm);
|
||||
homeLink.addEventListener('click', function (e) { e.preventDefault(); resetForm(); });
|
||||
@@ -179,6 +186,8 @@
|
||||
qrContainer.classList.remove('paid', 'confirming');
|
||||
uriBox.textContent = '';
|
||||
shareLinkInput.value = '';
|
||||
useShortLinkCheckbox.checked = false;
|
||||
currentInvoiceHash = null;
|
||||
// Reset proof
|
||||
invoiceCode = null;
|
||||
stopConfirmationPolling();
|
||||
@@ -326,7 +335,7 @@
|
||||
buildSummary(xmrAmount, desc, timer);
|
||||
updatePageTitle(xmrAmount, desc);
|
||||
|
||||
// Share link — keep existing short URL if present; otherwise shorten new hash
|
||||
// Share link
|
||||
var deadlineTs = null;
|
||||
if (timer && timer > 0) {
|
||||
if (!deadlineEndMs) {
|
||||
@@ -335,14 +344,8 @@
|
||||
deadlineTs = Math.floor(deadlineEndMs / 1000);
|
||||
}
|
||||
const hash = buildHash(addr, xmrAmount, desc, timer, deadlineTs);
|
||||
if (invoiceCode) {
|
||||
shareLinkInput.value = location.origin + '/s/' + invoiceCode;
|
||||
} else {
|
||||
shareLinkInput.value = location.origin + '/#' + hash;
|
||||
shortenUrl(hash).then(function (shortUrl) {
|
||||
if (shortUrl) shareLinkInput.value = shortUrl;
|
||||
});
|
||||
}
|
||||
currentInvoiceHash = hash;
|
||||
updateShareLink(hash);
|
||||
|
||||
// QR
|
||||
qrContainer.innerHTML = '';
|
||||
@@ -410,6 +413,7 @@
|
||||
const code = params.get('c');
|
||||
if (code) {
|
||||
invoiceCode = code;
|
||||
useShortLinkCheckbox.checked = true;
|
||||
// Verify short URL integrity (detect tampering)
|
||||
setTimeout(function () {
|
||||
verifyShortUrlIntegrity(code, hash);
|
||||
@@ -422,6 +426,26 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateShareLink(hash) {
|
||||
var longUrl = location.origin + '/#' + hash;
|
||||
shareLinkInput.value = longUrl;
|
||||
|
||||
if (!useShortLinkCheckbox.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (invoiceCode) {
|
||||
shareLinkInput.value = location.origin + '/s/' + invoiceCode;
|
||||
return;
|
||||
}
|
||||
|
||||
shortenUrl(hash).then(function (shortUrl) {
|
||||
if (shortUrl && useShortLinkCheckbox.checked && currentInvoiceHash === hash) {
|
||||
shareLinkInput.value = shortUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that the redirected hash still matches the stored short URL mapping.
|
||||
function verifyShortUrlIntegrity(code, currentHash) {
|
||||
fetch('/api/check-short.php?code=' + encodeURIComponent(code))
|
||||
|
||||
2
app.min.js
vendored
2
app.min.js
vendored
File diff suppressed because one or more lines are too long
21
i18n.js
21
i18n.js
@@ -26,6 +26,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Days',
|
||||
btn_generate: 'Create payment request',
|
||||
btn_open_wallet: 'Open in wallet',
|
||||
btn_copy_uri: 'Copy payment URI',
|
||||
btn_copy_addr: 'Copy address',
|
||||
btn_download_pdf: 'PDF Invoice',
|
||||
pdf_title: 'Payment Request',
|
||||
@@ -41,6 +42,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Currency',
|
||||
label_share_link: 'Shareable link',
|
||||
shortlink_toggle_label: 'Use short link (requires server trust)',
|
||||
shortlink_toggle_hint: 'Trade-off: short links are convenient, but a compromised server could swap invoice data on first access.',
|
||||
btn_new_request: 'New payment request',
|
||||
toast_copied: 'Copied!',
|
||||
countdown_expired: 'Payment deadline expired',
|
||||
@@ -75,6 +78,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Tage',
|
||||
btn_generate: 'Zahlungsanforderung erstellen',
|
||||
btn_open_wallet: 'In Wallet öffnen',
|
||||
btn_copy_uri: 'Zahlungs-URI kopieren',
|
||||
btn_copy_addr: 'Adresse kopieren',
|
||||
btn_download_pdf: 'PDF Rechnung',
|
||||
pdf_title: 'Zahlungsanforderung',
|
||||
@@ -90,6 +94,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Währung',
|
||||
label_share_link: 'Teilbarer Link',
|
||||
shortlink_toggle_label: 'Kurzlink verwenden (Server-Vertrauen erforderlich)',
|
||||
shortlink_toggle_hint: 'Trade-off: Kurzlinks sind bequem, aber ein kompromittierter Server könnte Rechnungsdaten beim ersten Aufruf austauschen.',
|
||||
btn_new_request: 'Neue Zahlungsanforderung',
|
||||
toast_copied: 'Kopiert!',
|
||||
countdown_expired: 'Zahlungsfrist abgelaufen',
|
||||
@@ -124,6 +130,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Jours',
|
||||
btn_generate: 'Créer une demande de paiement',
|
||||
btn_open_wallet: 'Ouvrir dans le wallet',
|
||||
btn_copy_uri: 'Copier l\'URI de paiement',
|
||||
btn_copy_addr: 'Copier l\'adresse',
|
||||
btn_download_pdf: 'Facture PDF',
|
||||
pdf_title: 'Demande de paiement',
|
||||
@@ -139,6 +146,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Devise',
|
||||
label_share_link: 'Lien partageable',
|
||||
shortlink_toggle_label: 'Utiliser un lien court (confiance serveur requise)',
|
||||
shortlink_toggle_hint: 'Compromis: les liens courts sont pratiques, mais un serveur compromis pourrait remplacer les donnees de facture au premier acces.',
|
||||
btn_new_request: 'Nouvelle demande de paiement',
|
||||
toast_copied: 'Copié !',
|
||||
countdown_expired: 'Délai de paiement expiré',
|
||||
@@ -173,6 +182,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Giorni',
|
||||
btn_generate: 'Crea richiesta di pagamento',
|
||||
btn_open_wallet: 'Apri nel wallet',
|
||||
btn_copy_uri: 'Copia URI pagamento',
|
||||
btn_copy_addr: 'Copia indirizzo',
|
||||
btn_download_pdf: 'Fattura PDF',
|
||||
pdf_title: 'Richiesta di pagamento',
|
||||
@@ -188,6 +198,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Valuta',
|
||||
label_share_link: 'Link condivisibile',
|
||||
shortlink_toggle_label: 'Usa link breve (richiede fiducia nel server)',
|
||||
shortlink_toggle_hint: 'Compromesso: i link brevi sono comodi, ma un server compromesso potrebbe sostituire i dati fattura al primo accesso.',
|
||||
btn_new_request: 'Nuova richiesta di pagamento',
|
||||
toast_copied: 'Copiato!',
|
||||
countdown_expired: 'Scadenza pagamento superata',
|
||||
@@ -222,6 +234,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Días',
|
||||
btn_generate: 'Crear solicitud de pago',
|
||||
btn_open_wallet: 'Abrir en wallet',
|
||||
btn_copy_uri: 'Copiar URI de pago',
|
||||
btn_copy_addr: 'Copiar dirección',
|
||||
btn_download_pdf: 'Factura PDF',
|
||||
pdf_title: 'Solicitud de pago',
|
||||
@@ -237,6 +250,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Moneda',
|
||||
label_share_link: 'Enlace compartible',
|
||||
shortlink_toggle_label: 'Usar enlace corto (requiere confiar en el servidor)',
|
||||
shortlink_toggle_hint: 'Compromiso: los enlaces cortos son comodos, pero un servidor comprometido podria cambiar los datos de la factura en el primer acceso.',
|
||||
btn_new_request: 'Nueva solicitud de pago',
|
||||
toast_copied: '¡Copiado!',
|
||||
countdown_expired: 'Plazo de pago vencido',
|
||||
@@ -271,6 +286,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Dias',
|
||||
btn_generate: 'Criar pedido de pagamento',
|
||||
btn_open_wallet: 'Abrir na wallet',
|
||||
btn_copy_uri: 'Copiar URI de pagamento',
|
||||
btn_copy_addr: 'Copiar endereço',
|
||||
btn_download_pdf: 'Fatura PDF',
|
||||
pdf_title: 'Pedido de pagamento',
|
||||
@@ -286,6 +302,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Moeda',
|
||||
label_share_link: 'Link partilhável',
|
||||
shortlink_toggle_label: 'Usar link curto (requer confianca no servidor)',
|
||||
shortlink_toggle_hint: 'Compromisso: links curtos sao praticos, mas um servidor comprometido pode trocar os dados da fatura no primeiro acesso.',
|
||||
btn_new_request: 'Novo pedido de pagamento',
|
||||
toast_copied: 'Copiado!',
|
||||
countdown_expired: 'Prazo de pagamento expirado',
|
||||
@@ -320,6 +338,7 @@ var I18n = (function () {
|
||||
placeholder_timer_custom: 'Дней',
|
||||
btn_generate: 'Создать запрос на оплату',
|
||||
btn_open_wallet: 'Открыть в кошельке',
|
||||
btn_copy_uri: 'Копировать платежный URI',
|
||||
btn_copy_addr: 'Копировать адрес',
|
||||
btn_download_pdf: 'PDF счёт',
|
||||
pdf_title: 'Запрос на оплату',
|
||||
@@ -335,6 +354,8 @@ var I18n = (function () {
|
||||
footer: footer,
|
||||
aria_currency: 'Валюта',
|
||||
label_share_link: 'Ссылка для отправки',
|
||||
shortlink_toggle_label: 'Использовать короткую ссылку (нужно доверять серверу)',
|
||||
shortlink_toggle_hint: 'Компромисс: короткие ссылки удобны, но скомпрометированный сервер может подменить данные счета при первом открытии.',
|
||||
btn_new_request: 'Новый запрос на оплату',
|
||||
toast_copied: 'Скопировано!',
|
||||
countdown_expired: 'Срок оплаты истёк',
|
||||
|
||||
2
i18n.min.js
vendored
2
i18n.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -67,6 +67,13 @@
|
||||
<div class="uri-box" id="uri" style="display:none"></div>
|
||||
<div class="share-link-box" id="shareLinkBox">
|
||||
<label data-i18n="label_share_link">Shareable link</label>
|
||||
<div class="shortlink-toggle-row">
|
||||
<label class="shortlink-toggle-label">
|
||||
<input type="checkbox" id="useShortLink">
|
||||
<span data-i18n="shortlink_toggle_label">Use short link (requires server trust)</span>
|
||||
</label>
|
||||
<div class="shortlink-tradeoff" data-i18n="shortlink_toggle_hint">Trade-off: short links are convenient, but a compromised server could swap invoice data on first access.</div>
|
||||
</div>
|
||||
<div class="share-link-row">
|
||||
<input type="text" id="shareLink" readonly data-i18n-aria="label_share_link">
|
||||
<button class="btn btn-secondary btn-icon" id="copyShareLink" title="Copy">
|
||||
@@ -76,6 +83,7 @@
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" id="openWallet" data-i18n="btn_open_wallet">Open in wallet</button>
|
||||
<button class="btn btn-secondary" id="copyUri" data-i18n="btn_copy_uri">Copy payment URI</button>
|
||||
<button class="btn btn-secondary" id="copyAddr" data-i18n="btn_copy_addr">Copy address</button>
|
||||
<button class="btn btn-secondary" id="downloadPdf" data-i18n="btn_download_pdf">PDF Invoice</button>
|
||||
</div>
|
||||
|
||||
27
style.css
27
style.css
@@ -459,6 +459,33 @@ textarea {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.shortlink-toggle-row {
|
||||
margin-bottom: 0.45rem;
|
||||
}
|
||||
|
||||
.shortlink-toggle-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
margin: 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text);
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.shortlink-toggle-label input {
|
||||
width: auto;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.shortlink-tradeoff {
|
||||
margin-top: 0.28rem;
|
||||
font-size: 0.7rem;
|
||||
color: #d08a61;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.share-link-row {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
|
||||
Reference in New Issue
Block a user