mirror of
				https://github.com/iib0011/omni-tools.git
				synced 2025-11-04 11:34:02 +01:00 
			
		
		
		
	feat: protect pdf
This commit is contained in:
		
							
								
								
									
										55
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										55
									
								
								.idea/workspace.xml
									
									
									
										generated
									
									
									
								
							@@ -4,8 +4,17 @@
 | 
			
		||||
    <option name="autoReloadType" value="SELECTIVE" />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="ChangeListManager">
 | 
			
		||||
    <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: vite worker format">
 | 
			
		||||
    <list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: uninstall @jspawn/ghostscript-wasm">
 | 
			
		||||
      <change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/index.tsx" afterDir="false" />
 | 
			
		||||
      <change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/meta.ts" afterDir="false" />
 | 
			
		||||
      <change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/service.ts" afterDir="false" />
 | 
			
		||||
      <change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/types.ts" afterDir="false" />
 | 
			
		||||
      <change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/utils.ts" afterDir="false" />
 | 
			
		||||
      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
 | 
			
		||||
      <change beforePath="$PROJECT_DIR$/src/lib/ghostscript/background-worker.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/ghostscript/background-worker.js" afterDir="false" />
 | 
			
		||||
      <change beforePath="$PROJECT_DIR$/src/lib/ghostscript/worker-init.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/ghostscript/worker-init.ts" afterDir="false" />
 | 
			
		||||
      <change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/service.ts" afterDir="false" />
 | 
			
		||||
      <change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/index.ts" afterDir="false" />
 | 
			
		||||
    </list>
 | 
			
		||||
    <option name="SHOW_DIALOG" value="false" />
 | 
			
		||||
    <option name="HIGHLIGHT_CONFLICTS" value="true" />
 | 
			
		||||
@@ -311,11 +320,11 @@
 | 
			
		||||
    </list>
 | 
			
		||||
    <recent_temporary>
 | 
			
		||||
      <list>
 | 
			
		||||
        <item itemvalue="npm.dev" />
 | 
			
		||||
        <item itemvalue="Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp" />
 | 
			
		||||
        <item itemvalue="Vitest.parsePageRanges" />
 | 
			
		||||
        <item itemvalue="Vitest.timeBetweenDates" />
 | 
			
		||||
        <item itemvalue="Vitest.calculateTimeBetweenDates" />
 | 
			
		||||
        <item itemvalue="npm.dev" />
 | 
			
		||||
      </list>
 | 
			
		||||
    </recent_temporary>
 | 
			
		||||
  </component>
 | 
			
		||||
@@ -406,22 +415,8 @@
 | 
			
		||||
      <workItem from="1743397561176" duration="25000" />
 | 
			
		||||
      <workItem from="1743458576265" duration="13083000" />
 | 
			
		||||
      <workItem from="1743690613245" duration="77000" />
 | 
			
		||||
    </task>
 | 
			
		||||
    <task id="LOCAL-00138" summary="feat: remove duplicate lines">
 | 
			
		||||
      <option name="closed" value="true" />
 | 
			
		||||
      <created>1740884332734</created>
 | 
			
		||||
      <option name="number" value="00138" />
 | 
			
		||||
      <option name="presentableId" value="LOCAL-00138" />
 | 
			
		||||
      <option name="project" value="LOCAL" />
 | 
			
		||||
      <updated>1740884332735</updated>
 | 
			
		||||
    </task>
 | 
			
		||||
    <task id="LOCAL-00139" summary="fix: tsc">
 | 
			
		||||
      <option name="closed" value="true" />
 | 
			
		||||
      <created>1740884971377</created>
 | 
			
		||||
      <option name="number" value="00139" />
 | 
			
		||||
      <option name="presentableId" value="LOCAL-00139" />
 | 
			
		||||
      <option name="project" value="LOCAL" />
 | 
			
		||||
      <updated>1740884971378</updated>
 | 
			
		||||
      <workItem from="1743691250813" duration="1550000" />
 | 
			
		||||
      <workItem from="1743699386059" duration="6244000" />
 | 
			
		||||
    </task>
 | 
			
		||||
    <task id="LOCAL-00140" summary="style: optimizations">
 | 
			
		||||
      <option name="closed" value="true" />
 | 
			
		||||
