// 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 = `
${file.name}
${formatFileSize( file.size )}
` elements.filesContainer.appendChild(fileElement) }) } function removeFile(index) { selectedFiles.splice(index, 1) if (selectedFiles.length === 0) { elements.fileList.classList.add("hidden") elements.decryptionForm.classList.add("hidden") } else { displaySelectedFiles() } } function formatFileSize(bytes) { if (bytes === 0) return "0 Bytes" const k = 1024 const sizes = ["Bytes", "KB", "MB", "GB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] } // File Decryption async function handleFileDecryption(e) { e.preventDefault() if ( isProcessing || !validateForm("decryption") || selectedFiles.length === 0 ) { return } isProcessing = true showDecryptionStatus(true) const privateKey = elements.privateKeyInput.value.trim() const passphrase = elements.keyPassphraseInput.value try { const results = [] for (let i = 0; i < selectedFiles.length; i++) { const file = selectedFiles[i] const progress = ((i + 1) / selectedFiles.length) * 100 updateProgress(progress, `Decrypting ${file.name}...`) // Read file content const fileContent = await readFileAsText(file) // Simulate API call for decryption const result = await simulateDecryptionAPI({ encryptedContent: fileContent, privateKey: privateKey, passphrase: passphrase, filename: file.name, }) if (result.success) { results.push({ filename: file.name, decryptedContent: result.decryptedContent, success: true, }) // Auto-download decrypted file const decryptedFilename = file.name.replace( /\.(asc|gpg|pgp)$/, "_decrypted.txt" ) downloadFile(result.decryptedContent, decryptedFilename, "text/plain") } else { results.push({ filename: file.name, error: result.error, success: false, }) } } displayDecryptionResults(results) } catch (error) { console.error("Decryption error:", error) showNotification("Decryption failed: " + error.message, "error") } finally { isProcessing = false setTimeout(() => { showDecryptionStatus(false) elements.decryptionForm.reset() selectedFiles = [] elements.fileList.classList.add("hidden") elements.decryptionForm.classList.add("hidden") }, 3000) } } function showDecryptionStatus(show) { elements.decryptionStatus.classList.toggle("hidden", !show) elements.decryptBtn.disabled = show if (show) { elements.decryptBtn.innerHTML = 'Decrypting...' updateProgress(0, "Preparing decryption...") } else { elements.decryptBtn.innerHTML = 'Decrypt Files' } } function updateProgress(percentage, message) { elements.progressBar.style.width = percentage + "%" elements.decryptionMessage.textContent = message } function displayDecryptionResults(results) { elements.decryptionResults.innerHTML = "" results.forEach((result) => { const resultElement = document.createElement("div") resultElement.className = `p-3 rounded-lg ${ result.success ? "bg-green-50 border border-green-200" : "bg-red-50 border border-red-200" }` if (result.success) { resultElement.innerHTML = `
${result.filename} - Decrypted successfully
` } else { resultElement.innerHTML = `
${result.filename} - ${result.error}
` } elements.decryptionResults.appendChild(resultElement) }) } // Utility Functions async function downloadFile(content, filename, mimeType) { // Create download link const url = URL.createObjectURL(content) const a = document.createElement("a") a.href = url a.download = filename // Trigger download document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } function readFileAsText(file) { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = (e) => resolve(e.target.result) reader.onerror = () => reject(new Error("Failed to read file")) reader.readAsText(file) }) } function showNotification(message, type = "info") { // Create notification element const notification = document.createElement("div") notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 ${ type === "error" ? "bg-red-500 text-white" : type === "success" ? "bg-green-500 text-white" : "bg-blue-500 text-white" }` notification.innerHTML = `
${message}
` document.body.appendChild(notification) // Remove notification after 5 seconds setTimeout(() => { notification.remove() }, 5000) } function resetFormStates() { // Reset generation form elements.keyGenerationForm.reset() showGenerationStatus("hidden") // Reset decryption form elements.decryptionForm.reset() selectedFiles = [] elements.fileList.classList.add("hidden") elements.decryptionForm.classList.add("hidden") showDecryptionStatus(false) // Clear validation errors document.querySelectorAll(".text-red-500").forEach((error) => { error.classList.add("hidden") }) // Reset field styling document.querySelectorAll(".border-red-500").forEach((field) => { field.classList.remove("border-red-500") field.classList.add("border-gray-300") }) isProcessing = false } // API Simulation Functions (Replace with actual API calls) async function keyGenerationAPI(data) { const response = await fetch("/api/generate-key", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }) return await response.json().then((result) => { return { ...result, success: true, } }) } async function publicKeyDownloadAPI(email) { const response = await fetch("/api/download/public-key", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: email }), }) if (!response.ok) { throw new Error("Failed to download public key") } return { ok: response.ok, blob: () => response.blob(), mimetype: response.headers.get("content-type"), filename: response.headers .get("content-disposition") ?.split("filename=")[1] ?.replace(/"/g, "") || "public.asc", } } async function simulateDecryptionAPI(data) { // Simulate API delay await new Promise((resolve) => setTimeout(resolve, 1000)) // Simulate API response return { success: true, decryptedContent: `Decrypted content from ${data.filename} This is a simulated decryption result. Original file: ${data.filename} Decrypted at: ${new Date().toISOString()} Your encrypted content would appear here in a real implementation.`, message: "File decrypted successfully", } // Uncomment for actual API call: /* const response = await fetch('/api/decrypt-file', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); return await response.json(); */ }