passed/static/js/main.js
2025-05-23 22:22:18 +02:00

200 lines
5.5 KiB
JavaScript

/**
* @param {(e: *) => void} errorHandler
* @param {Backend} backend
* @param {PasswordCrypto} crypto
* @param {string} urlSplit
*/
function initShare(errorHandler, backend, crypto, urlSplit) {
const form = document.querySelector("form#share-form")
const fieldset = document.querySelector("fieldset#share-fieldset")
const submit = document.querySelector("button#share-submit")
const dialog = document.querySelector("dialog#share-dialog")
const link = document.querySelector("input#share-link")
const copy = document.querySelector("button#share-copy")
const close = document.querySelector("button#share-close")
const generate = document.querySelector("a#share-generate")
const password = document.querySelector("textarea#share-password")
form.addEventListener("submit", async (e) => {
const data = new FormData(e.target)
e.preventDefault()
try {
fieldset.disabled = true
submit.ariaBusy = "true"
const encrypted = await crypto.encryptPassword(data.get("password"))
const id = await backend.createPassword(
encrypted.password,
parseInt(data.get("expires-in")),
)
const url = new URL(window.location)
url.hash = [id, encrypted.key, encrypted.iv].join(urlSplit)
url.search = ""
link.value = url.toString()
dialog.showModal()
} catch (e) {
errorHandler(e)
} finally {
fieldset.disabled = false
submit.ariaBusy = "false"
}
})
copy.addEventListener("click", () => {
link.select()
link.setSelectionRange(0, 99999)
navigator.clipboard.writeText(link.value)
})
close.addEventListener("click", () => {
dialog.close()
})
generate.addEventListener("click", () => {
let result = ""
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for (let i = 0; i < 12; i++) {
result += charset.charAt(Math.floor(Math.random() * charset.length))
}
password.value = result
})
}
/**
* @param {(e: *) => void} errorHandler
* @param {Backend} backend
* @param {PasswordCrypto} crypto
* @param {string} urlSplit
* @param {string} hidden
*/
function initView(errorHandler, backend, crypto, urlSplit, hidden) {
const share = document.querySelector("article#share")
const loading = document.querySelector("article#loading")
const confirm = document.querySelector("article#confirm")
const confirmNo = document.querySelector("button#confirm-no")
const confirmYes = document.querySelector("button#confirm-yes")
const notFound = document.querySelector("article#not-found")
const notFoundOk = document.querySelector("button#not-found-ok")
const view = document.querySelector("article#view")
const viewPassword = document.querySelector("textarea#view-password")
const viewOk = document.querySelector("button#view-ok")
const hash = window.location.hash
if (hash == "" || hash == "#") {
share.classList.remove(hidden)
return
}
const raw = hash.substring(1)
const [id, key, iv] = raw.split(urlSplit)
async function load() {
try {
loading.classList.remove(hidden)
const has = await backend.hasPassword(id)
if (!has) {
loading.classList.add(hidden)
notFound.classList.remove(hidden)
return
}
loading.classList.add(hidden)
confirm.classList.remove(hidden)
} catch (e) {
errorHandler(e)
} finally {
loading.classList.add(hidden)
}
}
notFoundOk.addEventListener("click", () => {
notFound.classList.add(hidden)
share.classList.remove(hidden)
window.location.hash = ""
})
confirmNo.addEventListener("click", () => {
confirm.classList.add(hidden)
share.classList.remove(hidden)
window.location.hash = ""
})
confirmYes.addEventListener("click", async () => {
try {
confirmYes.ariaBusy = "true"
confirmNo.disabled = true
confirmYes.disabled = true
const encrypted = await backend.getPassword(id)
const password = await crypto.decryptPassword(encrypted, key, iv)
viewPassword.innerText = password
confirm.classList.add(hidden)
view.classList.remove(hidden)
window.location.hash = ""
} catch (e) {
errorHandler(e)
} finally {
confirmYes.ariaBusy = "false"
confirmNo.disabled = false
confirmYes.disabled = false
}
})
viewOk.addEventListener("click", () => {
view.classList.add(hidden)
share.classList.remove(hidden)
window.location.hash = ""
})
load()
}
/**
* @param {Language} language
*/
function initLanguage(language) {
const selectLanguages = document.querySelectorAll("li.select-lang")
for (const selectLang of selectLanguages) {
const lang = selectLang.getAttribute("data-lang")
selectLang.addEventListener("click", async () => {
language.setLanguage(lang)
})
}
}
function init() {
const dialog = document.querySelector("dialog#error")
const error = document.querySelector("p#error-error")
const close = document.querySelector("button#error-close")
/**
* @param {*} error Error
*/
function handleError(err) {
console.error(err)
error.innerText = err
dialog.showModal()
}
close.addEventListener("click", () => {
dialog.close()
})
const backend = new Backend()
const crypto = new PasswordCrypto()
const language = new Language(handleError, "en")
const urlSplit = ":"
initShare(handleError, backend, crypto, urlSplit)
initView(handleError, backend, crypto, urlSplit, "hidden")
initLanguage(language)
}
document.addEventListener("DOMContentLoaded", () => init())