@@ -799,7 +794,23 @@
 | 
			
		||||
      <option name="project" value="LOCAL" />
 | 
			
		||||
      <updated>1743647707334</updated>
 | 
			
		||||
    </task>
 | 
			
		||||
    <option name="localTasksCounter" value="187" />
 | 
			
		||||
    <task id="LOCAL-00187" summary="fix: tests">
 | 
			
		||||
      <option name="closed" value="true" />
 | 
			
		||||
      <created>1743691399769</created>
 | 
			
		||||
      <option name="number" value="00187" />
 | 
			
		||||
      <option name="presentableId" value="LOCAL-00187" />
 | 
			
		||||
      <option name="project" value="LOCAL" />
 | 
			
		||||
      <updated>1743691399769</updated>
 | 
			
		||||
    </task>
 | 
			
		||||
    <task id="LOCAL-00188" summary="chore: uninstall @jspawn/ghostscript-wasm">
 | 
			
		||||
      <option name="closed" value="true" />
 | 
			
		||||
      <created>1743691471368</created>
 | 
			
		||||
      <option name="number" value="00188" />
 | 
			
		||||
      <option name="presentableId" value="LOCAL-00188" />
 | 
			
		||||
      <option name="project" value="LOCAL" />
 | 
			
		||||
      <updated>1743691471368</updated>
 | 
			
		||||
    </task>
 | 
			
		||||
    <option name="localTasksCounter" value="189" />
 | 
			
		||||
    <servers />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="TypeScriptGeneratedFilesManager">
 | 
			
		||||
@@ -846,8 +857,6 @@
 | 
			
		||||
    <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
 | 
			
		||||
    <option name="CHECK_NEW_TODO" value="false" />
 | 
			
		||||
    <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
 | 
			
		||||
    <MESSAGE value="fix: missing meta" />
 | 
			
		||||
    <MESSAGE value="feat: trim video" />
 | 
			
		||||
    <MESSAGE value="refactor: file inputs" />
 | 
			
		||||
    <MESSAGE value="feat: background removal" />
 | 
			
		||||
    <MESSAGE value="feat: split pdf" />
 | 
			
		||||
@@ -871,7 +880,9 @@
 | 
			
		||||
    <MESSAGE value="refactor: lib" />
 | 
			
		||||
    <MESSAGE value="fix: path" />
 | 
			
		||||
    <MESSAGE value="fix: vite worker format" />
 | 
			
		||||
    <option name="LAST_COMMIT_MESSAGE" value="fix: vite worker format" />
 | 
			
		||||
    <MESSAGE value="fix: tests" />
 | 
			
		||||
    <MESSAGE value="chore: uninstall @jspawn/ghostscript-wasm" />
 | 
			
		||||
    <option name="LAST_COMMIT_MESSAGE" value="chore: uninstall @jspawn/ghostscript-wasm" />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="XSLT-Support.FileAssociations.UIState">
 | 
			
		||||
    <expand />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
import { COMPRESS_ACTION, PROTECT_ACTION } from './worker-init';
 | 
			
		||||
 | 
			
		||||
