Skip to content

Commit 39f09d5

Browse files
committed
fix(attachments): polyfill pdfjs DOM globals so PDF text extraction skips native canvas (ABCA-745)
After fixing the v2 API + nodeModules bundling, PDF screening still failed live: pdfjs tried to load the native @napi-rs/canvas binding to supply DOMMatrix/ ImageData/Path2D, but the cross-platform binary isn't bundled for the Lambda's linux-arm64 (only the build host's darwin binary resolved) → 'Cannot find native binding' → 'DOMMatrix is not defined' → screening unavailable. Text extraction never actually uses canvas — pdfjs only touches those globals on its optional RENDER path. Defining them as inert no-op stubs BEFORE importing pdf-parse makes pdfjs skip the native-canvas load entirely and extract text headless — no native binary, host-independent. Verified: getText returns full text with canvas forced-absent + the three stubs present. This was platform-wide (confirm-uploads / Jira aws-samples#619 / Linear all fail-closed on every PDF) — PDF screening has been dead since the pdf-parse v2 bump.
1 parent 8db77e8 commit 39f09d5

1 file changed

Lines changed: 28 additions & 6 deletions

File tree

cdk/src/handlers/shared/attachment-screening.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,36 @@ const PDF_MAX_PAGES = 50;
248248
const PDF_MAX_TEXT_BYTES = 1024 * 1024; // 1 MB extracted text cap
249249
const PDF_EXTRACT_TIMEOUT_MS = 15_000;
250250

251+
/**
252+
* pdf-parse v2 is built on pdfjs, which references browser DOM globals
253+
* (`DOMMatrix`/`ImageData`/`Path2D`) that don't exist in the Node Lambda runtime.
254+
* For TEXT extraction (our only use) these are never actually invoked — pdfjs only
255+
* touches them on its optional canvas RENDER path. But if they're merely *undefined*,
256+
* pdfjs tries to load the native `@napi-rs/canvas` binding to supply them, which
257+
* fails on Lambda (the cross-platform native binary isn't bundled) and cascades to
258+
* `DOMMatrix is not defined` → PDF screening unavailable (ABCA-745, live-caught).
259+
*
260+
* Defining them as inert no-op stubs makes pdfjs skip the native-canvas load path
261+
* entirely and extract text headless — no native binary, host-independent. Verified:
262+
* `getText` returns the full text with canvas absent + these three stubs present.
263+
* Idempotent + non-clobbering (only fills genuinely-missing globals).
264+
*/
265+
function ensurePdfDomGlobals(): void {
266+
const g = globalThis as Record<string, unknown>;
267+
if (typeof g.DOMMatrix === 'undefined') g.DOMMatrix = class { /* inert stub — text extraction never calls it */ };
268+
if (typeof g.ImageData === 'undefined') g.ImageData = class { /* inert stub */ };
269+
if (typeof g.Path2D === 'undefined') g.Path2D = class { /* inert stub */ };
270+
}
271+
251272
async function extractPdfText(content: Buffer, filename: string): Promise<string> {
252273
// pdf-parse v2 (^2.4.5) exposes a `PDFParse` CLASS — `new PDFParse({ data }).getText()` —
253-
// NOT the v1 callable default export. It runs headless in the Node Lambda runtime
254-
// (the `DOMMatrix`/`Path2D` warnings from pdfjs's optional canvas layer are benign
255-
// for text extraction). Two things made this fail before (ABCA-745): the code called
256-
// the v1 `pdfParseFn(buf)` shape (undefined for v2), AND the webhook-processor Lambdas
257-
// bundled pdf-parse with esbuild instead of shipping it via `nodeModules`, mangling its
258-
// pdfjs/native deps at import — see linear-integration/jira-integration bundling.
274+
// NOT the v1 callable default export. Three things made this fail before (ABCA-745):
275+
// (1) the code called the v1 `pdfParseFn(buf)` shape (undefined on v2); (2) the
276+
// webhook processors esbuild-bundled pdf-parse instead of shipping it via `nodeModules`,
277+
// mangling its pdfjs/native deps; and (3) pdfjs tried to load the native
278+
// `@napi-rs/canvas` binding for its DOM globals — absent on Lambda — instead of just
279+
// extracting text. `ensurePdfDomGlobals` fixes (3); the bundling change fixes (2).
280+
ensurePdfDomGlobals();
259281
let PDFParse;
260282
try {
261283
// Destructure the class from the dynamic import and let TS infer its type from

0 commit comments

Comments
 (0)