@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.
- package/README.md +35 -0
- package/lib/cjs/asset-server.d.ts +32 -0
- package/lib/cjs/asset-server.d.ts.map +1 -0
- package/lib/cjs/asset-server.js +125 -0
- package/lib/cjs/asset-server.js.map +1 -0
- package/lib/cjs/browser-pool.d.ts +31 -0
- package/lib/cjs/browser-pool.d.ts.map +1 -0
- package/lib/cjs/browser-pool.js +83 -0
- package/lib/cjs/browser-pool.js.map +1 -0
- package/lib/cjs/index.d.ts +84 -19
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +203 -182
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/progress.d.ts +41 -0
- package/lib/cjs/progress.d.ts.map +1 -0
- package/lib/cjs/progress.js +179 -0
- package/lib/cjs/progress.js.map +1 -0
- package/lib/mjs/asset-server.d.ts +32 -0
- package/lib/mjs/asset-server.d.ts.map +1 -0
- package/lib/mjs/asset-server.js +118 -0
- package/lib/mjs/asset-server.js.map +1 -0
- package/lib/mjs/browser-pool.d.ts +31 -0
- package/lib/mjs/browser-pool.d.ts.map +1 -0
- package/lib/mjs/browser-pool.js +76 -0
- package/lib/mjs/browser-pool.js.map +1 -0
- package/lib/mjs/index.d.ts +84 -19
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +190 -180
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/progress.d.ts +41 -0
- package/lib/mjs/progress.d.ts.map +1 -0
- package/lib/mjs/progress.js +175 -0
- package/lib/mjs/progress.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +8 -7
|
@@ -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"}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
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
|
-
*
|
|
26
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
*
|
|
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
|
|
109
|
+
protected injectBaseHref(html: string, baseUrl: string): string;
|
|
47
110
|
/**
|
|
48
|
-
*
|
|
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
|
-
|
|
114
|
+
dispose(): Promise<void>;
|
|
51
115
|
/**
|
|
52
|
-
*
|
|
116
|
+
* Abstract method to perform specific rendering (PDF or image)
|
|
53
117
|
*/
|
|
54
|
-
protected
|
|
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:
|
|
123
|
+
protected addPageEventListeners(page: Page): () => void;
|
|
59
124
|
}
|
|
60
125
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/mjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|