@spinajs/templates-puppeteer 2.0.481 → 2.0.482

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.
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RenderProgressReporter = void 0;
4
+ const templates_1 = require("@spinajs/templates");
5
+ /** Minimum gap between resource-driven emissions (ms). */
6
+ const THROTTLE_MS = 150;
7
+ /** Re-emit interval during long phases so the bar moves even when nothing changes (ms). */
8
+ const HEARTBEAT_MS = 750;
9
+ /** Percent band [start, end] each phase occupies. */
10
+ const PHASE_BANDS = {
11
+ [templates_1.RenderPhase.Starting]: [0, 10],
12
+ [templates_1.RenderPhase.Preparing]: [10, 15],
13
+ [templates_1.RenderPhase.Loading]: [15, 80],
14
+ [templates_1.RenderPhase.Rendering]: [80, 95],
15
+ [templates_1.RenderPhase.Done]: [100, 100],
16
+ [templates_1.RenderPhase.Failed]: [0, 0],
17
+ };
18
+ /**
19
+ * Tracks a single puppeteer render and delivers throttled {@link IRenderProgress}
20
+ * snapshots to a callback. Resource counts come from the page's request lifecycle
21
+ * events; the percentage is phase-weighted (see {@link PHASE_BANDS}), and a
22
+ * heartbeat keeps it ticking during the long Loading/Rendering phases.
23
+ *
24
+ * When constructed without a callback it is entirely inert - no listeners, no
25
+ * timers, no allocations on the hot path - so callers can always create one.
26
+ */
27
+ class RenderProgressReporter {
28
+ constructor(filePath, callback) {
29
+ this.filePath = filePath;
30
+ this.startedAt = Date.now();
31
+ this.phaseNow = templates_1.RenderPhase.Starting;
32
+ this.loaded = 0;
33
+ this.pending = 0;
34
+ this.failed = 0;
35
+ this.lastEmitAt = 0;
36
+ this.lastPercent = 0;
37
+ this.heartbeat = null;
38
+ this.page = null;
39
+ // pre-bound so add/remove reference the same function identities
40
+ this.onRequest = () => {
41
+ this.pending++;
42
+ };
43
+ this.onRequestFinished = () => {
44
+ this.pending = Math.max(0, this.pending - 1);
45
+ this.loaded++;
46
+ this.emit();
47
+ };
48
+ this.onRequestFailed = () => {
49
+ this.pending = Math.max(0, this.pending - 1);
50
+ this.failed++;
51
+ this.emit();
52
+ };
53
+ this.enabled = !!callback;
54
+ this.callback = callback ?? (() => undefined);
55
+ }
56
+ /** Attach resource-lifecycle counters to the page. Call once, after newPage(). */
57
+ attach(page) {
58
+ if (!this.enabled) {
59
+ return;
60
+ }
61
+ this.page = page;
62
+ page.on('request', this.onRequest);
63
+ page.on('requestfinished', this.onRequestFinished);
64
+ page.on('requestfailed', this.onRequestFailed);
65
+ }
66
+ /** Transition to a new phase and emit immediately (bypassing the throttle). */
67
+ phase(phase, message) {
68
+ if (!this.enabled) {
69
+ return;
70
+ }
71
+ this.phaseNow = phase;
72
+ // Heartbeat only during the phases that can take a long time.
73
+ if (phase === templates_1.RenderPhase.Loading || phase === templates_1.RenderPhase.Rendering) {
74
+ this.startHeartbeat();
75
+ }
76
+ else {
77
+ this.stopHeartbeat();
78
+ }
79
+ this.emit(message, true);
80
+ }
81
+ /** Stop the heartbeat and detach page listeners. Safe to call multiple times. */
82
+ dispose() {
83
+ this.stopHeartbeat();
84
+ if (this.page) {
85
+ this.page.off('request', this.onRequest);
86
+ this.page.off('requestfinished', this.onRequestFinished);
87
+ this.page.off('requestfailed', this.onRequestFailed);
88
+ this.page = null;
89
+ }
90
+ }
91
+ startHeartbeat() {
92
+ if (this.heartbeat) {
93
+ return;
94
+ }
95
+ this.heartbeat = setInterval(() => this.emit(), HEARTBEAT_MS);
96
+ // never keep the process alive just to tick progress
97
+ this.heartbeat.unref?.();
98
+ }
99
+ stopHeartbeat() {
100
+ if (this.heartbeat) {
101
+ clearInterval(this.heartbeat);
102
+ this.heartbeat = null;
103
+ }
104
+ }
105
+ computePercent() {
106
+ if (this.phaseNow === templates_1.RenderPhase.Failed) {
107
+ return this.lastPercent;
108
+ }
109
+ const [start, end] = PHASE_BANDS[this.phaseNow];
110
+ if (this.phaseNow === templates_1.RenderPhase.Loading) {
111
+ // total is unknown up front; approximate with completed / seen-so-far
112
+ const seen = this.loaded + this.pending;
113
+ const ratio = seen > 0 ? this.loaded / seen : 0;
114
+ return Math.max(this.lastPercent, Math.min(end, Math.round(start + (end - start) * ratio)));
115
+ }
116
+ // milestone phases sit at their band start (Done -> 100); never regress
117
+ return Math.max(this.lastPercent, start);
118
+ }
119
+ emit(message, force = false) {
120
+ if (!this.enabled) {
121
+ return;
122
+ }
123
+ const now = Date.now();
124
+ if (!force && now - this.lastEmitAt < THROTTLE_MS) {
125
+ return;
126
+ }
127
+ this.lastEmitAt = now;
128
+ const percent = this.computePercent();
129
+ this.lastPercent = percent;
130
+ const progress = {
131
+ phase: this.phaseNow,
132
+ percent,
133
+ resourcesLoaded: this.loaded,
134
+ resourcesPending: this.pending,
135
+ resourcesFailed: this.failed,
136
+ elapsedMs: now - this.startedAt,
137
+ filePath: this.filePath,
138
+ message: message ?? this.defaultMessage(),
139
+ };
140
+ // progress must never stall or break a render: don't await, swallow errors
141
+ try {
142
+ const result = this.callback(progress);
143
+ if (result && typeof result.then === 'function') {
144
+ result.catch(() => {
145
+ /* swallow async listener errors */
146
+ });
147
+ }
148
+ }
149
+ catch {
150
+ /* swallow sync listener errors */
151
+ }
152
+ }
153
+ defaultMessage() {
154
+ switch (this.phaseNow) {
155
+ case templates_1.RenderPhase.Starting:
156
+ return 'Starting renderer';
157
+ case templates_1.RenderPhase.Preparing:
158
+ return 'Preparing HTML';
159
+ case templates_1.RenderPhase.Loading: {
160
+ const extra = [`${this.loaded} done`];
161
+ if (this.pending)
162
+ extra.push(`${this.pending} pending`);
163
+ if (this.failed)
164
+ extra.push(`${this.failed} failed`);
165
+ return `Loading resources (${extra.join(', ')})`;
166
+ }
167
+ case templates_1.RenderPhase.Rendering:
168
+ return 'Generating output';
169
+ case templates_1.RenderPhase.Done:
170
+ return 'Done';
171
+ case templates_1.RenderPhase.Failed:
172
+ return 'Failed';
173
+ default:
174
+ return '';
175
+ }
176
+ }
177
+ }
178
+ exports.RenderProgressReporter = RenderProgressReporter;
179
+ //# sourceMappingURL=progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/progress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import * as http from 'http';
2
+ import { Log } from '@spinajs/log';
3
+ /**
4
+ * A short-lived local static HTTP server used so headless Chromium can load
5
+ * relative asset URLs (images, css, fonts) - it refuses local files over the
6
+ * file:// protocol. Bound to loopback so the served directory is never
7
+ * network-exposed. One server is started per render and closed afterwards.
8
+ */
9
+ export declare class LocalAssetServer {
10
+ private readonly log;
11
+ private readonly portRange;
12
+ /**
13
+ * @param log - logger for lifecycle traces
14
+ * @param portRange - supplies the optional [min, max] port range; when absent
15
+ * an OS-assigned port (0) is used, which cannot collide.
16
+ */
17
+ constructor(log: Log, portRange: () => number[] | undefined);
18
+ /**
19
+ * Start a server for `basePath`. With an OS-assigned port a single attempt
20
+ * suffices; with a configured range a random pick can collide, so retry on
21
+ * EADDRINUSE with a different port.
22
+ */
23
+ serve(basePath: string): Promise<http.Server>;
24
+ /** Close a server, forcing connections closed if a graceful close times out. */
25
+ close(server: http.Server): Promise<void>;
26
+ /**
27
+ * Single attempt to start the static server on the given port (0 = OS-assigned).
28
+ * Bound to loopback so the served directory is not network-exposed.
29
+ */
30
+ private listenOnce;
31
+ }
32
+ //# sourceMappingURL=asset-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-server.d.ts","sourceRoot":"","sources":["../../src/asset-server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAI7B,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AASnC;;;;;GAKG;AACH,qBAAa,gBAAgB;IAMf,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAO,OAAO,CAAC,QAAQ,CAAC,SAAS;IALjE;;;;OAIG;gBAC0B,GAAG,EAAE,GAAG,EAAmB,SAAS,EAAE,MAAM,MAAM,EAAE,GAAG,SAAS;IAE7F;;;;OAIG;IACU,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;IA2B1D,gFAAgF;IACnE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BtD;;;OAGG;IACH,OAAO,CAAC,UAAU;CAgCnB"}
@@ -0,0 +1,118 @@
1
+ import Express from 'express';
2
+ import cors from 'cors';
3
+ import _ from 'lodash';
4
+ /** How long to wait for the local static server to start before giving up (ms). */
5
+ const SERVER_STARTUP_TIMEOUT_MS = 10000;
6
+ /** How long to wait for the local static server to close before forcing it (ms). */
7
+ const SERVER_CLOSE_TIMEOUT_MS = 5000;
8
+ /** Attempts to find a free port when a range is configured (a random pick can collide). */
9
+ const MAX_PORT_ATTEMPTS = 10;
10
+ /**
11
+ * A short-lived local static HTTP server used so headless Chromium can load
12
+ * relative asset URLs (images, css, fonts) - it refuses local files over the
13
+ * file:// protocol. Bound to loopback so the served directory is never
14
+ * network-exposed. One server is started per render and closed afterwards.
15
+ */
16
+ export class LocalAssetServer {
17
+ /**
18
+ * @param log - logger for lifecycle traces
19
+ * @param portRange - supplies the optional [min, max] port range; when absent
20
+ * an OS-assigned port (0) is used, which cannot collide.
21
+ */
22
+ constructor(log, portRange) {
23
+ this.log = log;
24
+ this.portRange = portRange;
25
+ }
26
+ /**
27
+ * Start a server for `basePath`. With an OS-assigned port a single attempt
28
+ * suffices; with a configured range a random pick can collide, so retry on
29
+ * EADDRINUSE with a different port.
30
+ */
31
+ async serve(basePath) {
32
+ const range = this.portRange();
33
+ const hasRange = !!range && range.length > 0;
34
+ const maxAttempts = hasRange ? MAX_PORT_ATTEMPTS : 1;
35
+ let lastErr;
36
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
37
+ const port = hasRange ? _.random(range[0], range[1]) : 0;
38
+ try {
39
+ return await this.listenOnce(basePath, port);
40
+ }
41
+ catch (err) {
42
+ lastErr = err;
43
+ if (err?.code === 'EADDRINUSE' && hasRange) {
44
+ this.log.trace(`Puppeteer asset server port ${port} in use, retrying (${attempt + 1}/${maxAttempts})`);
45
+ continue;
46
+ }
47
+ throw err;
48
+ }
49
+ }
50
+ this.log.error(lastErr, `Puppeteer asset server cannot start - no free port in range after ${maxAttempts} attempts`);
51
+ throw lastErr;
52
+ }
53
+ /** Close a server, forcing connections closed if a graceful close times out. */
54
+ async close(server) {
55
+ try {
56
+ await new Promise((resolve, reject) => {
57
+ const timeout = setTimeout(() => {
58
+ reject(new Error('Server close timeout'));
59
+ }, SERVER_CLOSE_TIMEOUT_MS);
60
+ server.close((err) => {
61
+ clearTimeout(timeout);
62
+ if (err)
63
+ reject(err);
64
+ else
65
+ resolve();
66
+ });
67
+ });
68
+ }
69
+ catch (err) {
70
+ this.log.warn(`Error closing server: ${err.message}`);
71
+ // Force close connections if available
72
+ try {
73
+ if ('closeAllConnections' in server) {
74
+ server.closeAllConnections();
75
+ }
76
+ }
77
+ catch (forceErr) {
78
+ this.log.error(`Error force closing server connections: ${forceErr.message}`);
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * Single attempt to start the static server on the given port (0 = OS-assigned).
84
+ * Bound to loopback so the served directory is not network-exposed.
85
+ */
86
+ listenOnce(basePath, port) {
87
+ const log = this.log;
88
+ const app = Express();
89
+ app.use(cors());
90
+ app.use(Express.static(basePath));
91
+ return new Promise((resolve, reject) => {
92
+ const server = app
93
+ .listen(port, '127.0.0.1')
94
+ .on('listening', function () {
95
+ log.trace(`Puppeteer asset server started on port ${this.address().port}`);
96
+ log.trace(`Puppeteer static file dir at ${basePath}`);
97
+ resolve(this);
98
+ })
99
+ .on('error', (err) => {
100
+ // Clean up the failed server, then reject so the caller can retry.
101
+ if (server) {
102
+ server.close(() => reject(err));
103
+ }
104
+ else {
105
+ reject(err);
106
+ }
107
+ });
108
+ // Set a timeout for server startup
109
+ setTimeout(() => {
110
+ if (!server.listening) {
111
+ server.close();
112
+ reject(new Error('Server startup timeout'));
113
+ }
114
+ }, SERVER_STARTUP_TIMEOUT_MS);
115
+ });
116
+ }
117
+ }
118
+ //# sourceMappingURL=asset-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-server.js","sourceRoot":"","sources":["../../src/asset-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { Browser, LaunchOptions } from 'puppeteer';
2
+ import { Log } from '@spinajs/log';
3
+ /**
4
+ * Lazily launches and pools a single Chromium browser, reused across renders and
5
+ * relaunched if it crashes/disconnects. Concurrent first-callers share one launch.
6
+ * The browser is closed only on {@link dispose}.
7
+ */
8
+ export declare class BrowserPool {
9
+ private readonly log;
10
+ private readonly launchOptions;
11
+ private pooled;
12
+ private launchPromise;
13
+ /**
14
+ * @param log - logger for lifecycle warnings
15
+ * @param launchOptions - supplies puppeteer launch options at acquire time
16
+ * (resolved lazily so DI-injected config is available).
17
+ */
18
+ constructor(log: Log, launchOptions: () => LaunchOptions);
19
+ /** The currently pooled browser, or null when none is live. */
20
+ get browser(): Browser | null;
21
+ /**
22
+ * Return the pooled browser, launching it on first use and relaunching if it
23
+ * has crashed/disconnected. Concurrent callers share a single launch.
24
+ */
25
+ acquire(): Promise<Browser>;
26
+ /** Close and drop the pooled browser. Best-effort: force-kills if a graceful close fails. */
27
+ dispose(): Promise<void>;
28
+ private safeClose;
29
+ private forceClose;
30
+ }
31
+ //# sourceMappingURL=browser-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-pool.d.ts","sourceRoot":"","sources":["../../src/browser-pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAwB,aAAa,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC;;;;GAIG;AACH,qBAAa,WAAW;IASV,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAO,OAAO,CAAC,QAAQ,CAAC,aAAa;IARrE,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,aAAa,CAAiC;IAEtD;;;;OAIG;gBAC0B,GAAG,EAAE,GAAG,EAAmB,aAAa,EAAE,MAAM,aAAa;IAE1F,+DAA+D;IAC/D,IAAW,OAAO,IAAI,OAAO,GAAG,IAAI,CAEnC;IAED;;;OAGG;IACU,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAmBxC,6FAA6F;IAChF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAUvB,SAAS;IAiBvB,OAAO,CAAC,UAAU;CAOnB"}
@@ -0,0 +1,76 @@
1
+ import { default as puppeteer } from 'puppeteer';
2
+ /**
3
+ * Lazily launches and pools a single Chromium browser, reused across renders and
4
+ * relaunched if it crashes/disconnects. Concurrent first-callers share one launch.
5
+ * The browser is closed only on {@link dispose}.
6
+ */
7
+ export class BrowserPool {
8
+ /**
9
+ * @param log - logger for lifecycle warnings
10
+ * @param launchOptions - supplies puppeteer launch options at acquire time
11
+ * (resolved lazily so DI-injected config is available).
12
+ */
13
+ constructor(log, launchOptions) {
14
+ this.log = log;
15
+ this.launchOptions = launchOptions;
16
+ this.pooled = null;
17
+ this.launchPromise = null;
18
+ }
19
+ /** The currently pooled browser, or null when none is live. */
20
+ get browser() {
21
+ return this.pooled;
22
+ }
23
+ /**
24
+ * Return the pooled browser, launching it on first use and relaunching if it
25
+ * has crashed/disconnected. Concurrent callers share a single launch.
26
+ */
27
+ async acquire() {
28
+ if (this.pooled?.connected) {
29
+ return this.pooled;
30
+ }
31
+ // drop a disconnected/crashed browser so we relaunch a fresh one
32
+ this.pooled = null;
33
+ if (!this.launchPromise) {
34
+ this.launchPromise = puppeteer
35
+ .launch(this.launchOptions())
36
+ .then((b) => (this.pooled = b))
37
+ // reset so a failed launch is retried on the next call
38
+ .finally(() => (this.launchPromise = null));
39
+ }
40
+ return this.launchPromise;
41
+ }
42
+ /** Close and drop the pooled browser. Best-effort: force-kills if a graceful close fails. */
43
+ async dispose() {
44
+ if (!this.pooled) {
45
+ return;
46
+ }
47
+ const browser = this.pooled;
48
+ this.pooled = null;
49
+ await this.safeClose(browser);
50
+ }
51
+ async safeClose(browser) {
52
+ try {
53
+ // close all pages first, then the browser itself
54
+ const pages = await browser.pages();
55
+ await Promise.allSettled(pages.map((page) => page.close()));
56
+ await browser.close();
57
+ }
58
+ catch (err) {
59
+ this.log.warn(`Error during normal browser cleanup: ${err.message}`);
60
+ try {
61
+ this.forceClose(browser);
62
+ }
63
+ catch (killErr) {
64
+ this.log.error(`Failed to force kill browser: ${killErr.message}`);
65
+ }
66
+ }
67
+ }
68
+ forceClose(browser) {
69
+ const proc = browser.process();
70
+ if (proc) {
71
+ proc.kill('SIGKILL');
72
+ this.log.warn('Browser process force killed');
73
+ }
74
+ }
75
+ }
76
+ //# sourceMappingURL=browser-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-pool.js","sourceRoot":"","sources":["../../src/browser-pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,OAAO,IAAI,SAAS,EAAiB,MAAM,WAAW,CAAC;AAGzE;;;;GAIG;AACH,MAAM,OAAO,WAAW;IAItB;;;;OAIG;IACH,YAA6B,GAAQ,EAAmB,aAAkC;QAA7D,QAAG,GAAH,GAAG,CAAK;QAAmB,kBAAa,GAAb,aAAa,CAAqB;QARlF,WAAM,GAAmB,IAAI,CAAC;QAC9B,kBAAa,GAA4B,IAAI,CAAC;IAOuC,CAAC;IAE9F,+DAA+D;IAC/D,IAAW,OAAO;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,OAAO;QAClB,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,iEAAiE;QACjE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAEnB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,aAAa,GAAG,SAAS;iBAC3B,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;iBAC5B,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC/B,uDAAuD;iBACtD,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC;QAChD,CAAC;QAED,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,6FAA6F;IACtF,KAAK,CAAC,OAAO;QAClB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,OAAgB;QACtC,IAAI,CAAC;YACH,iDAAiD;YACjD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACpC,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC5D,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,wCAAwC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAErE,IAAI,CAAC;gBACH,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;YAAC,OAAO,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,iCAAiC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YACrE,CAAC;QACH,CAAC;IACH,CAAC;IAEO,UAAU,CAAC,OAAgB;QACjC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;CACF"}
@@ -1,8 +1,13 @@
1
- import { Browser, Page, LaunchOptions } from 'puppeteer';
2
- import { TemplateRenderer, Templates } from '@spinajs/templates';
1
+ import { Page, LaunchOptions } from 'puppeteer';
2
+ import { TemplateRenderer, Templates, IRenderOptions, RenderProgressCallback } from '@spinajs/templates';
3
+ import { IInstanceCheck } from '@spinajs/di';
3
4
  import { Log } from '@spinajs/log';
4
- import * as http from 'http';
5
5
  import '@spinajs/templates-pug';
6
+ import { LocalAssetServer } from './asset-server.js';
7
+ import { BrowserPool } from './browser-pool.js';
8
+ export * from './progress.js';
9
+ export * from './asset-server.js';
10
+ export * from './browser-pool.js';
6
11
  export interface IPuppeteerRendererOptions {
7
12
  static: {
8
13
  portRange: number[];
@@ -22,39 +27,99 @@ export interface IPuppeteerRendererOptions {
22
27
  */
23
28
  debug?: {
24
29
  /**
25
- * If true, browser will remain open after rendering for inspection
26
- * Use it with headless: false in args to see the browser window ( puppetter.launch args )
30
+ * Controls whether the browser is closed after rendering.
31
+ * Defaults to closing. Set to `false` to keep the browser open after rendering
32
+ * for inspection ( use with headless: false in args to see the browser window ).
27
33
  */
28
34
  close?: boolean;
29
35
  };
30
36
  }
31
- export declare abstract class PuppeteerRenderer extends TemplateRenderer {
37
+ export declare abstract class PuppeteerRenderer extends TemplateRenderer implements IInstanceCheck {
32
38
  protected abstract Options: IPuppeteerRendererOptions;
39
+ /**
40
+ * Per-instance options that give this renderer its identity for the DI
41
+ * @PerInstanceCheck pooling (see subclasses). Renderers that are not pooled
42
+ * per-instance inherit the `undefined` default.
43
+ */
44
+ protected get instanceOptions(): unknown;
45
+ /**
46
+ * DI @PerInstanceCheck hook: an existing instance is reused only when it was
47
+ * created with the same options.
48
+ */
49
+ __checkInstance__(creationOptions: any): boolean;
33
50
  protected Log: Log;
34
51
  protected TemplatingService: Templates;
35
- renderToFile(template: string, model: any, filePath: string, language?: string): Promise<void>;
52
+ private _assetServer?;
53
+ private _browserPool?;
54
+ /** Local static server used so Chromium can load relative asset URLs. Lazily created. */
55
+ protected get assetServer(): LocalAssetServer;
36
56
  /**
37
- * Abstract method to perform specific rendering (PDF or image)
57
+ * Pooled browser, launched lazily and reused across renders, closed on dispose().
58
+ * One pool per options-set (see @PerInstanceCheck on subclasses). Lazily created.
38
59
  */
39
- protected abstract performRender(page: Page, filePath: string): Promise<void>;
40
- render(_templateName: string, _model: unknown, _language?: string): Promise<string>;
41
- protected compile(_path: string): Promise<void>;
42
- protected runLocalServer(basePath: string): Promise<http.Server>;
60
+ protected get browserPool(): BrowserPool;
61
+ private buildLaunchOptions;
62
+ renderToFile(template: string, model: any, filePath: string, language?: string, options?: IRenderOptions): Promise<void>;
63
+ /**
64
+ * Render a raw HTML string to a file (image/pdf per the concrete renderer).
65
+ * Intended for CI (e.g. screenshot comparison) where HTML is already produced.
66
+ *
67
+ * @param html - the HTML content to render
68
+ * @param filePath - output file path
69
+ * @param options.assetBasePath - directory served over the local http server so
70
+ * relative asset URLs in the HTML resolve (defaults to the output file's dir)
71
+ * @param options.viewport - fixed viewport for deterministic captures
72
+ */
73
+ renderHtmlToFile(html: string, filePath: string, options?: {
74
+ assetBasePath?: string;
75
+ viewport?: {
76
+ width: number;
77
+ height: number;
78
+ deviceScaleFactor?: number;
79
+ };
80
+ onProgress?: RenderProgressCallback;
81
+ }): Promise<void>;
82
+ /**
83
+ * Shared render orchestration: local static server + pooled browser + page +
84
+ * timeout watchdog + cleanup. `prepare` populates the page content (template-compiled
85
+ * HTML, or raw HTML). The pooled browser survives; only the page is closed.
86
+ */
87
+ protected renderContentToFile(filePath: string, opts: {
88
+ assetBasePath?: string;
89
+ onProgress?: RenderProgressCallback;
90
+ }, prepare: (page: Page, httpPort: number, markLoading: () => void) => Promise<void>): Promise<void>;
91
+ /** Effective render timeout: configured value, else the default. */
92
+ protected get renderTimeoutMs(): number;
93
+ /** Apply the configured (or default) navigation and render timeouts to a page. */
94
+ protected configurePageTimeouts(page: Page): void;
95
+ /**
96
+ * Arm a watchdog that closes the page if a render exceeds the configured
97
+ * timeout - only the page is closed, the pooled browser may be serving other
98
+ * concurrent renders. Returns a function that cancels the watchdog.
99
+ */
100
+ protected armRenderWatchdog(page: Page): () => void;
101
+ /** Warn if a render took longer than the configured warning threshold. */
102
+ protected warnIfSlow(filePath: string, duration: number): void;
103
+ /** Close a page, downgrading any close error to a warning (best-effort cleanup). */
104
+ protected closePage(page: Page): Promise<void>;
43
105
  /**
44
- * Enhanced browser cleanup with error handling
106
+ * Insert a <base href> into the HTML <head> so relative asset URLs resolve
107
+ * against the local static server. No-op if the HTML already declares a <base>.
45
108
  */
46
- protected safeBrowserCleanup(browser: Browser): Promise<void>;
109
+ protected injectBaseHref(html: string, baseUrl: string): string;
47
110
  /**
48
- * Force close browser with process termination
111
+ * Close the pooled browser when the service is disposed (e.g. DI.dispose() at shutdown).
112
+ * Callers that render one-shot (CLI, single email) should dispose so the process can exit.
49
113
  */
50
- protected forceCloseBrowser(browser: Browser): Promise<void>;
114
+ dispose(): Promise<void>;
51
115
  /**
52
- * Enhanced server cleanup with timeout
116
+ * Abstract method to perform specific rendering (PDF or image)
53
117
  */
54
- protected safeServerCleanup(server: http.Server): Promise<void>;
118
+ protected abstract performRender(page: Page, filePath: string): Promise<void>;
119
+ render(_templateName: string, _model: unknown, _language?: string): Promise<string>;
55
120
  /**
56
121
  * Add page event listeners with cleanup function
57
122
  */
58
- protected addPageEventListeners(page: any): () => void;
123
+ protected addPageEventListeners(page: Page): () => void;
59
124
  }
60
125
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAwB,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGjE,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B,OAAO,wBAAwB,CAAC;AAIhC,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE;QACN,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,IAAI,EAAE,aAAa,CAAC;IAEpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,KAAK,CAAC,EAAE;QAEN;;;WAGG;QACH,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAA;CACF;AAED,8BAAsB,iBAAkB,SAAQ,gBAAgB;IAC9D,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAGtD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,iBAAiB,EAAE,SAAS,CAAC;IAE1B,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgH3G;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEhE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;cAKhF,OAAO,CAAC,KAAK,EAAE,MAAM;cAErB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;IA2CtE;;OAEG;cACa,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBnE;;OAEG;cACa,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;cACa,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BrE;;OAEG;IACH,SAAS,CAAC,qBAAqB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,IAAI;CA0BvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}