From 6d19c5f2e41b6c0998cfbd7856c8ed0448dcd0c1 Mon Sep 17 00:00:00 2001 From: Sebastian Espei Date: Tue, 24 Mar 2026 01:55:48 +0100 Subject: [PATCH] Revert formatting churn in signing pages --- src/js/logic/digital-sign-pdf-page.ts | 1167 ++++++++++++------------- src/js/logic/sign-pdf-page.ts | 537 ++++++------ 2 files changed, 789 insertions(+), 915 deletions(-) diff --git a/src/js/logic/digital-sign-pdf-page.ts b/src/js/logic/digital-sign-pdf-page.ts index 42611d0..1b2d607 100644 --- a/src/js/logic/digital-sign-pdf-page.ts +++ b/src/js/logic/digital-sign-pdf-page.ts @@ -1,775 +1,690 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { - readFileAsArrayBuffer, - formatBytes, - downloadFile, - getPDFDocument, -} from '../utils/helpers.js'; +import { readFileAsArrayBuffer, formatBytes, downloadFile, getPDFDocument } from '../utils/helpers.js'; import { t } from '../i18n/i18n'; import { - signPdf, - parsePfxFile, - parseCombinedPem, - getCertificateInfo, + signPdf, + parsePfxFile, + parseCombinedPem, + getCertificateInfo, } from './digital-sign-pdf.js'; -import { - SignatureInfo, - VisibleSignatureOptions, - DigitalSignState, -} from '@/types'; +import { SignatureInfo, VisibleSignatureOptions, DigitalSignState } from '@/types'; const state: DigitalSignState = { - pdfFile: null, - pdfBytes: null, - certFile: null, - certData: null, - sigImageData: null, - sigImageType: null, + pdfFile: null, + pdfBytes: null, + certFile: null, + certData: null, + sigImageData: null, + sigImageType: null, }; function resetState(): void { - state.pdfFile = null; - state.pdfBytes = null; - state.certFile = null; - state.certData = null; - state.sigImageData = null; - state.sigImageType = null; + state.pdfFile = null; + state.pdfBytes = null; + state.certFile = null; + state.certData = null; + state.sigImageData = null; + state.sigImageType = null; - const fileDisplayArea = getElement('file-display-area'); - if (fileDisplayArea) fileDisplayArea.innerHTML = ''; + const fileDisplayArea = getElement('file-display-area'); + if (fileDisplayArea) fileDisplayArea.innerHTML = ''; - const certDisplayArea = getElement('cert-display-area'); - if (certDisplayArea) certDisplayArea.innerHTML = ''; + const certDisplayArea = getElement('cert-display-area'); + if (certDisplayArea) certDisplayArea.innerHTML = ''; - const fileInput = getElement('file-input'); - if (fileInput) fileInput.value = ''; + const fileInput = getElement('file-input'); + if (fileInput) fileInput.value = ''; - const certInput = getElement('cert-input'); - if (certInput) certInput.value = ''; + const certInput = getElement('cert-input'); + if (certInput) certInput.value = ''; - const sigImageInput = getElement('sig-image-input'); - if (sigImageInput) sigImageInput.value = ''; + const sigImageInput = getElement('sig-image-input'); + if (sigImageInput) sigImageInput.value = ''; - const sigImagePreview = getElement('sig-image-preview'); - if (sigImagePreview) sigImagePreview.classList.add('hidden'); + const sigImagePreview = getElement('sig-image-preview'); + if (sigImagePreview) sigImagePreview.classList.add('hidden'); - const certSection = getElement('certificate-section'); - if (certSection) certSection.classList.add('hidden'); + const certSection = getElement('certificate-section'); + if (certSection) certSection.classList.add('hidden'); - hidePasswordSection(); - hideSignatureOptions(); - hideCertInfo(); - updateProcessButton(); + hidePasswordSection(); + hideSignatureOptions(); + hideCertInfo(); + updateProcessButton(); } function getElement(id: string): T | null { - return document.getElementById(id) as T | null; + return document.getElementById(id) as T | null; } function initializePage(): void { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = getElement('file-input'); - const dropZone = getElement('drop-zone'); - const certInput = getElement('cert-input'); - const certDropZone = getElement('cert-drop-zone'); - const certPassword = getElement('cert-password'); - const processBtn = getElement('process-btn'); - const backBtn = getElement('back-to-tools'); + const fileInput = getElement('file-input'); + const dropZone = getElement('drop-zone'); + const certInput = getElement('cert-input'); + const certDropZone = getElement('cert-drop-zone'); + const certPassword = getElement('cert-password'); + const processBtn = getElement('process-btn'); + const backBtn = getElement('back-to-tools'); - if (fileInput) { - fileInput.addEventListener('change', handlePdfUpload); - fileInput.addEventListener('click', () => { - fileInput.value = ''; - }); - } + if (fileInput) { + fileInput.addEventListener('change', handlePdfUpload); + fileInput.addEventListener('click', () => { + fileInput.value = ''; + }); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); - }); + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handlePdfFile(droppedFiles[0]); - } - }); - } + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handlePdfFile(droppedFiles[0]); + } + }); + } - if (certInput) { - certInput.addEventListener('change', handleCertUpload); - certInput.addEventListener('click', () => { - certInput.value = ''; - }); - } + if (certInput) { + certInput.addEventListener('change', handleCertUpload); + certInput.addEventListener('click', () => { + certInput.value = ''; + }); + } - if (certDropZone) { - certDropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - certDropZone.classList.add('bg-gray-700'); - }); + if (certDropZone) { + certDropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + certDropZone.classList.add('bg-gray-700'); + }); - certDropZone.addEventListener('dragleave', () => { - certDropZone.classList.remove('bg-gray-700'); - }); + certDropZone.addEventListener('dragleave', () => { + certDropZone.classList.remove('bg-gray-700'); + }); - certDropZone.addEventListener('drop', (e) => { - e.preventDefault(); - certDropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleCertFile(droppedFiles[0]); - } - }); - } + certDropZone.addEventListener('drop', (e) => { + e.preventDefault(); + certDropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleCertFile(droppedFiles[0]); + } + }); + } - if (certPassword) { - certPassword.addEventListener('input', handlePasswordInput); - certPassword.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - handlePasswordInput(); - } - }); - } + if (certPassword) { + certPassword.addEventListener('input', handlePasswordInput); + certPassword.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + handlePasswordInput(); + } + }); + } - if (processBtn) { - processBtn.addEventListener('click', processSignature); - } + if (processBtn) { + processBtn.addEventListener('click', processSignature); + } - if (backBtn) { - backBtn.addEventListener('click', () => { - window.location.href = import.meta.env.BASE_URL; - }); - } + if (backBtn) { + backBtn.addEventListener('click', () => { + window.location.href = import.meta.env.BASE_URL; + }); + } - const enableVisibleSig = getElement('enable-visible-sig'); - const visibleSigOptions = getElement('visible-sig-options'); - const sigPage = getElement('sig-page'); - const customPageWrapper = getElement('custom-page-wrapper'); - const sigImageInput = getElement('sig-image-input'); - const sigImagePreview = getElement('sig-image-preview'); - const sigImageThumb = getElement('sig-image-thumb'); - const removeSigImage = getElement('remove-sig-image'); - const enableSigText = getElement('enable-sig-text'); - const sigTextOptions = getElement('sig-text-options'); + const enableVisibleSig = getElement('enable-visible-sig'); + const visibleSigOptions = getElement('visible-sig-options'); + const sigPage = getElement('sig-page'); + const customPageWrapper = getElement('custom-page-wrapper'); + const sigImageInput = getElement('sig-image-input'); + const sigImagePreview = getElement('sig-image-preview'); + const sigImageThumb = getElement('sig-image-thumb'); + const removeSigImage = getElement('remove-sig-image'); + const enableSigText = getElement('enable-sig-text'); + const sigTextOptions = getElement('sig-text-options'); - if (enableVisibleSig && visibleSigOptions) { - enableVisibleSig.addEventListener('change', () => { - if (enableVisibleSig.checked) { - visibleSigOptions.classList.remove('hidden'); - } else { - visibleSigOptions.classList.add('hidden'); - } - }); - } + if (enableVisibleSig && visibleSigOptions) { + enableVisibleSig.addEventListener('change', () => { + if (enableVisibleSig.checked) { + visibleSigOptions.classList.remove('hidden'); + } else { + visibleSigOptions.classList.add('hidden'); + } + }); + } - if (sigPage && customPageWrapper) { - sigPage.addEventListener('change', () => { - if (sigPage.value === 'custom') { - customPageWrapper.classList.remove('hidden'); - } else { - customPageWrapper.classList.add('hidden'); - } - }); - } + if (sigPage && customPageWrapper) { + sigPage.addEventListener('change', () => { + if (sigPage.value === 'custom') { + customPageWrapper.classList.remove('hidden'); + } else { + customPageWrapper.classList.add('hidden'); + } + }); + } - if (sigImageInput) { - sigImageInput.addEventListener('change', async (e) => { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - const file = input.files[0]; - const validTypes = ['image/png', 'image/jpeg', 'image/webp']; - if (!validTypes.includes(file.type)) { - showAlert( - 'Invalid Image', - 'Please select a PNG, JPG, or WebP image.' - ); - return; - } - state.sigImageData = (await readFileAsArrayBuffer(file)) as ArrayBuffer; - state.sigImageType = file.type.replace('image/', '') as - | 'png' - | 'jpeg' - | 'webp'; + if (sigImageInput) { + sigImageInput.addEventListener('change', async (e) => { + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + const file = input.files[0]; + const validTypes = ['image/png', 'image/jpeg', 'image/webp']; + if (!validTypes.includes(file.type)) { + showAlert('Invalid Image', 'Please select a PNG, JPG, or WebP image.'); + return; + } + state.sigImageData = await readFileAsArrayBuffer(file) as ArrayBuffer; + state.sigImageType = file.type.replace('image/', '') as 'png' | 'jpeg' | 'webp'; - if (sigImageThumb && sigImagePreview) { - const url = URL.createObjectURL(file); - sigImageThumb.src = url; - sigImagePreview.classList.remove('hidden'); - } - } - }); - } + if (sigImageThumb && sigImagePreview) { + const url = URL.createObjectURL(file); + sigImageThumb.src = url; + sigImagePreview.classList.remove('hidden'); + } + } + }); + } - if (removeSigImage && sigImagePreview) { - removeSigImage.addEventListener('click', () => { - state.sigImageData = null; - state.sigImageType = null; - sigImagePreview.classList.add('hidden'); - if (sigImageInput) sigImageInput.value = ''; - }); - } + if (removeSigImage && sigImagePreview) { + removeSigImage.addEventListener('click', () => { + state.sigImageData = null; + state.sigImageType = null; + sigImagePreview.classList.add('hidden'); + if (sigImageInput) sigImageInput.value = ''; + }); + } - if (enableSigText && sigTextOptions) { - enableSigText.addEventListener('change', () => { - if (enableSigText.checked) { - sigTextOptions.classList.remove('hidden'); - } else { - sigTextOptions.classList.add('hidden'); - } - }); - } + if (enableSigText && sigTextOptions) { + enableSigText.addEventListener('change', () => { + if (enableSigText.checked) { + sigTextOptions.classList.remove('hidden'); + } else { + sigTextOptions.classList.add('hidden'); + } + }); + } } function handlePdfUpload(e: Event): void { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handlePdfFile(input.files[0]); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handlePdfFile(input.files[0]); + } } async function handlePdfFile(file: File): Promise { - if ( - file.type !== 'application/pdf' && - !file.name.toLowerCase().endsWith('.pdf') - ) { - showAlert('Invalid File', 'Please select a PDF file.'); - return; - } + if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { + showAlert('Invalid File', 'Please select a PDF file.'); + return; + } - state.pdfFile = file; - state.pdfBytes = new Uint8Array( - (await readFileAsArrayBuffer(file)) as ArrayBuffer - ); + state.pdfFile = file; + state.pdfBytes = new Uint8Array(await readFileAsArrayBuffer(file) as ArrayBuffer); - updatePdfDisplay(); - showCertificateSection(); + updatePdfDisplay(); + showCertificateSection(); } async function updatePdfDisplay(): Promise { - const fileDisplayArea = getElement('file-display-area'); + const fileDisplayArea = getElement('file-display-area'); - if (!fileDisplayArea || !state.pdfFile) return; + if (!fileDisplayArea || !state.pdfFile) return; - fileDisplayArea.innerHTML = ''; - - const fileDiv = document.createElement('div'); - fileDiv.className = - 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = state.pdfFile.name; - - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${t('common.loadingPageCount')}`; - - infoContainer.append(nameSpan, metaSpan); - - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - state.pdfFile = null; - state.pdfBytes = null; fileDisplayArea.innerHTML = ''; - hideCertificateSection(); - updateProcessButton(); - }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - try { - if (state.pdfBytes) { - const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }) - .promise; - metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${pdfDoc.numPages} pages`; + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = state.pdfFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${t('common.loadingPageCount')}`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.pdfFile = null; + state.pdfBytes = null; + fileDisplayArea.innerHTML = ''; + hideCertificateSection(); + updateProcessButton(); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); + + try { + if (state.pdfBytes) { + const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }).promise; + metaSpan.textContent = `${formatBytes(state.pdfFile.size)} • ${pdfDoc.numPages} pages`; + } + } catch (error) { + console.error('Error loading PDF:', error); + metaSpan.textContent = `${formatBytes(state.pdfFile.size)}`; } - } catch (error) { - console.error('Error loading PDF:', error); - metaSpan.textContent = `${formatBytes(state.pdfFile.size)}`; - } } function showCertificateSection(): void { - const certSection = getElement('certificate-section'); - if (certSection) { - certSection.classList.remove('hidden'); - } + const certSection = getElement('certificate-section'); + if (certSection) { + certSection.classList.remove('hidden'); + } } function hideCertificateSection(): void { - const certSection = getElement('certificate-section'); - const signatureOptions = getElement('signature-options'); + const certSection = getElement('certificate-section'); + const signatureOptions = getElement('signature-options'); - if (certSection) { - certSection.classList.add('hidden'); - } - if (signatureOptions) { - signatureOptions.classList.add('hidden'); - } + if (certSection) { + certSection.classList.add('hidden'); + } + if (signatureOptions) { + signatureOptions.classList.add('hidden'); + } - state.certFile = null; - state.certData = null; + state.certFile = null; + state.certData = null; - const certDisplayArea = getElement('cert-display-area'); - if (certDisplayArea) { - certDisplayArea.innerHTML = ''; - } + const certDisplayArea = getElement('cert-display-area'); + if (certDisplayArea) { + certDisplayArea.innerHTML = ''; + } - const certInfo = getElement('cert-info'); - if (certInfo) { - certInfo.classList.add('hidden'); - } + const certInfo = getElement('cert-info'); + if (certInfo) { + certInfo.classList.add('hidden'); + } - const certPasswordSection = getElement( - 'cert-password-section' - ); - if (certPasswordSection) { - certPasswordSection.classList.add('hidden'); - } + const certPasswordSection = getElement('cert-password-section'); + if (certPasswordSection) { + certPasswordSection.classList.add('hidden'); + } } function handleCertUpload(e: Event): void { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleCertFile(input.files[0]); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleCertFile(input.files[0]); + } } async function handleCertFile(file: File): Promise { - const validExtensions = ['.pfx', '.p12', '.pem']; - const hasValidExtension = validExtensions.some((ext) => - file.name.toLowerCase().endsWith(ext) - ); - - if (!hasValidExtension) { - showAlert( - 'Invalid Certificate', - 'Please select a .pfx, .p12, or .pem certificate file.' + const validExtensions = ['.pfx', '.p12', '.pem']; + const hasValidExtension = validExtensions.some(ext => + file.name.toLowerCase().endsWith(ext) ); - return; - } - state.certFile = file; - state.certData = null; - - updateCertDisplay(); - - const isPemFile = file.name.toLowerCase().endsWith('.pem'); - - if (isPemFile) { - try { - const pemContent = await file.text(); - const isEncrypted = pemContent.includes('ENCRYPTED'); - - if (isEncrypted) { - showPasswordSection(); - updatePasswordLabel('Private Key Password'); - } else { - state.certData = parseCombinedPem(pemContent); - updateCertInfo(); - showSignatureOptions(); - - const certStatus = getElement('cert-status'); - if (certStatus) { - certStatus.innerHTML = - 'Certificate loaded '; - createIcons({ icons }); - certStatus.className = 'text-xs text-green-400'; - } - } - } catch (error) { - const certStatus = getElement('cert-status'); - if (certStatus) { - certStatus.textContent = 'Failed to parse PEM file'; - certStatus.className = 'text-xs text-red-400'; - } + if (!hasValidExtension) { + showAlert('Invalid Certificate', 'Please select a .pfx, .p12, or .pem certificate file.'); + return; } - } else { - showPasswordSection(); - updatePasswordLabel('Certificate Password'); - } - hideSignatureOptions(); - updateProcessButton(); + state.certFile = file; + state.certData = null; + + updateCertDisplay(); + + const isPemFile = file.name.toLowerCase().endsWith('.pem'); + + if (isPemFile) { + try { + const pemContent = await file.text(); + const isEncrypted = pemContent.includes('ENCRYPTED'); + + if (isEncrypted) { + showPasswordSection(); + updatePasswordLabel('Private Key Password'); + } else { + state.certData = parseCombinedPem(pemContent); + updateCertInfo(); + showSignatureOptions(); + + const certStatus = getElement('cert-status'); + if (certStatus) { + certStatus.innerHTML = 'Certificate loaded '; + createIcons({ icons }); + certStatus.className = 'text-xs text-green-400'; + } + } + } catch (error) { + const certStatus = getElement('cert-status'); + if (certStatus) { + certStatus.textContent = 'Failed to parse PEM file'; + certStatus.className = 'text-xs text-red-400'; + } + } + } else { + showPasswordSection(); + updatePasswordLabel('Certificate Password'); + } + + hideSignatureOptions(); + updateProcessButton(); } function updateCertDisplay(): void { - const certDisplayArea = getElement('cert-display-area'); + const certDisplayArea = getElement('cert-display-area'); - if (!certDisplayArea || !state.certFile) return; + if (!certDisplayArea || !state.certFile) return; - certDisplayArea.innerHTML = ''; - - const certDiv = document.createElement('div'); - certDiv.className = - 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = state.certFile.name; - - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.id = 'cert-status'; - metaSpan.textContent = 'Enter password to unlock'; - - infoContainer.append(nameSpan, metaSpan); - - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - state.certFile = null; - state.certData = null; certDisplayArea.innerHTML = ''; - hidePasswordSection(); - hideCertInfo(); - hideSignatureOptions(); - updateProcessButton(); - }; - certDiv.append(infoContainer, removeBtn); - certDisplayArea.appendChild(certDiv); - createIcons({ icons }); + const certDiv = document.createElement('div'); + certDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; + + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = state.certFile.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.id = 'cert-status'; + metaSpan.textContent = 'Enter password to unlock'; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + state.certFile = null; + state.certData = null; + certDisplayArea.innerHTML = ''; + hidePasswordSection(); + hideCertInfo(); + hideSignatureOptions(); + updateProcessButton(); + }; + + certDiv.append(infoContainer, removeBtn); + certDisplayArea.appendChild(certDiv); + createIcons({ icons }); } function showPasswordSection(): void { - const certPasswordSection = getElement( - 'cert-password-section' - ); - if (certPasswordSection) { - certPasswordSection.classList.remove('hidden'); - } + const certPasswordSection = getElement('cert-password-section'); + if (certPasswordSection) { + certPasswordSection.classList.remove('hidden'); + } - const certPassword = getElement('cert-password'); - if (certPassword) { - certPassword.value = ''; - certPassword.focus(); - } + const certPassword = getElement('cert-password'); + if (certPassword) { + certPassword.value = ''; + certPassword.focus(); + } } function updatePasswordLabel(labelText: string): void { - const label = document.querySelector('label[for="cert-password"]'); - if (label) { - label.textContent = labelText; - } + const label = document.querySelector('label[for="cert-password"]'); + if (label) { + label.textContent = labelText; + } } function hidePasswordSection(): void { - const certPasswordSection = getElement( - 'cert-password-section' - ); - if (certPasswordSection) { - certPasswordSection.classList.add('hidden'); - } + const certPasswordSection = getElement('cert-password-section'); + if (certPasswordSection) { + certPasswordSection.classList.add('hidden'); + } } function showSignatureOptions(): void { - const signatureOptions = getElement('signature-options'); - if (signatureOptions) { - signatureOptions.classList.remove('hidden'); - } - const visibleSigSection = getElement( - 'visible-signature-section' - ); - if (visibleSigSection) { - visibleSigSection.classList.remove('hidden'); - } + const signatureOptions = getElement('signature-options'); + if (signatureOptions) { + signatureOptions.classList.remove('hidden'); + } + const visibleSigSection = getElement('visible-signature-section'); + if (visibleSigSection) { + visibleSigSection.classList.remove('hidden'); + } } function hideSignatureOptions(): void { - const signatureOptions = getElement('signature-options'); - if (signatureOptions) { - signatureOptions.classList.add('hidden'); - } - const visibleSigSection = getElement( - 'visible-signature-section' - ); - if (visibleSigSection) { - visibleSigSection.classList.add('hidden'); - } + const signatureOptions = getElement('signature-options'); + if (signatureOptions) { + signatureOptions.classList.add('hidden'); + } + const visibleSigSection = getElement('visible-signature-section'); + if (visibleSigSection) { + visibleSigSection.classList.add('hidden'); + } } function hideCertInfo(): void { - const certInfo = getElement('cert-info'); - if (certInfo) { - certInfo.classList.add('hidden'); - } + const certInfo = getElement('cert-info'); + if (certInfo) { + certInfo.classList.add('hidden'); + } } async function handlePasswordInput(): Promise { - const certPassword = getElement('cert-password'); - const password = certPassword?.value ?? ''; + const certPassword = getElement('cert-password'); + const password = certPassword?.value ?? ''; - if (!state.certFile || !password) { - return; - } - - try { - const isPemFile = state.certFile.name.toLowerCase().endsWith('.pem'); - - if (isPemFile) { - const pemContent = await state.certFile.text(); - state.certData = parseCombinedPem(pemContent, password); - } else { - const certBytes = (await readFileAsArrayBuffer( - state.certFile - )) as ArrayBuffer; - state.certData = parsePfxFile(certBytes, password); + if (!state.certFile || !password) { + return; } - updateCertInfo(); - showSignatureOptions(); - updateProcessButton(); + try { + const isPemFile = state.certFile.name.toLowerCase().endsWith('.pem'); - const certStatus = getElement('cert-status'); - if (certStatus) { - certStatus.innerHTML = - 'Certificate unlocked '; - createIcons({ icons }); - certStatus.className = 'text-xs text-green-400'; - } - } catch (error) { - state.certData = null; - hideSignatureOptions(); - updateProcessButton(); + if (isPemFile) { + const pemContent = await state.certFile.text(); + state.certData = parseCombinedPem(pemContent, password); + } else { + const certBytes = await readFileAsArrayBuffer(state.certFile) as ArrayBuffer; + state.certData = parsePfxFile(certBytes, password); + } - const certStatus = getElement('cert-status'); - if (certStatus) { - const errorMessage = - error instanceof Error - ? error.message - : 'Invalid password or certificate'; - certStatus.textContent = errorMessage.includes('password') - ? 'Incorrect password' - : 'Failed to parse certificate'; - certStatus.className = 'text-xs text-red-400'; + updateCertInfo(); + showSignatureOptions(); + updateProcessButton(); + + const certStatus = getElement('cert-status'); + if (certStatus) { + certStatus.innerHTML = 'Certificate unlocked '; + createIcons({ icons }); + certStatus.className = 'text-xs text-green-400'; + } + } catch (error) { + state.certData = null; + hideSignatureOptions(); + updateProcessButton(); + + const certStatus = getElement('cert-status'); + if (certStatus) { + const errorMessage = error instanceof Error ? error.message : 'Invalid password or certificate'; + certStatus.textContent = errorMessage.includes('password') + ? 'Incorrect password' + : 'Failed to parse certificate'; + certStatus.className = 'text-xs text-red-400'; + } } - } } function updateCertInfo(): void { - if (!state.certData) return; + if (!state.certData) return; - const certInfo = getElement('cert-info'); - const certSubject = getElement('cert-subject'); - const certIssuer = getElement('cert-issuer'); - const certValidity = getElement('cert-validity'); + const certInfo = getElement('cert-info'); + const certSubject = getElement('cert-subject'); + const certIssuer = getElement('cert-issuer'); + const certValidity = getElement('cert-validity'); - if (!certInfo) return; + if (!certInfo) return; - const info = getCertificateInfo(state.certData.certificate); + const info = getCertificateInfo(state.certData.certificate); - if (certSubject) { - certSubject.textContent = info.subject; - } - if (certIssuer) { - certIssuer.textContent = info.issuer; - } - if (certValidity) { - const formatDate = (date: Date) => date.toLocaleDateString(); - certValidity.textContent = `${formatDate(info.validFrom)} - ${formatDate(info.validTo)}`; - } + if (certSubject) { + certSubject.textContent = info.subject; + } + if (certIssuer) { + certIssuer.textContent = info.issuer; + } + if (certValidity) { + const formatDate = (date: Date) => date.toLocaleDateString(); + certValidity.textContent = `${formatDate(info.validFrom)} - ${formatDate(info.validTo)}`; + } - certInfo.classList.remove('hidden'); + certInfo.classList.remove('hidden'); } function updateProcessButton(): void { - const processBtn = getElement('process-btn'); - if (!processBtn) return; + const processBtn = getElement('process-btn'); + if (!processBtn) return; - const canProcess = state.pdfBytes !== null && state.certData !== null; + const canProcess = state.pdfBytes !== null && state.certData !== null; - if (canProcess) { - processBtn.style.display = ''; - } else { - processBtn.style.display = 'none'; - } + if (canProcess) { + processBtn.style.display = ''; + } else { + processBtn.style.display = 'none'; + } } async function processSignature(): Promise { - if (!state.pdfBytes || !state.certData) { - showAlert( - 'Missing Data', - 'Please upload both a PDF and a valid certificate.' - ); - return; - } + if (!state.pdfBytes || !state.certData) { + showAlert('Missing Data', 'Please upload both a PDF and a valid certificate.'); + return; + } - const reason = getElement('sign-reason')?.value ?? ''; - const location = getElement('sign-location')?.value ?? ''; - const contactInfo = getElement('sign-contact')?.value ?? ''; + const reason = getElement('sign-reason')?.value ?? ''; + const location = getElement('sign-location')?.value ?? ''; + const contactInfo = getElement('sign-contact')?.value ?? ''; - const signatureInfo: SignatureInfo = {}; - if (reason) signatureInfo.reason = reason; - if (location) signatureInfo.location = location; - if (contactInfo) signatureInfo.contactInfo = contactInfo; + const signatureInfo: SignatureInfo = {}; + if (reason) signatureInfo.reason = reason; + if (location) signatureInfo.location = location; + if (contactInfo) signatureInfo.contactInfo = contactInfo; - let visibleSignature: VisibleSignatureOptions | undefined; + let visibleSignature: VisibleSignatureOptions | undefined; - const enableVisibleSig = getElement('enable-visible-sig'); - if (enableVisibleSig?.checked) { - const sigX = parseInt( - getElement('sig-x')?.value ?? '25', - 10 - ); - const sigY = parseInt( - getElement('sig-y')?.value ?? '700', - 10 - ); - const sigWidth = parseInt( - getElement('sig-width')?.value ?? '150', - 10 - ); - const sigHeight = parseInt( - getElement('sig-height')?.value ?? '70', - 10 - ); + const enableVisibleSig = getElement('enable-visible-sig'); + if (enableVisibleSig?.checked) { + const sigX = parseInt(getElement('sig-x')?.value ?? '25', 10); + const sigY = parseInt(getElement('sig-y')?.value ?? '700', 10); + const sigWidth = parseInt(getElement('sig-width')?.value ?? '150', 10); + const sigHeight = parseInt(getElement('sig-height')?.value ?? '70', 10); - const sigPageSelect = getElement('sig-page'); - let sigPage: number | string = 0; - let numPages = 1; + const sigPageSelect = getElement('sig-page'); + let sigPage: number | string = 0; + let numPages = 1; + + try { + const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }).promise; + numPages = pdfDoc.numPages; + } catch (error) { + console.error('Error getting PDF page count:', error); + } + + if (sigPageSelect) { + if (sigPageSelect.value === 'last') { + sigPage = (numPages - 1).toString(); + } else if (sigPageSelect.value === 'all') { + if (numPages === 1) { + sigPage = '0'; + } else { + sigPage = `0-${numPages - 1}`; + } + } else if (sigPageSelect.value === 'custom') { + sigPage = parseInt(getElement('sig-custom-page')?.value ?? '1', 10) - 1; + } else { + sigPage = parseInt(sigPageSelect.value, 10); + } + } + + const enableSigText = getElement('enable-sig-text'); + let sigText = enableSigText?.checked ? getElement('sig-text')?.value : undefined; + const sigTextColor = getElement('sig-text-color')?.value ?? '#000000'; + const sigTextSize = parseInt(getElement('sig-text-size')?.value ?? '12', 10); + + if (!state.sigImageData && !sigText && state.certData) { + const certInfo = getCertificateInfo(state.certData.certificate); + const date = new Date().toLocaleDateString(); + sigText = `Digitally signed by ${certInfo.subject}\n${date}`; + } + + let finalHeight = sigHeight; + if (sigText && !state.sigImageData) { + const lineCount = (sigText.match(/\n/g) || []).length + 1; + const lineHeightFactor = 1.4; + const padding = 16; + const calculatedHeight = Math.ceil(lineCount * sigTextSize * lineHeightFactor + padding); + finalHeight = Math.max(calculatedHeight, sigHeight); + } + + visibleSignature = { + enabled: true, + x: sigX, + y: sigY, + width: sigWidth, + height: finalHeight, + page: sigPage, + imageData: state.sigImageData ?? undefined, + imageType: state.sigImageType ?? undefined, + text: sigText, + textColor: sigTextColor, + textSize: sigTextSize, + }; + } + + showLoader('Applying digital signature...'); try { - const pdfDoc = await getPDFDocument({ data: state.pdfBytes.slice() }) - .promise; - numPages = pdfDoc.numPages; + const signedPdfBytes = await signPdf(state.pdfBytes, state.certData, { + signatureInfo, + visibleSignature, + }); + + const blob = new Blob([signedPdfBytes.slice().buffer], { type: 'application/pdf' }); + const originalName = state.pdfFile?.name ?? 'document.pdf'; + const signedName = originalName.replace(/\.pdf$/i, '_signed.pdf'); + + downloadFile(blob, signedName); + + hideLoader(); + showAlert('Success', 'PDF signed successfully! The signature can be verified in any PDF reader.', 'success', () => { resetState(); }); } catch (error) { - console.error('Error getting PDF page count:', error); - } + hideLoader(); + console.error('Signing error:', error); + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; - if (sigPageSelect) { - if (sigPageSelect.value === 'last') { - sigPage = (numPages - 1).toString(); - } else if (sigPageSelect.value === 'all') { - if (numPages === 1) { - sigPage = '0'; + // Check if this is a CORS/network error from certificate chain fetching + if (errorMessage.includes('Failed to fetch') || errorMessage.includes('CORS') || errorMessage.includes('NetworkError')) { + showAlert( + 'Signing Failed', + 'Failed to fetch certificate chain. This may be due to network issues or the certificate proxy being unavailable. Please check your internet connection and try again. If the issue persists, contact support.' + ); } else { - sigPage = `0-${numPages - 1}`; + showAlert('Signing Failed', `Failed to sign PDF: ${errorMessage}`); } - } else if (sigPageSelect.value === 'custom') { - sigPage = - parseInt( - getElement('sig-custom-page')?.value ?? '1', - 10 - ) - 1; - } else { - sigPage = parseInt(sigPageSelect.value, 10); - } } - - const enableSigText = getElement('enable-sig-text'); - let sigText = enableSigText?.checked - ? getElement('sig-text')?.value - : undefined; - const sigTextColor = - getElement('sig-text-color')?.value ?? '#000000'; - const sigTextSize = parseInt( - getElement('sig-text-size')?.value ?? '12', - 10 - ); - - if (!state.sigImageData && !sigText && state.certData) { - const certInfo = getCertificateInfo(state.certData.certificate); - const date = new Date().toLocaleDateString(); - sigText = `Digitally signed by ${certInfo.subject}\n${date}`; - } - - let finalHeight = sigHeight; - if (sigText && !state.sigImageData) { - const lineCount = (sigText.match(/\n/g) || []).length + 1; - const lineHeightFactor = 1.4; - const padding = 16; - const calculatedHeight = Math.ceil( - lineCount * sigTextSize * lineHeightFactor + padding - ); - finalHeight = Math.max(calculatedHeight, sigHeight); - } - - visibleSignature = { - enabled: true, - x: sigX, - y: sigY, - width: sigWidth, - height: finalHeight, - page: sigPage, - imageData: state.sigImageData ?? undefined, - imageType: state.sigImageType ?? undefined, - text: sigText, - textColor: sigTextColor, - textSize: sigTextSize, - }; - } - - showLoader('Applying digital signature...'); - - try { - const signedPdfBytes = await signPdf(state.pdfBytes, state.certData, { - signatureInfo, - visibleSignature, - }); - - const blob = new Blob([signedPdfBytes.slice().buffer], { - type: 'application/pdf', - }); - const originalName = state.pdfFile?.name ?? 'document.pdf'; - const signedName = originalName.replace(/\.pdf$/i, '_signed.pdf'); - - downloadFile(blob, signedName); - - hideLoader(); - showAlert( - 'Success', - 'PDF signed successfully! The signature can be verified in any PDF reader.', - 'success', - () => { - resetState(); - } - ); - } catch (error) { - hideLoader(); - console.error('Signing error:', error); - const errorMessage = - error instanceof Error ? error.message : 'Unknown error occurred'; - - // Check if this is a CORS/network error from certificate chain fetching - if ( - errorMessage.includes('Failed to fetch') || - errorMessage.includes('CORS') || - errorMessage.includes('NetworkError') - ) { - showAlert( - 'Signing Failed', - 'Failed to fetch certificate chain. This may be due to network issues or the certificate proxy being unavailable. Please check your internet connection and try again. If the issue persists, contact support.' - ); - } else { - showAlert('Signing Failed', `Failed to sign PDF: ${errorMessage}`); - } - } } if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } diff --git a/src/js/logic/sign-pdf-page.ts b/src/js/logic/sign-pdf-page.ts index f446f3f..633e8fd 100644 --- a/src/js/logic/sign-pdf-page.ts +++ b/src/js/logic/sign-pdf-page.ts @@ -1,354 +1,313 @@ import { createIcons, icons } from 'lucide'; import { showAlert, showLoader, hideLoader } from '../ui.js'; -import { - readFileAsArrayBuffer, - formatBytes, - downloadFile, - getPDFDocument, -} from '../utils/helpers.js'; +import { readFileAsArrayBuffer, formatBytes, downloadFile, getPDFDocument } from '../utils/helpers.js'; import { PDFDocument } from 'pdf-lib'; import { t } from '../i18n/i18n'; interface SignState { - file: File | null; - pdfDoc: any; - viewerIframe: HTMLIFrameElement | null; - viewerReady: boolean; - blobUrl: string | null; + file: File | null; + pdfDoc: any; + viewerIframe: HTMLIFrameElement | null; + viewerReady: boolean; + blobUrl: string | null; } const signState: SignState = { - file: null, - pdfDoc: null, - viewerIframe: null, - viewerReady: false, - blobUrl: null, + file: null, + pdfDoc: null, + viewerIframe: null, + viewerReady: false, + blobUrl: null, }; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initializePage); + document.addEventListener('DOMContentLoaded', initializePage); } else { - initializePage(); + initializePage(); } function initializePage() { - createIcons({ icons }); + createIcons({ icons }); - const fileInput = document.getElementById('file-input') as HTMLInputElement; - const dropZone = document.getElementById('drop-zone'); - const processBtn = document.getElementById('process-btn'); + const fileInput = document.getElementById('file-input') as HTMLInputElement; + const dropZone = document.getElementById('drop-zone'); + const processBtn = document.getElementById('process-btn'); - if (fileInput) { - fileInput.addEventListener('change', handleFileUpload); - } + if (fileInput) { + fileInput.addEventListener('change', handleFileUpload); + } - if (dropZone) { - dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropZone.classList.add('bg-gray-700'); + if (dropZone) { + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('bg-gray-700'); + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('bg-gray-700'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('bg-gray-700'); + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + handleFile(droppedFiles[0]); + } + }); + + // Clear value on click to allow re-selecting the same file + fileInput?.addEventListener('click', () => { + if (fileInput) fileInput.value = ''; + }); + } + + if (processBtn) { + processBtn.addEventListener('click', applyAndSaveSignatures); + } + + document.getElementById('back-to-tools')?.addEventListener('click', () => { + cleanup(); + window.location.href = import.meta.env.BASE_URL; }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('bg-gray-700'); - }); - - dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('bg-gray-700'); - const droppedFiles = e.dataTransfer?.files; - if (droppedFiles && droppedFiles.length > 0) { - handleFile(droppedFiles[0]); - } - }); - - // Clear value on click to allow re-selecting the same file - fileInput?.addEventListener('click', () => { - if (fileInput) fileInput.value = ''; - }); - } - - if (processBtn) { - processBtn.addEventListener('click', applyAndSaveSignatures); - } - - document.getElementById('back-to-tools')?.addEventListener('click', () => { - cleanup(); - window.location.href = import.meta.env.BASE_URL; - }); } function handleFileUpload(e: Event) { - const input = e.target as HTMLInputElement; - if (input.files && input.files.length > 0) { - handleFile(input.files[0]); - } + const input = e.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + handleFile(input.files[0]); + } } function handleFile(file: File) { - if ( - file.type !== 'application/pdf' && - !file.name.toLowerCase().endsWith('.pdf') - ) { - showAlert('Invalid File', 'Please select a PDF file.'); - return; - } + if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { + showAlert('Invalid File', 'Please select a PDF file.'); + return; + } - signState.file = file; - updateFileDisplay(); - setupSignTool(); + signState.file = file; + updateFileDisplay(); + setupSignTool(); } async function updateFileDisplay() { - const fileDisplayArea = document.getElementById('file-display-area'); + const fileDisplayArea = document.getElementById('file-display-area'); - if (!fileDisplayArea || !signState.file) return; + if (!fileDisplayArea || !signState.file) return; - fileDisplayArea.innerHTML = ''; - - const fileDiv = document.createElement('div'); - fileDiv.className = - 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - - const infoContainer = document.createElement('div'); - infoContainer.className = 'flex flex-col flex-1 min-w-0'; - - const nameSpan = document.createElement('div'); - nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; - nameSpan.textContent = signState.file.name; - - const metaSpan = document.createElement('div'); - metaSpan.className = 'text-xs text-gray-400'; - metaSpan.textContent = `${formatBytes(signState.file.size)} • ${t('common.loadingPageCount')}`; - - infoContainer.append(nameSpan, metaSpan); - - const removeBtn = document.createElement('button'); - removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; - removeBtn.innerHTML = ''; - removeBtn.onclick = () => { - signState.file = null; - signState.pdfDoc = null; fileDisplayArea.innerHTML = ''; - document.getElementById('signature-editor')?.classList.add('hidden'); - }; - fileDiv.append(infoContainer, removeBtn); - fileDisplayArea.appendChild(fileDiv); - createIcons({ icons }); + const fileDiv = document.createElement('div'); + fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg'; - // Load page count - try { - const arrayBuffer = await readFileAsArrayBuffer(signState.file); - const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; - metaSpan.textContent = `${formatBytes(signState.file.size)} • ${pdfDoc.numPages} pages`; - } catch (error) { - console.error('Error loading PDF:', error); - } + const infoContainer = document.createElement('div'); + infoContainer.className = 'flex flex-col flex-1 min-w-0'; + + const nameSpan = document.createElement('div'); + nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1'; + nameSpan.textContent = signState.file.name; + + const metaSpan = document.createElement('div'); + metaSpan.className = 'text-xs text-gray-400'; + metaSpan.textContent = `${formatBytes(signState.file.size)} • ${t('common.loadingPageCount')}`; + + infoContainer.append(nameSpan, metaSpan); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0'; + removeBtn.innerHTML = ''; + removeBtn.onclick = () => { + signState.file = null; + signState.pdfDoc = null; + fileDisplayArea.innerHTML = ''; + document.getElementById('signature-editor')?.classList.add('hidden'); + }; + + fileDiv.append(infoContainer, removeBtn); + fileDisplayArea.appendChild(fileDiv); + createIcons({ icons }); + + // Load page count + try { + const arrayBuffer = await readFileAsArrayBuffer(signState.file); + const pdfDoc = await getPDFDocument({ data: arrayBuffer }).promise; + metaSpan.textContent = `${formatBytes(signState.file.size)} • ${pdfDoc.numPages} pages`; + } catch (error) { + console.error('Error loading PDF:', error); + } } async function setupSignTool() { - const signatureEditor = document.getElementById('signature-editor'); - if (signatureEditor) { - signatureEditor.classList.remove('hidden'); - } + const signatureEditor = document.getElementById('signature-editor'); + if (signatureEditor) { + signatureEditor.classList.remove('hidden'); + } - showLoader('Loading PDF viewer...'); + showLoader('Loading PDF viewer...'); - const container = document.getElementById('canvas-container-sign'); - if (!container) { - console.error('Sign tool canvas container not found'); - hideLoader(); - return; - } + const container = document.getElementById('canvas-container-sign'); + if (!container) { + console.error('Sign tool canvas container not found'); + hideLoader(); + return; + } - if (!signState.file) { - console.error('No file loaded for signing'); - hideLoader(); - return; - } + if (!signState.file) { + console.error('No file loaded for signing'); + hideLoader(); + return; + } - container.textContent = ''; - const iframe = document.createElement('iframe'); - iframe.style.width = '100%'; - iframe.style.height = '100%'; - iframe.style.border = 'none'; - container.appendChild(iframe); - signState.viewerIframe = iframe; + container.textContent = ''; + const iframe = document.createElement('iframe'); + iframe.style.width = '100%'; + iframe.style.height = '100%'; + iframe.style.border = 'none'; + container.appendChild(iframe); + signState.viewerIframe = iframe; - const pdfBytes = await readFileAsArrayBuffer(signState.file); - const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' }); - signState.blobUrl = URL.createObjectURL(blob); + const pdfBytes = await readFileAsArrayBuffer(signState.file); + const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' }); + signState.blobUrl = URL.createObjectURL(blob); - try { - const existingPrefsRaw = localStorage.getItem('pdfjs.preferences'); - const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {}; - delete (existingPrefs as any).annotationEditorMode; - const newPrefs = { - ...existingPrefs, - enableSignatureEditor: true, - enablePermissions: false, - }; - localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs)); - } catch {} - - const viewerUrl = new URL( - `${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`, - window.location.origin - ); - const query = new URLSearchParams({ file: signState.blobUrl }); - iframe.src = `${viewerUrl.toString()}?${query.toString()}`; - - iframe.onload = () => { - hideLoader(); - signState.viewerReady = true; try { - const viewerWindow: any = iframe.contentWindow; - if (viewerWindow && viewerWindow.PDFViewerApplication) { - const app = viewerWindow.PDFViewerApplication; - const doc = viewerWindow.document; - const eventBus = app.eventBus; - eventBus?._on('annotationeditoruimanager', () => { - const editorModeButtons = doc.getElementById('editorModeButtons'); - editorModeButtons?.classList.remove('hidden'); - const editorSignature = doc.getElementById('editorSignature'); - editorSignature?.removeAttribute('hidden'); - const editorSignatureButton = doc.getElementById( - 'editorSignatureButton' - ) as HTMLButtonElement | null; - if (editorSignatureButton) { - editorSignatureButton.disabled = false; - } - const editorStamp = doc.getElementById('editorStamp'); - editorStamp?.removeAttribute('hidden'); - const editorStampButton = doc.getElementById( - 'editorStampButton' - ) as HTMLButtonElement | null; - if (editorStampButton) { - editorStampButton.disabled = false; - } - try { - const highlightBtn = doc.getElementById( - 'editorHighlightButton' - ) as HTMLButtonElement | null; - highlightBtn?.click(); - } catch {} - }); - } - } catch (e) { - console.error('Could not initialize PDF.js viewer for signing:', e); - } + const existingPrefsRaw = localStorage.getItem('pdfjs.preferences'); + const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {}; + delete (existingPrefs as any).annotationEditorMode; + const newPrefs = { + ...existingPrefs, + enableSignatureEditor: true, + enablePermissions: false, + }; + localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs)); + } catch { } - const saveBtn = document.getElementById( - 'process-btn' - ) as HTMLButtonElement | null; - if (saveBtn) { - saveBtn.style.display = ''; - } - }; + const viewerUrl = new URL(`${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`, window.location.origin); + const query = new URLSearchParams({ file: signState.blobUrl }); + iframe.src = `${viewerUrl.toString()}?${query.toString()}`; + + iframe.onload = () => { + hideLoader(); + signState.viewerReady = true; + try { + const viewerWindow: any = iframe.contentWindow; + if (viewerWindow && viewerWindow.PDFViewerApplication) { + const app = viewerWindow.PDFViewerApplication; + const doc = viewerWindow.document; + const eventBus = app.eventBus; + eventBus?._on('annotationeditoruimanager', () => { + const editorModeButtons = doc.getElementById('editorModeButtons'); + editorModeButtons?.classList.remove('hidden'); + const editorSignature = doc.getElementById('editorSignature'); + editorSignature?.removeAttribute('hidden'); + const editorSignatureButton = doc.getElementById('editorSignatureButton') as HTMLButtonElement | null; + if (editorSignatureButton) { + editorSignatureButton.disabled = false; + } + const editorStamp = doc.getElementById('editorStamp'); + editorStamp?.removeAttribute('hidden'); + const editorStampButton = doc.getElementById('editorStampButton') as HTMLButtonElement | null; + if (editorStampButton) { + editorStampButton.disabled = false; + } + try { + const highlightBtn = doc.getElementById('editorHighlightButton') as HTMLButtonElement | null; + highlightBtn?.click(); + } catch { } + }); + } + } catch (e) { + console.error('Could not initialize PDF.js viewer for signing:', e); + } + + const saveBtn = document.getElementById('process-btn') as HTMLButtonElement | null; + if (saveBtn) { + saveBtn.style.display = ''; + } + }; } async function applyAndSaveSignatures() { - if (!signState.viewerReady || !signState.viewerIframe) { - showAlert('Viewer not ready', 'Please wait for the PDF viewer to load.'); - return; - } - - try { - const viewerWindow: any = signState.viewerIframe.contentWindow; - if (!viewerWindow || !viewerWindow.PDFViewerApplication) { - showAlert('Viewer not ready', 'The PDF viewer is still initializing.'); - return; + if (!signState.viewerReady || !signState.viewerIframe) { + showAlert('Viewer not ready', 'Please wait for the PDF viewer to load.'); + return; } - const app = viewerWindow.PDFViewerApplication; - const flattenCheckbox = document.getElementById( - 'flatten-signature-toggle' - ) as HTMLInputElement | null; - const shouldFlatten = flattenCheckbox?.checked; - - if (shouldFlatten) { - showLoader('Flattening and saving PDF...'); - - const rawPdfBytes = await app.pdfDocument.saveDocument( - app.pdfDocument.annotationStorage - ); - const pdfBytes = new Uint8Array(rawPdfBytes); - const pdfDoc = await PDFDocument.load(pdfBytes); - pdfDoc.getForm().flatten(); - const flattenedPdfBytes = await pdfDoc.save(); - - const blob = new Blob([flattenedPdfBytes as BlobPart], { - type: 'application/pdf', - }); - downloadFile( - blob, - `signed_flattened_${signState.file?.name || 'document.pdf'}` - ); - - hideLoader(); - showAlert('Success', 'Signed PDF saved successfully!', 'success', () => { - resetState(); - }); - } else { - app.eventBus?.dispatch('download', { source: app }); - showAlert( - 'Success', - 'Signed PDF downloaded successfully!', - 'success', - () => { - resetState(); + try { + const viewerWindow: any = signState.viewerIframe.contentWindow; + if (!viewerWindow || !viewerWindow.PDFViewerApplication) { + showAlert('Viewer not ready', 'The PDF viewer is still initializing.'); + return; } - ); + + const app = viewerWindow.PDFViewerApplication; + const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null; + const shouldFlatten = flattenCheckbox?.checked; + + if (shouldFlatten) { + showLoader('Flattening and saving PDF...'); + + const rawPdfBytes = await app.pdfDocument.saveDocument(app.pdfDocument.annotationStorage); + const pdfBytes = new Uint8Array(rawPdfBytes); + const pdfDoc = await PDFDocument.load(pdfBytes); + pdfDoc.getForm().flatten(); + const flattenedPdfBytes = await pdfDoc.save(); + + const blob = new Blob([flattenedPdfBytes as BlobPart], { type: 'application/pdf' }); + downloadFile(blob, `signed_flattened_${signState.file?.name || 'document.pdf'}`); + + hideLoader(); + showAlert('Success', 'Signed PDF saved successfully!', 'success', () => { + resetState(); + }); + } else { + app.eventBus?.dispatch('download', { source: app }); + showAlert('Success', 'Signed PDF downloaded successfully!', 'success', () => { + resetState(); + }); + } + } catch (error) { + console.error('Failed to export the signed PDF:', error); + hideLoader(); + showAlert('Export failed', 'Could not export the signed PDF. Please try again.'); } - } catch (error) { - console.error('Failed to export the signed PDF:', error); - hideLoader(); - showAlert( - 'Export failed', - 'Could not export the signed PDF. Please try again.' - ); - } } function resetState() { - cleanup(); - signState.file = null; - signState.viewerIframe = null; - signState.viewerReady = false; + cleanup(); + signState.file = null; + signState.viewerIframe = null; + signState.viewerReady = false; - const signatureEditor = document.getElementById('signature-editor'); - if (signatureEditor) { - signatureEditor.classList.add('hidden'); - } + const signatureEditor = document.getElementById('signature-editor'); + if (signatureEditor) { + signatureEditor.classList.add('hidden'); + } - const container = document.getElementById('canvas-container-sign'); - if (container) { - container.textContent = ''; - } + const container = document.getElementById('canvas-container-sign'); + if (container) { + container.textContent = ''; + } - const fileDisplayArea = document.getElementById('file-display-area'); - if (fileDisplayArea) { - fileDisplayArea.innerHTML = ''; - } + const fileDisplayArea = document.getElementById('file-display-area'); + if (fileDisplayArea) { + fileDisplayArea.innerHTML = ''; + } - const processBtn = document.getElementById( - 'process-btn' - ) as HTMLButtonElement | null; - if (processBtn) { - processBtn.style.display = 'none'; - } + const processBtn = document.getElementById('process-btn') as HTMLButtonElement | null; + if (processBtn) { + processBtn.style.display = 'none'; + } - const flattenCheckbox = document.getElementById( - 'flatten-signature-toggle' - ) as HTMLInputElement | null; - if (flattenCheckbox) { - flattenCheckbox.checked = false; - } + const flattenCheckbox = document.getElementById('flatten-signature-toggle') as HTMLInputElement | null; + if (flattenCheckbox) { + flattenCheckbox.checked = false; + } } function cleanup() { - if (signState.blobUrl) { - URL.revokeObjectURL(signState.blobUrl); - signState.blobUrl = null; - } + if (signState.blobUrl) { + URL.revokeObjectURL(signState.blobUrl); + signState.blobUrl = null; + } }