@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 +321 -65
- package/dist/daemon_main.js +29 -44
- package/dist/daemon_main.js.map +1 -1
- package/dist/index.d.ts +551 -57
- package/dist/index.js +425 -73
- package/dist/index.js.map +1 -1
- package/dist/protocol-BTeWJDOa.js +474 -0
- package/dist/protocol-BTeWJDOa.js.map +1 -0
- package/native/binding.cc +184 -3
- package/package.json +7 -11
- package/src/index.ts +179 -89
- package/src/lib/binding.ts +110 -0
- package/src/lib/cache.ts +323 -0
- package/src/lib/client.ts +32 -17
- package/src/lib/config.ts +113 -61
- package/src/lib/daemon.ts +97 -62
- package/src/lib/endpoint.ts +19 -12
- package/src/lib/engine.ts +356 -0
- package/src/lib/platform.ts +1 -7
- package/src/lib/request.ts +14 -16
- package/src/types.ts +216 -42
- package/dist/native.d.ts +0 -66
- package/dist/native.js +0 -127
- package/dist/native.js.map +0 -1
- package/dist/platform-DU8DYqmA.js +0 -32
- package/dist/platform-DU8DYqmA.js.map +0 -1
- package/dist/pool-BSgS6vkr.js +0 -356
- package/dist/pool-BSgS6vkr.js.map +0 -1
- package/dist/request-qZXS3N9f.js +0 -43
- package/dist/request-qZXS3N9f.js.map +0 -1
- package/dist/types-x9HtkzeE.d.ts +0 -156
- package/src/lib/pool.ts +0 -243
- package/src/lib/worker.ts +0 -220
- package/src/native.ts +0 -234
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,418 @@
|
|
|
1
|
-
import { a as PageGotoParams, c as StartOptions, l as Viewport, n as DaemonOptions, o as PurgeOptions, r as DaemonStatus, s as ScreenshotOptions, t as Clip, u as WorkerEvent } from "./types-x9HtkzeE.js";
|
|
2
1
|
import { EventEmitter } from "node:events";
|
|
3
2
|
import net from "node:net";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/** A region of the document, in CSS pixels. */
|
|
5
|
+
interface Clip {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
interface PageGotoParams {
|
|
12
|
+
/** Milliseconds before the load is abandoned. Default 30000. */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
/**
|
|
15
|
+
* `load` waits for parsing to finish, the load event to fire and every
|
|
16
|
+
* request to complete. `networkidle` additionally waits for a 500ms window
|
|
17
|
+
* with nothing in flight, which matters for documents that keep fetching
|
|
18
|
+
* after the load event -- CSS that pulls in more CSS, or a font a late style
|
|
19
|
+
* change brought in.
|
|
20
|
+
*/
|
|
21
|
+
waitUntil?: 'load' | 'networkidle';
|
|
22
|
+
}
|
|
23
|
+
/** The viewport the document is laid out in. */
|
|
24
|
+
interface Viewport {
|
|
25
|
+
/** CSS pixels. Default 1280. */
|
|
26
|
+
width?: number;
|
|
27
|
+
/** CSS pixels. Default 720. */
|
|
28
|
+
height?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What a capture may do with the HTTP cache, spelled the way `fetch` spells
|
|
32
|
+
* it.
|
|
33
|
+
*
|
|
34
|
+
* - `default`: ordinary HTTP semantics. A fresh entry is used without asking,
|
|
35
|
+
* a stale one is revalidated, and the response updates the cache.
|
|
36
|
+
* - `reload`: read nothing, write everything -- the browser's reload button.
|
|
37
|
+
* The next capture is fast again.
|
|
38
|
+
* - `no-store`: neither read nor write. For a page that should not be left on
|
|
39
|
+
* this machine's disk, which an authenticated one usually should not.
|
|
40
|
+
* - `only-if-cached`: the network may not be touched and a miss is an error.
|
|
41
|
+
* Useful for a deterministic re-render of something already fetched.
|
|
42
|
+
*/
|
|
43
|
+
type CacheMode = 'default' | 'reload' | 'no-store' | 'only-if-cached';
|
|
44
|
+
/** Where the milliseconds went. */
|
|
45
|
+
interface CaptureTiming {
|
|
46
|
+
/**
|
|
47
|
+
* Fetching the top-level document. For a cold `https:` URL this is DNS, TCP,
|
|
48
|
+
* TLS and a round trip, and it is routinely larger than everything below --
|
|
49
|
+
* which is the single most useful thing this object says.
|
|
50
|
+
*/
|
|
51
|
+
fetch: number;
|
|
52
|
+
/** Parse, subresources, style, layout, prepaint, paint. */
|
|
53
|
+
render: number;
|
|
54
|
+
encode: number;
|
|
55
|
+
/** Wall clock for the whole capture, so the three above can be checked. */
|
|
56
|
+
total: number;
|
|
57
|
+
}
|
|
58
|
+
/** What one capture cost, and where its bytes came from. */
|
|
59
|
+
interface CaptureStats {
|
|
60
|
+
/** Every resource the document asked for, itself included. */
|
|
61
|
+
requests: number;
|
|
62
|
+
/**
|
|
63
|
+
* Answered from the HTTP cache -- the body came from disk.
|
|
64
|
+
*
|
|
65
|
+
* Not the same as "no network was touched". A stale entry that can be
|
|
66
|
+
* revalidated costs a conditional request and a 304, and counts here too;
|
|
67
|
+
* what the cache saved is the download rather than the round trip. That is
|
|
68
|
+
* why `timing.fetch` can be tens of milliseconds with this set.
|
|
69
|
+
*/
|
|
70
|
+
fromCache: number;
|
|
71
|
+
failed: number;
|
|
72
|
+
/** Decoded body bytes, summed -- not the transfer size. */
|
|
73
|
+
bytes: number;
|
|
74
|
+
/** The document's own status. 0 for a `file:` URL. */
|
|
75
|
+
httpStatus: number;
|
|
76
|
+
/** After redirects, which is what relative URLs resolved against. */
|
|
77
|
+
finalUrl: string;
|
|
78
|
+
timing: CaptureTiming;
|
|
79
|
+
}
|
|
80
|
+
/** One screenshot, and what taking it cost. */
|
|
81
|
+
interface ScreenshotResult {
|
|
82
|
+
/**
|
|
83
|
+
* The encoded image, or `null` when `path` was given: the engine wrote the
|
|
84
|
+
* file itself and there is nothing left to hand back.
|
|
85
|
+
*/
|
|
86
|
+
image: Buffer | null;
|
|
87
|
+
stats: CaptureStats;
|
|
88
|
+
}
|
|
89
|
+
interface ScreenshotOptions {
|
|
90
|
+
/** An http/https/file URL, or a local path. */
|
|
91
|
+
file: string;
|
|
92
|
+
/** Default `png`. */
|
|
93
|
+
type?: 'png' | 'jpeg' | 'webp';
|
|
94
|
+
/** Capture the whole document rather than the viewport. */
|
|
95
|
+
fullPage?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Capture the box of the first element matching this CSS selector. Resolved
|
|
98
|
+
* inside the renderer with Document::querySelector -- there is no JavaScript
|
|
99
|
+
* engine, so nothing is injected into the page.
|
|
100
|
+
*/
|
|
101
|
+
selector?: string;
|
|
102
|
+
/** 1-100, `jpeg` and `webp` only. Default 90. */
|
|
103
|
+
quality?: number;
|
|
104
|
+
/** Device scale factor, 0.01-8. Default 1. */
|
|
105
|
+
scale?: number;
|
|
106
|
+
/**
|
|
107
|
+
* Keep the alpha channel instead of painting the page's white backdrop.
|
|
108
|
+
* Rejected for `jpeg`, which has no alpha channel.
|
|
109
|
+
*/
|
|
110
|
+
omitBackground?: boolean;
|
|
111
|
+
/** Write the image here instead of returning it, saving a round trip. */
|
|
112
|
+
path?: string;
|
|
113
|
+
pageGotoParams?: PageGotoParams;
|
|
114
|
+
/** A region of the document, in CSS pixels. */
|
|
115
|
+
clip?: Clip;
|
|
116
|
+
/** The viewport the document is laid out in. */
|
|
117
|
+
viewport?: Viewport;
|
|
118
|
+
/**
|
|
119
|
+
* Let the document read `file:` subresources. Off by default: a library does
|
|
120
|
+
* not get to decide for its caller that a document may read the filesystem it
|
|
121
|
+
* is rendered on.
|
|
122
|
+
*/
|
|
123
|
+
allowFileAccess?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* What this capture may do with the HTTP cache. Default `default`.
|
|
126
|
+
*
|
|
127
|
+
* It applies to the subresources as well as the document: a `reload` that
|
|
128
|
+
* refreshed the HTML and reused yesterday's stylesheet would be a confusing
|
|
129
|
+
* thing to have asked for.
|
|
130
|
+
*/
|
|
131
|
+
cache?: CacheMode;
|
|
132
|
+
/**
|
|
133
|
+
* Extra request headers, sent with the document and with the subresources
|
|
134
|
+
* that are same-origin with it.
|
|
135
|
+
*
|
|
136
|
+
* Same-origin is the whole rule and it is not configurable. A caller passing
|
|
137
|
+
* `Authorization` or `Cookie` means it for the site being photographed; a
|
|
138
|
+
* page that pulls a script from a CDN must not have the credential
|
|
139
|
+
* forwarded there.
|
|
140
|
+
*/
|
|
141
|
+
headers?: Record<string, string>;
|
|
142
|
+
}
|
|
143
|
+
interface StartOptions {
|
|
144
|
+
/**
|
|
145
|
+
* Root of the HTTP disk cache. `null` disables caching entirely.
|
|
146
|
+
*
|
|
147
|
+
* The default is a per-project directory under the system temporary
|
|
148
|
+
* directory -- see `cache.getDir()`. Caching is on by default because the
|
|
149
|
+
* alternative turned out to be worse: without it every capture of an
|
|
150
|
+
* `https:` URL pays for DNS, TLS and a round trip, which for a small page is
|
|
151
|
+
* most of the time the call takes and all of the time the caller did not
|
|
152
|
+
* expect to spend.
|
|
153
|
+
*/
|
|
154
|
+
cacheDir?: string | null;
|
|
155
|
+
/**
|
|
156
|
+
* Ceiling on the cache directory, in bytes. Default 256 MB.
|
|
157
|
+
*
|
|
158
|
+
* Zero is not "unlimited" -- it hands the decision to the backend, which
|
|
159
|
+
* sizes itself against the volume's free space. That was a reasonable
|
|
160
|
+
* default when every user of the cache had named a directory on purpose; for
|
|
161
|
+
* one that appears by default under `~/.shotium` because somebody imported a
|
|
162
|
+
* library, a number somebody chose is better than a number nobody did.
|
|
163
|
+
*/
|
|
164
|
+
cacheMaxBytes?: number;
|
|
165
|
+
/** Overrides the built-in user agent string. */
|
|
166
|
+
userAgent?: string;
|
|
167
|
+
/**
|
|
168
|
+
* Where `shotium_data.pak` and `shotium_strings.pak` are. Defaults to the
|
|
169
|
+
* directory the engine was loaded from, which is where they ship.
|
|
170
|
+
*/
|
|
171
|
+
resourceDir?: string;
|
|
172
|
+
}
|
|
173
|
+
/** What `start()` reports about the engine it brought up. */
|
|
174
|
+
interface StartResult {
|
|
175
|
+
/**
|
|
176
|
+
* Whether this lifecycle is started.
|
|
177
|
+
*
|
|
178
|
+
* A process has at most one engine, so `false` here does not mean there is
|
|
179
|
+
* nothing running -- it means this `Runtime` is stood down. `cacheDir` below
|
|
180
|
+
* is still answered from the engine, because a stood-down engine keeps its
|
|
181
|
+
* cache directory and reporting `null` would say the cache had gone away
|
|
182
|
+
* when what went away was the willingness to render.
|
|
183
|
+
*/
|
|
184
|
+
running: boolean;
|
|
185
|
+
/** The directory in use, or `null` when caching is off. */
|
|
186
|
+
cacheDir: string | null;
|
|
187
|
+
/**
|
|
188
|
+
* Whether that directory is actually being cached into.
|
|
189
|
+
*
|
|
190
|
+
* A directory that cannot be created or written to costs nothing visible:
|
|
191
|
+
* the engine renders exactly as well without a cache, only slower, and every
|
|
192
|
+
* capture pays for the network again for a reason nothing reports. `false`
|
|
193
|
+
* with a `cacheDir` set means the open failed; `false` with `cacheDir: null`
|
|
194
|
+
* means no cache was asked for.
|
|
195
|
+
*
|
|
196
|
+
* It is not about sharing. Several processes may use one directory and all
|
|
197
|
+
* of them cache -- the backend takes no cross-process lock -- so `true` in
|
|
198
|
+
* two processes at once is the ordinary answer.
|
|
199
|
+
*/
|
|
200
|
+
cacheActive: boolean;
|
|
201
|
+
}
|
|
202
|
+
interface DaemonOptions extends StartOptions {
|
|
203
|
+
/**
|
|
204
|
+
* Address the daemon by name instead of by configuration. Without it the
|
|
205
|
+
* endpoint is a hash of `cacheDir`, `userAgent` and `resourceDir`, so a
|
|
206
|
+
* client never attaches to a daemon that renders with something other than
|
|
207
|
+
* what it asked for.
|
|
208
|
+
*/
|
|
209
|
+
name?: string;
|
|
210
|
+
/** The pipe or socket to use, overriding both the name and the hash. */
|
|
211
|
+
endpoint?: string;
|
|
212
|
+
/**
|
|
213
|
+
* Exit after this long with no connections and nothing rendering. Default
|
|
214
|
+
* 300000; `0` never exits.
|
|
215
|
+
*/
|
|
216
|
+
idleTimeoutMs?: number;
|
|
217
|
+
/**
|
|
218
|
+
* Render one throwaway document at startup, so the first real request does
|
|
219
|
+
* not pay for whatever the engine initialises lazily. Default true.
|
|
220
|
+
*/
|
|
221
|
+
prewarm?: boolean;
|
|
222
|
+
/** Fail instead of starting a daemon when none is listening. */
|
|
223
|
+
spawn?: boolean;
|
|
224
|
+
/** Where a spawned daemon's diagnostics go. Default `$SHOTIUM_DAEMON_LOG`. */
|
|
225
|
+
logFile?: string;
|
|
226
|
+
/** How long to wait for a daemon this process started to bind. */
|
|
227
|
+
startTimeoutMs?: number;
|
|
228
|
+
}
|
|
229
|
+
interface DaemonStatus {
|
|
230
|
+
ok?: boolean;
|
|
231
|
+
running?: boolean;
|
|
232
|
+
spawned?: boolean;
|
|
233
|
+
pid: number;
|
|
234
|
+
endpoint: string;
|
|
235
|
+
cacheDir: string | null;
|
|
236
|
+
userAgent?: string;
|
|
237
|
+
resourceDir?: string;
|
|
238
|
+
/** The engine has rendered at least once. */
|
|
239
|
+
warm: boolean;
|
|
240
|
+
uptimeMs: number;
|
|
241
|
+
connections: number;
|
|
242
|
+
inFlight: number;
|
|
243
|
+
served: number;
|
|
244
|
+
idleTimeoutMs: number;
|
|
245
|
+
version: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Options for `releaseMemory()`.
|
|
249
|
+
*
|
|
250
|
+
* Named for what it does rather than for `purge`, which it was called until
|
|
251
|
+
* 0.3. With `cache.clear()` in the API the old name reads as though it clears
|
|
252
|
+
* the cache, and it does not: it hands back blink's heap, skia's caches and
|
|
253
|
+
* PartitionAlloc's free lists, all of which the engine rebuilds on demand.
|
|
254
|
+
* Nothing on disk is touched.
|
|
255
|
+
*/
|
|
256
|
+
interface ReleaseMemoryOptions {
|
|
257
|
+
/**
|
|
258
|
+
* Also ask the OS to take the engine's pages back. The next screenshot pays
|
|
259
|
+
* them back in soft page faults -- a few milliseconds -- so this is for when
|
|
260
|
+
* there may not be a next one soon.
|
|
261
|
+
*/
|
|
262
|
+
releaseWorkingSet?: boolean;
|
|
263
|
+
}
|
|
264
|
+
/** Which cache directory an operation is about. */
|
|
265
|
+
interface CacheTarget {
|
|
266
|
+
/**
|
|
267
|
+
* `current` (the default) is this project's directory, `all` is every
|
|
268
|
+
* directory shotium has created under the shared root -- `~/.shotium/cache`
|
|
269
|
+
* -- and a string is either an absolute path or one project hash as
|
|
270
|
+
* `getDir()` reports it.
|
|
271
|
+
*
|
|
272
|
+
* The absolute path is there because `start({cacheDir})` accepts any
|
|
273
|
+
* directory: without it, a caller who chose their own cache would have the
|
|
274
|
+
* one cache these methods could not see.
|
|
275
|
+
*
|
|
276
|
+
* `all` exists because the directories are per-project by default, so
|
|
277
|
+
* "clear shotium's caches" is otherwise something a caller cannot express
|
|
278
|
+
* without already knowing where the other projects were.
|
|
279
|
+
*/
|
|
280
|
+
target?: 'current' | 'all' | (string & {});
|
|
281
|
+
}
|
|
282
|
+
/** One resource the cache is holding. */
|
|
283
|
+
interface CacheEntry {
|
|
284
|
+
/** The resource, not the backend's key -- see `cache.getFiles()`. */
|
|
285
|
+
url: string;
|
|
286
|
+
/** Milliseconds since the Unix epoch. */
|
|
287
|
+
lastUsedMs: number;
|
|
288
|
+
bytes: number;
|
|
289
|
+
/** Which cache directory it was found in. */
|
|
290
|
+
dir: string;
|
|
291
|
+
}
|
|
292
|
+
interface CacheClearOptions extends CacheTarget {
|
|
293
|
+
/**
|
|
294
|
+
* Glob patterns matched against entry URLs -- not against filenames, which
|
|
295
|
+
* are hashes and would match nothing anybody would think to write.
|
|
296
|
+
*
|
|
297
|
+
* Supports `*` (within a path segment), `**` (across segments), `?` and
|
|
298
|
+
* `{a,b}`. Matching happens here rather than in the engine: the entries come
|
|
299
|
+
* back first, the patterns are applied to their URLs, and the ones that
|
|
300
|
+
* matched are what gets removed.
|
|
301
|
+
*/
|
|
302
|
+
glob?: string[];
|
|
303
|
+
/**
|
|
304
|
+
* Remove entries not used for this many seconds. `0`, the default, means no
|
|
305
|
+
* age limit.
|
|
306
|
+
*/
|
|
307
|
+
maxAge?: number;
|
|
308
|
+
/**
|
|
309
|
+
* Evict least-recently-used entries until the directory is at or below this
|
|
310
|
+
* many bytes. `0`, the default, means no size limit.
|
|
311
|
+
*/
|
|
312
|
+
maxSize?: number;
|
|
313
|
+
}
|
|
314
|
+
interface CacheClearResult {
|
|
315
|
+
/**
|
|
316
|
+
* How many entries went. `-1` when the whole directory was dropped in one
|
|
317
|
+
* operation, which the backend does without counting them.
|
|
318
|
+
*/
|
|
319
|
+
removed: number;
|
|
320
|
+
bytesBefore: number;
|
|
321
|
+
bytesAfter: number;
|
|
322
|
+
/** Which directory this result is for. */
|
|
323
|
+
dir: string;
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/lib/binding.d.ts
|
|
327
|
+
/**
|
|
328
|
+
* The engine handle the addon hands back. Opaque on purpose: everything that
|
|
329
|
+
* can be done with it is a call on the binding below.
|
|
330
|
+
*/
|
|
331
|
+
type Engine = unknown;
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/lib/cache.d.ts
|
|
334
|
+
/**
|
|
335
|
+
* The cache, from the outside.
|
|
336
|
+
*
|
|
337
|
+
* Every method takes the engine handle if there is one, and that is not an
|
|
338
|
+
* optimisation. Within one process a cache directory has one backend: asking
|
|
339
|
+
* for a second one on the directory the engine holds waits for the engine's to
|
|
340
|
+
* go away, which it will not do while the engine is up. Borrowing is the only
|
|
341
|
+
* thing that returns.
|
|
342
|
+
*
|
|
343
|
+
* "If there is one" means the process, not the lifecycle. `stop()` stands the
|
|
344
|
+
* engine down without tearing it down, so an engine that has been stopped
|
|
345
|
+
* still holds its directory and still has to be borrowed from -- which is also
|
|
346
|
+
* what makes the cache survive a stop, and outlive one, and be worth having.
|
|
347
|
+
*
|
|
348
|
+
* Across processes there is no such constraint -- several of them may share a
|
|
349
|
+
* directory and all of them cache.
|
|
350
|
+
*
|
|
351
|
+
* The engine is fetched through a callback rather than held, because this
|
|
352
|
+
* object is built once at import time and the engine comes and goes.
|
|
353
|
+
*/
|
|
354
|
+
declare class Cache {
|
|
355
|
+
private readonly engineHandle;
|
|
356
|
+
constructor(engineHandle: () => Engine | null);
|
|
357
|
+
/**
|
|
358
|
+
* This project's cache directory, absolute and with forward slashes.
|
|
359
|
+
*
|
|
360
|
+
* It exists whether or not anything has been written to it -- the answer is
|
|
361
|
+
* "where the cache goes", not "where a cache is".
|
|
362
|
+
*/
|
|
363
|
+
getDir(options?: CacheTarget): string;
|
|
364
|
+
/** Every directory the target names. `all` can be several; the rest, one. */
|
|
365
|
+
getDirs(options?: CacheTarget): string[];
|
|
366
|
+
/**
|
|
367
|
+
* What the cache is holding, by URL.
|
|
368
|
+
*
|
|
369
|
+
* Named `getFiles` for the operation callers reach for, and deliberately not
|
|
370
|
+
* returning filenames: the files in a cache directory are called things like
|
|
371
|
+
* `5349fbae98c6d9a1_0`, because the name is a hash of the entry key. A list
|
|
372
|
+
* of those answers no question anybody has. The URLs are what the entries
|
|
373
|
+
* are, and they are what `clear({glob})` matches against.
|
|
374
|
+
*
|
|
375
|
+
* This opens every entry to read its key and size, so it is a diagnostic
|
|
376
|
+
* rather than something to put on a request path.
|
|
377
|
+
*/
|
|
378
|
+
getFiles(options?: CacheTarget): Promise<CacheEntry[]>;
|
|
379
|
+
/**
|
|
380
|
+
* Removes what the options select. With no options, everything.
|
|
381
|
+
*
|
|
382
|
+
* The three filters compose, and `glob` is applied here rather than in the
|
|
383
|
+
* engine: the entries are listed, their URLs are matched, and the ones that
|
|
384
|
+
* matched are what the engine is asked to remove. That keeps the pattern
|
|
385
|
+
* dialect in the layer whose users have opinions about pattern dialects, and
|
|
386
|
+
* keeps the engine's interface to exact URLs.
|
|
387
|
+
*
|
|
388
|
+
* Removal goes through the cache backend, never through the filesystem.
|
|
389
|
+
* Deleting the files directly would leave the backend's index naming entries
|
|
390
|
+
* that are no longer there, and the next process to open the directory
|
|
391
|
+
* either rebuilds the index from disk or, having found it inconsistent,
|
|
392
|
+
* discards it. That is the difference between clearing a cache and
|
|
393
|
+
* corrupting one.
|
|
394
|
+
*/
|
|
395
|
+
clear(options?: CacheClearOptions): Promise<CacheClearResult[]>;
|
|
396
|
+
/**
|
|
397
|
+
* The engine handle, when there is an engine.
|
|
398
|
+
*
|
|
399
|
+
* Passed for every directory and not only the engine's own. It is never
|
|
400
|
+
* wrong to pass it -- the engine's thread can open any directory, and for
|
|
401
|
+
* the one it already has open, borrowing its backend is the only thing that
|
|
402
|
+
* returns. It is passing `null` while an engine is up that hangs, which is
|
|
403
|
+
* why this is conditional on neither the directory asked for nor on whether
|
|
404
|
+
* the engine is currently accepting captures.
|
|
405
|
+
*/
|
|
406
|
+
private handleFor;
|
|
407
|
+
}
|
|
408
|
+
//#endregion
|
|
4
409
|
//#region src/lib/client.d.ts
|
|
5
410
|
interface ClientReply {
|
|
6
411
|
id: number;
|
|
7
412
|
ok?: boolean;
|
|
8
413
|
error?: string;
|
|
9
414
|
path?: string;
|
|
415
|
+
stats?: CaptureStats;
|
|
10
416
|
}
|
|
11
417
|
interface ClientResult {
|
|
12
418
|
header: ClientReply;
|
|
@@ -26,8 +432,13 @@ declare class DaemonClient extends EventEmitter {
|
|
|
26
432
|
private settle;
|
|
27
433
|
private failAll;
|
|
28
434
|
send(message: Record<string, unknown>): Promise<ClientResult>;
|
|
29
|
-
/**
|
|
30
|
-
|
|
435
|
+
/**
|
|
436
|
+
* One screenshot, and what taking it cost.
|
|
437
|
+
*
|
|
438
|
+
* The same shape the in-process engine returns, so that moving a program
|
|
439
|
+
* between the two is an import change and nothing else.
|
|
440
|
+
*/
|
|
441
|
+
screenshot(options: ScreenshotOptions): Promise<ScreenshotResult>;
|
|
31
442
|
status(): Promise<DaemonStatus>;
|
|
32
443
|
shutdown(): Promise<{
|
|
33
444
|
ok: boolean;
|
|
@@ -36,14 +447,14 @@ declare class DaemonClient extends EventEmitter {
|
|
|
36
447
|
}
|
|
37
448
|
//#endregion
|
|
38
449
|
//#region src/index.d.ts
|
|
39
|
-
/** The five things a caller does with the resident
|
|
450
|
+
/** The five things a caller does with the resident engine. */
|
|
40
451
|
interface Daemon {
|
|
41
452
|
/** Connects, starting a daemon if none is listening. */
|
|
42
453
|
connect(options?: DaemonOptions): Promise<DaemonClient>;
|
|
43
454
|
/** One screenshot through the daemon, connection and all. */
|
|
44
455
|
screenshot(options: ScreenshotOptions & {
|
|
45
456
|
daemon?: DaemonOptions;
|
|
46
|
-
}): Promise<
|
|
457
|
+
}): Promise<ScreenshotResult>;
|
|
47
458
|
/** Starts one if it is not up, and reports what is there either way. */
|
|
48
459
|
start(options?: DaemonOptions): Promise<DaemonStatus & {
|
|
49
460
|
spawned: boolean;
|
|
@@ -57,79 +468,162 @@ interface Daemon {
|
|
|
57
468
|
endpoint: string;
|
|
58
469
|
}>;
|
|
59
470
|
}
|
|
60
|
-
interface Runtime {
|
|
61
|
-
on(event: 'ready', listener: (info: {
|
|
62
|
-
workers: number;
|
|
63
|
-
}) => void): this;
|
|
64
|
-
on(event: 'exit', listener: (event: WorkerEvent) => void): this;
|
|
65
|
-
on(event: 'crash', listener: (event: WorkerEvent) => void): this;
|
|
66
|
-
on(event: 'timeout', listener: (event: {
|
|
67
|
-
worker: number;
|
|
68
|
-
timeout: number;
|
|
69
|
-
}) => void): this;
|
|
70
|
-
on(event: 'worker-restart', listener: (event: {
|
|
71
|
-
worker: number;
|
|
72
|
-
reason: string;
|
|
73
|
-
delay: number;
|
|
74
|
-
}) => void): this;
|
|
75
|
-
/** A worker could not be started at all -- a missing or unusable binary. */
|
|
76
|
-
on(event: 'worker-error', listener: (event: {
|
|
77
|
-
worker: number;
|
|
78
|
-
error: Error;
|
|
79
|
-
}) => void): this;
|
|
80
|
-
on(event: 'stderr', listener: (event: {
|
|
81
|
-
worker: number;
|
|
82
|
-
line: string;
|
|
83
|
-
}) => void): this;
|
|
84
|
-
}
|
|
85
471
|
/**
|
|
86
|
-
* The
|
|
472
|
+
* The engine, and its lifecycle, in this process.
|
|
473
|
+
*
|
|
474
|
+
* import shotium from '@shotkit/shotium';
|
|
475
|
+
*
|
|
476
|
+
* shotium.start();
|
|
477
|
+
* const {image, stats} = await shotium.screenshot({
|
|
478
|
+
* file: 'https://example.com',
|
|
479
|
+
* });
|
|
480
|
+
* await shotium.stop();
|
|
481
|
+
*
|
|
482
|
+
* `start` and `stop` are explicit because starting Blink is the expensive part
|
|
483
|
+
* -- tens of milliseconds and a working set that stays resident -- and only
|
|
484
|
+
* the caller knows whether the next screenshot is coming in a moment or never.
|
|
485
|
+
* Neither call is required: a screenshot starts the engine if it is not up.
|
|
486
|
+
* What they buy is control over when that cost is paid, and the certainty that
|
|
487
|
+
* it has been given back.
|
|
87
488
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
489
|
+
* Neither is rationed, either. They may be called in any order and as often as
|
|
490
|
+
* a program likes: `stop()` stands the engine down and `start()` picks the
|
|
491
|
+
* same one back up, warm cache and all. What cannot happen is a *second*
|
|
492
|
+
* engine -- Blink is initialised once per process and there is no undo -- but
|
|
493
|
+
* that is a fact about how many there are, not about how many times the one
|
|
494
|
+
* may be asked for.
|
|
91
495
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
496
|
+
* The methods are on the module rather than under a `runtime` namespace, which
|
|
497
|
+
* they were until 0.3. There was never anything else to start, so the word
|
|
498
|
+
* carried nothing; and `runtime.cache` would have been the wrong place for the
|
|
499
|
+
* cache besides, since a cache directory outlives every engine that writes to
|
|
500
|
+
* it and can be read when no engine is running at all.
|
|
501
|
+
*
|
|
502
|
+
* `Runtime` is still exported for a caller who wants to own a lifecycle rather
|
|
503
|
+
* than share the module's. It is a lifecycle and not an engine: there is one
|
|
504
|
+
* engine per process, and a second Runtime that starts adopts the same one
|
|
505
|
+
* rather than building another. Parallelism is more processes, not more
|
|
506
|
+
* Runtimes.
|
|
507
|
+
*
|
|
508
|
+
* `daemon` is the same engine in a process of its own, behind a socket, for
|
|
509
|
+
* callers whose own process does not live long enough to be worth starting
|
|
510
|
+
* one.
|
|
95
511
|
*/
|
|
96
|
-
declare class Runtime
|
|
97
|
-
private
|
|
512
|
+
declare class Runtime {
|
|
513
|
+
private engine;
|
|
514
|
+
/**
|
|
515
|
+
* The HTTP cache: where it is, what is in it, and how to empty it.
|
|
516
|
+
*
|
|
517
|
+
* On the Runtime as well as on the module because a caller holding their own
|
|
518
|
+
* Runtime needs the engine handle to reach a directory that engine has open:
|
|
519
|
+
* within one process a directory has one backend, so borrowing is the only
|
|
520
|
+
* way in.
|
|
521
|
+
*/
|
|
522
|
+
readonly cache: Cache;
|
|
98
523
|
get running(): boolean;
|
|
99
524
|
/**
|
|
100
|
-
* Starts the
|
|
101
|
-
*
|
|
525
|
+
* Starts the engine, or picks the running one back up.
|
|
526
|
+
*
|
|
527
|
+
* Callable as often as you like, in any order with `stop()`; library code
|
|
528
|
+
* can call it defensively. The first call in a process builds the engine and
|
|
529
|
+
* every later one adopts it -- the same engine, the same warm cache. The one
|
|
530
|
+
* thing it will refuse is a *different* configuration: the options below are
|
|
531
|
+
* fixed when the engine is built, and there is no second build, so naming
|
|
532
|
+
* one that disagrees with what is running throws rather than rendering with
|
|
533
|
+
* a value you did not ask for.
|
|
534
|
+
*
|
|
535
|
+
* Every option has a default. `cacheDir` is the HTTP disk cache and defaults
|
|
536
|
+
* to a per-project directory under `~/.shotium/cache`, and not under the
|
|
537
|
+
* temporary directory, which is defined by not surviving. `null` turns it
|
|
538
|
+
* off. `resourceDir` is where `shotium_data.pak` and
|
|
539
|
+
* `shotium_strings.pak` are, and defaults to the directory the engine was
|
|
540
|
+
* loaded from, which is where they ship.
|
|
102
541
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
542
|
+
* The return value is worth reading once. `cacheActive: false` with a
|
|
543
|
+
* `cacheDir` set means the directory could not be opened and this engine is
|
|
544
|
+
* running without a cache -- correctly, silently, and a round trip slower on
|
|
545
|
+
* everything.
|
|
546
|
+
*/
|
|
547
|
+
start(options?: StartOptions): StartResult;
|
|
548
|
+
/** What `start()` returned, asked again. */
|
|
549
|
+
status(): StartResult;
|
|
550
|
+
/**
|
|
551
|
+
* Stands the engine down, after whatever is queued.
|
|
552
|
+
*
|
|
553
|
+
* The queue drains, the memory the engine can rebuild goes back to the OS,
|
|
554
|
+
* and `running` becomes false. Blink itself stays initialised, because there
|
|
555
|
+
* is no way to un-initialise it -- so the disk cache stays where it is, and
|
|
556
|
+
* `start()` or the next `screenshot()` picks the same engine back up.
|
|
557
|
+
*
|
|
558
|
+
* Which makes this a caller saying they are done for now rather than a
|
|
559
|
+
* destructor. It does the same work as `releaseMemory({releaseWorkingSet:
|
|
560
|
+
* true})` and additionally stops accepting captures.
|
|
107
561
|
*/
|
|
108
|
-
start(options?: StartOptions): this;
|
|
109
|
-
/** Stops every worker. The pool can be started again afterwards. */
|
|
110
562
|
stop(): Promise<void>;
|
|
111
563
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
564
|
+
* Hands back what the engine is holding but can rebuild: Blink's heap,
|
|
565
|
+
* skia's caches, PartitionAlloc's free lists. Worth calling when a batch has
|
|
566
|
+
* ended and the next one may be a while away.
|
|
567
|
+
*
|
|
568
|
+
* This is memory and nothing else. It was called `purge()` until 0.3, which
|
|
569
|
+
* next to `cache.clear()` read as though it emptied the HTTP cache; it does
|
|
570
|
+
* not touch the disk at all.
|
|
114
571
|
*/
|
|
115
|
-
|
|
572
|
+
releaseMemory(options?: ReleaseMemoryOptions): void;
|
|
573
|
+
/**
|
|
574
|
+
* Renders one screenshot, and reports what it cost.
|
|
575
|
+
*
|
|
576
|
+
* `image` is the encoded bytes, or `null` when `path` was given and the
|
|
577
|
+
* engine wrote the file itself. `stats` says how many resources were
|
|
578
|
+
* fetched, how many came from the cache, and where the milliseconds went --
|
|
579
|
+
* which for an `https:` URL is usually the answer to "why did this take so
|
|
580
|
+
* long", because a cold connection costs more than the render does.
|
|
581
|
+
*/
|
|
582
|
+
screenshot(options: ScreenshotOptions): Promise<ScreenshotResult>;
|
|
116
583
|
}
|
|
117
|
-
/** The shared
|
|
584
|
+
/** The shared engine: one per process, started on first use. */
|
|
118
585
|
declare const runtime: Runtime;
|
|
119
|
-
/** One screenshot through the shared
|
|
120
|
-
declare const screenshot: (options: ScreenshotOptions) => Promise<
|
|
586
|
+
/** One screenshot through the shared engine, starting it if it is not up. */
|
|
587
|
+
declare const screenshot: (options: ScreenshotOptions) => Promise<ScreenshotResult>;
|
|
588
|
+
declare const start: (options?: StartOptions) => StartResult;
|
|
589
|
+
declare const status: () => StartResult;
|
|
590
|
+
declare const stop: () => Promise<void>;
|
|
591
|
+
declare const releaseMemory: (options?: ReleaseMemoryOptions) => void;
|
|
592
|
+
/**
|
|
593
|
+
* The HTTP cache.
|
|
594
|
+
*
|
|
595
|
+
* At the top level rather than under the engine because it outlives one: the
|
|
596
|
+
* directory is on disk whether or not anything is running, `getDir()` answers
|
|
597
|
+
* before the first `start()`, and clearing it is something a program may want
|
|
598
|
+
* to do without bringing Blink up at all. When an engine *is* up, these
|
|
599
|
+
* borrow its cache backend, because within one process a directory has one
|
|
600
|
+
* backend and that is the only way in.
|
|
601
|
+
*/
|
|
602
|
+
declare const cache: Cache;
|
|
121
603
|
/**
|
|
122
|
-
* The resident
|
|
604
|
+
* The resident engine: a process that outlives the one that started it,
|
|
123
605
|
* reachable over a named pipe on Windows and a unix socket elsewhere. For
|
|
124
606
|
* callers that are short-lived themselves. See lib/daemon.ts.
|
|
607
|
+
*
|
|
608
|
+
* It has no `cache` of its own. A daemon's cache directory is reported by
|
|
609
|
+
* `daemon.status()`, and clearing it is done by pointing `cache.clear()` at
|
|
610
|
+
* that directory or by stopping the daemon -- a cross-process cache protocol
|
|
611
|
+
* would be a second implementation of this module for something nobody does on
|
|
612
|
+
* a request path.
|
|
125
613
|
*/
|
|
126
614
|
declare const daemon: Daemon;
|
|
127
615
|
declare const _default: {
|
|
128
616
|
Runtime: typeof Runtime;
|
|
129
|
-
|
|
130
|
-
screenshot: (options: ScreenshotOptions) => Promise<Buffer | null>;
|
|
617
|
+
cache: Cache;
|
|
131
618
|
daemon: Daemon;
|
|
619
|
+
releaseMemory: (options?: ReleaseMemoryOptions) => void;
|
|
620
|
+
runtime: Runtime;
|
|
621
|
+
screenshot: (options: ScreenshotOptions) => Promise<ScreenshotResult>;
|
|
622
|
+
start: (options?: StartOptions) => StartResult;
|
|
623
|
+
status: () => StartResult;
|
|
624
|
+
stop: () => Promise<void>;
|
|
625
|
+
readonly running: boolean;
|
|
132
626
|
};
|
|
133
627
|
//#endregion
|
|
134
|
-
export { type Clip, Daemon, type DaemonClient, type DaemonOptions, type DaemonStatus, type PageGotoParams, type
|
|
628
|
+
export { Cache, type CacheClearOptions, type CacheClearResult, type CacheEntry, type CacheMode, type CacheTarget, type CaptureStats, type CaptureTiming, type Clip, Daemon, type DaemonClient, type DaemonOptions, type DaemonStatus, type PageGotoParams, type ReleaseMemoryOptions, Runtime, type ScreenshotOptions, type ScreenshotResult, type StartOptions, type StartResult, type Viewport, cache, daemon, _default as default, releaseMemory, runtime, screenshot, start, status, stop };
|
|
135
629
|
//# sourceMappingURL=index.d.ts.map
|