@shad0wz7/nestjs-pdf 3.1.0 → 3.2.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,10 +1,10 @@
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+**.
3
+ > **v3 — ESM-first, NestJS 10, 11 & 12.** This is the active `main` line: authored
4
+ > in ESM, published as a dual CJS/ESM package, and supports **NestJS 10, 11 and 12**.
5
5
  >
6
- > Still on **NestJS 11** or need a pure CommonJS package? Use the maintenance
7
- > `v2` branch instead:
6
+ > Need a pure CommonJS package or a Node.js version older than `20.19`? Use the
7
+ > maintenance `v2` branch instead:
8
8
  >
9
9
  > ```bash
10
10
  > npm install @shad0wz7/nestjs-pdf@^2
@@ -27,12 +27,13 @@ Main features:
27
27
  - Generate PDFs from HTML, Handlebars, EJS, Pug, MJML, Nunjucks, Eta or Mustache templates
28
28
  - Concurrency limiting for Puppeteer jobs (p-limit)
29
29
  - Add signature fields based on a text anchor in an existing PDF
30
+ - Text or image watermarks (centered or tiled) on generated or existing PDFs
30
31
  - Simple API (module and service) to integrate easily into a Nest application
31
32
 
32
33
  ## Requirements
33
34
 
34
35
  - **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
36
+ - **NestJS** `^10.0.0`, `^11.0.0` or `^12.0.0` (`@nestjs/common`, `@nestjs/core`) and `rxjs ^7.8.0` as peer dependencies
36
37
 
37
38
  ## Installation
38
39
 
@@ -139,6 +140,93 @@ If the anchor is not found, the signature field will be added at a default posit
139
140
 
140
141
  **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
142
 
143
+ ### Watermark
144
+
145
+ Stamp a text or image watermark while generating a PDF, with the `watermark` option (per call or in `forRoot`):
146
+
147
+ ```ts
148
+ const pdf = await pdfService.generatePdfFromHtml(html, {
149
+ watermark: { text: 'CONFIDENTIAL', color: '#ff0000', opacity: 0.2 },
150
+ });
151
+ ```
152
+
153
+ or on an existing PDF:
154
+
155
+ ```ts
156
+ const stamped = await pdfService.addWatermark(pdf, {
157
+ image: logoPngBytes, // PNG or JPEG
158
+ imageWidth: 200,
159
+ position: 'tile',
160
+ pages: [1, 2],
161
+ });
162
+ ```
163
+
164
+ | Option | Default | Description |
165
+ | ------------- | --------------- | ------------------------------------------------------------ |
166
+ | `text` | | Text to stamp (`text` or `image` is required) |
167
+ | `image` | | PNG or JPEG bytes |
168
+ | `opacity` | `0.15` | 0 to 1 |
169
+ | `rotation` | `45` | Degrees, counter-clockwise |
170
+ | `fontSize` | `60` | Text only |
171
+ | `font` | `HelveticaBold` | A PDF standard font (`StandardFonts` key, WinAnsi text only) |
172
+ | `color` | `#808080` | `#rgb` or `#rrggbb`, text only |
173
+ | `imageWidth` | 50% of page | In points, aspect ratio preserved |
174
+ | `position` | `center` | `center` or `tile` |
175
+ | `tileSpacing` | `100` | Gap in points between tiles |
176
+ | `pages` | all | 1-based page numbers |
177
+
178
+ Invalid options or unreadable PDFs throw a `WatermarkException` (`WATERMARK_ERROR`).
179
+
180
+ ### Cancellation and timeout
181
+
182
+ Pass an `AbortSignal` in the options to cancel a generation, for example when the HTTP client disconnects, or use `AbortSignal.timeout()` for a timeout:
183
+
184
+ ```ts
185
+ const controller = new AbortController();
186
+ req.on('close', () => controller.abort());
187
+
188
+ const pdf = await pdfService.generatePdfFromHtml(html, {
189
+ signal: AbortSignal.any([controller.signal, AbortSignal.timeout(20_000)]),
190
+ });
191
+ ```
192
+
193
+ 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.
194
+
195
+ ### Restricting what the HTML can load (SSRF protection)
196
+
197
+ 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:
198
+
199
+ ```ts
200
+ NestjsPdfModule.forRoot({
201
+ security: {}, // defaults: private networks and file: URLs are blocked
202
+ });
203
+
204
+ // or, stricter / with exceptions
205
+ await pdfService.generatePdfFromHtml(html, {
206
+ security: {
207
+ allowedHosts: ['cdn.example.com', '*.static.example.com'],
208
+ allowedFileRoots: ['/app/templates/assets'],
209
+ javascriptEnabled: false,
210
+ },
211
+ });
212
+ ```
213
+
214
+ | Option | Default | Description |
215
+ | ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
216
+ | `blockPrivateNetworks` | `true` | Refuse http(s) requests to loopback, private, link-local (cloud metadata) and other non-public addresses, redirects included |
217
+ | `allowedHosts` | | Only allow http(s) requests to these hosts (`example.com` or `*.example.com`). Listed hosts are trusted even if they are private |
218
+ | `allowedFileRoots` | `[]` | `file:` requests are refused unless the file is inside one of these directories (symbolic links are resolved) |
219
+ | `javascriptEnabled` | `true` | Set to `false` to stop scripts from running in the page |
220
+
221
+ `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.
222
+
223
+ Good to know:
224
+
225
+ - Plain links (`<a href="file:///…">`) are not requests, so they are never affected.
226
+ - 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.
227
+ - 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.
228
+ - `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.
229
+
142
230
  ## Options and configuration
143
231
 
144
232
  The library exposes Puppeteer options via the `PuppeteerParameters` interface (see `src/puppeteer/puppeteer-parameters.interface.ts`). You can configure:
@@ -146,6 +234,9 @@ The library exposes Puppeteer options via the `PuppeteerParameters` interface (s
146
234
  | | Description |
147
235
  | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
236
  | `pdfOptions` | The pdf options can be found on: https://pptr.dev/api/puppeteer.pdfoptions. Native Puppeteer options for `page.pdf` |
237
+ | `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) |
238
+ | `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) |
239
+ | `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
240
  | `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
241
  | `ejsOptions` | The ejs options can be found on [EJS documentation](https://ejs.co/#options) |
151
242
  | `pugOptions` | The pug options can be found on [Pug documentation](https://pugjs.org/api/reference.html#options) |
@@ -183,7 +274,6 @@ these are the default extra arguments passed to Puppeteer:
183
274
  '--disable-ipc-flooding-protection',
184
275
  '--disable-notifications',
185
276
  '--disable-offer-store-unmasked-wallet-cards',
186
- '--disable-popup-blocking',
187
277
  '--disable-print-preview',
188
278
  '--disable-prompt-on-repost',
189
279
  '--disable-renderer-backgrounding',