refactor: move all TypeScript interfaces to centralized src/js/types folder
- Create type files with barrel export via @/types alias - Update logic files to use centralized type imports
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { AddAttachmentState } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
@@ -5,12 +6,6 @@ import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
|
||||
const worker = new Worker(import.meta.env.BASE_URL + 'workers/add-attachments.worker.js');
|
||||
|
||||
interface AddAttachmentState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFLibDocument | null;
|
||||
attachments: File[];
|
||||
}
|
||||
|
||||
const pageState: AddAttachmentState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
|
||||
@@ -2,11 +2,8 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { AddBlankPageState } from '@/types';
|
||||
|
||||
interface AddBlankPageState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFLibDocument | null;
|
||||
}
|
||||
|
||||
const pageState: AddBlankPageState = {
|
||||
file: null,
|
||||
|
||||
@@ -2,13 +2,9 @@ import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, hexToRgb, formatBytes, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument, rgb, degrees, StandardFonts } from 'pdf-lib';
|
||||
import { AddWatermarkState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFLibDocument | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = { file: null, pdfDoc: null };
|
||||
const pageState: AddWatermarkState = { file: null, pdfDoc: null };
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { AlternateMergeState } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, getPDFDocument } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
interface AlternateMergeState {
|
||||
files: File[];
|
||||
pdfBytes: Map<string, ArrayBuffer>;
|
||||
pdfDocs: Map<string, any>;
|
||||
}
|
||||
|
||||
const pageState: AlternateMergeState = {
|
||||
files: [],
|
||||
pdfBytes: new Map(),
|
||||
|
||||
@@ -2,9 +2,9 @@ import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, hexToRgb, formatBytes } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument, rgb } from 'pdf-lib';
|
||||
import { BackgroundColorState } from '@/types';
|
||||
|
||||
interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; }
|
||||
const pageState: PageState = { file: null, pdfDoc: null };
|
||||
const pageState: BackgroundColorState = { file: null, pdfDoc: null };
|
||||
|
||||
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializePage); }
|
||||
else { initializePage(); }
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { ChangePermissionsState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: ChangePermissionsState = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,15 +3,11 @@ import { downloadFile, formatBytes, hexToRgb, getPDFDocument } from '../utils/he
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument, rgb } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { CombineSinglePageState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface CombineState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFLibDocument | null;
|
||||
}
|
||||
|
||||
const pageState: CombineState = {
|
||||
const pageState: CombineSinglePageState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
};
|
||||
|
||||
@@ -2,17 +2,10 @@ import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { getPDFDocument } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { CompareState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface CompareState {
|
||||
pdfDoc1: pdfjsLib.PDFDocumentProxy | null;
|
||||
pdfDoc2: pdfjsLib.PDFDocumentProxy | null;
|
||||
currentPage: number;
|
||||
viewMode: 'overlay' | 'side-by-side';
|
||||
isSyncScroll: boolean;
|
||||
}
|
||||
|
||||
const pageState: CompareState = {
|
||||
pdfDoc1: null,
|
||||
pdfDoc2: null,
|
||||
|
||||
@@ -4,18 +4,10 @@ import { downloadFile, readFileAsArrayBuffer, formatBytes, getPDFDocument } from
|
||||
import Cropper from 'cropperjs';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { CropperState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface CropperState {
|
||||
pdfDoc: any;
|
||||
currentPageNum: number;
|
||||
cropper: any;
|
||||
originalPdfBytes: ArrayBuffer | null;
|
||||
pageCrops: Record<number, any>;
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
const cropperState: CropperState = {
|
||||
pdfDoc: null,
|
||||
currentPageNum: 1,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { DecryptPdfState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: DecryptPdfState = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,18 +3,11 @@ import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { readFileAsArrayBuffer, formatBytes, downloadFile, getPDFDocument, parsePageRanges } from '../utils/helpers.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { DeletePagesState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface DeleteState {
|
||||
file: File | null;
|
||||
pdfDoc: any;
|
||||
pdfJsDoc: any;
|
||||
totalPages: number;
|
||||
pagesToDelete: Set<number>;
|
||||
}
|
||||
|
||||
const deleteState: DeleteState = {
|
||||
const deleteState: DeletePagesState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
pdfJsDoc: null,
|
||||
|
||||
@@ -6,19 +6,8 @@ import {
|
||||
parsePfxFile,
|
||||
parseCombinedPem,
|
||||
getCertificateInfo,
|
||||
type CertificateData,
|
||||
type SignatureInfo,
|
||||
type VisibleSignatureOptions,
|
||||
} from './digital-sign-pdf.js';
|
||||
|
||||
interface DigitalSignState {
|
||||
pdfFile: File | null;
|
||||
pdfBytes: Uint8Array | null;
|
||||
certFile: File | null;
|
||||
certData: CertificateData | null;
|
||||
sigImageData: ArrayBuffer | null;
|
||||
sigImageType: 'png' | 'jpeg' | 'webp' | null;
|
||||
}
|
||||
import { SignatureInfo, VisibleSignatureOptions, DigitalSignState } from '@/types';
|
||||
|
||||
const state: DigitalSignState = {
|
||||
pdfFile: null,
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
import { PdfSigner, type SignOption } from 'zgapdfsigner';
|
||||
import forge from 'node-forge';
|
||||
|
||||
export interface SignatureInfo {
|
||||
reason?: string;
|
||||
location?: string;
|
||||
contactInfo?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface CertificateData {
|
||||
p12Buffer: ArrayBuffer;
|
||||
password: string;
|
||||
certificate: forge.pki.Certificate;
|
||||
}
|
||||
|
||||
export interface SignPdfOptions {
|
||||
signatureInfo?: SignatureInfo;
|
||||
visibleSignature?: VisibleSignatureOptions;
|
||||
}
|
||||
|
||||
export interface VisibleSignatureOptions {
|
||||
enabled: boolean;
|
||||
imageData?: ArrayBuffer;
|
||||
imageType?: 'png' | 'jpeg' | 'webp';
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
page: number | string;
|
||||
text?: string;
|
||||
textColor?: string;
|
||||
textSize?: number;
|
||||
}
|
||||
import { CertificateData, SignPdfOptions } from '@/types';
|
||||
|
||||
export function parsePfxFile(pfxBytes: ArrayBuffer, password: string): CertificateData {
|
||||
const pfxAsn1 = forge.asn1.fromDer(forge.util.createBuffer(new Uint8Array(pfxBytes)));
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { DividePagesState } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, parsePageRanges } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
|
||||
interface DividePagesState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFLibDocument | null;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
const pageState: DividePagesState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
import { EditAttachmentState, AttachmentInfo } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
const worker = new Worker(import.meta.env.BASE_URL + 'workers/edit-attachments.worker.js');
|
||||
|
||||
interface AttachmentInfo {
|
||||
index: number;
|
||||
name: string;
|
||||
page: number;
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
interface EditAttachmentState {
|
||||
file: File | null;
|
||||
allAttachments: AttachmentInfo[];
|
||||
attachmentsToRemove: Set<number>;
|
||||
}
|
||||
|
||||
const pageState: EditAttachmentState = {
|
||||
file: null,
|
||||
allAttachments: [],
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { EditMetadataState } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument, PDFName, PDFString } from 'pdf-lib';
|
||||
|
||||
interface EditMetadataState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFLibDocument | null;
|
||||
}
|
||||
|
||||
const pageState: EditMetadataState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { EncryptPdfState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: EncryptPdfState = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,9 @@ import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, hexToRgb } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument, rgb, PageSizes } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { FixPageSizeState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: FixPageSizeState = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,9 @@ import { downloadFile, formatBytes, readFileAsArrayBuffer } from '../utils/helpe
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { FlattenPdfState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
files: File[];
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: FlattenPdfState = {
|
||||
files: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, hexToRgb, formatBytes, parsePageRanges } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument, rgb, StandardFonts } from 'pdf-lib';
|
||||
import { HeaderFooterState } from '@/types';
|
||||
|
||||
interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; }
|
||||
const pageState: PageState = { file: null, pdfDoc: null };
|
||||
const pageState: HeaderFooterState = { file: null, pdfDoc: null };
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializePage);
|
||||
|
||||
@@ -3,11 +3,11 @@ import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, formatBytes, getPDFDocument } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { InvertColorsState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; }
|
||||
const pageState: PageState = { file: null, pdfDoc: null };
|
||||
const pageState: InvertColorsState = { file: null, pdfDoc: null };
|
||||
|
||||
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializePage); }
|
||||
else { initializePage(); }
|
||||
|
||||
@@ -2,12 +2,9 @@ import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { LinearizePdfState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
files: File[];
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: LinearizePdfState = {
|
||||
files: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -7,20 +7,10 @@ import fontkit from '@pdf-lib/fontkit';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { getFontForLanguage } from '../utils/font-loader.js';
|
||||
import { OcrWord, OcrState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface Word {
|
||||
text: string;
|
||||
bbox: { x0: number; y0: number; x1: number; y1: number };
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
interface OcrState {
|
||||
file: File | null;
|
||||
searchablePdfBytes: Uint8Array | null;
|
||||
}
|
||||
|
||||
const pageState: OcrState = {
|
||||
file: null,
|
||||
searchablePdfBytes: null,
|
||||
@@ -35,10 +25,10 @@ const whitelistPresets: Record<string, string> = {
|
||||
forms: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,()-_/@#:',
|
||||
};
|
||||
|
||||
function parseHOCR(hocrText: string): Word[] {
|
||||
function parseHOCR(hocrText: string): OcrWord[] {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(hocrText, 'text/html');
|
||||
const words: Word[] = [];
|
||||
const words: OcrWord[] = [];
|
||||
|
||||
const wordElements = doc.querySelectorAll('.ocrx_word');
|
||||
|
||||
@@ -264,7 +254,7 @@ async function runOCR() {
|
||||
if (data.hocr) {
|
||||
const words = parseHOCR(data.hocr);
|
||||
|
||||
words.forEach(function (word: Word) {
|
||||
words.forEach(function (word: OcrWord) {
|
||||
const { x0, y0, x1, y1 } = word.bbox;
|
||||
const text = word.text.replace(/[\u0000-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\uFEFF]/g, '');
|
||||
|
||||
|
||||
@@ -2,13 +2,9 @@ import { showAlert } from '../ui.js';
|
||||
import { formatBytes, getStandardPageName, convertPoints } from '../utils/helpers.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { PageDimensionsState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFDocument | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: PageDimensionsState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
};
|
||||
|
||||
@@ -3,17 +3,10 @@ import { downloadFile, parsePageRanges, getPDFDocument, formatBytes } from '../u
|
||||
import { PDFDocument, PageSizes } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PosterizeState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface PosterizeState {
|
||||
file: File | null;
|
||||
pdfJsDoc: pdfjsLib.PDFDocumentProxy | null;
|
||||
pdfBytes: Uint8Array | null;
|
||||
pageSnapshots: Record<number, ImageData>;
|
||||
currentPage: number;
|
||||
}
|
||||
|
||||
const pageState: PosterizeState = {
|
||||
file: null,
|
||||
pdfJsDoc: null,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { RemoveRestrictionsState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: RemoveRestrictionsState = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,9 @@ import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { PDFDocument, PDFName } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { SanitizePdfState } from '@/types';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFDocument | null;
|
||||
}
|
||||
|
||||
const pageState: PageState = {
|
||||
const pageState: SanitizePdfState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
};
|
||||
|
||||
@@ -3,11 +3,11 @@ import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, hexToRgb, formatBytes, getPDFDocument, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { TextColorState } from '@/types';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
|
||||
interface PageState { file: File | null; pdfDoc: PDFLibDocument | null; }
|
||||
const pageState: PageState = { file: null, pdfDoc: null };
|
||||
const pageState: TextColorState = { file: null, pdfDoc: null };
|
||||
|
||||
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializePage); }
|
||||
else { initializePage(); }
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { readFileAsArrayBuffer, formatBytes } from '../utils/helpers.js';
|
||||
import { validatePdfSignatures, type SignatureValidationResult } from './validate-signature-pdf.js';
|
||||
import { validatePdfSignatures } from './validate-signature-pdf.js';
|
||||
import forge from 'node-forge';
|
||||
|
||||
interface ValidateSignatureState {
|
||||
pdfFile: File | null;
|
||||
pdfBytes: Uint8Array | null;
|
||||
results: SignatureValidationResult[];
|
||||
trustedCertFile: File | null;
|
||||
trustedCert: forge.pki.Certificate | null;
|
||||
}
|
||||
import { SignatureValidationResult, ValidateSignatureState } from '@/types';
|
||||
|
||||
const state: ValidateSignatureState = {
|
||||
pdfFile: null,
|
||||
|
||||
@@ -1,63 +1,21 @@
|
||||
import forge from 'node-forge';
|
||||
import { ExtractedSignature, SignatureValidationResult } from '@/types';
|
||||
|
||||
export interface SignatureValidationResult {
|
||||
signatureIndex: number;
|
||||
isValid: boolean;
|
||||
signerName: string;
|
||||
signerOrg?: string;
|
||||
signerEmail?: string;
|
||||
issuer: string;
|
||||
issuerOrg?: string;
|
||||
signatureDate?: Date;
|
||||
validFrom: Date;
|
||||
validTo: Date;
|
||||
isExpired: boolean;
|
||||
isSelfSigned: boolean;
|
||||
isTrusted: boolean;
|
||||
algorithms: {
|
||||
digest: string;
|
||||
signature: string;
|
||||
};
|
||||
serialNumber: string;
|
||||
reason?: string;
|
||||
location?: string;
|
||||
contactInfo?: string;
|
||||
byteRange?: number[];
|
||||
coverageStatus: 'full' | 'partial' | 'unknown';
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface ExtractedSignature {
|
||||
index: number;
|
||||
contents: Uint8Array;
|
||||
byteRange: number[];
|
||||
reason?: string;
|
||||
location?: string;
|
||||
contactInfo?: string;
|
||||
name?: string;
|
||||
signingTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all digital signatures from a PDF file
|
||||
*/
|
||||
export function extractSignatures(pdfBytes: Uint8Array): ExtractedSignature[] {
|
||||
const signatures: ExtractedSignature[] = [];
|
||||
const pdfString = new TextDecoder('latin1').decode(pdfBytes);
|
||||
|
||||
// Find all signature objects by looking for /Type /Sig
|
||||
// Find all signature objects for /Type /Sig
|
||||
const sigRegex = /\/Type\s*\/Sig\b/g;
|
||||
let sigMatch;
|
||||
let sigIndex = 0;
|
||||
|
||||
while ((sigMatch = sigRegex.exec(pdfString)) !== null) {
|
||||
try {
|
||||
// Find the containing object
|
||||
const searchStart = Math.max(0, sigMatch.index - 5000);
|
||||
const searchEnd = Math.min(pdfString.length, sigMatch.index + 10000);
|
||||
const context = pdfString.substring(searchStart, searchEnd);
|
||||
|
||||
// Extract ByteRange
|
||||
const byteRangeMatch = context.match(/\/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/);
|
||||
if (!byteRangeMatch) continue;
|
||||
|
||||
@@ -68,14 +26,12 @@ export function extractSignatures(pdfBytes: Uint8Array): ExtractedSignature[] {
|
||||
parseInt(byteRangeMatch[4], 10),
|
||||
];
|
||||
|
||||
// Extract Contents (the actual PKCS#7 signature)
|
||||
const contentsMatch = context.match(/\/Contents\s*<([0-9A-Fa-f]+)>/);
|
||||
if (!contentsMatch) continue;
|
||||
|
||||
const hexContents = contentsMatch[1];
|
||||
const contentsBytes = hexToBytes(hexContents);
|
||||
|
||||
// Extract optional fields
|
||||
const reasonMatch = context.match(/\/Reason\s*\(([^)]*)\)/);
|
||||
const locationMatch = context.match(/\/Location\s*\(([^)]*)\)/);
|
||||
const contactMatch = context.match(/\/ContactInfo\s*\(([^)]*)\)/);
|
||||
@@ -105,9 +61,6 @@ export function extractSignatures(pdfBytes: Uint8Array): ExtractedSignature[] {
|
||||
return signatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a single extracted signature
|
||||
*/
|
||||
export function validateSignature(
|
||||
signature: ExtractedSignature,
|
||||
pdfBytes: Uint8Array,
|
||||
@@ -133,21 +86,17 @@ export function validateSignature(
|
||||
};
|
||||
|
||||
try {
|
||||
// Parse the PKCS#7 signature - convert Uint8Array to binary string
|
||||
const binaryString = String.fromCharCode.apply(null, Array.from(signature.contents));
|
||||
const asn1 = forge.asn1.fromDer(binaryString);
|
||||
const p7 = forge.pkcs7.messageFromAsn1(asn1) as any;
|
||||
|
||||
// Get signer info
|
||||
if (!p7.certificates || p7.certificates.length === 0) {
|
||||
result.errorMessage = 'No certificates found in signature';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Use the first certificate (signer's certificate)
|
||||
const signerCert = p7.certificates[0] as forge.pki.Certificate;
|
||||
|
||||
// Extract signer information
|
||||
const subjectCN = signerCert.subject.getField('CN');
|
||||
const subjectO = signerCert.subject.getField('O');
|
||||
const subjectE = signerCert.subject.getField('E') || signerCert.subject.getField('emailAddress');
|
||||
@@ -163,22 +112,17 @@ export function validateSignature(
|
||||
result.validTo = signerCert.validity.notAfter;
|
||||
result.serialNumber = signerCert.serialNumber;
|
||||
|
||||
// Check if expired
|
||||
const now = new Date();
|
||||
result.isExpired = now > result.validTo || now < result.validFrom;
|
||||
|
||||
// Check if self-signed
|
||||
result.isSelfSigned = signerCert.isIssuer(signerCert);
|
||||
|
||||
// Check trust against provided certificate
|
||||
if (trustedCert) {
|
||||
try {
|
||||
// Check if the signer cert is issued by the trusted cert
|
||||
// or if the trusted cert matches one of the certs in the chain
|
||||
const isTrustedIssuer = trustedCert.isIssuer(signerCert);
|
||||
const isSameCert = signerCert.serialNumber === trustedCert.serialNumber;
|
||||
|
||||
// Also check if any cert in the PKCS#7 chain matches or is issued by trusted cert
|
||||
let chainTrusted = false;
|
||||
for (const cert of p7.certificates) {
|
||||
if (trustedCert.isIssuer(cert) ||
|
||||
@@ -194,7 +138,6 @@ export function validateSignature(
|
||||
}
|
||||
}
|
||||
|
||||
// Extract algorithm info
|
||||
result.algorithms = {
|
||||
digest: getDigestAlgorithmName(signerCert.siginfo?.algorithmOid || ''),
|
||||
signature: getSignatureAlgorithmName(signerCert.signatureOid || ''),
|
||||
@@ -219,7 +162,6 @@ export function validateSignature(
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Check byte range coverage
|
||||
if (signature.byteRange && signature.byteRange.length === 4) {
|
||||
const [start1, len1, start2, len2] = signature.byteRange;
|
||||
const totalCovered = len1 + len2;
|
||||
@@ -232,7 +174,6 @@ export function validateSignature(
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as valid if we could parse it
|
||||
result.isValid = true;
|
||||
|
||||
} catch (e) {
|
||||
@@ -242,9 +183,6 @@ export function validateSignature(
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all signatures in a PDF
|
||||
*/
|
||||
export async function validatePdfSignatures(
|
||||
pdfBytes: Uint8Array,
|
||||
trustedCert?: forge.pki.Certificate
|
||||
@@ -253,24 +191,16 @@ export async function validatePdfSignatures(
|
||||
return signatures.map(sig => validateSignature(sig, pdfBytes, trustedCert));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of signatures in a PDF without full validation
|
||||
*/
|
||||
export function countSignatures(pdfBytes: Uint8Array): number {
|
||||
return extractSignatures(pdfBytes).length;
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
function hexToBytes(hex: string): Uint8Array {
|
||||
const bytes = new Uint8Array(hex.length / 2);
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
||||
}
|
||||
|
||||
// PDF signature /Contents are padded with trailing null bytes.
|
||||
// node-forge ASN.1 parser fails with "Unparsed DER bytes remain" if we include them.
|
||||
// Find the actual end of the DER data by stripping trailing zeros.
|
||||
let actualLength = bytes.length;
|
||||
while (actualLength > 0 && bytes[actualLength - 1] === 0) {
|
||||
actualLength--;
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { formatBytes, formatIsoDate, getPDFDocument } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
|
||||
interface ViewMetadataState {
|
||||
file: File | null;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
import { ViewMetadataState } from '@/types';
|
||||
|
||||
const pageState: ViewMetadataState = {
|
||||
file: null,
|
||||
|
||||
Reference in New Issue
Block a user