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
This commit is contained in:
abdullahalam123
2025-11-18 20:31:13 +05:30
parent 3019195fe5
commit 151060204d
5 changed files with 152 additions and 26 deletions

View File

@@ -35,6 +35,6 @@ http {
add_header X-Content-Type-Options "nosniff" always; add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always; add_header X-XSS-Protection "1; mode=block" always;
add_header Cross-Origin-Opener-Policy "same-origin" 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;
} }
} }

View File

@@ -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;

View File

@@ -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
);
}
};

View File

@@ -1,10 +1,40 @@
import { showLoader, hideLoader, showAlert } from '../ui'; import { showLoader, hideLoader, showAlert } from '../ui';
import { readFileAsArrayBuffer, downloadFile } from '../utils/helpers'; import { readFileAsArrayBuffer, downloadFile } from '../utils/helpers';
import { state } from '../state'; import { state } from '../state';
const worker = new Worker('/workers/add-attachments.worker.js');
let attachments: File[] = []; 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() { export async function addAttachments() {
if (!state.pdfDoc) { if (!state.files || state.files.length === 0) {
showAlert('Error', 'Main PDF is not loaded.'); showAlert('Error', 'Main PDF is not loaded.');
return; return;
} }
@@ -15,37 +45,37 @@ export async function addAttachments() {
showLoader('Embedding files into PDF...'); showLoader('Embedding files into PDF...');
try { 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++) { for (let i = 0; i < attachments.length; i++) {
const file = attachments[i]; const file = attachments[i];
showLoader(`Attaching ${file.name} (${i + 1}/${attachments.length})...`); showLoader(`Reading ${file.name} (${i + 1}/${attachments.length})...`);
const fileBytes = await readFileAsArrayBuffer(file); const fileBuffer = (await readFileAsArrayBuffer(file)) as ArrayBuffer;
attachmentBuffers.push(fileBuffer);
await pdfDoc.attach(fileBytes as ArrayBuffer, file.name, { attachmentNames.push(file.name);
mimeType: file.type || 'application/octet-stream',
description: `Attached file: ${file.name}`,
creationDate: new Date(),
modificationDate: new Date(file.lastModified),
});
} }
const pdfBytes = await pdfDoc.save(); showLoader('Attaching files to PDF...');
downloadFile(
new Blob([pdfBytes], { type: 'application/pdf' }), const message = {
`attached-${state.files[0].name}` 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) { } catch (error: any) {
console.error('Error attaching files:', error); console.error('Error attaching files:', error);
showAlert('Error', `Failed to attach files: ${error.message}`);
} finally {
hideLoader(); hideLoader();
showAlert('Error', `Failed to attach files: ${error.message}`);
clearAttachments(); clearAttachments();
} }
} }
@@ -88,6 +118,11 @@ export function setupAddAttachmentsTool() {
return; return;
} }
if (!state.files || state.files.length === 0) {
console.error('No PDF file loaded for adding attachments.');
return;
}
optionsDiv.classList.remove('hidden'); optionsDiv.classList.remove('hidden');
attachmentInput.addEventListener('change', (e) => { attachmentInput.addEventListener('change', (e) => {

View File

@@ -29,9 +29,11 @@ const init = () => {
simpleNav.innerHTML = ` simpleNav.innerHTML = `
<div class="container mx-auto px-4"> <div class="container mx-auto px-4">
<div class="flex justify-start items-center h-16"> <div class="flex justify-start items-center h-16">
<div class="flex-shrink-0 flex items-center"> <div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
<img src="images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8"> <img src="images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8">
<span class="text-white font-bold text-xl ml-2">BentoPDF</span> <span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div> </div>
</div> </div>
</div> </div>