From 151060204d52953a1e5345a1b252e3f57fed9a83 Mon Sep 17 00:00:00 2001 From: abdullahalam123 Date: Tue, 18 Nov 2025 20:31:13 +0530 Subject: [PATCH] feat(attachments,security): migrate attachment processing to web worker and update CORS policy - Add web worker for PDF attachment processing to improve performance and prevent UI blocking - Create add-attachments.worker.js with TypeScript definitions for type safety - Implement worker message handling with success and error responses - Update nginx.conf to use Cross-Origin-Resource-Policy instead of Cross-Origin-Embedder-Policy for better cross-origin resource sharing - Refactor add-attachments.ts to use worker-based processing with transferable objects - Update main.ts to add home logo click handler for navigation - Improve error handling with worker error event listener - Add validation checks for PDF file availability before processing attachments --- nginx.conf | 2 +- public/workers/add-attachments.worker.d.ts | 20 ++++++ public/workers/add-attachments.worker.js | 69 ++++++++++++++++++ src/js/logic/add-attachments.ts | 81 ++++++++++++++++------ src/js/main.ts | 6 +- 5 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 public/workers/add-attachments.worker.d.ts create mode 100644 public/workers/add-attachments.worker.js diff --git a/nginx.conf b/nginx.conf index ad4db09..c825a8d 100644 --- a/nginx.conf +++ b/nginx.conf @@ -35,6 +35,6 @@ http { add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Cross-Origin-Opener-Policy "same-origin" always; - add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Resource-Policy "cross-origin" always; } } \ No newline at end of file diff --git a/public/workers/add-attachments.worker.d.ts b/public/workers/add-attachments.worker.d.ts new file mode 100644 index 0000000..8c57b42 --- /dev/null +++ b/public/workers/add-attachments.worker.d.ts @@ -0,0 +1,20 @@ +declare const coherentpdf: typeof import('../../src/types/coherentpdf.global').coherentpdf; + +interface AddAttachmentsMessage { + command: 'add-attachments'; + pdfBuffer: ArrayBuffer; + attachmentBuffers: ArrayBuffer[]; + attachmentNames: string[]; +} + +interface AddAttachmentsSuccessResponse { + status: 'success'; + modifiedPDF: ArrayBuffer; +} + +interface AddAttachmentsErrorResponse { + status: 'error'; + message: string; +} + +type AddAttachmentsResponse = AddAttachmentsSuccessResponse | AddAttachmentsErrorResponse; diff --git a/public/workers/add-attachments.worker.js b/public/workers/add-attachments.worker.js new file mode 100644 index 0000000..6b53e8f --- /dev/null +++ b/public/workers/add-attachments.worker.js @@ -0,0 +1,69 @@ +self.importScripts('/coherentpdf.browser.min.js'); + +function addAttachmentsToPDFInWorker(pdfBuffer, attachmentBuffers, attachmentNames) { + try { + const uint8Array = new Uint8Array(pdfBuffer); + + let pdf; + try { + pdf = coherentpdf.fromMemory(uint8Array, ''); + } catch (error) { + self.postMessage({ + status: 'error', + message: `Failed to load PDF. Error: ${error.message || error}` + }); + return; + } + + // Add each attachment to the PDF + for (let i = 0; i < attachmentBuffers.length; i++) { + try { + const attachmentData = new Uint8Array(attachmentBuffers[i]); + const attachmentName = attachmentNames[i]; + + // Attach file at document level (page 0) + coherentpdf.attachFileFromMemory(attachmentData, attachmentName, pdf); + } catch (error) { + console.warn(`Failed to attach file ${attachmentNames[i]}:`, error); + self.postMessage({ + status: 'error', + message: `Failed to attach file ${attachmentNames[i]}: ${error.message || error}` + }); + coherentpdf.deletePdf(pdf); + return; + } + } + + // Save the modified PDF + const modifiedBytes = coherentpdf.toMemory(pdf, false, false); + coherentpdf.deletePdf(pdf); + + const buffer = modifiedBytes.buffer.slice( + modifiedBytes.byteOffset, + modifiedBytes.byteOffset + modifiedBytes.byteLength + ); + + self.postMessage({ + status: 'success', + modifiedPDF: buffer + }, [buffer]); + + } catch (error) { + self.postMessage({ + status: 'error', + message: error instanceof Error + ? error.message + : 'Unknown error occurred while adding attachments.' + }); + } +} + +self.onmessage = (e) => { + if (e.data.command === 'add-attachments') { + addAttachmentsToPDFInWorker( + e.data.pdfBuffer, + e.data.attachmentBuffers, + e.data.attachmentNames + ); + } +}; diff --git a/src/js/logic/add-attachments.ts b/src/js/logic/add-attachments.ts index 4908589..e5dea02 100644 --- a/src/js/logic/add-attachments.ts +++ b/src/js/logic/add-attachments.ts @@ -1,10 +1,40 @@ import { showLoader, hideLoader, showAlert } from '../ui'; import { readFileAsArrayBuffer, downloadFile } from '../utils/helpers'; import { state } from '../state'; + +const worker = new Worker('/workers/add-attachments.worker.js'); + let attachments: File[] = []; +worker.onmessage = (e) => { + const data = e.data; + + if (data.status === 'success' && data.modifiedPDF !== undefined) { + hideLoader(); + + downloadFile( + new Blob([new Uint8Array(data.modifiedPDF)], { type: 'application/pdf' }), + `attached-${state.files[0].name}` + ); + + showAlert('Success', `${attachments.length} file(s) attached successfully.`); + clearAttachments(); + } else if (data.status === 'error') { + hideLoader(); + showAlert('Error', data.message || 'Unknown error occurred.'); + clearAttachments(); + } +}; + +worker.onerror = (error) => { + hideLoader(); + console.error('Worker error:', error); + showAlert('Error', 'Worker error occurred. Check console for details.'); + clearAttachments(); +}; + export async function addAttachments() { - if (!state.pdfDoc) { + if (!state.files || state.files.length === 0) { showAlert('Error', 'Main PDF is not loaded.'); return; } @@ -15,37 +45,37 @@ export async function addAttachments() { showLoader('Embedding files into PDF...'); try { - const pdfDoc = state.pdfDoc; + const pdfFile = state.files[0]; + const pdfBuffer = (await readFileAsArrayBuffer(pdfFile)) as ArrayBuffer; + + const attachmentBuffers: ArrayBuffer[] = []; + const attachmentNames: string[] = []; for (let i = 0; i < attachments.length; i++) { const file = attachments[i]; - showLoader(`Attaching ${file.name} (${i + 1}/${attachments.length})...`); - - const fileBytes = await readFileAsArrayBuffer(file); - - await pdfDoc.attach(fileBytes as ArrayBuffer, file.name, { - mimeType: file.type || 'application/octet-stream', - description: `Attached file: ${file.name}`, - creationDate: new Date(), - modificationDate: new Date(file.lastModified), - }); + showLoader(`Reading ${file.name} (${i + 1}/${attachments.length})...`); + + const fileBuffer = (await readFileAsArrayBuffer(file)) as ArrayBuffer; + attachmentBuffers.push(fileBuffer); + attachmentNames.push(file.name); } - const pdfBytes = await pdfDoc.save(); - downloadFile( - new Blob([pdfBytes], { type: 'application/pdf' }), - `attached-${state.files[0].name}` - ); + showLoader('Attaching files to PDF...'); + + const message = { + command: 'add-attachments', + pdfBuffer: pdfBuffer, + attachmentBuffers: attachmentBuffers, + attachmentNames: attachmentNames + }; + + const transferables = [pdfBuffer, ...attachmentBuffers]; + worker.postMessage(message, transferables); - showAlert( - 'Success', - `${attachments.length} file(s) attached successfully.` - ); } catch (error: any) { console.error('Error attaching files:', error); - showAlert('Error', `Failed to attach files: ${error.message}`); - } finally { hideLoader(); + showAlert('Error', `Failed to attach files: ${error.message}`); clearAttachments(); } } @@ -88,6 +118,11 @@ export function setupAddAttachmentsTool() { return; } + if (!state.files || state.files.length === 0) { + console.error('No PDF file loaded for adding attachments.'); + return; + } + optionsDiv.classList.remove('hidden'); attachmentInput.addEventListener('change', (e) => { diff --git a/src/js/main.ts b/src/js/main.ts index dc82989..a4ec284 100644 --- a/src/js/main.ts +++ b/src/js/main.ts @@ -29,9 +29,11 @@ const init = () => { simpleNav.innerHTML = `
-
+