@shotkit/shotium 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,110 +7,182 @@
7
7
 
8
8
  ---
9
9
 
10
- ## Overview
10
+ ## Quick Start
11
11
 
12
- `@shotkit/shotium` provides Node.js / TypeScript bindings for **shotium**, a stripped-down Chromium engine built specifically for fast static page rendering. By completely removing V8 and browser chrome overhead, Shotium delivers cold starts under 350 ms, single-shot captures in ~47 ms, and an idle memory footprint of ~58 MB.
12
+ ### 1. Installation
13
+
14
+ ```bash
15
+ # npm
16
+ npm install @shotkit/shotium
17
+
18
+ # pnpm
19
+ pnpm add @shotkit/shotium
20
+
21
+ # yarn
22
+ yarn add @shotkit/shotium
23
+
24
+ # bun
25
+ bun add @shotkit/shotium
26
+ ```
27
+
28
+ Prebuilt platform binaries are installed automatically via npm optional dependencies across six architectures (Windows, macOS, and Linux on x64 and arm64). No local compiler or postinstall build script is required.
29
+
30
+ The package is published as native ESM:
31
+ - `import` is supported on Node.js 18+.
32
+ - Synchronous `require()` is supported on Node.js 22.12+ / 20.19+.
33
+ - Earlier Node.js versions can use dynamic `await import('@shotkit/shotium')`.
34
+
35
+ ### 2. Basic Example
13
36
 
14
37
  ```ts
15
- import shotium from '@shotkit/shotium';
38
+ import shotium, { screenshot } from '@shotkit/shotium';
16
39
 
17
- shotium.runtime.start({ workers: 4 });
40
+ // 1. Initialize engine
41
+ shotium.start();
18
42
 
19
- const png = await shotium.screenshot({
20
- file: 'https://example.com',
21
- viewport: { width: 1280, height: 720 },
22
- fullPage: true,
43
+ // 2. Render remote URLs, local HTML files, or inline HTML strings (data:text/html)
44
+ const { image, stats } = await screenshot({
45
+ file: 'data:text/html,<h1 style="color: #0969da; font-family: sans-serif;">Hello Shotium</h1>',
46
+ viewport: { width: 800, height: 400 },
23
47
  });
24
48
 
25
- await shotium.runtime.stop();
49
+ console.log(`Rendered in ${stats.timing.render}ms, Total: ${stats.timing.total}ms`);
50
+
51
+ // 3. Shut down engine and release resources
52
+ await shotium.stop();
26
53
  ```
27
54
 
28
55
  ---
29
56
 
