refactor: modernize JavaScript with ES6+ syntax (arrow functions, destructuring, template literals)
This commit is contained in:
139
src/html.gleam
139
src/html.gleam
@@ -112,16 +112,18 @@ pub fn not_found() -> String {
|
|||||||
|
|
||||||
fn decrypt_js(encrypted_content: String) -> String {
|
fn decrypt_js(encrypted_content: String) -> String {
|
||||||
"
|
"
|
||||||
(async function() {
|
(async () => {
|
||||||
const encryptedContent = '" <> escape_js_string(encrypted_content) <> "';
|
const encryptedContent = '" <> escape_js_string(encrypted_content) <> "';
|
||||||
const hash = window.location.hash.slice(1);
|
const hash = location.hash.slice(1);
|
||||||
const loadingEl = document.getElementById('loading');
|
const els = {
|
||||||
const errorEl = document.getElementById('error');
|
loading: document.getElementById('loading'),
|
||||||
const contentEl = document.getElementById('content-display');
|
error: document.getElementById('error'),
|
||||||
|
content: document.getElementById('content-display')
|
||||||
|
};
|
||||||
|
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
loadingEl.classList.add('hidden');
|
els.loading.classList.add('hidden');
|
||||||
errorEl.classList.remove('hidden');
|
els.error.classList.remove('hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,29 +132,18 @@ fn decrypt_js(encrypted_content: String) -> String {
|
|||||||
const encryptedBytes = Uint8Array.from(atob(encryptedContent), c => c.charCodeAt(0));
|
const encryptedBytes = Uint8Array.from(atob(encryptedContent), c => c.charCodeAt(0));
|
||||||
const keyBytes = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
|
const keyBytes = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
|
||||||
const iv = encryptedBytes.slice(0, 12);
|
const iv = encryptedBytes.slice(0, 12);
|
||||||
const ciphertext = encryptedBytes.slice(12);
|
|
||||||
const key = await crypto.subtle.importKey(
|
const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']);
|
||||||
'raw',
|
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encryptedBytes.slice(12));
|
||||||
keyBytes,
|
|
||||||
{ name: 'AES-GCM' },
|
els.content.textContent = new TextDecoder().decode(decrypted);
|
||||||
false,
|
els.loading.classList.add('hidden');
|
||||||
['decrypt']
|
els.error.classList.add('hidden');
|
||||||
);
|
els.content.classList.remove('hidden');
|
||||||
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');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Decryption failed:', e);
|
console.error('Decryption failed:', e);
|
||||||
loadingEl.classList.add('hidden');
|
els.loading.classList.add('hidden');
|
||||||
errorEl.classList.remove('hidden');
|
els.error.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
"
|
"
|
||||||
@@ -172,73 +163,57 @@ fn crypto_js() -> String {
|
|||||||
async function encryptContent(content) {
|
async function encryptContent(content) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const data = encoder.encode(content);
|
const data = encoder.encode(content);
|
||||||
const key = await crypto.subtle.generateKey(
|
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
|
||||||
{ name: 'AES-GCM', length: 256 },
|
|
||||||
true,
|
|
||||||
['encrypt', 'decrypt']
|
|
||||||
);
|
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
const encrypted = await crypto.subtle.encrypt(
|
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data);
|
||||||
{ name: 'AES-GCM', iv: iv },
|
|
||||||
key,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
const keyData = await crypto.subtle.exportKey('raw', key);
|
const keyData = await crypto.subtle.exportKey('raw', key);
|
||||||
const keyBytes = new Uint8Array(keyData);
|
return {
|
||||||
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
encrypted: btoa(String.fromCharCode(...new Uint8Array([...iv, ...new Uint8Array(encrypted)]))),
|
||||||
combined.set(iv);
|
key: btoa(String.fromCharCode(...new Uint8Array(keyData)))
|
||||||
combined.set(new Uint8Array(encrypted), iv.length);
|
};
|
||||||
const encryptedBase64 = btoa(String.fromCharCode(...combined));
|
|
||||||
const keyBase64 = btoa(String.fromCharCode(...keyBytes));
|
|
||||||
return { encrypted: encryptedBase64, key: keyBase64 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function base64ToUrlSafeBase64(base64) {
|
function base64ToUrlSafe(base64) {
|
||||||
let result = base64.split('+').join('-');
|
return base64.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
|
||||||
result = result.split('/').join('_');
|
|
||||||
while (result.endsWith('=')) {
|
|
||||||
result = result.slice(0, -1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = '<label>Share this URL</label>';
|
||||||
|
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();
|
e.preventDefault();
|
||||||
const content = document.getElementById('content').value;
|
const content = document.getElementById('content').value;
|
||||||
const result = await encryptContent(content);
|
const { encrypted, key } = await encryptContent(content);
|
||||||
document.getElementById('encrypted-content').value = result.encrypted;
|
document.getElementById('encrypted-content').value = encrypted;
|
||||||
const response = await fetch('/', {
|
const csrfToken = document.getElementById('csrf-token').value;
|
||||||
|
|
||||||
|
const res = await fetch('/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
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);
|
document.body.replaceChildren(...doc.body.childNodes);
|
||||||
|
showShareUrl(pasteUrl);
|
||||||
const card = document.querySelector('.card');
|
history.replaceState({}, '', pasteUrl);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user