// Application State let currentTab = "generate" let selectedFiles = [] let isProcessing = false // DOM Elements const elements = { // Tabs tabGenerate: document.getElementById("tab-generate"), tabDecrypt: document.getElementById("tab-decrypt"), generateSection: document.getElementById("generate-section"), decryptSection: document.getElementById("decrypt-section"), // Key Generation Form keyGenerationForm: document.getElementById("key-generation-form"), nameInput: document.getElementById("name"), emailInput: document.getElementById("email"), passphraseInput: document.getElementById("passphrase"), confirmPassphraseInput: document.getElementById("confirm-passphrase"), commentInput: document.getElementById("comment"), generateBtn: document.getElementById("generate-btn"), generationStatus: document.getElementById("generation-status"), loadingStatus: document.getElementById("loading-status"), successStatus: document.getElementById("success-status"), errorStatus: document.getElementById("error-status"), errorMessage: document.getElementById("error-message"), // File Decryption dropZone: document.getElementById("drop-zone"), fileInput: document.getElementById("file-input"), selectFilesBtn: document.getElementById("select-files-btn"), fileList: document.getElementById("file-list"), filesContainer: document.getElementById("files-container"), decryptionForm: document.getElementById("decryption-form"), privateKeyInput: document.getElementById("private-key"), keyPassphraseInput: document.getElementById("key-passphrase"), decryptBtn: document.getElementById("decrypt-btn"), decryptionStatus: document.getElementById("decryption-status"), progressBar: document.getElementById("progress-bar"), decryptionMessage: document.getElementById("decryption-message"), decryptionResults: document.getElementById("decryption-results"), } // Initialize Application document.addEventListener("DOMContentLoaded", function () { initializeEventListeners() setupFormValidation() setupFileHandling() }) // Event Listeners function initializeEventListeners() { // Tab Navigation elements.tabGenerate.addEventListener("click", () => switchTab("generate")) elements.tabDecrypt.addEventListener("click", () => switchTab("decrypt")) // Key Generation Form elements.keyGenerationForm.addEventListener("submit", handleKeyGeneration) // File Upload elements.selectFilesBtn.addEventListener("click", () => elements.fileInput.click() ) elements.fileInput.addEventListener("change", handleFileSelection) // Drag and Drop elements.dropZone.addEventListener("dragover", handleDragOver) elements.dropZone.addEventListener("dragleave", handleDragLeave) elements.dropZone.addEventListener("drop", handleFileDrop) // Decryption Form elements.decryptionForm.addEventListener("submit", handleFileDecryption) // Keyboard accessibility for drop zone elements.dropZone.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault() elements.fileInput.click() } }) } // Tab Management function switchTab(tabName) { currentTab = tabName // Update tab buttons document.querySelectorAll(".tab-button").forEach((btn) => { btn.classList.remove("active") btn.classList.add("text-white/70") }) const activeTab = document.getElementById(`tab-${tabName}`) activeTab.classList.add("active") activeTab.classList.remove("text-white/70") activeTab.classList.add("text-white") // Update tab content elements.generateSection.classList.toggle("hidden", tabName !== "generate") elements.decryptSection.classList.toggle("hidden", tabName !== "decrypt") // Reset states when switching tabs resetFormStates() } // Form Validation function setupFormValidation() { // Real-time validation elements.nameInput.addEventListener("input", () => validateField("name")) elements.emailInput.addEventListener("input", () => validateField("email")) elements.passphraseInput.addEventListener("input", () => validateField("passphrase") ) elements.confirmPassphraseInput.addEventListener("input", () => validateField("confirm-passphrase") ) elements.privateKeyInput.addEventListener("input", () => validateField("private-key") ) elements.keyPassphraseInput.addEventListener("input", () => validateField("key-passphrase") ) } function validateField(fieldName) { const field = document.getElementById(fieldName) const errorElement = document.getElementById(`${fieldName}-error`) let isValid = true let errorMessage = "" switch (fieldName) { case "name": if (!field.value.trim()) { isValid = false errorMessage = "Full name is required" } break case "email": if (!field.value.trim()) { isValid = false errorMessage = "Email address is required" } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.value)) { isValid = false errorMessage = "Please enter a valid email address" } break case "passphrase": if (!field.value) { isValid = false errorMessage = "Passphrase is required" } else if (field.value.length < 8) { isValid = false errorMessage = "Passphrase must be at least 8 characters long" } break case "confirm-passphrase": if (field.value !== elements.passphraseInput.value) { isValid = false errorMessage = "Passphrases do not match" } break case "private-key": if (!field.value.trim()) { isValid = false errorMessage = "Private key is required" } else if ( !field.value.includes("-----BEGIN PGP PRIVATE KEY BLOCK-----") ) { isValid = false errorMessage = "Please enter a valid PGP private key" } break case "key-passphrase": if (!field.value) { isValid = false errorMessage = "Key passphrase is required" } break } // Update field styling and error message if (isValid) { field.classList.remove("border-red-500") field.classList.add("border-gray-300") errorElement.classList.add("hidden") errorElement.textContent = "" } else { field.classList.add("border-red-500") field.classList.remove("border-gray-300") errorElement.classList.remove("hidden") errorElement.textContent = errorMessage } return isValid } function validateForm(formType) { let isValid = true if (formType === "generation") { isValid = validateField("name") && isValid isValid = validateField("email") && isValid isValid = validateField("passphrase") && isValid isValid = validateField("confirm-passphrase") && isValid } else if (formType === "decryption") { isValid = validateField("private-key") && isValid isValid = validateField("key-passphrase") && isValid } return isValid } // Key Generation async function handleKeyGeneration(e) { e.preventDefault() if (isProcessing || !validateForm("generation")) { return } isProcessing = true showGenerationStatus("loading") const formData = { name: elements.nameInput.value.trim(), email: elements.emailInput.value.trim(), passphrase: elements.passphraseInput.value, comment: elements.commentInput.value.trim() || undefined, } try { // Simulate API call (replace with actual API endpoint) const responseKeyGeneration = await keyGenerationAPI(formData) if (!responseKeyGeneration.success) { console.error("Key generation failed:", responseKeyGeneration.error) throw new Error("Key generation failed") } // Auto-download public key const responsePublicKey = await publicKeyDownloadAPI(formData.email) if (!responsePublicKey.ok) { const errorData = await response.json() console.error("Failed to download public key:", errorData) throw new Error("Failed to download public key") } downloadFile( await responsePublicKey.blob(), responsePublicKey.filename, responsePublicKey.mimetype ) showGenerationStatus("success") // Reset form after successful generation setTimeout(() => { elements.keyGenerationForm.reset() showGenerationStatus("hidden") }, 3000) } catch (error) { console.error("Key generation error:", error) showGenerationStatus("error", error.message) } finally { isProcessing = false } } function showGenerationStatus(type, message = "") { elements.generationStatus.classList.remove("hidden") elements.loadingStatus.classList.add("hidden") elements.successStatus.classList.add("hidden") elements.errorStatus.classList.add("hidden") if (type === "loading") { elements.loadingStatus.classList.remove("hidden") elements.generateBtn.disabled = true elements.generateBtn.innerHTML = 'Generating...' } else if (type === "success") { elements.successStatus.classList.remove("hidden") elements.generateBtn.disabled = false elements.generateBtn.innerHTML = 'Generate Key Pair' } else if (type === "error") { elements.errorStatus.classList.remove("hidden") elements.errorMessage.textContent = message elements.generateBtn.disabled = false elements.generateBtn.innerHTML = 'Generate Key Pair' } else { elements.generationStatus.classList.add("hidden") elements.generateBtn.disabled = false elements.generateBtn.innerHTML = 'Generate Key Pair' } } // File Handling function setupFileHandling() { // Prevent default drag behaviors ;["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { document.addEventListener(eventName, preventDefaults, false) }) function preventDefaults(e) { e.preventDefault() e.stopPropagation() } } function handleDragOver(e) { e.preventDefault() elements.dropZone.classList.add("drag-over") } function handleDragLeave(e) { e.preventDefault() elements.dropZone.classList.remove("drag-over") } function handleFileDrop(e) { e.preventDefault() elements.dropZone.classList.remove("drag-over") const files = Array.from(e.dataTransfer.files) processFiles(files) } function handleFileSelection(e) { const files = Array.from(e.target.files) processFiles(files) } function processFiles(files) { const validFiles = [] const allowedTypes = [".asc", ".gpg", ".pgp", ".txt"] const maxSize = 10 * 1024 * 1024 // 10MB files.forEach((file) => { const extension = "." + file.name.split(".").pop().toLowerCase() if (!allowedTypes.includes(extension)) { showNotification( `File "${file.name}" has an unsupported format.`, "error" ) return } if (file.size > maxSize) { showNotification(`File "${file.name}" is too large (max 10MB).`, "error") return } validFiles.push(file) }) if (validFiles.length > 0) { selectedFiles = validFiles displaySelectedFiles() elements.decryptionForm.classList.remove("hidden") } } function displaySelectedFiles() { elements.fileList.classList.remove("hidden") elements.filesContainer.innerHTML = "" selectedFiles.forEach((file, index) => { const fileElement = document.createElement("div") fileElement.className = "flex items-center justify-between p-3 bg-gray-50 rounded-lg" fileElement.innerHTML = `