30
- ## Installation
57
+ ## Table of Contents
58
+
59
+ - [Overview & Key Highlights](#overview--key-highlights)
60
+ - [Execution Mode Selection Guide](#execution-mode-selection-guide)
61
+ - [Usage Modes](#usage-modes)
62
+ - [1. In-Process Engine](#1-in-process-engine)
63
+ - [2. Resident Daemon](#2-resident-daemon-daemon)
64
+ - [API Reference](#api-reference)
65
+ - [`ScreenshotOptions` & `ScreenshotResult`](#screenshotoptions)
66
+ - [`StartOptions` & `StartResult`](#startoptions)
67
+ - [`CaptureStats` Metrics Breakdown](#capturestats)
68
+ - [`daemon` Module & Status](#daemon-module)
69
+ - [`cache` Management Module](#cache-module)
70
+ - [License](#license)
31
71
 
32
- ```bash
33
- npm install @shotkit/shotium
34
- ```
72
+ ---
73
+
74
+ ## Overview & Key Highlights
75
+
76
+ `@shotkit/shotium` provides Node.js / TypeScript bindings for **shotium**, a stripped-down Chromium engine built specifically for fast static page rendering.
77
+
78
+ By completely removing V8 and multi-process browser overhead, Shotium delivers:
79
+ - **Fast Cold Starts**: Under 350 ms
80
+ - **High Render Speed**: ~47 ms for single viewport capture (down to ~31 ms in-process)
81
+ - **Low Memory Footprint**: ~58 MB idle memory
82
+
83
+ The engine is loaded into the host process as a Node-API addon wrapping a shared library. With zero child process overhead, no inter-process image copying is needed, and `screenshot()` directly returns the encoded image buffer from Blink.
35
84
 
36
- Prebuilt platform binaries are installed automatically via npm optional dependencies.
85
+ ---
86
+
87
+ ## Execution Mode Selection Guide
88
+
89
+ | Use Case | Recommended Mode | Key Benefit |
90
+ |---|---|---|
91
+ | **Long-Running Web / API Services** (Express, Fastify, NestJS) | **In-Process Engine** | Zero IPC overhead, zero process startup cost, lowest per-shot latency (~31 ms). |
92
+ | **CLI Tools / CI Pipelines / Serverless Tasks** | **Resident Daemon** | Cross-process pre-warmed engine reuse; connects in **2.3 ms**, eliminating cold starts. |
37
93
 
38
94
  ---
39
95
 
40
- ## Usage
96
+ ## Usage Modes
41
97
 
42
- ### 1. Multi-Process Pool (`runtime`)
98
+ ### 1. In-Process Engine
43
99
 
44
- Recommended for standard backend servers and continuous job queues.
100
+ The engine runs directly inside your Node.js process via Node-API, bound to the C ABI in [`shot/shot_api.h`](https://github.com/sj817/shotium/blob/main/shot/shot_api.h). `screenshot()` returns the image buffer encoded directly by Blink (~**31 ms** per shot).
45
101
 
46
102
  ```ts
47
- import { runtime, screenshot } from '@shotkit/shotium';
103
+ import shotium, { screenshot } from '@shotkit/shotium';
48
104
 
49
- // Optional: listen to runtime lifecycle events
50
- runtime.on('crash', ({ worker }) => console.warn(`Worker ${worker} recovered from crash`));
51
- runtime.on('timeout', ({ worker, timeout }) => console.warn(`Worker ${worker} timed out (${timeout}ms)`));
105
+ // Start engine and retrieve cache status
106
+ const { cacheDir, cacheActive } = shotium.start();
52
107
 
53
- // Start pool
54
- runtime.start({
55
- workers: 4, // Default: Math.max(1, Math.floor(cpuCount / 2))
56
- cacheDir: '/var/tmp/shotium-cache' // Optional HTTP disk cache
57
- });
58
-
59
- // Take screenshot (returns Buffer or writes to disk if 'path' is specified)
60
- const buffer = await screenshot({
108
+ // 1. Capture remote URL
109
+ const res1 = await screenshot({
61
110
  file: 'https://example.com',
62
111
  viewport: { width: 1280, height: 720 },
63
112
  type: 'webp',
64
113
  quality: 85,
65
114
  });
66
115
 
67
- await runtime.stop();
116
+ // 2. Capture dynamically assembled inline HTML string (no temporary files on disk)
117
+ const html = `<div style="padding: 24px; background: #f6f8fa;"><h2>Invoice #1024</h2></div>`;
118
+ const res2 = await screenshot({
119
+ file: `data:text/html;charset=utf-8,${encodeURIComponent(html)}`,
120
+ viewport: { width: 600, height: 300 },
121
+ });
122
+
123
+ // 3. Memory reclamation strategies:
124
+ // - releaseMemory(): clears Blink heap, Skia caches, and PartitionAlloc free lists (instant)
125
+ // - releaseWorkingSet: true: additionally asks OS to reclaim physical working set memory
126
+ shotium.releaseMemory({ releaseWorkingSet: true });
127
+
128
+ // 4. Shut down engine
129
+ await shotium.stop();
68
130
  ```
69
131
 
132
+ #### Lifecycle & Execution Model
133
+
134
+ - **Process Singleton**:
135
+ - Blink relies on process-wide statics that cannot be cleanly uninitialized.
136
+ - Each process hosts a single engine instance; subsequent `new Runtime()` calls reuse the existing instance.
137
+ - **Start / Stop Semantics**:
138
+ - Calling `stop()` drains the task queue, marks `running: false`, and releases working set memory.
139
+ - Calling `start()` subsequent times re-activates the engine and preserves its warm disk cache.
140
+ - **Configuration Consistency**:
141
+ - Engine options are established at initial creation.
142
+ - Passing conflicting options in subsequent `start()` calls throws an explicit error rather than silently ignoring parameters.
143
+ - **Concurrency & Parallelism**:
144
+ - Within a single engine instance, concurrent screenshot calls are queued and rendered sequentially.
145
+ - To achieve parallel throughput, scale horizontally across multiple worker processes.
146
+ - **Status Reporting**:
147
+ - `start()` and `status()` return `{ running, cacheDir, cacheActive }`.
148
+ - If `cacheDir` cannot be opened, `cacheActive` is set to `false`, and the engine operates safely in cacheless mode.
149
+
70
150
  ---
71
151
 
72
152
  ### 2. Resident Daemon (`daemon`)
73
153
 
74
154
  Recommended for CLI tools, ephemeral CI tasks, or serverless workers where startup latency is critical.
75
155
 
76
- `daemon` keeps a pre-warmed worker pool listening behind a local socket (Named Pipe on Windows, Unix domain socket on POSIX).
156
+ The daemon runs the engine in a standalone background process exposed via a local IPC socket (Named Pipe on Windows, Unix domain socket on POSIX). It pre-renders a blank page upon startup to warm up all subsystems, allowing clients to connect with ~**2.3 ms** latency.
77
157
 
78
158
  ```ts
79
159
  import { daemon } from '@shotkit/shotium';
80
160
 
81
- // Connect to existing daemon (automatically starts one if none is running)
82
- const client = await daemon.connect({ workers: 4 });
161
+ // Connect to existing daemon, or automatically launch one in background
162
+ const client = await daemon.connect();
83
163
 
84
- const png = await client.screenshot({
164
+ // Dispatch screenshot request
165
+ const { image, stats } = await client.screenshot({
85
166
  file: 'https://example.com',
86
167
  viewport: { width: 1280, height: 720 },
87
168
  });
88
169
 
170
+ // Manage daemon status or release memory from client connection
171
+ const clientStatus = await client.status();
172
+ await client.releaseMemory({ releaseWorkingSet: false });
173
+
89
174
  client.close();
90
175
 
91
- // Check status or stop daemon
92
- const status = await daemon.status();
176
+ // Global daemon operations (optional)
177
+ const daemonInfo = await daemon.status();
178
+ console.log(`Daemon PID: ${daemonInfo.pid}, Uptime: ${daemonInfo.uptimeMs}ms, Served: ${daemonInfo.served}`);
179
+
180
+ // Stop daemon
93
181
  await daemon.stop();
94
182
  ```
95
183
 
96
- ---
97
-
98
- ### 3. In-Process Native Engine (`@shotkit/shotium/native`)
99
-
100
- Recommended for single-process, single-threaded batch rendering with minimum overhead (~31 ms per shot).
101
-
102
- ```ts
103
- import { native } from '@shotkit/shotium/native';
104
-
105
- const png = await native.screenshot({
106
- file: 'https://example.com',
107
- viewport: { width: 1280, height: 720 },
108
- });
109
-
110
- // Purge cache and release working set after batch
111
- native.purge({ releaseWorkingSet: true });
112
- await native.stop();
113
- ```
184
+ - **Request Multiplexing**: Multiple requests can be dispatched concurrently over a single connection; each message carries a unique `id`. The daemon processes and returns responses in completion order.
185
+ - **Multi-Instance Isolation**: Each daemon instance renders requests serially. To scale concurrent rendering, launch multiple distinct daemon instances using unique `name` identifiers.
114
186
 
115
187
  ---
116
188
 
@@ -120,10 +192,10 @@ await native.stop();
120
192
 
121
193
  ```ts
122
194
  interface ScreenshotOptions {
123
- /** Target URL (http/https/file) or local file path */
195
+ /** Target URL (http/https/file/data) or local file path */
124
196
  file: string;
125
197
 
126
- /** Output format (default: 'png') */
198
+ /** Output image format (default: 'png') */
127
199
  type?: 'png' | 'jpeg' | 'webp';
128
200
 
129
201
  /** Viewport dimensions (default: 1280x720) */
@@ -132,40 +204,224 @@ interface ScreenshotOptions {
132
204
  /** Capture full scrollable document */
133
205
  fullPage?: boolean;
134
206
 
135
- /** Capture element bounding box matching selector */
207
+ /** Capture bounding box of matching CSS selector (resolved via Document::querySelector) */
136
208
  selector?: string;
137
209
 
138
- /** Capture specific rectangular crop */
210
+ /** Capture specific rectangular coordinate region */
139
211
  clip?: { x: number; y: number; width: number; height: number };
140
212
 
141
- /** Image compression quality: 1-100 (jpeg and webp only, default: 90) */
213
+ /** Compression quality: 1-100 (jpeg and webp only, default: 90) */
142
214
  quality?: number;
143
215
 
144
216
  /** Device scale factor: 0.01 - 8.0 (default: 1.0) */
145
217
  scale?: number;
146
218
 
147
- /** Preserve transparent background (png/webp only) */
219
+ /** Preserve transparent background (png and webp only; jpeg has no alpha channel) */
148
220
  omitBackground?: boolean;
149
221
 
150
- /** Output file destination path (returns null if specified) */
222
+ /** Output file path. If specified, writes directly to disk and image returns null */
151
223
  path?: string;
152
224
 
153
- /** Navigation & wait options */
225
+ /** Page navigation options */
154
226
  pageGotoParams?: {
227
+ /** Timeout in milliseconds (default: 30000) */
155
228
  timeout?: number;
229
+ /**
230
+ * load: wait until DOM is parsed and basic resources loaded (default)
231
+ * networkidle: additionally wait for 500ms window with 0 in-flight requests (for late WebFonts/CSS)
232
+ */
156
233
  waitUntil?: 'load' | 'networkidle';
157
234
  };
158
235
 
159
- /** Allow document to access local file:// resources (default: false) */
236
+ /** Allow document to read local file:// subresources (default: false) */
160
237
  allowFileAccess?: boolean;
161
238
 
162
- /** Auto retry count on failure (default: 0) */
163
- retry?: number;
239
+ /** HTTP cache strategy (default: 'default') */
240
+ cache?: 'default' | 'reload' | 'no-store' | 'only-if-cached';
241
+
242
+ /** Extra request headers, sent to same-origin URLs only */
243
+ headers?: Record<string, string>;
244
+ }
245
+ ```
246
+
247
+ > **Note**: `fullPage`, `selector`, and `clip` are mutually exclusive. Specifying more than one will throw a validation error.
248
+
249
+ #### `ScreenshotResult` Return Structure
250
+
251
+ ```ts
252
+ interface ScreenshotResult {
253
+ /** Encoded image buffer; null when path parameter was specified (written directly to disk) */
254
+ image: Buffer | null;
255
+ /** Detailed capture timing and network statistics */
256
+ stats: CaptureStats;
257
+ }
258
+ ```
259
+
260
+ #### Parameter Details
261
+
262
+ - **`file` Input Schemes**:
263
+ - Remote URLs: `https://example.com`
264
+ - Local Paths: `./template.html`, `/absolute/path/index.html`, `file:///...`
265
+ - Inline HTML Strings: `data:text/html;charset=utf-8,<h1>Hello</h1>`
266
+ - **`cache` Strategies** (follows Web Fetch API, applies to document and subresources):
267
+ - `default`: Standard HTTP caching behavior.
268
+ - `reload`: Bypasses existing cache, fetches fresh resources from server, and updates cache.
269
+ - `no-store`: Completely disables reading and writing to cache.
270
+ - `only-if-cached`: Retrieves cached entries only; throws an immediate error if cache misses (no network request).
271
+ - **`headers` Scope**:
272
+ - Strictly adheres to Same-Origin policy.
273
+ - Passed headers (such as `Authorization` or `Cookie`) are sent only to the target site, and never forwarded to cross-origin external stylesheets or fonts.
274
+
275
+ ---
276
+
277
+ ### `StartOptions`
278
+
279
+ ```ts
280
+ interface StartOptions {
281
+ /**
282
+ * HTTP disk cache directory. Defaults to a project-specific directory
283
+ * under ~/.shotium/cache; set to null to disable disk caching.
284
+ */
285
+ cacheDir?: string | null;
286
+
287
+ /** Maximum size ceiling for cache directory in bytes (default: 256 MB) */
288
+ cacheMaxBytes?: number;
289
+
290
+ /** Custom User-Agent string */
291
+ userAgent?: string;
292
+
293
+ /** Directory containing shotium_data.pak and shotium_strings.pak */
294
+ resourceDir?: string;
295
+ }
296
+ ```
297
+
298
+ #### `StartResult` Return Structure
299
+
300
+ ```ts
301
+ interface StartResult {
302
+ /** Whether the runtime instance is currently started */
303
+ running: boolean;
304
+ /** Active cache directory path (null when caching is disabled) */
305
+ cacheDir: string | null;
306
+ /** Whether cache directory was opened successfully and is active */
307
+ cacheActive: boolean;
308
+ }
309
+ ```
310
+
311
+ ---
312
+
313
+ ### `CaptureStats`
314
+
315
+ Every capture operation returns detailed timing breakdown and network statistics:
316
+
317
+ ```ts
318
+ interface CaptureStats {
319
+ requests: number; // Total resources requested by document (including itself)
320
+ fromCache: number; // Number of resource bodies served from HTTP disk cache
321
+ failed: number; // Number of failed subresource requests
322
+ bytes: number; // Total decoded body bytes (not transfer size)
323
+ httpStatus: number; // Main document HTTP status code (0 for file: / data: URLs)
324
+ finalUrl: string; // Final URL after resolving redirects
325
+ timing: {
326
+ fetch: number; // Document retrieval: DNS, TCP, TLS, and round-trip latency
327
+ render: number; // Rendering: parsing, subresources, styles, layout, paint
328
+ encode: number; // Image encoding duration
329
+ total: number; // Total wall-clock duration
330
+ };
331
+ }
332
+ ```
333
+
334
+ #### Metrics & Timing Breakdown
335
+
336
+ - **Network Latency Breakdown**: For cold `https:` requests, `timing.fetch` represents the majority of total latency. Cache hits reduce fetch latency to sub-millisecond levels:
337
+
338
+ | Scenario | `fetch` Latency | `render` Latency | `total` Latency |
339
+ |---|---|---|---|
340
+ | **Local file / Inline HTML** (`file:` / `data:`) | 0.2 ms | 20 ms | 25 ms |
341
+ | **HTTPS (Cold request)** | 321.1 ms | 16 ms | 350 ms |
342
+ | **HTTPS (Cache hit)** | 0.7 ms | 18 ms | 31 ms |
343
+
344
+ - **`fromCache` Semantics**: Indicates that the response body was served from disk. If an entry is revalidated via conditional request (304 Not Modified), network round-trip latency is still incurred while saving body payload transfer.
345
+ - **Failure Diagnostics (`error.stats`)**: When a capture fails or times out, the error object includes `error.stats` containing network metrics prior to the error.
346
+
347
+ ---
348
+
349
+ ### `daemon` Module
350
+
351
+ Manages resident daemon instances and IPC connections:
352
+
353
+ ```ts
354
+ import { daemon } from '@shotkit/shotium';
355
+
356
+ // 1. Establish IPC connection
357
+ const client = await daemon.connect({
358
+ name: 'custom-pool', // Optional: daemon naming isolation
359
+ idleTimeoutMs: 300000, // Idle exit timeout when no connections active (default 5 min; 0 = never)
360
+ prewarm: true, // Pre-renders a blank page upon startup to warm up engine (default true)
361
+ });
362
+
363
+ // 2. Client instance methods
364
+ const res = await client.screenshot({ file: 'https://example.com' });
365
+ const status = await client.status();
366
+ await client.releaseMemory({ releaseWorkingSet: false });
367
+ client.close();
368
+
369
+ // 3. Global daemon management
370
+ const info: DaemonStatus = await daemon.status();
371
+ await daemon.stop();
372
+ ```
373
+
374
+ #### `DaemonStatus` Structure
375
+
376
+ ```ts
377
+ interface DaemonStatus {
378
+ pid: number; // Daemon OS process ID
379
+ endpoint: string; // IPC socket path / named pipe
380
+ cacheDir: string | null; // Active disk cache directory
381
+ warm: boolean; // Whether engine pre-warm has completed
382
+ uptimeMs: number; // Uptime in milliseconds
383
+ connections: number; // Current active client connections
384
+ inFlight: number; // Requests currently being rendered
385
+ served: number; // Total completed requests
386
+ idleTimeoutMs: number; // Configured idle timeout
387
+ version: string; // Engine version
164
388
  }
165
389
  ```
166
390
 
167
391
  ---
168
392
 
393
+ ### `cache` Module
394
+
395
+ Manages persistent HTTP disk caching across processes and engine lifecycles:
396
+
397
+ ```ts
398
+ import { cache } from '@shotkit/shotium';
399
+
400
+ // 1. Directory query
401
+ cache.getDir(); // Current project's cache directory (absolute path)
402
+ cache.getDirs({ target: 'all' }); // List all shotium cache directories on the system
403
+
404
+ // 2. List cached file metadata
405
+ const files = await cache.getFiles(); // [{ url, lastUsedMs, bytes, dir }, ...]
406
+
407
+ // 3. Evict cache and inspect result
408
+ const result: CacheClearResult = await cache.clear({
409
+ glob: ['https://example.com/**'], // Evict by URL glob pattern
410
+ maxAge: 86400, // Evict entries unused for > 24h (seconds)
411
+ maxSize: 64 * 1024 * 1024, // Evict via LRU to under 64 MB
412
+ });
413
+
414
+ console.log(`Removed: ${result.removed}, Bytes before: ${result.bytesBefore}, Bytes after: ${result.bytesAfter}`);
415
+ ```
416
+
417
+ #### Cache Design & Guidelines
418
+
419
+ - **Directory Structure**: Stored under `~/.shotium/cache/<project-hash>`, avoiding ephemeral `/tmp` directories that are automatically purged on reboot.
420
+ - **Index Integrity**: Cache files are stored using URL-key hashes with an internal index file. Cache eviction must be performed via the `cache` API rather than manual file deletion to preserve index consistency.
421
+ - **Cross-Process Sharing**: Multiple processes may concurrently access the same cache directory safely.
422
+
423
+ ---
424
+
169
425
  ## License
170
426
 
171
427
  BSD-3-Clause. See [LICENSE](https://github.com/sj817/shotium/blob/main/LICENSE) for details.
@@ -1,6 +1,8 @@
1
- import { a as resolveStartOptions, i as endpointFor, n as FrameReader, r as encodeFrame, t as Pool } from "./pool-BSgS6vkr.js";
2
- import { EventEmitter } from "node:events";
1
+ import { d as resolveStartOptions, i as Engine, n as encodeFrame, r as endpointFor, t as FrameReader } from "./protocol-BTeWJDOa.js";
3
2
  import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { EventEmitter } from "node:events";
4
6
  import net from "node:net";
5
7
 
6
8
  //#region src/lib/daemon.ts
@@ -12,15 +14,13 @@ const VERSION = (() => {
12
14
  return "0.0.0";
13
15
  }
14
16
  })();
15
- const SUPERVISOR_MARGIN_MS = 1e4;
16
- const DEFAULT_TIMEOUT_MS = 3e4;
17
17
  const DEFAULT_IDLE_TIMEOUT_MS = 3e5;
18
18
  var Daemon = class extends EventEmitter {
19
19
  options;
20
20
  endpointPath;
21
21
  idleTimeoutMs;
22
22
  prewarmOnStart;
23
- pool = null;
23
+ engine = new Engine();
24
24
  server = null;
25
25
  sockets = /* @__PURE__ */ new Set();
26
26
  inFlight = 0;
@@ -47,25 +47,12 @@ var Daemon = class extends EventEmitter {
47
47
  return this.warmed;
48
48
  }
49
49
  async listen() {
50
- const pool = new Pool(this.options);
51
- this.pool = pool;
52
- for (const event of [
53
- "exit",
54
- "crash",
55
- "timeout",
56
- "worker-restart",
57
- "worker-error",
58
- "stderr"
59
- ]) pool.on(event, (payload) => this.emit(event, payload));
60
- pool.start();
50
+ this.engine.start(this.options);
61
51
  this.server = net.createServer((socket) => this.accept(socket));
62
52
  this.server.on("error", (error) => this.emit("error", error));
63
53
  await this.bind();
64
54
  this.armIdleTimer();
65
- this.emit("ready", {
66
- endpoint: this.endpointPath,
67
- workers: this.options.workers
68
- });
55
+ this.emit("ready", { endpoint: this.endpointPath });
69
56
  if (this.prewarmOnStart) await this.prewarm();
70
57
  return this;
71
58
  }
@@ -112,29 +99,30 @@ var Daemon = class extends EventEmitter {
112
99
  }
113
100
  }
114
101
  async prewarm() {
115
- const blank = "data:text/html,<!doctype html><title>shotium</title>";
116
- await Promise.all(Array.from({ length: this.options.workers }, () => {
117
- return this.pool.submit({
102
+ const blank = path.join(os.tmpdir(), `shotium-prewarm-${process.pid}.html`);
103
+ try {
104
+ fs.writeFileSync(blank, "<!doctype html><title>shotium</title><p>shotium");
105
+ await this.engine.capture({
118
106
  file: blank,
119
107
  width: 16,
120
108
  height: 16
121
- }, {
122
- timeout: 4e4,
123
- retry: 1
124
- }).catch(() => null);
125
- }));
126
- this.warmed = true;
127
- this.emit("warm", { workers: this.options.workers });
109
+ });
110
+ this.warmed = true;
111
+ } catch (error) {
112
+ this.emit("error", error);
113
+ } finally {
114
+ fs.rmSync(blank, { force: true });
115
+ }
116
+ this.emit("warm", { warm: this.warmed });
128
117
  }
129
118
  status() {
130
119
  return {
131
120
  ok: true,
132
121
  pid: process.pid,
133
122
  endpoint: this.endpointPath,
134
- binary: this.options.binary,
135
- workers: this.options.workers,
136
123
  cacheDir: this.options.cacheDir,
137
- args: this.options.args,
124
+ userAgent: this.options.userAgent,
125
+ resourceDir: this.options.resourceDir,
138
126
  warm: this.warmed,
139
127
  uptimeMs: Date.now() - this.startedAt,
140
128
  connections: this.sockets.size,
@@ -208,30 +196,27 @@ var Daemon = class extends EventEmitter {
208
196
  return;
209
197
  }
210
198
  const request = message.request || {};
211
- const timeout = (typeof message.timeout === "number" ? message.timeout : DEFAULT_TIMEOUT_MS) + SUPERVISOR_MARGIN_MS;
212
- const retry = typeof message.retry === "number" ? message.retry : 0;
213
199
  this.inFlight += 1;
214
200
  this.armIdleTimer();
215
201
  this.emit("request", {
216
202
  id,
217
203
  file: request.file
218
204
  });
219
- this.pool.submit(request, {
220
- timeout,
221
- retry
222
- }).then((result) => {
205
+ this.engine.capture(request).then(({ image, stats }) => {
223
206
  this.served += 1;
224
207
  this.reply(socket, {
225
208
  id,
226
209
  ok: true,
227
- bytes: result.image ? result.image.length : 0,
228
- path: result.header ? result.header.path : void 0
229
- }, result.image);
210
+ bytes: image ? image.length : 0,
211
+ path: request.path,
212
+ stats
213
+ }, image);
230
214
  }).catch((error) => {
231
215
  this.reply(socket, {
232
216
  id,
233
217
  ok: false,
234
- error: String(error.message || error)
218
+ error: String(error.message || error),
219
+ stats: error.stats
235
220
  });
236
221
  }).finally(() => {
237
222
  this.inFlight -= 1;
@@ -267,7 +252,7 @@ var Daemon = class extends EventEmitter {
267
252
  for (const socket of this.sockets) socket.destroy();
268
253
  this.sockets.clear();
269
254
  await new Promise((resolve) => this.server.close(() => resolve()));
270
- await this.pool.stop();
255
+ await this.engine.dispose();
271
256
  this.emit("close", {});
272
257
  }
273
258
  };