( event: RenderChunkEvent, deps?: HandlerDeps, )
| 300 | // ── RenderChunk ───────────────────────────────────────────────────────────── |
| 301 | |
| 302 | async function handleRenderChunk( |
| 303 | event: RenderChunkEvent, |
| 304 | deps?: HandlerDeps, |
| 305 | ): Promise<RenderChunkLambdaResult> { |
| 306 | const started = Date.now(); |
| 307 | const s3 = deps?.s3 ?? getS3Client(); |
| 308 | const primitive = deps?.primitives?.renderChunk ?? renderChunk; |
| 309 | |
| 310 | // Sparticuz decompresses Chromium into /tmp on first call; warm starts |
| 311 | // skip the work (path already cached). Guard the env-var mutation too so |
| 312 | // a caller-supplied PRODUCER_HEADLESS_SHELL_PATH (e.g. the SAM-local |
| 313 | // RIE smoke) wins over the auto-resolution. |
| 314 | if (!deps?.skipChromeResolution && !process.env.PRODUCER_HEADLESS_SHELL_PATH) { |
| 315 | const chromePath = await resolveChromeExecutablePath(); |
| 316 | // The OSS engine resolves Chrome via `PRODUCER_HEADLESS_SHELL_PATH` |
| 317 | // first (see `browserManager.resolveHeadlessShellPath`); set it before |
| 318 | // invoking the primitive so launch picks up the bundled binary. |
| 319 | process.env.PRODUCER_HEADLESS_SHELL_PATH = chromePath; |
| 320 | } |
| 321 | |
| 322 | const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-lambda-chunk-")); |
| 323 | const planTar = join(work, "plan.tar.gz"); |
| 324 | const planDir = join(work, "plan"); |
| 325 | |
| 326 | try { |
| 327 | await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar); |
| 328 | await untarDirectory(planTar, planDir); |
| 329 | |
| 330 | // Verify the plan's hash matches what Step Functions told us to render. |
| 331 | // The producer's renderChunk re-checks internally (defense-in-depth), |
| 332 | // but doing it here at the handler boundary lets us fail before paying |
| 333 | // the Chrome-launch + render cost on a misrouted chunk. Throws a |
| 334 | // typed PLAN_HASH_MISMATCH that Step Functions can route as |
| 335 | // non-retryable. |
| 336 | verifyPlanHash(planDir, event.PlanHash); |
| 337 | |
| 338 | const chunkOutputBase = join( |
| 339 | work, |
| 340 | event.Format === "png-sequence" |
| 341 | ? `chunk-${pad(event.ChunkIndex)}` |
| 342 | : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`, |
| 343 | ); |
| 344 | |
| 345 | const result: ChunkResult = await primitive(planDir, event.ChunkIndex, chunkOutputBase); |
| 346 | |
| 347 | const chunkUri = await uploadChunkOutput( |
| 348 | s3, |
| 349 | result, |
| 350 | event.ChunkOutputS3Prefix, |
| 351 | event.ChunkIndex, |
| 352 | ); |
| 353 | |
| 354 | return { |
| 355 | Action: "renderChunk", |
| 356 | ChunkS3Uri: chunkUri, |
| 357 | ChunkIndex: event.ChunkIndex, |
| 358 | Sha256: result.sha256, |
| 359 | FramesEncoded: result.framesEncoded, |
no test coverage detected