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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
20
public/workers/add-attachments.worker.d.ts
vendored
Normal file
20
public/workers/add-attachments.worker.d.ts
vendored
Normal 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;
|
||||||
69
public/workers/add-attachments.worker.js
Normal file
69
public/workers/add-attachments.worker.js
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user