feat: protect pdf

This commit is contained in:
Ibrahima G. Coulibaly
2025-04-03 18:42:24 +00:00
parent 141a0a3a24
commit 5545f0f344
11 changed files with 369 additions and 58 deletions

View File

@@ -1,5 +1,6 @@
import { InitialValuesType } from './types';
import { compressWithGhostScript } from '../../../../lib/ghostscript/worker-init';
import { loadPDFData } from '../utils';
/**
* Compresses a PDF file using either Ghostscript WASM (preferred)
@@ -25,20 +26,3 @@ export async function compressPdf(
const compressedFileUrl: string = await compressWithGhostScript(dataObject);
return await loadPDFData(compressedFileUrl, pdfFile.name);
}
function loadPDFData(url: string, filename: string): Promise<File> {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
window.URL.revokeObjectURL(url);
const blob = new Blob([xhr.response], { type: 'application/pdf' });
const newFile = new File([blob], filename, {
type: 'application/pdf'
});
resolve(newFile);
};
xhr.send();
});
}

View File

@@ -1,10 +1,12 @@
import { tool as pdfRotatePdf } from './rotate-pdf/meta';
import { meta as splitPdfMeta } from './split-pdf/meta';
import { tool as compressPdfTool } from './compress-pdf/meta';
import { tool as protectPdfTool } from './protect-pdf/meta';
import { DefinedTool } from '@tools/defineTool';
export const pdfTools: DefinedTool[] = [
splitPdfMeta,
pdfRotatePdf,
compressPdfTool
compressPdfTool,
protectPdfTool
];

View File

@@ -0,0 +1,110 @@
import { Box } from '@mui/material';
import React, { useContext, useState } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import ToolPdfInput from '@components/input/ToolPdfInput';
import ToolFileResult from '@components/result/ToolFileResult';
import { InitialValuesType } from './types';
import { protectPdf } from './service';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';
const initialValues: InitialValuesType = {
password: '',
confirmPassword: ''
};
export default function ProtectPdf({
title,
longDescription
}: ToolComponentProps) {
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const { showSnackBar } = useContext(CustomSnackBarContext);
const compute = async (values: InitialValuesType, input: File | null) => {
if (!input) return;
try {
// Validate passwords match
if (values.password !== values.confirmPassword) {
showSnackBar('Passwords do not match', 'error');
return;
}
// Validate password is not empty
if (!values.password) {
showSnackBar('Password cannot be empty', 'error');
return;
}
setIsProcessing(true);
const protectedPdf = await protectPdf(input, values);
setResult(protectedPdf);
} catch (error) {
console.error('Error protecting PDF:', error);
showSnackBar(
`Failed to protect PDF: ${
error instanceof Error ? error.message : String(error)
}`,
'error'
);
setResult(null);
} finally {
setIsProcessing(false);
}
};
return (
<ToolContent
title={title}
input={input}
setInput={setInput}
initialValues={initialValues}
compute={compute}
inputComponent={
<ToolPdfInput
value={input}
onChange={setInput}
accept={['application/pdf']}
title={'Input PDF'}
/>
}
resultComponent={
<ToolFileResult
title={'Protected PDF'}
value={result}
extension={'pdf'}
loading={isProcessing}
loadingText={'Protecting PDF'}
/>
}
getGroups={({ values, updateField }) => [
{
title: 'Password Settings',
component: (
<Box>
<TextFieldWithDesc
title="Password"
description="Enter a password to protect your PDF"
placeholder="Enter password"
type="password"
value={values.password}
onOwnChange={(value) => updateField('password', value)}
/>
<TextFieldWithDesc
title="Confirm Password"
description="Re-enter your password to confirm"
placeholder="Confirm password"
type="password"
value={values.confirmPassword}
onOwnChange={(value) => updateField('confirmPassword', value)}
/>
</Box>
)
}
]}
/>
);
}

View File

@@ -0,0 +1,27 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('pdf', {
name: 'Protect PDF',
path: 'protect-pdf',
icon: 'material-symbols:lock',
description:
'Add password protection to your PDF files securely in your browser',
shortDescription: 'Password protect PDF files securely',
keywords: [
'pdf',
'protect',
'password',
'secure',
'encrypt',
'lock',
'private',
'confidential',
'security',
'browser',
'encryption'
],
longDescription:
'Add password protection to your PDF files securely in your browser. Your files never leave your device, ensuring complete privacy while securing your documents with password encryption. Perfect for protecting sensitive information, confidential documents, or personal data.',
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,45 @@
import { PDFDocument } from 'pdf-lib';
import { InitialValuesType } from './types';
import {
compressWithGhostScript,
protectWithGhostScript
} from '../../../../lib/ghostscript/worker-init';
import { loadPDFData } from '../utils';
/**
* Protects a PDF file with a password
*
* @param pdfFile - The PDF file to protect
* @param options - Protection options including password and protection type
* @returns A Promise that resolves to a password-protected PDF File
*/
export async function protectPdf(
pdfFile: File,
options: InitialValuesType
): Promise<File> {
// Check if file is a PDF
if (pdfFile.type !== 'application/pdf') {
throw new Error('The provided file is not a PDF');
}
// Check if passwords match
if (options.password !== options.confirmPassword) {
throw new Error('Passwords do not match');
}
// Check if password is empty
if (!options.password) {
throw new Error('Password cannot be empty');
}
const dataObject = {
psDataURL: URL.createObjectURL(pdfFile),
password: options.password
};
const protectedFileUrl: string = await protectWithGhostScript(dataObject);
console.log('protected', protectedFileUrl);
return await loadPDFData(
protectedFileUrl,
pdfFile.name.replace('.pdf', '-protected.pdf')
);
}

View File

@@ -0,0 +1,6 @@
export type ProtectionType = 'owner' | 'user';
export type InitialValuesType = {
password: string;
confirmPassword: string;
};

View File

@@ -0,0 +1,16 @@
export function loadPDFData(url: string, filename: string): Promise<File> {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
window.URL.revokeObjectURL(url);
const blob = new Blob([xhr.response], { type: 'application/pdf' });
const newFile = new File([blob], filename, {
type: 'application/pdf'
});
resolve(newFile);
};
xhr.send();
});
}