@shad0wz7/nestjs-pdf 3.1.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,5 @@
1
1
  # nestjs-pdf
2
2
 
3
- > **v3 — ESM-first, NestJS 12.** This is the active `main` line: authored in
4
- > ESM, published as a dual CJS/ESM package, and targets **NestJS 12+**.
5
- >
6
- > Still on **NestJS 11** or need a pure CommonJS package? Use the maintenance
7
- > `v2` branch instead:
8
- >
9
- > ```bash
10
- > npm install @shad0wz7/nestjs-pdf@^2
11
- > ```
12
- >
13
- > See the [`v2` branch README](https://github.com/ShadowZ11/nestjs-pdf/blob/v2/README.md)
14
- > for details. It receives maintenance/security fixes only — new features land on v3.
15
-
16
3
  [![npm version](https://img.shields.io/npm/v/%40shad0wz7%2Fnestjs-pdf?label=npm%20version)](https://www.npmjs.com/package/%40shad0wz7%2Fnestjs-pdf)
17
4
  [![NPM License](https://img.shields.io/npm/l/%40shad0wz7%2Fnestjs-pdf)](https://github.com/ShadowZ11/nestjs-pdf?tab=MIT-1-ov-file)
18
5
  [![npm downloads](https://img.shields.io/npm/dm/%40shad0wz7%2Fnestjs-pdf?label=npm%20downloads)](https://www.npmjs.com/package/%40shad0wz7%2Fnestjs-pdf)
@@ -20,19 +7,20 @@
20
7
  [![codecov](https://codecov.io/github/ShadowZ11/nestjs-pdf/graph/badge.svg?token=K0MNB1ZKYK)](https://codecov.io/github/ShadowZ11/nestjs-pdf)
21
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-6.0.3-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
22
9
 
23
- A NestJS library to generate and manipulate PDFs using Puppeteer and multiple template engines, including Handlebars, EJS, Pug, MJML, Nunjucks, Eta and Mustache.
10
+ A NestJS library to generate and manipulate PDFs using Puppeteer and multiple template engines, including Handlebars, EJS, Pug, MJML, Nunjucks, Eta, Mustache and Liquid.
24
11
 
25
12
  Main features:
26
13
 
27
- - Generate PDFs from HTML, Handlebars, EJS, Pug, MJML, Nunjucks, Eta or Mustache templates
14
+ - Generate PDFs from HTML, Handlebars, EJS, Pug, MJML, Nunjucks, Eta, Mustache or Liquid templates
28
15
  - Concurrency limiting for Puppeteer jobs (p-limit)
29
16
  - Add signature fields based on a text anchor in an existing PDF
17
+ - Text or image watermarks (centered or tiled) on generated or existing PDFs
30
18
  - Simple API (module and service) to integrate easily into a Nest application
31
19
 
32
20
  ## Requirements
33
21
 
34
22
  - **Node.js** `^20.19.0`, `^22.12.0` or `>=24.0.0` (aligned with NestJS 12)
35
- - **NestJS** `^12.0.0` (`@nestjs/common`, `@nestjs/core`) and `rxjs ^7.8.0` as peer dependencies
23
+ - **NestJS** `^10.0.0`, `^11.0.0` or `^12.0.0` (`@nestjs/common`, `@nestjs/core`) and `rxjs ^7.8.0` as peer dependencies
36
24
 
37
25
  ## Installation
38
26
 
@@ -49,19 +37,39 @@ yarn add @shad0wz7/nestjs-pdf
49
37
  pnpm add ../path/to/nestjs-pdf
50
38
  ```
51
39
 
52
- ## Quick start
40
+ ### Template engines
53
41
 
54
- Import the module and configure it in your Nest application:
42
+ Template engines are **optional peer dependencies**: none of them is installed with the library. Install only the ones you use, as dependencies of your own project:
43
+
44
+ | Engine | Package to install |
45
+ | ---------- | ------------------ |
46
+ | Handlebars | `handlebars` |
47
+ | EJS | `ejs` |
48
+ | Pug | `pug` |
49
+ | MJML | `mjml` |
50
+ | Nunjucks | `nunjucks` |
51
+ | Eta | `eta` |
52
+ | Mustache | `mustache` |
53
+ | Liquid | `liquidjs` |
54
+
55
+ ```bash
56
+ # e.g. to render Handlebars templates
57
+ npm install handlebars
58
+ ```
59
+
60
+ Engines are loaded lazily, the first time you render a template with them: your application starts fine with any subset of engines installed. Rendering with an engine whose package is missing throws an `EngineNotAvailableException` (code `ENGINE_NOT_AVAILABLE`) telling you what to install:
61
+
62
+ ```text
63
+ Pug engine is not available: the 'pug' package is not installed. Run `npm install pug` to use it.
64
+ ```
65
+
66
+ Anchor-based signature placement also relies on `pdfjs-dist`, which needs `@napi-rs/canvas` to be installed in your project (see the signature example in [Quick start](#quick-start)).
55
67
 
56
- To use a template engine, you need to install the corresponding package as a dependency in your project:
68
+ > **Upgrading from v3:** engines used to be installed automatically. If you relied on that, add the engines you use to your project's dependencies.
57
69
 
58
- - For Handlebars: `npm install handlebars`
59
- - For EJS: `npm install ejs`
60
- - For Pug: `npm install pug`
61
- - For MJML: `npm install mjml`
62
- - For Nunjucks: `npm install nunjucks`
63
- - For Eta: `npm install eta`
64
- - For Mustache: `npm install mustache`
70
+ ## Quick start
71
+
72
+ Import the module and configure it in your Nest application:
65
73
 
66
74
  ```ts
67
75
  import { Module } from '@nestjs/common';
@@ -139,6 +147,93 @@ If the anchor is not found, the signature field will be added at a default posit
139
147
 
140
148
  **Warning:** if you encounter issues like "Failed to load pdfjs-dist." Make sure to install `@napi-rs/canvas` as a dependency in your project, as it's required by the library for PDF manipulation.
141
149
 
150
+ ### Watermark
151
+
152
+ Stamp a text or image watermark while generating a PDF, with the `watermark` option (per call or in `forRoot`):
153
+
154
+ ```ts
155
+ const pdf = await pdfService.generatePdfFromHtml(html, {
156
+ watermark: { text: 'CONFIDENTIAL', color: '#ff0000', opacity: 0.2 },
157
+ });
158
+ ```
159
+
160
+ or on an existing PDF:
161
+
162
+ ```ts
163
+ const stamped = await pdfService.addWatermark(pdf, {
164
+ image: logoPngBytes, // PNG or JPEG
165
+ imageWidth: 200,
166
+ position: 'tile',
167
+ pages: [1, 2],
168
+ });
169
+ ```
170
+
171
+ | Option | Default | Description |
172
+ | ------------- | --------------- | ------------------------------------------------------------ |
173
+ | `text` | | Text to stamp (`text` or `image` is required) |
174
+ | `image` | | PNG or JPEG bytes |
175
+ | `opacity` | `0.15` | 0 to 1 |
176
+ | `rotation` | `45` | Degrees, counter-clockwise |
177
+ | `fontSize` | `60` | Text only |
178
+ | `font` | `HelveticaBold` | A PDF standard font (`StandardFonts` key, WinAnsi text only) |
179
+ | `color` | `#808080` | `#rgb` or `#rrggbb`, text only |
180
+ | `imageWidth` | 50% of page | In points, aspect ratio preserved |
181
+ | `position` | `center` | `center` or `tile` |
182
+ | `tileSpacing` | `100` | Gap in points between tiles |
183
+ | `pages` | all | 1-based page numbers |
184
+
185
+ Invalid options or unreadable PDFs throw a `WatermarkException` (`WATERMARK_ERROR`).
186
+
187
+ ### Cancellation and timeout
188
+
189
+ Pass an `AbortSignal` in the options to cancel a generation, for example when the HTTP client disconnects, or use `AbortSignal.timeout()` for a timeout:
190
+
191
+ ```ts
192
+ const controller = new AbortController();
193
+ req.on('close', () => controller.abort());
194
+
195
+ const pdf = await pdfService.generatePdfFromHtml(html, {
196
+ signal: AbortSignal.any([controller.signal, AbortSignal.timeout(20_000)]),
197
+ });
198
+ ```
199
+
200
+ The promise rejects with the signal's `reason` (an `AbortError` by default, a `TimeoutError` for `AbortSignal.timeout()`). A job aborted while it waits for a free slot is dropped without starting a browser context; a running job closes its context.
201
+
202
+ ### Restricting what the HTML can load (SSRF protection)
203
+
204
+ If the HTML you render contains content you do not control, it can make the browser call internal services (`http://169.254.169.254/`, `http://localhost:…`, …). Enable the request filter with the `security` option, per call or in `forRoot`. It is **off unless you set it**, so existing setups are unchanged:
205
+
206
+ ```ts
207
+ NestjsPdfModule.forRoot({
208
+ security: {}, // defaults: private networks and file: URLs are blocked
209
+ });
210
+
211
+ // or, stricter / with exceptions
212
+ await pdfService.generatePdfFromHtml(html, {
213
+ security: {
214
+ allowedHosts: ['cdn.example.com', '*.static.example.com'],
215
+ allowedFileRoots: ['/app/templates/assets'],
216
+ javascriptEnabled: false,
217
+ },
218
+ });
219
+ ```
220
+
221
+ | Option | Default | Description |
222
+ | ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
223
+ | `blockPrivateNetworks` | `true` | Refuse http(s) requests to loopback, private, link-local (cloud metadata) and other non-public addresses, redirects included |
224
+ | `allowedHosts` | | Only allow http(s) requests to these hosts (`example.com` or `*.example.com`). Listed hosts are trusted even if they are private |
225
+ | `allowedFileRoots` | `[]` | `file:` requests are refused unless the file is inside one of these directories (symbolic links are resolved) |
226
+ | `javascriptEnabled` | `true` | Set to `false` to stop scripts from running in the page |
227
+
228
+ `data:`, `blob:` and `about:` are always allowed; every other protocol is refused. A blocked request is dropped (the PDF is still generated, without that resource) and logged without its query string.
229
+
230
+ Good to know:
231
+
232
+ - Plain links (`<a href="file:///…">`) are not requests, so they are never affected.
233
+ - Chrome already refuses to load `file:` resources from generated HTML, unless you launch it with `--allow-file-access-from-files`; `allowedFileRoots` is there for that case.
234
+ - Names are checked by resolving them before the request, so a domain that changes its address between the check and the request (DNS rebinding) can slip through. Use `allowedHosts` (or `javascriptEnabled: false`) when the content is fully untrusted.
235
+ - `window.open` pop-ups are blocked: the library no longer passes `--disable-popup-blocking` to Chrome. If you set `extraPuppeteerArgs`, do not add it back.
236
+
142
237
  ## Options and configuration
143
238
 
144
239
  The library exposes Puppeteer options via the `PuppeteerParameters` interface (see `src/puppeteer/puppeteer-parameters.interface.ts`). You can configure:
@@ -146,6 +241,9 @@ The library exposes Puppeteer options via the `PuppeteerParameters` interface (s
146
241
  | | Description |
147
242
  | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
243
  | `pdfOptions` | The pdf options can be found on: https://pptr.dev/api/puppeteer.pdfoptions. Native Puppeteer options for `page.pdf` |
244
+ | `watermark` | Stamps a text or image watermark on the generated PDF: `text` or `image`, `opacity`, `rotation`, `fontSize`, `font`, `color`, `imageWidth`, `position`, `tileSpacing`, `pages`. See [Watermark](#watermark) |
245
+ | `signal` | An `AbortSignal` that cancels the generation, including while it waits for a free slot. The promise rejects with `signal.reason`; use `AbortSignal.timeout(ms)` for a timeout. See [Cancellation and timeout](#cancellation-and-timeout) |
246
+ | `security` | Filters what the rendered HTML may load (SSRF protection): `blockPrivateNetworks`, `allowedHosts`, `allowedFileRoots`, `javascriptEnabled`. Disabled unless set; pass `{}` for the defaults. See [Restricting what the HTML can load](#restricting-what-the-html-can-load-ssrf-protection) |
149
247
  | `hbsOptions` | Handlebars configuration: `templateDirectory` (base dir for `renderFile`), `partialDirectory` (files registered as partials), `compileOptions`, `templateOptions`, and `helpers` (`{ name, fn }[]`). Like the other engines it can be set globally here or overridden per call (`generatePdfFromTemplateHbs*(..., { hbsOptions })`). Helpers/partials run on an isolated Handlebars environment (no global leakage). |
150
248
  | `ejsOptions` | The ejs options can be found on [EJS documentation](https://ejs.co/#options) |
151
249
  | `pugOptions` | The pug options can be found on [Pug documentation](https://pugjs.org/api/reference.html#options) |
@@ -153,6 +251,7 @@ The library exposes Puppeteer options via the `PuppeteerParameters` interface (s
153
251
  | `nunjucksOptions` | The nunjucks options can be found on [Nunjucks documentation](https://mozilla.github.io/nunjucks/api.html#configure) |
154
252
  | `etaOptions` | The eta options can be found on [Eta documentation](https://eta.js.org/docs/4.x.x/api/configuration) |
155
253
  | `mustacheOptions` | The mustache options can be found on [Mustache documentation](https://mustache.github.io/mustache.5.html) |
254
+ | `liquidOptions` | The liquid options can be found on [LiquidJS documentation](https://liquidjs.com/tutorials/options.html) |
156
255
  | `browser` | The browser to use for the PDF generation. By default: `Browser.CHROMIUM`. Allowed values `Browser.CHROMIUM`, `Browser.CHROMEHEADLESSSHELL`, `Browser.CHROME`, `Browser.FIREFOX`, `Browser.CHROMEDRIVER` ([Official documentation of Browser](https://pptr.dev/browsers-api/browsers.browser)) |
157
256
  | `browserTag` | The version of the browser to use for the PDF generation. By default: `BrowserTag.LATEST` if Browser is **CHROMIUM** else `BrowserTag.STABLE`. Allowed values `BrowserTag.STABLE`, `BrowserTag.LATEST`, `BrowserTag.BETA`, `BrowserTag.DEV`, `BrowserTag.CANARY` |
158
257
  | `browserInstallBaseUrl` | The baseUrl used for the installation of the browser. This baseUrl is passed to the [`install`](https://pptr.dev/browsers-api/browsers.install) method of `@puppeteer/browsers` |
@@ -183,7 +282,6 @@ these are the default extra arguments passed to Puppeteer:
183
282
  '--disable-ipc-flooding-protection',
184
283
  '--disable-notifications',
185
284
  '--disable-offer-store-unmasked-wallet-cards',
186
- '--disable-popup-blocking',
187
285
  '--disable-print-preview',
188
286
  '--disable-prompt-on-repost',
189
287
  '--disable-renderer-backgrounding',
@@ -210,7 +308,7 @@ You can use `forRootAsync(...)` if you need to provide configuration asynchronou
210
308
 
211
309
  ## Error handling
212
310
 
213
- All errors thrown by the library (missing template engine, template rendering failures, browser installation issues, PDF generation failures, signature placement issues, ...) are typed exceptions extending the abstract `NestjsPdfException` class, exported from the package root alongside a stable `NestjsPdfErrorCode` enum:
311
+ All errors thrown by the library (missing template engine, template rendering failures, browser installation issues, PDF generation failures, signature placement issues, invalid watermarks, ...) are typed exceptions extending the abstract `NestjsPdfException` class, exported from the package root alongside a stable `NestjsPdfErrorCode` enum:
214
312
 
215
313
  ```typescript
216
314
  import {