diff --git a/apps/remix/app/components/general/document/document-certificate-qr-view.tsx b/apps/remix/app/components/general/document/document-certificate-qr-view.tsx index 1213319bb..108530843 100644 --- a/apps/remix/app/components/general/document/document-certificate-qr-view.tsx +++ b/apps/remix/app/components/general/document/document-certificate-qr-view.tsx @@ -108,10 +108,10 @@ export const DocumentCertificateQRView = ({ version="current" envelope={{ id: envelopeItems[0].envelopeId, - envelopeItems, status: DocumentStatus.COMPLETED, type: EnvelopeType.DOCUMENT, }} + envelopeItems={envelopeItems} token={token} > { ({ diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx index 50f09bc01..a4015b84c 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx @@ -157,6 +157,7 @@ export default function DocumentPage({ params }: Route.ComponentProps) { - + diff --git a/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx b/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx index aaba7092f..73f313fc8 100644 --- a/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx +++ b/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx @@ -494,7 +494,12 @@ const SigningPageV2 = ({ data }: { data: Awaited - + diff --git a/apps/remix/app/routes/embed+/_v0+/direct.$token.tsx b/apps/remix/app/routes/embed+/_v0+/direct.$token.tsx index 85592464d..01788540d 100644 --- a/apps/remix/app/routes/embed+/_v0+/direct.$token.tsx +++ b/apps/remix/app/routes/embed+/_v0+/direct.$token.tsx @@ -320,7 +320,12 @@ const EmbedDirectTemplatePageV2 = ({ user={user} isDirectTemplate={true} > - + - + ({ originalWidth: page.originalWidth, originalHeight: page.originalHeight, diff --git a/packages/app-tests/e2e/pdf-viewer/pdf-viewer.spec.ts b/packages/app-tests/e2e/pdf-viewer/pdf-viewer.spec.ts new file mode 100644 index 000000000..44a1ec86a --- /dev/null +++ b/packages/app-tests/e2e/pdf-viewer/pdf-viewer.spec.ts @@ -0,0 +1,272 @@ +import { expect, test } from '@playwright/test'; +import { FieldType } from '@prisma/client'; +import path from 'node:path'; + +import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { createEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/create-embedding-presign-token'; +import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token'; +import { prefixedId } from '@documenso/lib/universal/id'; +import { formatDirectTemplatePath } from '@documenso/lib/utils/templates'; +import { prisma } from '@documenso/prisma'; +import { + seedBlankDocument, + seedPendingDocumentWithFullFields, +} from '@documenso/prisma/seed/documents'; +import { seedBlankTemplate, seedDirectTemplate } from '@documenso/prisma/seed/templates'; +import { seedUser } from '@documenso/prisma/seed/users'; + +import { apiSignin } from '../fixtures/authentication'; + +const PDF_PAGE_SELECTOR = 'img[data-page-number]'; + +async function addSecondEnvelopeItem(envelopeId: string) { + const firstItem = await prisma.envelopeItem.findFirstOrThrow({ + where: { envelopeId }, + orderBy: { order: 'asc' }, + include: { documentData: true }, + }); + + const newDocumentData = await prisma.documentData.create({ + data: { + type: firstItem.documentData.type, + data: firstItem.documentData.data, + initialData: firstItem.documentData.initialData, + }, + }); + + await prisma.envelopeItem.create({ + data: { + id: prefixedId('envelope_item'), + title: `${firstItem.title} - Page 2`, + documentDataId: newDocumentData.id, + order: 2, + envelopeId, + }, + }); +} + +test.describe('PDF Viewer Rendering', () => { + test.describe('Authenticated Pages', () => { + test('should render PDF on all authenticated pages (V1 and V2)', async ({ page }) => { + const { user, team } = await seedUser(); + + const documentV1 = await seedBlankDocument(user, team.id); + const documentV2 = await seedBlankDocument(user, team.id, { internalVersion: 2 }); + await addSecondEnvelopeItem(documentV2.id); + + const templateV1 = await seedBlankTemplate(user, team.id); + const templateV2 = await seedBlankTemplate(user, team.id, { + createTemplateOptions: { internalVersion: 2 }, + }); + await addSecondEnvelopeItem(templateV2.id); + + await apiSignin({ + page, + email: user.email, + redirectPath: `/t/${team.url}/documents/${documentV1.id}`, + }); + + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/documents/${documentV2.id}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/templates/${templateV1.id}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/templates/${templateV2.id}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/documents/${documentV1.id}/edit`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/documents/${documentV2.id}/edit?step=addFields`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/templates/${templateV1.id}/edit`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/templates/${templateV2.id}/edit?step=addFields`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/t/${team.url}/documents/${documentV1.id}`); + await page.locator(PDF_PAGE_SELECTOR).first().waitFor({ state: 'visible', timeout: 30_000 }); + }); + }); + + test.describe('Recipient Signing', () => { + test('should render PDF on signing page (V1 and V2)', async ({ page }) => { + const { user, team } = await seedUser(); + + const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({ + owner: user, + teamId: team.id, + recipients: ['signer-v1@test.documenso.com'], + fields: [FieldType.SIGNATURE], + }); + + const { document: documentV2, recipients: recipientsV2 } = + await seedPendingDocumentWithFullFields({ + owner: user, + teamId: team.id, + recipients: ['signer-v2@test.documenso.com'], + fields: [FieldType.SIGNATURE], + updateDocumentOptions: { internalVersion: 2 }, + }); + await addSecondEnvelopeItem(documentV2.id); + + await page.goto(`/sign/${recipientsV1[0].token}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/sign/${recipientsV2[0].token}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + }); + }); + + test.describe('Direct Template', () => { + test('should render PDF on direct template page (V1 and V2)', async ({ page }) => { + const { user, team } = await seedUser(); + + const templateV1 = await seedDirectTemplate({ + title: 'PDF Viewer Test Template V1', + userId: user.id, + teamId: team.id, + }); + + const templateV2 = await seedDirectTemplate({ + title: 'PDF Viewer Test Template V2', + userId: user.id, + teamId: team.id, + internalVersion: 2, + }); + await addSecondEnvelopeItem(templateV2.id); + + await page.goto(formatDirectTemplatePath(templateV1.directLink?.token || '')); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(formatDirectTemplatePath(templateV2.directLink?.token || '')); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + }); + }); + + test.describe('Embed Pages', () => { + test('should render PDF on embed sign page (V1 and V2)', async ({ page }) => { + const { user, team } = await seedUser(); + + const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({ + owner: user, + teamId: team.id, + recipients: ['embed-signer-v1@test.documenso.com'], + fields: [FieldType.SIGNATURE], + }); + + const { document: documentV2, recipients: recipientsV2 } = + await seedPendingDocumentWithFullFields({ + owner: user, + teamId: team.id, + recipients: ['embed-signer-v2@test.documenso.com'], + fields: [FieldType.SIGNATURE], + updateDocumentOptions: { internalVersion: 2 }, + }); + await addSecondEnvelopeItem(documentV2.id); + + await page.goto(`/embed/sign/${recipientsV1[0].token}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/embed/sign/${recipientsV2[0].token}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + }); + + test('should render PDF on embed direct template page (V1 and V2)', async ({ page }) => { + const { user, team } = await seedUser(); + + const templateV1 = await seedDirectTemplate({ + title: 'Embed Direct Template V1', + userId: user.id, + teamId: team.id, + }); + + const templateV2 = await seedDirectTemplate({ + title: 'Embed Direct Template V2', + userId: user.id, + teamId: team.id, + internalVersion: 2, + }); + await addSecondEnvelopeItem(templateV2.id); + + await page.goto(`/embed/direct/${templateV1.directLink?.token || ''}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + + await page.goto(`/embed/direct/${templateV2.directLink?.token || ''}`); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + await page.getByRole('button', { name: /Page 2/ }).click(); + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + }); + + test('should render PDF on embed authoring document create page', async ({ page }) => { + const { user, team } = await seedUser(); + + const { token: apiToken } = await createApiToken({ + userId: user.id, + teamId: team.id, + tokenName: 'pdf-viewer-test', + expiresIn: null, + }); + + const { token: presignToken } = await createEmbeddingPresignToken({ + apiToken, + }); + + const embedParams = { darkModeDisabled: false, features: {} }; + const hash = btoa(encodeURIComponent(JSON.stringify(embedParams))); + + await page.goto( + `${NEXT_PUBLIC_WEBAPP_URL()}/embed/v1/authoring/document/create?token=${presignToken}#${hash}`, + ); + + await expect(page.getByText('Configure Document')).toBeVisible({ timeout: 15_000 }); + + const titleInput = page.getByLabel('Title'); + await titleInput.click(); + await titleInput.fill('PDF Viewer E2E Test'); + + const emailInput = page.getByPlaceholder('Email').first(); + await emailInput.click(); + await emailInput.fill('test-signer@documenso.com'); + + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page + .locator('input[type=file]') + .first() + .evaluate((el) => { + if (el instanceof HTMLInputElement) { + el.click(); + } + }), + ]); + + await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf')); + + await page.waitForTimeout(2000); + + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 }); + }); + }); +}); diff --git a/packages/lib/client-only/providers/envelope-render-provider.tsx b/packages/lib/client-only/providers/envelope-render-provider.tsx index 8f10fb6af..e9ec9da75 100644 --- a/packages/lib/client-only/providers/envelope-render-provider.tsx +++ b/packages/lib/client-only/providers/envelope-render-provider.tsx @@ -9,8 +9,11 @@ import { } from 'react'; import React from 'react'; -import type { Field, Recipient } from '@prisma/client'; +import { DocumentDataType, type Field, type Recipient } from '@prisma/client'; +import pMap from 'p-map'; +import { PDF_IMAGE_RENDER_SCALE } from '@documenso/lib/constants/pdf-viewer'; +import { getEnvelopeItemPdfUrl } from '@documenso/lib/utils/envelope-download'; import type { TGetEnvelopeItemsMetaResponse } from '@documenso/remix/server/api/files/files.types'; import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors'; import { AVAILABLE_RECIPIENT_COLORS } from '@documenso/ui/lib/recipient-colors'; @@ -26,6 +29,9 @@ import { getEnvelopeItemMetaUrl, getEnvelopeItemPageImageUrl } from '../../utils */ export const EAGER_LOAD_PAGE_COUNT = 5; +// Todo: Embeds +export const PRESIGNED_ENVELOPE_ITEM_ID_PREFIX = 'PRESIGNED_'; + export type PageRenderData = BasePageRenderData & { scale: number; }; @@ -48,7 +54,13 @@ type EnvelopeRenderOverrideSettings = { showRecipientSigningStatus?: boolean; }; -type EnvelopeRenderItem = TEnvelope['envelopeItems'][number]; +type EnvelopeRenderItem = { + id: string; + title: string; + order: number; + envelopeId: string; + data?: Uint8Array | null; +}; type EnvelopeRenderProviderValue = { version: DocumentDataVersion; @@ -76,7 +88,12 @@ interface EnvelopeRenderProviderProps { */ version: DocumentDataVersion; - envelope: Pick; + envelope: Pick; + + /** + * The envelope items to render. + */ + envelopeItems: EnvelopeRenderItem[]; /** * Optional fields which are passed down to renderers for custom rendering needs. @@ -100,6 +117,13 @@ interface EnvelopeRenderProviderProps { */ token: string | undefined; + /** + * The presign token to access the envelope. + * + * If not provided, it will be assumed that the current user can access the document. + */ + presignToken?: string | undefined; + /** * Custom override settings for generic page renderers. */ @@ -124,8 +148,10 @@ export const useCurrentEnvelopeRender = () => { export const EnvelopeRenderProvider = ({ children, envelope, + envelopeItems: envelopeItemsFromProps, fields, token, + presignToken, recipients = [], version, overrideSettings, @@ -144,12 +170,12 @@ export const EnvelopeRenderProvider = ({ const fetchStartedAtRef = useRef(0); const envelopeItems = useMemo( - () => envelope.envelopeItems.sort((a, b) => a.order - b.order), - [envelope.envelopeItems], + () => envelopeItemsFromProps.sort((a, b) => a.order - b.order), + [envelopeItemsFromProps], ); const [currentItem, setCurrentItem] = useState( - envelope.envelopeItems[0] ?? null, + envelopeItems[0] ?? null, ); /** @@ -157,10 +183,18 @@ export const EnvelopeRenderProvider = ({ */ useEffect(() => { void fetchEnvelopeRenderData(); - }, [envelope.id, envelope.envelopeItems.length, token, version]); + }, [envelope.id, envelopeItems, token, version]); + + const fetchEnvelopeRenderData = useCallback(async () => { + if (envelopeItems.length === 0) { + setEnvelopeItemsMetaLoadingState('loaded'); + return; + } + + // Handle "create" embedding mode since all the files are local in that scenario. + if (!envelope.id) { + await handleLocalFileFetch(); - const fetchEnvelopeRenderData = async () => { - if (!envelope.id || envelope.envelopeItems.length === 0) { return; } @@ -175,6 +209,7 @@ export const EnvelopeRenderProvider = ({ const metaUrl = getEnvelopeItemMetaUrl({ envelopeId: envelope.id, token, + presignToken, }); const response = await fetch(metaUrl); @@ -193,7 +228,61 @@ export const EnvelopeRenderProvider = ({ // Build a map of envelope items by ID const metaMap: Record = {}; - for (const item of data.envelopeItems) { + const s3EnvelopeItems = data.envelopeItems.filter( + (item) => item.documentDataType === DocumentDataType.S3_PATH, + ); + + const localPdfs: { envelopeItemId: string; documentDataId: string; data: Uint8Array }[] = []; + + // Handle local envelope items from embedding flows. + for (const item of envelopeItems) { + if (item.data) { + localPdfs.push({ + envelopeItemId: item.id, + documentDataId: item.id, + data: new Uint8Array(item.data as Uint8Array), + }); + } + } + + const bytes64EnvelopeItems = data.envelopeItems.filter( + (item) => item.documentDataType !== DocumentDataType.S3_PATH, + ); + + // Handle byte64 envelope items from the database. + const pdfs = await pMap( + bytes64EnvelopeItems, + async (item) => { + const envelopeItemPdfUrl = getEnvelopeItemPdfUrl({ + type: 'view', + envelopeItem: { + id: item.envelopeItemId, + envelopeId: envelope.id, + }, + token, + presignToken, + }); + + const response = await fetch(envelopeItemPdfUrl); + + if (!response.ok) { + throw new Error(`Failed to fetch envelope item PDF: ${response.status}`); + } + + const pdfBytes = await response.arrayBuffer(); + + return { + envelopeItemId: item.envelopeItemId, + documentDataId: item.documentDataId, + data: new Uint8Array(pdfBytes), + }; + }, + { concurrency: 5 }, + ); + + localPdfs.push(...pdfs); + + for (const item of s3EnvelopeItems) { metaMap[item.envelopeItemId] = item.pages.map((page, pageIndex) => { const imageUrl = getEnvelopeItemPageImageUrl({ envelopeId: envelope.id, @@ -201,6 +290,7 @@ export const EnvelopeRenderProvider = ({ documentDataId: item.documentDataId, pageIndex, token, + presignToken, version, }); @@ -216,6 +306,46 @@ export const EnvelopeRenderProvider = ({ }); } + if (localPdfs.length > 0) { + // Dynamically import "pdfToImagesClientSide" function to prevent bundling. + const { pdfToImagesClientSide } = await import( + '@documenso/lib/server-only/ai/pdf-to-images.client' + ); + + await pMap( + localPdfs, + async (item) => { + const pdfImages = await pdfToImagesClientSide(item.data, { + scale: PDF_IMAGE_RENDER_SCALE, + }); + + metaMap[item.envelopeItemId] = pdfImages.map((image) => ({ + envelopeItemId: item.envelopeItemId, + documentDataId: item.documentDataId, + pageIndex: image.pageIndex, + pageNumber: image.pageIndex + 1, + pageWidth: image.width, + pageHeight: image.height, + imageUrl: image.image, + })); + }, + { concurrency: 10 }, + ); + } + + // Append all local embedding files. + const localFiles = envelopeItems.filter( + (item) => item.id.startsWith(PRESIGNED_ENVELOPE_ITEM_ID_PREFIX) && item.data, + ); + + for (const item of localFiles) { + if (!item.data) { + throw new Error('Not possible'); + } + + // Handle local files + } + setEnvelopeItemsMeta(metaMap); setEnvelopeItemsMetaLoadingState('loaded'); @@ -226,10 +356,51 @@ export const EnvelopeRenderProvider = ({ setEnvelopeItemsMetaLoadingState('error'); } } + }, [envelope.id, envelopeItems, token, version]); + + const handleLocalFileFetch = async () => { + setEnvelopeItemsMetaLoadingState('loading'); + + try { + // Build a map of envelope items by ID + const metaMap: Record = {}; + + // Dynamically import "pdfToImagesClientSide" function to prevent bundling. + const { pdfToImagesClientSide } = await import( + '@documenso/lib/server-only/ai/pdf-to-images.client' + ); + + for (const item of envelopeItems) { + if (item.data) { + // Clone the buffer so PDF.js can transfer it to its worker without detaching the one in state + const pdfBytes = new Uint8Array(structuredClone(item.data)); + + const pdfImages = await pdfToImagesClientSide(pdfBytes, { + scale: PDF_IMAGE_RENDER_SCALE, + }); + + metaMap[item.id] = pdfImages.map((image) => ({ + envelopeItemId: item.id, + documentDataId: item.id, + pageIndex: image.pageIndex, + pageNumber: image.pageIndex + 1, + pageWidth: image.width, + pageHeight: image.height, + imageUrl: image.image, + })); + } + } + + setEnvelopeItemsMeta(metaMap); + setEnvelopeItemsMetaLoadingState('loaded'); + } catch (error) { + console.error('Failed to load envelope data:', error); + setEnvelopeItemsMetaLoadingState('error'); + } }; const setCurrentEnvelopeItem = (envelopeItemId: string) => { - const foundItem = envelope.envelopeItems.find((item) => item.id === envelopeItemId); + const foundItem = envelopeItems.find((item) => item.id === envelopeItemId); setCurrentItem(foundItem ?? null); }; diff --git a/packages/lib/types/document-data.ts b/packages/lib/types/document-data.ts index 683d8417e..497250ee4 100644 --- a/packages/lib/types/document-data.ts +++ b/packages/lib/types/document-data.ts @@ -1,7 +1,9 @@ +import { DocumentDataType } from '@prisma/client'; import { z } from 'zod'; export const ZDocumentDataMetaSchema = z.object({ // Could store other things such as PDF size, etc here. + documentDataType: z.nativeEnum(DocumentDataType), pages: z .object({ originalWidth: z.number().describe('Original PDF page width'), diff --git a/packages/lib/universal/upload/put-file.server.ts b/packages/lib/universal/upload/put-file.server.ts index 610ea89c7..0bbb06b6b 100644 --- a/packages/lib/universal/upload/put-file.server.ts +++ b/packages/lib/universal/upload/put-file.server.ts @@ -49,7 +49,7 @@ export const putPdfFileServerSide = async (file: File) => { const newDocumentData = await createDocumentData({ type, data }); - void extractAndStorePdfImages(arrayBuffer, newDocumentData.id).catch((err) => { + void extractAndStorePdfImages(arrayBuffer, newDocumentData.id, type).catch((err) => { console.error(`Error extracting and storing PDF images: ${err}`); // Do nothing. @@ -64,6 +64,7 @@ export const putPdfFileServerSide = async (file: File) => { export const extractAndStorePdfImages = async ( arrayBuffer: ArrayBuffer, documentDataId: string, + documentDataType: DocumentDataType, ): Promise => { const images = await pdfToImages(new Uint8Array(arrayBuffer)); @@ -77,6 +78,7 @@ export const extractAndStorePdfImages = async ( const documentDataMetadata = ZDocumentDataMetaSchema.parse({ pages: pageMetadata, + documentDataType, } satisfies TDocumentDataMeta); // Only update metadata (page dimensions). Never update type, data, or initialData: @@ -140,7 +142,7 @@ export const putNormalizedPdfFileServerSide = async ( data: documentData.data, }); - void extractAndStorePdfImages(normalized, newDocumentData.id).catch((err) => { + void extractAndStorePdfImages(normalized, newDocumentData.id, documentData.type).catch((err) => { console.error(`Error extracting and storing PDF images: ${err}`); // Do nothing.