From 0de588b2d67719b01729605e38f40858d97488cb Mon Sep 17 00:00:00 2001 From: Kharec Date: Sun, 1 Mar 2026 10:00:57 +0100 Subject: [PATCH] refactor: modernize JavaScript with ES6+ syntax (arrow functions, destructuring, template literals) --- src/html.gleam | 145 ++++++++++++++++++++----------------------------- 1 file changed, 60 insertions(+), 85 deletions(-) diff --git a/src/html.gleam b/src/html.gleam index 44460c6..16582de 100644 --- a/src/html.gleam +++ b/src/html.gleam @@ -112,47 +112,38 @@ pub fn not_found() -> String { fn decrypt_js(encrypted_content: String) -> String { " -(async function() { +(async () => { const encryptedContent = '" <> escape_js_string(encrypted_content) <> "'; - const hash = window.location.hash.slice(1); - const loadingEl = document.getElementById('loading'); - const errorEl = document.getElementById('error'); - const contentEl = document.getElementById('content-display'); - + const hash = location.hash.slice(1); + const els = { + loading: document.getElementById('loading'), + error: document.getElementById('error'), + content: document.getElementById('content-display') + }; + if (!hash) { - loadingEl.classList.add('hidden'); - errorEl.classList.remove('hidden'); + els.loading.classList.add('hidden'); + els.error.classList.remove('hidden'); return; } - + try { const keyBase64 = hash.replace(/-/g, '+').replace(/_/g, '/'); const encryptedBytes = Uint8Array.from(atob(encryptedContent), c => c.charCodeAt(0)); const keyBytes = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0)); const iv = encryptedBytes.slice(0, 12); - const ciphertext = encryptedBytes.slice(12); - const key = await crypto.subtle.importKey( - 'raw', - keyBytes, - { name: 'AES-GCM' }, - false, - ['decrypt'] - ); - const decrypted = await crypto.subtle.decrypt( - { name: 'AES-GCM', iv: iv }, - key, - ciphertext - ); - const decoder = new TextDecoder(); - const plaintext = decoder.decode(decrypted); - contentEl.textContent = plaintext; - loadingEl.classList.add('hidden'); - errorEl.classList.add('hidden'); - contentEl.classList.remove('hidden'); + + const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']); + const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encryptedBytes.slice(12)); + + els.content.textContent = new TextDecoder().decode(decrypted); + els.loading.classList.add('hidden'); + els.error.classList.add('hidden'); + els.content.classList.remove('hidden'); } catch (e) { console.error('Decryption failed:', e); - loadingEl.classList.add('hidden'); - errorEl.classList.remove('hidden'); + els.loading.classList.add('hidden'); + els.error.classList.remove('hidden'); } })(); " @@ -172,73 +163,57 @@ fn crypto_js() -> String { async function encryptContent(content) { const encoder = new TextEncoder(); const data = encoder.encode(content); - const key = await crypto.subtle.generateKey( - { name: 'AES-GCM', length: 256 }, - true, - ['encrypt', 'decrypt'] - ); + const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']); const iv = crypto.getRandomValues(new Uint8Array(12)); - const encrypted = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: iv }, - key, - data - ); + const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data); const keyData = await crypto.subtle.exportKey('raw', key); - const keyBytes = new Uint8Array(keyData); - const combined = new Uint8Array(iv.length + encrypted.byteLength); - combined.set(iv); - combined.set(new Uint8Array(encrypted), iv.length); - const encryptedBase64 = btoa(String.fromCharCode(...combined)); - const keyBase64 = btoa(String.fromCharCode(...keyBytes)); - return { encrypted: encryptedBase64, key: keyBase64 }; + return { + encrypted: btoa(String.fromCharCode(...new Uint8Array([...iv, ...new Uint8Array(encrypted)]))), + key: btoa(String.fromCharCode(...new Uint8Array(keyData))) + }; } -async function base64ToUrlSafeBase64(base64) { - let result = base64.split('+').join('-'); - result = result.split('/').join('_'); - while (result.endsWith('=')) { - result = result.slice(0, -1); - } - return result; +function base64ToUrlSafe(base64) { + return base64.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); } -document.getElementById('paste-form').addEventListener('submit', async function(e) { +function showShareUrl(url) { + const card = document.querySelector('.card'); + const div = document.createElement('div'); + div.className = 'share-url'; + div.innerHTML = ''; + const input = document.createElement('input'); + input.type = 'text'; + input.readOnly = true; + input.value = url; + input.onclick = () => input.select(); + div.appendChild(input); + card.insertBefore(div, card.firstChild); + input.select(); +} + +document.getElementById('paste-form').addEventListener('submit', async (e) => { e.preventDefault(); const content = document.getElementById('content').value; - const result = await encryptContent(content); - document.getElementById('encrypted-content').value = result.encrypted; - const response = await fetch('/', { + const { encrypted, key } = await encryptContent(content); + document.getElementById('encrypted-content').value = encrypted; + const csrfToken = document.getElementById('csrf-token').value; + + const res = await fetch('/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: 'encrypted_content=' + encodeURIComponent(result.encrypted) + '&csrf_token=' + encodeURIComponent(document.getElementById('csrf-token').value) + body: `encrypted_content=${encodeURIComponent(encrypted)}&csrf_token=${encodeURIComponent(csrfToken)}` }); - const html = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const keyUrlSafe = await base64ToUrlSafeBase64(result.key); - const url = new URL(window.location.href); - const pasteId = doc.querySelector('[data-paste-id]').getAttribute('data-paste-id'); - const pasteUrl = url.origin + '/paste/' + pasteId + '#' + keyUrlSafe; - + + const html = await res.text(); + const doc = new DOMParser().parseFromString(html, 'text/html'); + const pasteId = doc.querySelector('[data-paste-id]')?.getAttribute('data-paste-id'); + if (!pasteId) return; + + const pasteUrl = `${location.origin}/paste/${pasteId}#${base64ToUrlSafe(key)}`; document.body.replaceChildren(...doc.body.childNodes); - - const card = document.querySelector('.card'); - const urlDiv = document.createElement('div'); - urlDiv.className = 'share-url'; - const lbl = document.createElement('label'); - lbl.textContent = 'Share this URL'; - urlDiv.appendChild(lbl); - const inp = document.createElement('input'); - inp.type = 'text'; - inp.readOnly = true; - inp.value = pasteUrl; - inp.onclick = function() { this.select(); }; - urlDiv.appendChild(inp); - card.insertBefore(urlDiv, card.firstChild); - - window.history.replaceState({}, '', pasteUrl); - - inp.select(); + showShareUrl(pasteUrl); + history.replaceState({}, '', pasteUrl); }); " }