@azuro-org/images-generator 2.0.0-beta.1 → 2.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 +179 -98
- package/dist/generator.d.ts +0 -2
- package/dist/index.es.js +46 -51
- package/lib/generator.d.ts +0 -2
- package/lib/index.js +46 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,80 +1,152 @@
|
|
|
1
1
|
# @azuro-org/images-generator
|
|
2
2
|
|
|
3
|
+
Puppeteer-based image generator: render HTML templates to PNG/JPEG. Uses a shared browser instance; relaunches automatically if the browser disconnects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @azuro-org/images-generator
|
|
9
|
+
```
|
|
10
|
+
|
|
3
11
|
## Usage
|
|
4
12
|
|
|
13
|
+
Create a `Generator` instance, call `run()` to start the browser, then use `generate()` for each image. Call `shutdown()` when done to close the browser.
|
|
14
|
+
|
|
5
15
|
```typescript
|
|
6
|
-
import {
|
|
7
|
-
import template
|
|
16
|
+
import { Generator } from '@azuro-org/images-generator';
|
|
17
|
+
import template from '@azuro-org/images-generator/lib/templates/bet-nft';
|
|
8
18
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
const generator = new Generator();
|
|
20
|
+
|
|
21
|
+
await generator.run();
|
|
12
22
|
|
|
13
|
-
//
|
|
14
|
-
const buffer =
|
|
23
|
+
// Single image – returns Uint8Array
|
|
24
|
+
const buffer = await generator.generate({
|
|
15
25
|
template,
|
|
16
|
-
props,
|
|
17
|
-
})
|
|
26
|
+
props: { /* ... */ },
|
|
27
|
+
});
|
|
18
28
|
|
|
19
|
-
//
|
|
20
|
-
|
|
29
|
+
// Save to file
|
|
30
|
+
await generator.generate({
|
|
21
31
|
template,
|
|
22
|
-
props,
|
|
32
|
+
props: { /* ... */ },
|
|
23
33
|
output: './dist',
|
|
24
|
-
|
|
34
|
+
filename: 'my-image',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await generator.shutdown();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Batch generation with shutdown
|
|
41
|
+
|
|
42
|
+
Run multiple generations in parallel and close the browser when finished:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
await generator.run();
|
|
46
|
+
|
|
47
|
+
await Promise.all([
|
|
48
|
+
generator.generate({ template, props: props1, output: './dist', filename: 'image-1' }),
|
|
49
|
+
generator.generate({ template, props: props2, output: './dist', filename: 'image-2' }),
|
|
50
|
+
]).finally(() => {
|
|
51
|
+
generator.shutdown();
|
|
52
|
+
});
|
|
25
53
|
```
|
|
26
54
|
|
|
27
|
-
|
|
55
|
+
### Auto-recovery
|
|
56
|
+
|
|
57
|
+
If the browser process exits or crashes, the next `generate()` call will launch a new browser automatically. You can also call `run()` again after a disconnect.
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### `Generator`
|
|
62
|
+
|
|
63
|
+
**Constructor**
|
|
28
64
|
|
|
29
65
|
```typescript
|
|
30
|
-
|
|
66
|
+
const generator = new Generator(options?: GeneratorOptions);
|
|
67
|
+
|
|
68
|
+
interface GeneratorOptions {
|
|
69
|
+
headless?: boolean; // default: true
|
|
70
|
+
timeout?: number; // default: 30000 (ms)
|
|
71
|
+
args?: string[]; // extra Chromium args
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Methods**
|
|
76
|
+
|
|
77
|
+
- **`run(): Promise<void>`** — Launches the browser. Idempotent if already running.
|
|
78
|
+
- **`generate<P>(props: GenerateProps<P>): Promise<Uint8Array>`** — Renders the template with the given props. Ensures the browser is running (calls `run()` if needed).
|
|
79
|
+
- **`shutdown(): Promise<void>`** — Closes the browser and cleans up.
|
|
80
|
+
|
|
81
|
+
### `generate()` options
|
|
31
82
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
83
|
+
```typescript
|
|
84
|
+
interface GenerateProps<P> {
|
|
85
|
+
template: Template<P>;
|
|
86
|
+
props: P;
|
|
87
|
+
output?: string; // folder path for the output file
|
|
88
|
+
filename?: string; // file name without extension (used with output)
|
|
89
|
+
options?: GenerateOptions;
|
|
36
90
|
}
|
|
37
91
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
92
|
+
interface GenerateOptions {
|
|
93
|
+
quality?: number; // JPEG quality, default 85
|
|
94
|
+
fullPage?: boolean; // default false
|
|
95
|
+
waitForFonts?: boolean; // default true
|
|
96
|
+
waitTimeout?: number; // ms, default 2000
|
|
97
|
+
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; // default 'domcontentloaded'
|
|
98
|
+
skipAnimations?: boolean; // default true
|
|
99
|
+
}
|
|
44
100
|
```
|
|
45
101
|
|
|
102
|
+
## Templates
|
|
103
|
+
|
|
104
|
+
Templates are in `lib/templates/<name>` (or `dist/templates/<name>` for ESM). Each template exports a default `Template` and a `Props` type.
|
|
105
|
+
|
|
106
|
+
| Template | Import path |
|
|
107
|
+
|--------------------|----------------------------------------------------------|
|
|
108
|
+
| Bet NFT | `@azuro-org/images-generator/lib/templates/bet-nft` |
|
|
109
|
+
| Bet OG | `@azuro-org/images-generator/lib/templates/bet-og` |
|
|
110
|
+
| Combo Bet OG | `@azuro-org/images-generator/lib/templates/combo-bet-og` |
|
|
111
|
+
| Freebet | `@azuro-org/images-generator/lib/templates/freebet` |
|
|
112
|
+
| Trendle Leaderboard| `@azuro-org/images-generator/lib/templates/trendle-leaderboard` |
|
|
113
|
+
| Trendle Social | `@azuro-org/images-generator/lib/templates/trendle-social` |
|
|
114
|
+
| Trendle Trading | `@azuro-org/images-generator/lib/templates/trendle-trading` |
|
|
46
115
|
|
|
47
|
-
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Examples
|
|
48
119
|
|
|
49
120
|
<details>
|
|
50
121
|
<summary><b>Bet Opengraph</b></summary>
|
|
51
122
|
<p>
|
|
52
123
|
|
|
53
124
|
```typescript
|
|
54
|
-
import {
|
|
125
|
+
import { Generator } from '@azuro-org/images-generator';
|
|
55
126
|
import template from '@azuro-org/images-generator/lib/templates/bet-og';
|
|
56
127
|
|
|
57
|
-
|
|
128
|
+
const generator = new Generator();
|
|
129
|
+
await generator.run();
|
|
130
|
+
|
|
131
|
+
await generator.generate({
|
|
58
132
|
template,
|
|
133
|
+
output: './dist',
|
|
134
|
+
filename: 'bet-og',
|
|
59
135
|
props: {
|
|
60
136
|
title: 'Decentralized betting is awesome!',
|
|
61
137
|
game: {
|
|
62
138
|
country: 'International Tournaments',
|
|
63
139
|
league: 'ESL Challenger League North America',
|
|
64
140
|
participants: [
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
image: 'https://content.bookmaker.xyz/avatars/provider-3/4757.png',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: 'Los Grandes Academy',
|
|
71
|
-
image: 'https://content.bookmaker.xyz/avatars/provider-3/4739.png',
|
|
72
|
-
},
|
|
141
|
+
{ name: 'WINDINGO', image: 'https://content.bookmaker.xyz/avatars/provider-3/4757.png' },
|
|
142
|
+
{ name: 'Los Grandes Academy', image: 'https://content.bookmaker.xyz/avatars/provider-3/4739.png' },
|
|
73
143
|
],
|
|
74
144
|
startsAt: Date.now(),
|
|
75
|
-
}
|
|
145
|
+
},
|
|
76
146
|
},
|
|
77
|
-
})
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await generator.shutdown();
|
|
78
150
|
```
|
|
79
151
|
|
|
80
152
|
### Result
|
|
@@ -88,34 +160,27 @@ generateImage({
|
|
|
88
160
|
<p>
|
|
89
161
|
|
|
90
162
|
```typescript
|
|
91
|
-
import {
|
|
163
|
+
import { Generator } from '@azuro-org/images-generator';
|
|
92
164
|
import template from '@azuro-org/images-generator/lib/templates/combo-bet-og';
|
|
93
165
|
|
|
94
|
-
|
|
166
|
+
const generator = new Generator();
|
|
167
|
+
await generator.run();
|
|
168
|
+
|
|
169
|
+
await generator.generate({
|
|
95
170
|
template,
|
|
171
|
+
output: './dist',
|
|
172
|
+
filename: 'combo-bet-og',
|
|
96
173
|
props: {
|
|
97
174
|
title: 'Decentralized betting is awesome!',
|
|
98
175
|
data: {
|
|
99
176
|
totalOdds: 1.57,
|
|
100
177
|
possiblePayout: 1017.17,
|
|
101
178
|
asset: 'USDT',
|
|
102
|
-
}
|
|
179
|
+
},
|
|
103
180
|
},
|
|
104
|
-
})
|
|
181
|
+
});
|
|
105
182
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
generateImage({
|
|
109
|
-
template,
|
|
110
|
-
props: {
|
|
111
|
-
title: 'Decentralized betting is awesome!',
|
|
112
|
-
data: {
|
|
113
|
-
totalOdds: 1.57,
|
|
114
|
-
payout: 500,
|
|
115
|
-
asset: 'USDT',
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
})
|
|
183
|
+
await generator.shutdown();
|
|
119
184
|
```
|
|
120
185
|
|
|
121
186
|
### Result
|
|
@@ -129,30 +194,31 @@ generateImage({
|
|
|
129
194
|
<p>
|
|
130
195
|
|
|
131
196
|
```typescript
|
|
132
|
-
import {
|
|
197
|
+
import { Generator } from '@azuro-org/images-generator';
|
|
133
198
|
import template from '@azuro-org/images-generator/lib/templates/bet-nft';
|
|
134
199
|
|
|
135
|
-
|
|
200
|
+
const generator = new Generator();
|
|
201
|
+
await generator.run();
|
|
202
|
+
|
|
203
|
+
await generator.generate({
|
|
136
204
|
template,
|
|
205
|
+
output: './dist',
|
|
206
|
+
filename: 'bet-nft',
|
|
137
207
|
props: {
|
|
138
208
|
type: 'match',
|
|
139
209
|
sport: 'Football',
|
|
140
210
|
league: 'International Tournaments · FIFA - World Cup',
|
|
141
|
-
team1: {
|
|
142
|
-
|
|
143
|
-
name: 'Ecuador',
|
|
144
|
-
},
|
|
145
|
-
team2: {
|
|
146
|
-
img: 'https://content.bookmaker.xyz/avatars/provider-3/4739.png',
|
|
147
|
-
name: 'Senegal',
|
|
148
|
-
},
|
|
211
|
+
team1: { img: 'https://content.bookmaker.xyz/avatars/provider-3/4757.png', name: 'Ecuador' },
|
|
212
|
+
team2: { img: 'https://content.bookmaker.xyz/avatars/provider-3/4739.png', name: 'Senegal' },
|
|
149
213
|
date: 'Dec 24, 2020',
|
|
150
214
|
betAmount: '100 xDAI',
|
|
151
215
|
outcome: 'Senegal',
|
|
152
216
|
betOdds: '1.7',
|
|
153
217
|
currentOdds: '1.2',
|
|
154
218
|
},
|
|
155
|
-
})
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await generator.shutdown();
|
|
156
222
|
```
|
|
157
223
|
|
|
158
224
|
### Result
|
|
@@ -162,20 +228,27 @@ generateImage({
|
|
|
162
228
|
</details>
|
|
163
229
|
|
|
164
230
|
<details>
|
|
165
|
-
<summary><b>
|
|
231
|
+
<summary><b>Freebet</b></summary>
|
|
166
232
|
<p>
|
|
167
233
|
|
|
168
234
|
```typescript
|
|
169
|
-
import {
|
|
235
|
+
import { Generator } from '@azuro-org/images-generator';
|
|
170
236
|
import template from '@azuro-org/images-generator/lib/templates/freebet';
|
|
171
237
|
|
|
172
|
-
|
|
238
|
+
const generator = new Generator();
|
|
239
|
+
await generator.run();
|
|
240
|
+
|
|
241
|
+
await generator.generate({
|
|
173
242
|
template,
|
|
243
|
+
output: './dist',
|
|
244
|
+
filename: 'freebet',
|
|
174
245
|
props: {
|
|
175
246
|
amount: '5 xDAI',
|
|
176
247
|
date: '12.01.2022',
|
|
177
248
|
},
|
|
178
|
-
})
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
await generator.shutdown();
|
|
179
252
|
```
|
|
180
253
|
|
|
181
254
|
### Result
|
|
@@ -184,53 +257,61 @@ generateImage({
|
|
|
184
257
|
</p>
|
|
185
258
|
</details>
|
|
186
259
|
|
|
260
|
+
---
|
|
187
261
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
## Add new template
|
|
262
|
+
## Contributing
|
|
191
263
|
|
|
192
|
-
|
|
193
|
-
3. Use `index.html` for HTML. Write CSS in `index.html` file.
|
|
194
|
-
4. Create `templates/{your_template_name}/images` folder for images if required.
|
|
264
|
+
### Add a new template
|
|
195
265
|
|
|
266
|
+
1. Copy `src/templates/_template` to `src/templates/{your_template_name}`.
|
|
267
|
+
2. Put layout and styles in `index.html`.
|
|
268
|
+
3. Add a `images` folder for static assets if needed.
|
|
196
269
|
|
|
197
|
-
|
|
270
|
+
### Template definition
|
|
198
271
|
|
|
199
|
-
|
|
272
|
+
Use `Template<Props>`, `getBase64Image`, and `downloadImage` from `../../utils`:
|
|
200
273
|
|
|
201
274
|
```typescript
|
|
202
|
-
import
|
|
203
|
-
|
|
204
|
-
import { type Template, getFile, downloadImage, createGenerator } from '../../utils'
|
|
275
|
+
import { type Template, downloadImage } from '../../utils';
|
|
276
|
+
import html from './index.html';
|
|
205
277
|
|
|
206
278
|
export type Props = {
|
|
207
|
-
team1ImageSrc: string
|
|
208
|
-
team2ImageSrc: string
|
|
209
|
-
date: string
|
|
210
|
-
}
|
|
279
|
+
team1ImageSrc: string;
|
|
280
|
+
team2ImageSrc: string;
|
|
281
|
+
date: string;
|
|
282
|
+
};
|
|
211
283
|
|
|
212
|
-
const template = {
|
|
284
|
+
const template: Template<Props> = {
|
|
213
285
|
width: 800,
|
|
214
286
|
height: 400,
|
|
215
287
|
type: 'jpeg',
|
|
216
288
|
html: async (props: Props) => {
|
|
217
|
-
const { team1ImageSrc, team2ImageSrc, date } = props
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
const team1Img = await downloadImage(team1ImageSrc)
|
|
222
|
-
const team2Img = await downloadImage(team2ImageSrc)
|
|
223
|
-
|
|
289
|
+
const { team1ImageSrc, team2ImageSrc, date } = props;
|
|
290
|
+
const team1Img = await downloadImage(team1ImageSrc);
|
|
291
|
+
const team2Img = await downloadImage(team2ImageSrc);
|
|
224
292
|
return html
|
|
225
293
|
.replace('{image1}', team1Img)
|
|
226
294
|
.replace('{image2}', team2Img)
|
|
227
|
-
.replace('{date}', date)
|
|
295
|
+
.replace('{date}', date);
|
|
228
296
|
},
|
|
229
|
-
}
|
|
297
|
+
};
|
|
230
298
|
|
|
231
|
-
export default template
|
|
299
|
+
export default template;
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Template shape**
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
type Template<Props> = {
|
|
306
|
+
width: number;
|
|
307
|
+
height: number;
|
|
308
|
+
type: 'png' | 'jpeg';
|
|
309
|
+
html: (props: Props) => string | Promise<string>;
|
|
310
|
+
headless?: boolean;
|
|
311
|
+
scaleFactor?: 1 | 2;
|
|
312
|
+
};
|
|
232
313
|
```
|
|
233
314
|
|
|
234
|
-
|
|
315
|
+
### Publish
|
|
235
316
|
|
|
236
|
-
Publish
|
|
317
|
+
Publish with `npm publish`. For access to the `@azuro-org` scope, contact the maintainers.
|
package/dist/generator.d.ts
CHANGED
|
@@ -21,13 +21,11 @@ type GenerateProps<P> = {
|
|
|
21
21
|
};
|
|
22
22
|
declare class Generator {
|
|
23
23
|
private browser;
|
|
24
|
-
private isInitialized;
|
|
25
24
|
private readonly options;
|
|
26
25
|
constructor(options?: GeneratorOptions);
|
|
27
26
|
run(): Promise<void>;
|
|
28
27
|
generate<P>(props: GenerateProps<P>): Promise<Uint8Array>;
|
|
29
28
|
shutdown(): Promise<void>;
|
|
30
29
|
private cleanup;
|
|
31
|
-
get initialized(): boolean;
|
|
32
30
|
}
|
|
33
31
|
export default Generator;
|
package/dist/index.es.js
CHANGED
|
@@ -64,7 +64,6 @@ function __generator(thisArg, body) {
|
|
|
64
64
|
function Generator(options) {
|
|
65
65
|
if (options === void 0) { options = {}; }
|
|
66
66
|
this.browser = null;
|
|
67
|
-
this.isInitialized = false;
|
|
68
67
|
this.options = __assign({ headless: true, timeout: 30000, args: [
|
|
69
68
|
'--no-sandbox',
|
|
70
69
|
'--disable-setuid-sandbox',
|
|
@@ -97,10 +96,11 @@ function __generator(thisArg, body) {
|
|
|
97
96
|
var _a;
|
|
98
97
|
return __awaiter(this, void 0, void 0, function () {
|
|
99
98
|
var launchOptions, _b, error_1;
|
|
99
|
+
var _this = this;
|
|
100
100
|
return __generator(this, function (_c) {
|
|
101
101
|
switch (_c.label) {
|
|
102
102
|
case 0:
|
|
103
|
-
if (this.
|
|
103
|
+
if (this.browser) {
|
|
104
104
|
return [2 /*return*/];
|
|
105
105
|
}
|
|
106
106
|
_c.label = 1;
|
|
@@ -116,7 +116,9 @@ function __generator(thisArg, body) {
|
|
|
116
116
|
return [4 /*yield*/, puppeteer.launch(launchOptions)];
|
|
117
117
|
case 2:
|
|
118
118
|
_b.browser = _c.sent();
|
|
119
|
-
this.
|
|
119
|
+
this.browser.on('disconnected', function () {
|
|
120
|
+
_this.browser = null;
|
|
121
|
+
});
|
|
120
122
|
return [3 /*break*/, 5];
|
|
121
123
|
case 3:
|
|
122
124
|
error_1 = _c.sent();
|
|
@@ -136,27 +138,30 @@ function __generator(thisArg, body) {
|
|
|
136
138
|
return __generator(this, function (_l) {
|
|
137
139
|
switch (_l.label) {
|
|
138
140
|
case 0:
|
|
139
|
-
if (!this.
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
if (!(!this.browser || !this.browser.connected)) return [3 /*break*/, 2];
|
|
142
|
+
return [4 /*yield*/, this.run()];
|
|
143
|
+
case 1:
|
|
144
|
+
_l.sent();
|
|
145
|
+
_l.label = 2;
|
|
146
|
+
case 2:
|
|
142
147
|
template = props.template, templateProps = props.props, output = props.output, filename = props.filename, _c = props.options, options = _c === void 0 ? {} : _c;
|
|
143
148
|
return [4 /*yield*/, this.browser.newPage()];
|
|
144
|
-
case
|
|
149
|
+
case 3:
|
|
145
150
|
page = _l.sent();
|
|
146
151
|
// Set reasonable timeout instead of infinite
|
|
147
152
|
page.setDefaultNavigationTimeout((_a = this.options.timeout) !== null && _a !== void 0 ? _a : 30000);
|
|
148
153
|
page.setDefaultTimeout((_b = this.options.timeout) !== null && _b !== void 0 ? _b : 30000);
|
|
149
154
|
// Optimize page performance
|
|
150
155
|
return [4 /*yield*/, page.setCacheEnabled(false)];
|
|
151
|
-
case
|
|
156
|
+
case 4:
|
|
152
157
|
// Optimize page performance
|
|
153
158
|
_l.sent();
|
|
154
159
|
return [4 /*yield*/, page.setJavaScriptEnabled(true)];
|
|
155
|
-
case
|
|
160
|
+
case 5:
|
|
156
161
|
_l.sent();
|
|
157
162
|
// Block unnecessary network requests for faster rendering
|
|
158
163
|
return [4 /*yield*/, page.setRequestInterception(true)];
|
|
159
|
-
case
|
|
164
|
+
case 6:
|
|
160
165
|
// Block unnecessary network requests for faster rendering
|
|
161
166
|
_l.sent();
|
|
162
167
|
page.on('request', function (req) {
|
|
@@ -176,20 +181,20 @@ function __generator(thisArg, body) {
|
|
|
176
181
|
}
|
|
177
182
|
width = template.width, height = template.height, type = template.type, getHtml = template.html;
|
|
178
183
|
_d = options.quality, quality = _d === void 0 ? type === 'jpeg' ? 85 : undefined : _d, _e = options.fullPage, fullPage = _e === void 0 ? false : _e, _f = options.waitForFonts, waitForFonts = _f === void 0 ? true : _f, _g = options.waitTimeout, waitTimeout = _g === void 0 ? 2000 : _g, _h = options.waitUntil, waitUntil = _h === void 0 ? 'domcontentloaded' : _h, _j = options.skipAnimations, skipAnimations = _j === void 0 ? true : _j;
|
|
179
|
-
_l.label =
|
|
180
|
-
case
|
|
181
|
-
_l.trys.push([
|
|
184
|
+
_l.label = 7;
|
|
185
|
+
case 7:
|
|
186
|
+
_l.trys.push([7, 20, , 21]);
|
|
182
187
|
// Set viewport for this generation
|
|
183
188
|
return [4 /*yield*/, page.setViewport({
|
|
184
189
|
width: width,
|
|
185
190
|
height: height,
|
|
186
191
|
deviceScaleFactor: 1,
|
|
187
192
|
})];
|
|
188
|
-
case
|
|
193
|
+
case 8:
|
|
189
194
|
// Set viewport for this generation
|
|
190
195
|
_l.sent();
|
|
191
196
|
return [4 /*yield*/, getHtml(templateProps)];
|
|
192
|
-
case
|
|
197
|
+
case 9:
|
|
193
198
|
htmlContent = _l.sent();
|
|
194
199
|
if (!htmlContent || typeof htmlContent !== 'string') {
|
|
195
200
|
throw new Error('Template html function must return a non-empty string');
|
|
@@ -201,10 +206,10 @@ function __generator(thisArg, body) {
|
|
|
201
206
|
return setTimeout(function () { return reject(new Error('Content loading timeout')); }, waitTimeout);
|
|
202
207
|
}),
|
|
203
208
|
])];
|
|
204
|
-
case
|
|
209
|
+
case 10:
|
|
205
210
|
// Load content with faster wait strategy
|
|
206
211
|
_l.sent();
|
|
207
|
-
if (!waitForFonts) return [3 /*break*/,
|
|
212
|
+
if (!waitForFonts) return [3 /*break*/, 12];
|
|
208
213
|
return [4 /*yield*/, Promise.race([
|
|
209
214
|
page.evaluate(function () { return document.fonts.ready; }),
|
|
210
215
|
new Promise(function (_, reject) {
|
|
@@ -213,24 +218,24 @@ function __generator(thisArg, body) {
|
|
|
213
218
|
]).catch(function () {
|
|
214
219
|
// Font loading timeout is not critical, continue anyway
|
|
215
220
|
})];
|
|
216
|
-
case 9:
|
|
217
|
-
_l.sent();
|
|
218
|
-
_l.label = 10;
|
|
219
|
-
case 10:
|
|
220
|
-
if (!!skipAnimations) return [3 /*break*/, 12];
|
|
221
|
-
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
|
|
222
221
|
case 11:
|
|
223
222
|
_l.sent();
|
|
224
223
|
_l.label = 12;
|
|
225
224
|
case 12:
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
if (!!skipAnimations) return [3 /*break*/, 14];
|
|
226
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
|
|
227
|
+
case 13:
|
|
228
|
+
_l.sent();
|
|
229
|
+
_l.label = 14;
|
|
230
230
|
case 14:
|
|
231
|
+
if (!fullPage) return [3 /*break*/, 15];
|
|
232
|
+
_k = page;
|
|
233
|
+
return [3 /*break*/, 17];
|
|
234
|
+
case 15: return [4 /*yield*/, page.$('body')];
|
|
235
|
+
case 16:
|
|
231
236
|
_k = _l.sent();
|
|
232
|
-
_l.label =
|
|
233
|
-
case
|
|
237
|
+
_l.label = 17;
|
|
238
|
+
case 17:
|
|
234
239
|
content = _k;
|
|
235
240
|
if (!content) {
|
|
236
241
|
throw new Error('Failed to find content element');
|
|
@@ -243,17 +248,17 @@ function __generator(thisArg, body) {
|
|
|
243
248
|
screenshotOptions.path = "".concat(output.replace(/\/$/, ''), "/").concat(filename.replace(/\..+$/, ''), ".").concat(type);
|
|
244
249
|
}
|
|
245
250
|
return [4 /*yield*/, content.screenshot(screenshotOptions)];
|
|
246
|
-
case
|
|
251
|
+
case 18:
|
|
247
252
|
imageBuffer = _l.sent();
|
|
248
253
|
return [4 /*yield*/, page.close()];
|
|
249
|
-
case
|
|
254
|
+
case 19:
|
|
250
255
|
_l.sent();
|
|
251
256
|
return [2 /*return*/, imageBuffer];
|
|
252
|
-
case
|
|
257
|
+
case 20:
|
|
253
258
|
error_2 = _l.sent();
|
|
254
259
|
errorMessage = error_2 instanceof Error ? error_2.message : 'Unknown error occurred';
|
|
255
260
|
throw new Error("Failed to generate image: ".concat(errorMessage));
|
|
256
|
-
case
|
|
261
|
+
case 21: return [2 /*return*/];
|
|
257
262
|
}
|
|
258
263
|
});
|
|
259
264
|
});
|
|
@@ -275,33 +280,23 @@ function __generator(thisArg, body) {
|
|
|
275
280
|
return __generator(this, function (_b) {
|
|
276
281
|
switch (_b.label) {
|
|
277
282
|
case 0:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
case 1:
|
|
281
|
-
_b.trys.push([1, 4, , 5]);
|
|
282
|
-
if (!this.browser) return [3 /*break*/, 3];
|
|
283
|
+
_b.trys.push([0, 3, , 4]);
|
|
284
|
+
if (!this.browser) return [3 /*break*/, 2];
|
|
283
285
|
return [4 /*yield*/, this.browser.close().catch(function () {
|
|
284
286
|
// Ignore errors during cleanup
|
|
285
287
|
})];
|
|
286
|
-
case
|
|
288
|
+
case 1:
|
|
287
289
|
_b.sent();
|
|
288
290
|
this.browser = null;
|
|
289
|
-
_b.label =
|
|
290
|
-
case
|
|
291
|
-
case
|
|
291
|
+
_b.label = 2;
|
|
292
|
+
case 2: return [3 /*break*/, 4];
|
|
293
|
+
case 3:
|
|
292
294
|
_b.sent();
|
|
293
|
-
return [3 /*break*/,
|
|
294
|
-
case
|
|
295
|
+
return [3 /*break*/, 4];
|
|
296
|
+
case 4: return [2 /*return*/];
|
|
295
297
|
}
|
|
296
298
|
});
|
|
297
299
|
});
|
|
298
300
|
};
|
|
299
|
-
Object.defineProperty(Generator.prototype, "initialized", {
|
|
300
|
-
get: function () {
|
|
301
|
-
return this.isInitialized;
|
|
302
|
-
},
|
|
303
|
-
enumerable: false,
|
|
304
|
-
configurable: true
|
|
305
|
-
});
|
|
306
301
|
return Generator;
|
|
307
302
|
}());export{Generator};
|
package/lib/generator.d.ts
CHANGED
|
@@ -21,13 +21,11 @@ type GenerateProps<P> = {
|
|
|
21
21
|
};
|
|
22
22
|
declare class Generator {
|
|
23
23
|
private browser;
|
|
24
|
-
private isInitialized;
|
|
25
24
|
private readonly options;
|
|
26
25
|
constructor(options?: GeneratorOptions);
|
|
27
26
|
run(): Promise<void>;
|
|
28
27
|
generate<P>(props: GenerateProps<P>): Promise<Uint8Array>;
|
|
29
28
|
shutdown(): Promise<void>;
|
|
30
29
|
private cleanup;
|
|
31
|
-
get initialized(): boolean;
|
|
32
30
|
}
|
|
33
31
|
export default Generator;
|
package/lib/index.js
CHANGED
|
@@ -64,7 +64,6 @@ function __generator(thisArg, body) {
|
|
|
64
64
|
function Generator(options) {
|
|
65
65
|
if (options === void 0) { options = {}; }
|
|
66
66
|
this.browser = null;
|
|
67
|
-
this.isInitialized = false;
|
|
68
67
|
this.options = __assign({ headless: true, timeout: 30000, args: [
|
|
69
68
|
'--no-sandbox',
|
|
70
69
|
'--disable-setuid-sandbox',
|
|
@@ -97,10 +96,11 @@ function __generator(thisArg, body) {
|
|
|
97
96
|
var _a;
|
|
98
97
|
return __awaiter(this, void 0, void 0, function () {
|
|
99
98
|
var launchOptions, _b, error_1;
|
|
99
|
+
var _this = this;
|
|
100
100
|
return __generator(this, function (_c) {
|
|
101
101
|
switch (_c.label) {
|
|
102
102
|
case 0:
|
|
103
|
-
if (this.
|
|
103
|
+
if (this.browser) {
|
|
104
104
|
return [2 /*return*/];
|
|
105
105
|
}
|
|
106
106
|
_c.label = 1;
|
|
@@ -116,7 +116,9 @@ function __generator(thisArg, body) {
|
|
|
116
116
|
return [4 /*yield*/, puppeteer__default["default"].launch(launchOptions)];
|
|
117
117
|
case 2:
|
|
118
118
|
_b.browser = _c.sent();
|
|
119
|
-
this.
|
|
119
|
+
this.browser.on('disconnected', function () {
|
|
120
|
+
_this.browser = null;
|
|
121
|
+
});
|
|
120
122
|
return [3 /*break*/, 5];
|
|
121
123
|
case 3:
|
|
122
124
|
error_1 = _c.sent();
|
|
@@ -136,27 +138,30 @@ function __generator(thisArg, body) {
|
|
|
136
138
|
return __generator(this, function (_l) {
|
|
137
139
|
switch (_l.label) {
|
|
138
140
|
case 0:
|
|
139
|
-
if (!this.
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
if (!(!this.browser || !this.browser.connected)) return [3 /*break*/, 2];
|
|
142
|
+
return [4 /*yield*/, this.run()];
|
|
143
|
+
case 1:
|
|
144
|
+
_l.sent();
|
|
145
|
+
_l.label = 2;
|
|
146
|
+
case 2:
|
|
142
147
|
template = props.template, templateProps = props.props, output = props.output, filename = props.filename, _c = props.options, options = _c === void 0 ? {} : _c;
|
|
143
148
|
return [4 /*yield*/, this.browser.newPage()];
|
|
144
|
-
case
|
|
149
|
+
case 3:
|
|
145
150
|
page = _l.sent();
|
|
146
151
|
// Set reasonable timeout instead of infinite
|
|
147
152
|
page.setDefaultNavigationTimeout((_a = this.options.timeout) !== null && _a !== void 0 ? _a : 30000);
|
|
148
153
|
page.setDefaultTimeout((_b = this.options.timeout) !== null && _b !== void 0 ? _b : 30000);
|
|
149
154
|
// Optimize page performance
|
|
150
155
|
return [4 /*yield*/, page.setCacheEnabled(false)];
|
|
151
|
-
case
|
|
156
|
+
case 4:
|
|
152
157
|
// Optimize page performance
|
|
153
158
|
_l.sent();
|
|
154
159
|
return [4 /*yield*/, page.setJavaScriptEnabled(true)];
|
|
155
|
-
case
|
|
160
|
+
case 5:
|
|
156
161
|
_l.sent();
|
|
157
162
|
// Block unnecessary network requests for faster rendering
|
|
158
163
|
return [4 /*yield*/, page.setRequestInterception(true)];
|
|
159
|
-
case
|
|
164
|
+
case 6:
|
|
160
165
|
// Block unnecessary network requests for faster rendering
|
|
161
166
|
_l.sent();
|
|
162
167
|
page.on('request', function (req) {
|
|
@@ -176,20 +181,20 @@ function __generator(thisArg, body) {
|
|
|
176
181
|
}
|
|
177
182
|
width = template.width, height = template.height, type = template.type, getHtml = template.html;
|
|
178
183
|
_d = options.quality, quality = _d === void 0 ? type === 'jpeg' ? 85 : undefined : _d, _e = options.fullPage, fullPage = _e === void 0 ? false : _e, _f = options.waitForFonts, waitForFonts = _f === void 0 ? true : _f, _g = options.waitTimeout, waitTimeout = _g === void 0 ? 2000 : _g, _h = options.waitUntil, waitUntil = _h === void 0 ? 'domcontentloaded' : _h, _j = options.skipAnimations, skipAnimations = _j === void 0 ? true : _j;
|
|
179
|
-
_l.label =
|
|
180
|
-
case
|
|
181
|
-
_l.trys.push([
|
|
184
|
+
_l.label = 7;
|
|
185
|
+
case 7:
|
|
186
|
+
_l.trys.push([7, 20, , 21]);
|
|
182
187
|
// Set viewport for this generation
|
|
183
188
|
return [4 /*yield*/, page.setViewport({
|
|
184
189
|
width: width,
|
|
185
190
|
height: height,
|
|
186
191
|
deviceScaleFactor: 1,
|
|
187
192
|
})];
|
|
188
|
-
case
|
|
193
|
+
case 8:
|
|
189
194
|
// Set viewport for this generation
|
|
190
195
|
_l.sent();
|
|
191
196
|
return [4 /*yield*/, getHtml(templateProps)];
|
|
192
|
-
case
|
|
197
|
+
case 9:
|
|
193
198
|
htmlContent = _l.sent();
|
|
194
199
|
if (!htmlContent || typeof htmlContent !== 'string') {
|
|
195
200
|
throw new Error('Template html function must return a non-empty string');
|
|
@@ -201,10 +206,10 @@ function __generator(thisArg, body) {
|
|
|
201
206
|
return setTimeout(function () { return reject(new Error('Content loading timeout')); }, waitTimeout);
|
|
202
207
|
}),
|
|
203
208
|
])];
|
|
204
|
-
case
|
|
209
|
+
case 10:
|
|
205
210
|
// Load content with faster wait strategy
|
|
206
211
|
_l.sent();
|
|
207
|
-
if (!waitForFonts) return [3 /*break*/,
|
|
212
|
+
if (!waitForFonts) return [3 /*break*/, 12];
|
|
208
213
|
return [4 /*yield*/, Promise.race([
|
|
209
214
|
page.evaluate(function () { return document.fonts.ready; }),
|
|
210
215
|
new Promise(function (_, reject) {
|
|
@@ -213,24 +218,24 @@ function __generator(thisArg, body) {
|
|
|
213
218
|
]).catch(function () {
|
|
214
219
|
// Font loading timeout is not critical, continue anyway
|
|
215
220
|
})];
|
|
216
|
-
case 9:
|
|
217
|
-
_l.sent();
|
|
218
|
-
_l.label = 10;
|
|
219
|
-
case 10:
|
|
220
|
-
if (!!skipAnimations) return [3 /*break*/, 12];
|
|
221
|
-
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
|
|
222
221
|
case 11:
|
|
223
222
|
_l.sent();
|
|
224
223
|
_l.label = 12;
|
|
225
224
|
case 12:
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
if (!!skipAnimations) return [3 /*break*/, 14];
|
|
226
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
|
|
227
|
+
case 13:
|
|
228
|
+
_l.sent();
|
|
229
|
+
_l.label = 14;
|
|
230
230
|
case 14:
|
|
231
|
+
if (!fullPage) return [3 /*break*/, 15];
|
|
232
|
+
_k = page;
|
|
233
|
+
return [3 /*break*/, 17];
|
|
234
|
+
case 15: return [4 /*yield*/, page.$('body')];
|
|
235
|
+
case 16:
|
|
231
236
|
_k = _l.sent();
|
|
232
|
-
_l.label =
|
|
233
|
-
case
|
|
237
|
+
_l.label = 17;
|
|
238
|
+
case 17:
|
|
234
239
|
content = _k;
|
|
235
240
|
if (!content) {
|
|
236
241
|
throw new Error('Failed to find content element');
|
|
@@ -243,17 +248,17 @@ function __generator(thisArg, body) {
|
|
|
243
248
|
screenshotOptions.path = "".concat(output.replace(/\/$/, ''), "/").concat(filename.replace(/\..+$/, ''), ".").concat(type);
|
|
244
249
|
}
|
|
245
250
|
return [4 /*yield*/, content.screenshot(screenshotOptions)];
|
|
246
|
-
case
|
|
251
|
+
case 18:
|
|
247
252
|
imageBuffer = _l.sent();
|
|
248
253
|
return [4 /*yield*/, page.close()];
|
|
249
|
-
case
|
|
254
|
+
case 19:
|
|
250
255
|
_l.sent();
|
|
251
256
|
return [2 /*return*/, imageBuffer];
|
|
252
|
-
case
|
|
257
|
+
case 20:
|
|
253
258
|
error_2 = _l.sent();
|
|
254
259
|
errorMessage = error_2 instanceof Error ? error_2.message : 'Unknown error occurred';
|
|
255
260
|
throw new Error("Failed to generate image: ".concat(errorMessage));
|
|
256
|
-
case
|
|
261
|
+
case 21: return [2 /*return*/];
|
|
257
262
|
}
|
|
258
263
|
});
|
|
259
264
|
});
|
|
@@ -275,33 +280,23 @@ function __generator(thisArg, body) {
|
|
|
275
280
|
return __generator(this, function (_b) {
|
|
276
281
|
switch (_b.label) {
|
|
277
282
|
case 0:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
case 1:
|
|
281
|
-
_b.trys.push([1, 4, , 5]);
|
|
282
|
-
if (!this.browser) return [3 /*break*/, 3];
|
|
283
|
+
_b.trys.push([0, 3, , 4]);
|
|
284
|
+
if (!this.browser) return [3 /*break*/, 2];
|
|
283
285
|
return [4 /*yield*/, this.browser.close().catch(function () {
|
|
284
286
|
// Ignore errors during cleanup
|
|
285
287
|
})];
|
|
286
|
-
case
|
|
288
|
+
case 1:
|
|
287
289
|
_b.sent();
|
|
288
290
|
this.browser = null;
|
|
289
|
-
_b.label =
|
|
290
|
-
case
|
|
291
|
-
case
|
|
291
|
+
_b.label = 2;
|
|
292
|
+
case 2: return [3 /*break*/, 4];
|
|
293
|
+
case 3:
|
|
292
294
|
_b.sent();
|
|
293
|
-
return [3 /*break*/,
|
|
294
|
-
case
|
|
295
|
+
return [3 /*break*/, 4];
|
|
296
|
+
case 4: return [2 /*return*/];
|
|
295
297
|
}
|
|
296
298
|
});
|
|
297
299
|
});
|
|
298
300
|
};
|
|
299
|
-
Object.defineProperty(Generator.prototype, "initialized", {
|
|
300
|
-
get: function () {
|
|
301
|
-
return this.isInitialized;
|
|
302
|
-
},
|
|
303
|
-
enumerable: false,
|
|
304
|
-
configurable: true
|
|
305
|
-
});
|
|
306
301
|
return Generator;
|
|
307
302
|
}());exports.Generator=Generator;
|