function loadScript() {
 | 
			
		||||
  import('./gs-worker.js');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var Module;
 | 
			
		||||
 | 
			
		||||
function _GSPS2PDF(dataStruct, responseCallback) {
 | 
			
		||||
function compressPdf(dataStruct, responseCallback) {
 | 
			
		||||
  const compressionLevel = dataStruct.compressionLevel || 'medium';
 | 
			
		||||
 | 
			
		||||
  // Set PDF settings based on compression level
 | 
			
		||||
@@ -44,7 +46,11 @@ function _GSPS2PDF(dataStruct, responseCallback) {
 | 
			
		||||
          });
 | 
			
		||||
          var blob = new Blob([uarray], { type: 'application/octet-stream' });
 | 
			
		||||
          var pdfDataURL = self.URL.createObjectURL(blob);
 | 
			
		||||
          responseCallback({ pdfDataURL: pdfDataURL, url: dataStruct.url });
 | 
			
		||||
          responseCallback({
 | 
			
		||||
            pdfDataURL: pdfDataURL,
 | 
			
		||||
            url: dataStruct.url,
 | 
			
		||||
            type: COMPRESS_ACTION
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      arguments: [
 | 
			
		||||
@@ -76,6 +82,77 @@ function _GSPS2PDF(dataStruct, responseCallback) {
 | 
			
		||||
  xhr.send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function protectPdf(dataStruct, responseCallback) {
 | 
			
		||||
  const password = dataStruct.password || '';
 | 
			
		||||
 | 
			
		||||
  // Validate password
 | 
			
		||||
  if (!password) {
 | 
			
		||||
    responseCallback({
 | 
			
		||||
      error: 'Password is required for encryption',
 | 
			
		||||
      url: dataStruct.url
 | 
			
		||||
    });
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  var xhr = new XMLHttpRequest();
 | 
			
		||||
  xhr.open('GET', dataStruct.psDataURL);
 | 
			
		||||
  xhr.responseType = 'arraybuffer';
 | 
			
		||||
  xhr.onload = function () {
 | 
			
		||||
    console.log('onload');
 | 
			
		||||
    // release the URL
 | 
			
		||||
    self.URL.revokeObjectURL(dataStruct.psDataURL);
 | 
			
		||||
    //set up EMScripten environment
 | 
			
		||||
    Module = {
 | 
			
		||||
      preRun: [
 | 
			
		||||
        function () {
 | 
			
		||||
          self.Module.FS.writeFile('input.pdf', new Uint8Array(xhr.response));
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      postRun: [
 | 
			
		||||
        function () {
 | 
			
		||||
          var uarray = self.Module.FS.readFile('output.pdf', {
 | 
			
		||||
            encoding: 'binary'
 | 
			
		||||
          });
 | 
			
		||||
          var blob = new Blob([uarray], { type: 'application/octet-stream' });
 | 
			
		||||
          var pdfDataURL = self.URL.createObjectURL(blob);
 | 
			
		||||
          responseCallback({
 | 
			
		||||
            pdfDataURL: pdfDataURL,
 | 
			
		||||
            url: dataStruct.url,
 | 
			
		||||
            type: PROTECT_ACTION
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      arguments: [
 | 
			
		||||
        '-sDEVICE=pdfwrite',
 | 
			
		||||
        '-dCompatibilityLevel=1.4',
 | 
			
		||||
        `-sOwnerPassword=${password}`,
 | 
			
		||||
        `-sUserPassword=${password}`,
 | 
			
		||||
        // Permissions (prevent copying/printing/etc)
 | 
			
		||||
        '-dEncryptionPermissions=-4',
 | 
			
		||||
        '-DNOPAUSE',
 | 
			
		||||
        '-dQUIET',
 | 
			
		||||
        '-dBATCH',
 | 
			
		||||
        '-sOutputFile=output.pdf',
 | 
			
		||||
        'input.pdf'
 | 
			
		||||
      ],
 | 
			
		||||
      print: function (text) {},
 | 
			
		||||
      printErr: function (text) {},
 | 
			
		||||
      totalDependencies: 0,
 | 
			
		||||
      noExitRuntime: 1
 | 
			
		||||
    };
 | 
			
		||||
    // Module.setStatus("Loading Ghostscript...");
 | 
			
		||||
    if (!self.Module) {
 | 
			
		||||
      self.Module = Module;
 | 
			
		||||
      loadScript();
 | 
			
		||||
    } else {
 | 
			
		||||
      self.Module['calledRun'] = false;
 | 
			
		||||
      self.Module['postRun'] = Module.postRun;
 | 
			
		||||
      self.Module['preRun'] = Module.preRun;
 | 
			
		||||
      self.Module.callMain();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  xhr.send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
self.addEventListener('message', function ({ data: e }) {
 | 
			
		||||
  console.log('message', e);
 | 
			
		||||
  // e.data contains the message sent to the worker.
 | 
			
		||||
@@ -83,7 +160,14 @@ self.addEventListener('message', function ({ data: e }) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  console.log('Message received from main script', e.data);
 | 
			
		||||
  _GSPS2PDF(e.data, ({ pdfDataURL }) => self.postMessage(pdfDataURL));
 | 
			
		||||
  const responseCallback = ({ pdfDataURL, type }) => {
 | 
			
		||||
    self.postMessage(pdfDataURL);
 | 
			
		||||
  };
 | 
			
		||||
  if (e.data.type === COMPRESS_ACTION) {
 | 
			
		||||
    compressPdf(e.data, responseCallback);
 | 
			
		||||
  } else if (e.data.type === PROTECT_ACTION) {
 | 
			
		||||
    protectPdf(e.data, responseCallback);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
console.log('Worker ready');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
export async function compressWithGhostScript(dataStruct) {
 | 
			
		||||
  const worker = new Worker(
 | 
			
		||||
    new URL('./background-worker.js', import.meta.url),
 | 
			
		||||
    { type: 'module' }
 | 
			
		||||
  );
 | 
			
		||||
  worker.postMessage({ data: dataStruct, target: 'wasm' });
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    const listener = (e) => {
 | 
			
		||||
      resolve(e.data);
 | 
			
		||||
      worker.removeEventListener('message', listener);
 | 
			
		||||
      setTimeout(() => worker.terminate(), 0);
 | 
			
		||||
    };
 | 
			
		||||
    worker.addEventListener('message', listener);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/lib/ghostscript/worker-init.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/lib/ghostscript/worker-init.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
export const COMPRESS_ACTION = 'compress-pdf';
 | 
			
		||||
export const PROTECT_ACTION = 'protect-pdf';
 | 
			
		||||
 | 
			
		||||
export async function compressWithGhostScript(dataStruct: {
 | 
			
		||||
  psDataURL: string;
 | 
			
		||||
}): Promise<string> {
 | 
			
		||||
  const worker = getWorker();
 | 
			
		||||
  worker.postMessage({
 | 
			
		||||
    data: { ...dataStruct, type: COMPRESS_ACTION },
 | 
			
		||||
    target: 'wasm'
 | 
			
		||||
  });
 | 
			
		||||
  return getListener(worker);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function protectWithGhostScript(dataStruct: {
 | 
			
		||||
  psDataURL: string;
 | 
			
		||||
}): Promise<string> {
 | 
			
		||||
  const worker = getWorker();
 | 
			
		||||
  worker.postMessage({
 | 
			
		||||
    data: { ...dataStruct, type: PROTECT_ACTION },
 | 
			
		||||
    target: 'wasm'
 | 
			
		||||
  });
 | 
			
		||||
  return getListener(worker);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getListener = (worker: Worker): Promise<string> => {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    const listener = (e: MessageEvent) => {
 | 
			
		||||
      resolve(e.data);
 | 
			
		||||
      worker.removeEventListener('message', listener);
 | 
			
		||||
      setTimeout(() => worker.terminate(), 0);
 | 
			
		||||
    };
 | 
			
		||||
    worker.addEventListener('message', listener);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getWorker = () => {
 | 
			
		||||
  return new Worker(new URL('./background-worker.js', import.meta.url), {
 | 
			
		||||
    type: 'module'
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@@ -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();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								src/pages/tools/pdf/protect-pdf/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/pages/tools/pdf/protect-pdf/index.tsx
									
									
									
									
									
										Normal 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>
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      ]}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/pages/tools/pdf/protect-pdf/meta.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/pages/tools/pdf/protect-pdf/meta.ts
									
									
									
									
									
										Normal 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'))
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										45
									
								
								src/pages/tools/pdf/protect-pdf/service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/pages/tools/pdf/protect-pdf/service.ts
									
									
									
									
									
										Normal 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')
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/pages/tools/pdf/protect-pdf/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/pages/tools/pdf/protect-pdf/types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
export type ProtectionType = 'owner' | 'user';
 | 
			
		||||
 | 
			
		||||
export type InitialValuesType = {
 | 
			
		||||
  password: string;
 | 
			
		||||
  confirmPassword: string;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										16
									
								
								src/pages/tools/pdf/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/pages/tools/pdf/utils.ts
									
									
									
									
									
										Normal 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();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user