@shad0wz7/nestjs-pdf 2.5.2 → 2.5.4
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 +15 -3
- package/dist/index.cjs +23 -23
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +23 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# nestjs-pdf
|
|
2
2
|
|
|
3
|
+
> **⚠️ Maintenance branch — NestJS 11 / CommonJS (v2.x).**
|
|
4
|
+
> This is the `v2` line: CommonJS-only, built for **NestJS 11**. It's kept for
|
|
5
|
+
> maintenance/security fixes only — no new features land here.
|
|
6
|
+
>
|
|
7
|
+
> Active development has moved to **v3**, on the `main` branch: ESM-first with
|
|
8
|
+
> a dual CJS/ESM build, and requires **NestJS 12+**. See its
|
|
9
|
+
> [README](https://github.com/ShadowZ11/nestjs-pdf/blob/main/README.md) for
|
|
10
|
+
> details, and pin this line explicitly if you need it:
|
|
11
|
+
>
|
|
12
|
+
> ```bash
|
|
13
|
+
> npm install @shad0wz7/nestjs-pdf@^2
|
|
14
|
+
> ```
|
|
15
|
+
|
|
3
16
|
[](https://www.npmjs.com/package/%40shad0wz7%2Fnestjs-pdf)
|
|
4
17
|
[](https://github.com/ShadowZ11/nestjs-pdf?tab=MIT-1-ov-file)
|
|
5
18
|
[](https://www.npmjs.com/package/%40shad0wz7%2Fnestjs-pdf)
|
|
@@ -7,7 +20,6 @@
|
|
|
7
20
|
[](https://codecov.io/github/ShadowZ11/nestjs-pdf)
|
|
8
21
|
[](https://www.typescriptlang.org/)
|
|
9
22
|
|
|
10
|
-
|
|
11
23
|
A NestJS library to generate and manipulate PDFs using Puppeteer and multiple template engines, including Handlebars, EJS, Pug, MJML, Nunjucks, Eta and Mustache.
|
|
12
24
|
|
|
13
25
|
Main features:
|
|
@@ -117,9 +129,8 @@ If the anchor is not found, the signature field will be added at a default posit
|
|
|
117
129
|
|
|
118
130
|
The library exposes Puppeteer options via the `PuppeteerParameters` interface (see `src/puppeteer/puppeteer-parameters.interface.ts`). You can configure:
|
|
119
131
|
|
|
120
|
-
|
|
121
132
|
| | Description |
|
|
122
|
-
|
|
133
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
134
|
| `pdfOptions` | The pdf options can be found on: https://pptr.dev/api/puppeteer.pdfoptions. Native Puppeteer options for `page.pdf` |
|
|
124
135
|
| `hbsOptions` | The handlebars can be found on [@gboutte/nestjs-hbs](https://github.com/gboutte/nestjs-hbs) as it use a peer dependency and not handlebars directly |
|
|
125
136
|
| `ejsOptions` | The ejs options can be found on [EJS documentation](https://ejs.co/#options) |
|
|
@@ -139,6 +150,7 @@ The library exposes Puppeteer options via the `PuppeteerParameters` interface (s
|
|
|
139
150
|
| `executablePath` | The path to the browser executable to use. If not specified, Puppeteer will install Chromium in cache dir. (useLockedBrowser is useless with this param specified) |
|
|
140
151
|
|
|
141
152
|
these are the default extra arguments passed to Puppeteer:
|
|
153
|
+
|
|
142
154
|
```ts
|
|
143
155
|
[
|
|
144
156
|
'--autoplay-policy=user-gesture-required',
|
package/dist/index.cjs
CHANGED
|
@@ -63,12 +63,14 @@ async function loadPdfjs() {
|
|
|
63
63
|
pdfjsPromise = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
64
64
|
} catch (error) {
|
|
65
65
|
pdfjsPromise = void 0;
|
|
66
|
+
/* v8 ignore next -- import() rejections are always Error instances in practice */
|
|
67
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
66
68
|
throw new Error([
|
|
67
69
|
"Failed to load pdfjs-dist.",
|
|
68
70
|
"If you use anchor-based signature placement in Node.js, make sure @napi-rs/canvas is installed.",
|
|
69
71
|
"Try: npm install @napi-rs/canvas",
|
|
70
72
|
"",
|
|
71
|
-
`Original error: ${
|
|
73
|
+
`Original error: ${reason}`
|
|
72
74
|
].join("\n"), { cause: error });
|
|
73
75
|
}
|
|
74
76
|
return pdfjsPromise;
|
|
@@ -109,7 +111,6 @@ function addSignatureFieldAt(pdfDoc, pageIndex, opts) {
|
|
|
109
111
|
const { fieldName, x, y, width, height } = opts;
|
|
110
112
|
const context = pdfDoc.context;
|
|
111
113
|
const page = pdfDoc.getPages()[pageIndex];
|
|
112
|
-
if (!page) throw new Error(`Page ${pageIndex} not found`);
|
|
113
114
|
const rectArray = context.obj([
|
|
114
115
|
pdf_lib.PDFNumber.of(x),
|
|
115
116
|
pdf_lib.PDFNumber.of(y),
|
|
@@ -166,7 +167,9 @@ async function addSignatureFieldUsingAnchor(pdf, fieldName = "SignatureDebtor",
|
|
|
166
167
|
const sigHeight = 50;
|
|
167
168
|
if (anchor) {
|
|
168
169
|
const pageIndex = anchor.pageIndex;
|
|
169
|
-
const
|
|
170
|
+
const page = pdfDoc.getPages()[pageIndex];
|
|
171
|
+
if (!page) throw new Error(`Page ${pageIndex} not found`);
|
|
172
|
+
const { width: pw, height: ph } = page.getSize();
|
|
170
173
|
const padX = 40;
|
|
171
174
|
const padY = 7;
|
|
172
175
|
let x = anchor.x - sigWidth / 2 + padX;
|
|
@@ -184,7 +187,10 @@ async function addSignatureFieldUsingAnchor(pdf, fieldName = "SignatureDebtor",
|
|
|
184
187
|
} else {
|
|
185
188
|
const pages = pdfDoc.getPages();
|
|
186
189
|
const pageIndex = pages.length - 1;
|
|
187
|
-
const
|
|
190
|
+
const page = pages.at(-1);
|
|
191
|
+
/* v8 ignore next -- pdf-lib always produces at least one page on save/load; only a hand-crafted, malformed PDF could hit this */
|
|
192
|
+
if (!page) throw new Error(`Page ${pageIndex} not found`);
|
|
193
|
+
const { width: pageWidth } = page.getSize();
|
|
188
194
|
addSignatureFieldAt(pdfDoc, pageIndex, {
|
|
189
195
|
fieldName,
|
|
190
196
|
x: (pageWidth - sigWidth) / 2,
|
|
@@ -256,19 +262,23 @@ let BrowserService = class BrowserService {
|
|
|
256
262
|
totalJobs = 0;
|
|
257
263
|
recycleRequested = false;
|
|
258
264
|
recycling = false;
|
|
265
|
+
shuttingDown = false;
|
|
266
|
+
/** Max time to wait for in-flight PDF jobs to finish before closing the browser on shutdown. */
|
|
267
|
+
shutdownDrainTimeoutMs = 15e3;
|
|
259
268
|
browserTag;
|
|
260
269
|
useLockedBrowser;
|
|
261
270
|
buildId;
|
|
262
271
|
constructor(pdfParams) {
|
|
263
272
|
this.cacheDir = (0, node_path.resolve)(".cache/puppeteer-browser");
|
|
264
273
|
this.options = pdfParams;
|
|
265
|
-
if (pdfParams.cleanupBrowserCacheOnExit) this.registerCleanupHooks();
|
|
266
274
|
this.loadBuildId();
|
|
267
275
|
this.loadBrowser();
|
|
268
276
|
this.loadBrowserTag();
|
|
269
277
|
this.loadUseLockedBrowser();
|
|
270
278
|
}
|
|
271
279
|
async onModuleDestroy() {
|
|
280
|
+
this.shuttingDown = true;
|
|
281
|
+
await this.waitForIdle(this.shutdownDrainTimeoutMs);
|
|
272
282
|
if (this._browserInstance?.connected) try {
|
|
273
283
|
await this._browserInstance.close();
|
|
274
284
|
} catch (error) {
|
|
@@ -276,6 +286,12 @@ let BrowserService = class BrowserService {
|
|
|
276
286
|
}
|
|
277
287
|
await this.cleanupCacheBestEffort();
|
|
278
288
|
}
|
|
289
|
+
/** Wait until no PDF job is in flight, or until the timeout elapses. */
|
|
290
|
+
async waitForIdle(timeoutMs) {
|
|
291
|
+
const start = Date.now();
|
|
292
|
+
while (this.activeJobs > 0 && Date.now() - start < timeoutMs) await sleep(50);
|
|
293
|
+
if (this.activeJobs > 0) _nestjs_common.Logger.warn(`Shutdown: ${this.activeJobs} PDF job(s) still running after ${timeoutMs}ms, closing the browser anyway`);
|
|
294
|
+
}
|
|
279
295
|
async getBrowserInstance(args, headless, executablePathBin) {
|
|
280
296
|
if (!this._browserInstance?.connected) this._browserInstance = await this.launchBrowser(args, headless, executablePathBin);
|
|
281
297
|
return this._browserInstance;
|
|
@@ -292,6 +308,7 @@ let BrowserService = class BrowserService {
|
|
|
292
308
|
}
|
|
293
309
|
}
|
|
294
310
|
markJobStarted() {
|
|
311
|
+
if (this.shuttingDown) throw new Error("nestjs-pdf is shutting down, refusing new PDF jobs");
|
|
295
312
|
this.activeJobs += 1;
|
|
296
313
|
this.totalJobs += 1;
|
|
297
314
|
if (this.totalJobs >= 200) this.recycleRequested = true;
|
|
@@ -323,7 +340,7 @@ let BrowserService = class BrowserService {
|
|
|
323
340
|
const dir = this.cacheDir;
|
|
324
341
|
try {
|
|
325
342
|
if (!(0, node_fs.existsSync)(dir)) return;
|
|
326
|
-
logger.log(`Cleanup puppeteer cache
|
|
343
|
+
logger.log(`Cleanup puppeteer cache: ${dir}`);
|
|
327
344
|
await rmWithRetries(dir, 6);
|
|
328
345
|
if ((0, node_fs.existsSync)(dir)) {
|
|
329
346
|
await sleep(100);
|
|
@@ -372,23 +389,6 @@ let BrowserService = class BrowserService {
|
|
|
372
389
|
}
|
|
373
390
|
return null;
|
|
374
391
|
}
|
|
375
|
-
registerCleanupHooks() {
|
|
376
|
-
const cleanup = async (signal) => {
|
|
377
|
-
try {
|
|
378
|
-
_nestjs_common.Logger.log(`Received ${signal}, cleaning up puppeteer cache...`, "NestJsPdf");
|
|
379
|
-
await this.cleanupCacheBestEffort();
|
|
380
|
-
process.exit(0);
|
|
381
|
-
} catch {
|
|
382
|
-
process.exit(1);
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
const cleanupHandler = (signal) => {
|
|
386
|
-
cleanup(signal);
|
|
387
|
-
};
|
|
388
|
-
process.once("SIGTERM", cleanupHandler);
|
|
389
|
-
process.once("SIGINT", cleanupHandler);
|
|
390
|
-
process.once("exit", cleanupHandler);
|
|
391
|
-
}
|
|
392
392
|
loadBrowser() {
|
|
393
393
|
let browser;
|
|
394
394
|
if (this.options?.browser === void 0) browser = _puppeteer_browsers.Browser.CHROMIUM;
|
package/dist/index.d.cts
CHANGED
|
@@ -39,11 +39,16 @@ declare class BrowserService implements OnModuleDestroy {
|
|
|
39
39
|
totalJobs: number;
|
|
40
40
|
private recycleRequested;
|
|
41
41
|
private recycling;
|
|
42
|
+
private shuttingDown;
|
|
43
|
+
/** Max time to wait for in-flight PDF jobs to finish before closing the browser on shutdown. */
|
|
44
|
+
shutdownDrainTimeoutMs: number;
|
|
42
45
|
private browserTag;
|
|
43
46
|
private useLockedBrowser;
|
|
44
47
|
private buildId;
|
|
45
48
|
constructor(pdfParams: PuppeteerParameters);
|
|
46
49
|
onModuleDestroy(): Promise<void>;
|
|
50
|
+
/** Wait until no PDF job is in flight, or until the timeout elapses. */
|
|
51
|
+
private waitForIdle;
|
|
47
52
|
getBrowserInstance(args: Array<string>, headless: boolean | 'shell' | undefined, executablePathBin: string | undefined): Promise<Browser$1>;
|
|
48
53
|
createContext(args: Array<string>, headless: Headless, executablePath?: string): Promise<BrowserContext>;
|
|
49
54
|
markJobStarted(): void;
|
|
@@ -51,7 +56,6 @@ declare class BrowserService implements OnModuleDestroy {
|
|
|
51
56
|
recycleBrowserIfNeeded(): Promise<void>;
|
|
52
57
|
private cleanupCacheBestEffort;
|
|
53
58
|
install(lock?: boolean): Promise<import("@puppeteer/browsers", { with: { "resolution-mode": "import" } }).InstalledBrowser | null | undefined>;
|
|
54
|
-
private registerCleanupHooks;
|
|
55
59
|
private loadBrowser;
|
|
56
60
|
setBrowser(browser: Browser): void;
|
|
57
61
|
getBrowser(): Browser;
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/helpers/getRelativePath.helper.ts","../src/puppeteer/browser/browser.service.ts","../src/puppeteer/engines/ejs/ejs.service.ts","../src/puppeteer/engines/eta/eta.service.ts","../src/puppeteer/engines/mustache/mustache.service.ts","../src/puppeteer/engines/nunjucks/nunjucks.service.ts","../src/puppeteer/engines/pug/pug.service.ts","../src/puppeteer/puppeteer-parameters.interface.ts","../src/nestjs-pdf.module.ts","../src/puppeteer/engines/mjml/mjml.service.ts","../src/puppeteer/puppeteer.service.ts","../src/nestjs-pdf.service.ts","../src/puppeteer/puppeteer.module.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAWgB,uBACd,mBACA;;;aCIU;EACV;EACA;EACA;EACA;EACA;;KAGU;cAqCC,0BAA0B;mBACpB;mBACA;UAET;UACA;EAED;EACA;UAEC;UACA;UAEA;UACA;UACA;EAE8B,YAAA,WAAW;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/helpers/getRelativePath.helper.ts","../src/puppeteer/browser/browser.service.ts","../src/puppeteer/engines/ejs/ejs.service.ts","../src/puppeteer/engines/eta/eta.service.ts","../src/puppeteer/engines/mustache/mustache.service.ts","../src/puppeteer/engines/nunjucks/nunjucks.service.ts","../src/puppeteer/engines/pug/pug.service.ts","../src/puppeteer/puppeteer-parameters.interface.ts","../src/nestjs-pdf.module.ts","../src/puppeteer/engines/mjml/mjml.service.ts","../src/puppeteer/puppeteer.service.ts","../src/nestjs-pdf.service.ts","../src/puppeteer/puppeteer.module.ts"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -39,11 +39,16 @@ declare class BrowserService implements OnModuleDestroy {
|
|
|
39
39
|
totalJobs: number;
|
|
40
40
|
private recycleRequested;
|
|
41
41
|
private recycling;
|
|
42
|
+
private shuttingDown;
|
|
43
|
+
/** Max time to wait for in-flight PDF jobs to finish before closing the browser on shutdown. */
|
|
44
|
+
shutdownDrainTimeoutMs: number;
|
|
42
45
|
private browserTag;
|
|
43
46
|
private useLockedBrowser;
|
|
44
47
|
private buildId;
|
|
45
48
|
constructor(pdfParams: PuppeteerParameters);
|
|
46
49
|
onModuleDestroy(): Promise<void>;
|
|
50
|
+
/** Wait until no PDF job is in flight, or until the timeout elapses. */
|
|
51
|
+
private waitForIdle;
|
|
47
52
|
getBrowserInstance(args: Array<string>, headless: boolean | 'shell' | undefined, executablePathBin: string | undefined): Promise<Browser$1>;
|
|
48
53
|
createContext(args: Array<string>, headless: Headless, executablePath?: string): Promise<BrowserContext>;
|
|
49
54
|
markJobStarted(): void;
|
|
@@ -51,7 +56,6 @@ declare class BrowserService implements OnModuleDestroy {
|
|
|
51
56
|
recycleBrowserIfNeeded(): Promise<void>;
|
|
52
57
|
private cleanupCacheBestEffort;
|
|
53
58
|
install(lock?: boolean): Promise<import("@puppeteer/browsers", { with: { "resolution-mode": "import" } }).InstalledBrowser | null | undefined>;
|
|
54
|
-
private registerCleanupHooks;
|
|
55
59
|
private loadBrowser;
|
|
56
60
|
setBrowser(browser: Browser): void;
|
|
57
61
|
getBrowser(): Browser;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/helpers/getRelativePath.helper.ts","../src/puppeteer/browser/browser.service.ts","../src/puppeteer/engines/ejs/ejs.service.ts","../src/puppeteer/engines/eta/eta.service.ts","../src/puppeteer/engines/mustache/mustache.service.ts","../src/puppeteer/engines/nunjucks/nunjucks.service.ts","../src/puppeteer/engines/pug/pug.service.ts","../src/puppeteer/puppeteer-parameters.interface.ts","../src/nestjs-pdf.module.ts","../src/puppeteer/engines/mjml/mjml.service.ts","../src/puppeteer/puppeteer.service.ts","../src/nestjs-pdf.service.ts","../src/puppeteer/puppeteer.module.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAWgB,uBACd,mBACA;;;aCIU;EACV;EACA;EACA;EACA;EACA;;KAGU;cAqCC,0BAA0B;mBACpB;mBACA;UAET;UACA;EAED;EACA;UAEC;UACA;UAEA;UACA;UACA;EAE8B,YAAA,WAAW;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/helpers/getRelativePath.helper.ts","../src/puppeteer/browser/browser.service.ts","../src/puppeteer/engines/ejs/ejs.service.ts","../src/puppeteer/engines/eta/eta.service.ts","../src/puppeteer/engines/mustache/mustache.service.ts","../src/puppeteer/engines/nunjucks/nunjucks.service.ts","../src/puppeteer/engines/pug/pug.service.ts","../src/puppeteer/puppeteer-parameters.interface.ts","../src/nestjs-pdf.module.ts","../src/puppeteer/engines/mjml/mjml.service.ts","../src/puppeteer/puppeteer.service.ts","../src/nestjs-pdf.service.ts","../src/puppeteer/puppeteer.module.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -40,12 +40,14 @@ async function loadPdfjs() {
|
|
|
40
40
|
pdfjsPromise = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
41
41
|
} catch (error) {
|
|
42
42
|
pdfjsPromise = void 0;
|
|
43
|
+
/* v8 ignore next -- import() rejections are always Error instances in practice */
|
|
44
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
43
45
|
throw new Error([
|
|
44
46
|
"Failed to load pdfjs-dist.",
|
|
45
47
|
"If you use anchor-based signature placement in Node.js, make sure @napi-rs/canvas is installed.",
|
|
46
48
|
"Try: npm install @napi-rs/canvas",
|
|
47
49
|
"",
|
|
48
|
-
`Original error: ${
|
|
50
|
+
`Original error: ${reason}`
|
|
49
51
|
].join("\n"), { cause: error });
|
|
50
52
|
}
|
|
51
53
|
return pdfjsPromise;
|
|
@@ -86,7 +88,6 @@ function addSignatureFieldAt(pdfDoc, pageIndex, opts) {
|
|
|
86
88
|
const { fieldName, x, y, width, height } = opts;
|
|
87
89
|
const context = pdfDoc.context;
|
|
88
90
|
const page = pdfDoc.getPages()[pageIndex];
|
|
89
|
-
if (!page) throw new Error(`Page ${pageIndex} not found`);
|
|
90
91
|
const rectArray = context.obj([
|
|
91
92
|
PDFNumber.of(x),
|
|
92
93
|
PDFNumber.of(y),
|
|
@@ -143,7 +144,9 @@ async function addSignatureFieldUsingAnchor(pdf, fieldName = "SignatureDebtor",
|
|
|
143
144
|
const sigHeight = 50;
|
|
144
145
|
if (anchor) {
|
|
145
146
|
const pageIndex = anchor.pageIndex;
|
|
146
|
-
const
|
|
147
|
+
const page = pdfDoc.getPages()[pageIndex];
|
|
148
|
+
if (!page) throw new Error(`Page ${pageIndex} not found`);
|
|
149
|
+
const { width: pw, height: ph } = page.getSize();
|
|
147
150
|
const padX = 40;
|
|
148
151
|
const padY = 7;
|
|
149
152
|
let x = anchor.x - sigWidth / 2 + padX;
|
|
@@ -161,7 +164,10 @@ async function addSignatureFieldUsingAnchor(pdf, fieldName = "SignatureDebtor",
|
|
|
161
164
|
} else {
|
|
162
165
|
const pages = pdfDoc.getPages();
|
|
163
166
|
const pageIndex = pages.length - 1;
|
|
164
|
-
const
|
|
167
|
+
const page = pages.at(-1);
|
|
168
|
+
/* v8 ignore next -- pdf-lib always produces at least one page on save/load; only a hand-crafted, malformed PDF could hit this */
|
|
169
|
+
if (!page) throw new Error(`Page ${pageIndex} not found`);
|
|
170
|
+
const { width: pageWidth } = page.getSize();
|
|
165
171
|
addSignatureFieldAt(pdfDoc, pageIndex, {
|
|
166
172
|
fieldName,
|
|
167
173
|
x: (pageWidth - sigWidth) / 2,
|
|
@@ -233,19 +239,23 @@ let BrowserService = class BrowserService {
|
|
|
233
239
|
totalJobs = 0;
|
|
234
240
|
recycleRequested = false;
|
|
235
241
|
recycling = false;
|
|
242
|
+
shuttingDown = false;
|
|
243
|
+
/** Max time to wait for in-flight PDF jobs to finish before closing the browser on shutdown. */
|
|
244
|
+
shutdownDrainTimeoutMs = 15e3;
|
|
236
245
|
browserTag;
|
|
237
246
|
useLockedBrowser;
|
|
238
247
|
buildId;
|
|
239
248
|
constructor(pdfParams) {
|
|
240
249
|
this.cacheDir = resolve(".cache/puppeteer-browser");
|
|
241
250
|
this.options = pdfParams;
|
|
242
|
-
if (pdfParams.cleanupBrowserCacheOnExit) this.registerCleanupHooks();
|
|
243
251
|
this.loadBuildId();
|
|
244
252
|
this.loadBrowser();
|
|
245
253
|
this.loadBrowserTag();
|
|
246
254
|
this.loadUseLockedBrowser();
|
|
247
255
|
}
|
|
248
256
|
async onModuleDestroy() {
|
|
257
|
+
this.shuttingDown = true;
|
|
258
|
+
await this.waitForIdle(this.shutdownDrainTimeoutMs);
|
|
249
259
|
if (this._browserInstance?.connected) try {
|
|
250
260
|
await this._browserInstance.close();
|
|
251
261
|
} catch (error) {
|
|
@@ -253,6 +263,12 @@ let BrowserService = class BrowserService {
|
|
|
253
263
|
}
|
|
254
264
|
await this.cleanupCacheBestEffort();
|
|
255
265
|
}
|
|
266
|
+
/** Wait until no PDF job is in flight, or until the timeout elapses. */
|
|
267
|
+
async waitForIdle(timeoutMs) {
|
|
268
|
+
const start = Date.now();
|
|
269
|
+
while (this.activeJobs > 0 && Date.now() - start < timeoutMs) await sleep(50);
|
|
270
|
+
if (this.activeJobs > 0) Logger.warn(`Shutdown: ${this.activeJobs} PDF job(s) still running after ${timeoutMs}ms, closing the browser anyway`);
|
|
271
|
+
}
|
|
256
272
|
async getBrowserInstance(args, headless, executablePathBin) {
|
|
257
273
|
if (!this._browserInstance?.connected) this._browserInstance = await this.launchBrowser(args, headless, executablePathBin);
|
|
258
274
|
return this._browserInstance;
|
|
@@ -269,6 +285,7 @@ let BrowserService = class BrowserService {
|
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
markJobStarted() {
|
|
288
|
+
if (this.shuttingDown) throw new Error("nestjs-pdf is shutting down, refusing new PDF jobs");
|
|
272
289
|
this.activeJobs += 1;
|
|
273
290
|
this.totalJobs += 1;
|
|
274
291
|
if (this.totalJobs >= 200) this.recycleRequested = true;
|
|
@@ -300,7 +317,7 @@ let BrowserService = class BrowserService {
|
|
|
300
317
|
const dir = this.cacheDir;
|
|
301
318
|
try {
|
|
302
319
|
if (!existsSync(dir)) return;
|
|
303
|
-
logger.log(`Cleanup puppeteer cache
|
|
320
|
+
logger.log(`Cleanup puppeteer cache: ${dir}`);
|
|
304
321
|
await rmWithRetries(dir, 6);
|
|
305
322
|
if (existsSync(dir)) {
|
|
306
323
|
await sleep(100);
|
|
@@ -349,23 +366,6 @@ let BrowserService = class BrowserService {
|
|
|
349
366
|
}
|
|
350
367
|
return null;
|
|
351
368
|
}
|
|
352
|
-
registerCleanupHooks() {
|
|
353
|
-
const cleanup = async (signal) => {
|
|
354
|
-
try {
|
|
355
|
-
Logger.log(`Received ${signal}, cleaning up puppeteer cache...`, "NestJsPdf");
|
|
356
|
-
await this.cleanupCacheBestEffort();
|
|
357
|
-
process.exit(0);
|
|
358
|
-
} catch {
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
const cleanupHandler = (signal) => {
|
|
363
|
-
cleanup(signal);
|
|
364
|
-
};
|
|
365
|
-
process.once("SIGTERM", cleanupHandler);
|
|
366
|
-
process.once("SIGINT", cleanupHandler);
|
|
367
|
-
process.once("exit", cleanupHandler);
|
|
368
|
-
}
|
|
369
369
|
loadBrowser() {
|
|
370
370
|
let browser;
|
|
371
371
|
if (this.options?.browser === void 0) browser = Browser.CHROMIUM;
|