@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.js
CHANGED
|
@@ -1,12 +1,262 @@
|
|
|
1
|
-
import { a as resolveStartOptions, i as
|
|
2
|
-
import { n as timeoutFor, r as toRequest, t as SUPERVISOR_MARGIN_MS } from "./request-qZXS3N9f.js";
|
|
3
|
-
import { EventEmitter } from "node:events";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { a as emptyStats, c as cacheRoot, d as resolveStartOptions, f as load, i as Engine, l as defaultCacheDir, n as encodeFrame, o as timeoutFor, r as endpointFor, s as toRequest, t as FrameReader, u as normalizePath } from "./protocol-BTeWJDOa.js";
|
|
5
2
|
import fs from "node:fs";
|
|
6
|
-
import net from "node:net";
|
|
7
3
|
import path from "node:path";
|
|
8
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
7
|
+
import net from "node:net";
|
|
8
|
+
|
|
9
|
+
//#region src/lib/cache.ts
|
|
10
|
+
/**
|
|
11
|
+
* Turns one glob into a regular expression over a URL.
|
|
12
|
+
*
|
|
13
|
+
* The dialect is the small one everybody already knows -- `*`, `**`, `?`,
|
|
14
|
+
* `{a,b}` -- and it is implemented here rather than depended on because this
|
|
15
|
+
* package has no runtime dependencies and a matcher is thirty lines. `*` stops
|
|
16
|
+
* at `/` and `**` does not, which is the distinction that makes
|
|
17
|
+
* `https://example.com/*` mean one level and `https://example.com/**` mean the
|
|
18
|
+
* site.
|
|
19
|
+
*
|
|
20
|
+
* Everything else is escaped, which matters more than usual here: the subjects
|
|
21
|
+
* are URLs, and a URL is mostly characters that mean something to a regular
|
|
22
|
+
* expression.
|
|
23
|
+
*/
|
|
24
|
+
function globToRegExp(pattern) {
|
|
25
|
+
let out = "";
|
|
26
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
27
|
+
const c = pattern[i];
|
|
28
|
+
if (c === "*") {
|
|
29
|
+
if (pattern[i + 1] === "*") {
|
|
30
|
+
out += ".*";
|
|
31
|
+
i++;
|
|
32
|
+
if (pattern[i + 1] === "/") {
|
|
33
|
+
out += "/?";
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
} else out += "[^/]*";
|
|
37
|
+
} else if (c === "?") out += "[^/]";
|
|
38
|
+
else if (c === "{") {
|
|
39
|
+
const end = pattern.indexOf("}", i);
|
|
40
|
+
if (end === -1) out += "\\{";
|
|
41
|
+
else {
|
|
42
|
+
const alternatives = pattern.slice(i + 1, end).split(",").map(escapeLiteral);
|
|
43
|
+
out += `(?:${alternatives.join("|")})`;
|
|
44
|
+
i = end;
|
|
45
|
+
}
|
|
46
|
+
} else out += escapeLiteral(c);
|
|
47
|
+
}
|
|
48
|
+
return new RegExp(`^${out}$`);
|
|
49
|
+
}
|
|
50
|
+
function escapeLiteral(text) {
|
|
51
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
52
|
+
}
|
|
53
|
+
/** Whether `url` matches any of `patterns`. No patterns matches nothing. */
|
|
54
|
+
function matchesAny(url, patterns) {
|
|
55
|
+
return patterns.some((pattern) => pattern.test(url));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* What a cache directory occupies, for the one path that reports a size
|
|
59
|
+
* without a backend to ask.
|
|
60
|
+
*
|
|
61
|
+
* The sum of the files rather than the sum of the entries, so it will differ
|
|
62
|
+
* from what `clear()` reports through the backend by the index and by whatever
|
|
63
|
+
* rounding the filesystem does. It is the honest number for "what is about to
|
|
64
|
+
* be deleted", which is what it is used for.
|
|
65
|
+
*/
|
|
66
|
+
function directorySize(dir) {
|
|
67
|
+
let total = 0;
|
|
68
|
+
let names = [];
|
|
69
|
+
try {
|
|
70
|
+
names = fs.readdirSync(dir, { withFileTypes: true });
|
|
71
|
+
} catch {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
for (const entry of names) {
|
|
75
|
+
const full = path.join(dir, entry.name);
|
|
76
|
+
if (entry.isDirectory()) {
|
|
77
|
+
total += directorySize(full);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
total += fs.statSync(full).size;
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
return total;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Which directories an operation covers.
|
|
88
|
+
*
|
|
89
|
+
* `current` is this project's, `all` is every directory under the shared root,
|
|
90
|
+
* and anything else is taken as a project hash. `all` reads the root rather
|
|
91
|
+
* than remembering what it created: another process's directory is as much
|
|
92
|
+
* shotium's as this one's, and a caller asking to clear them all means the
|
|
93
|
+
* ones on disk.
|
|
94
|
+
*/
|
|
95
|
+
function resolveTargets(target) {
|
|
96
|
+
if (target === "all") {
|
|
97
|
+
const root = cacheRoot();
|
|
98
|
+
let names = [];
|
|
99
|
+
try {
|
|
100
|
+
names = fs.readdirSync(root);
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
return names.map((name) => normalizePath(path.join(root, name))).filter((dir) => {
|
|
105
|
+
try {
|
|
106
|
+
return fs.statSync(dir).isDirectory();
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (target === void 0 || target === "current") return [defaultCacheDir()];
|
|
113
|
+
if (path.isAbsolute(target)) return [normalizePath(target)];
|
|
114
|
+
return [normalizePath(path.join(cacheRoot(), target))];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The cache, from the outside.
|
|
118
|
+
*
|
|
119
|
+
* Every method takes the engine handle if there is one, and that is not an
|
|
120
|
+
* optimisation. Within one process a cache directory has one backend: asking
|
|
121
|
+
* for a second one on the directory the engine holds waits for the engine's to
|
|
122
|
+
* go away, which it will not do while the engine is up. Borrowing is the only
|
|
123
|
+
* thing that returns.
|
|
124
|
+
*
|
|
125
|
+
* "If there is one" means the process, not the lifecycle. `stop()` stands the
|
|
126
|
+
* engine down without tearing it down, so an engine that has been stopped
|
|
127
|
+
* still holds its directory and still has to be borrowed from -- which is also
|
|
128
|
+
* what makes the cache survive a stop, and outlive one, and be worth having.
|
|
129
|
+
*
|
|
130
|
+
* Across processes there is no such constraint -- several of them may share a
|
|
131
|
+
* directory and all of them cache.
|
|
132
|
+
*
|
|
133
|
+
* The engine is fetched through a callback rather than held, because this
|
|
134
|
+
* object is built once at import time and the engine comes and goes.
|
|
135
|
+
*/
|
|
136
|
+
var Cache = class {
|
|
137
|
+
engineHandle;
|
|
138
|
+
constructor(engineHandle) {
|
|
139
|
+
this.engineHandle = engineHandle;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* This project's cache directory, absolute and with forward slashes.
|
|
143
|
+
*
|
|
144
|
+
* It exists whether or not anything has been written to it -- the answer is
|
|
145
|
+
* "where the cache goes", not "where a cache is".
|
|
146
|
+
*/
|
|
147
|
+
getDir(options = {}) {
|
|
148
|
+
const targets = resolveTargets(options.target);
|
|
149
|
+
return targets.length > 0 ? targets[0] : defaultCacheDir();
|
|
150
|
+
}
|
|
151
|
+
/** Every directory the target names. `all` can be several; the rest, one. */
|
|
152
|
+
getDirs(options = {}) {
|
|
153
|
+
return resolveTargets(options.target);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* What the cache is holding, by URL.
|
|
157
|
+
*
|
|
158
|
+
* Named `getFiles` for the operation callers reach for, and deliberately not
|
|
159
|
+
* returning filenames: the files in a cache directory are called things like
|
|
160
|
+
* `5349fbae98c6d9a1_0`, because the name is a hash of the entry key. A list
|
|
161
|
+
* of those answers no question anybody has. The URLs are what the entries
|
|
162
|
+
* are, and they are what `clear({glob})` matches against.
|
|
163
|
+
*
|
|
164
|
+
* This opens every entry to read its key and size, so it is a diagnostic
|
|
165
|
+
* rather than something to put on a request path.
|
|
166
|
+
*/
|
|
167
|
+
async getFiles(options = {}) {
|
|
168
|
+
const native = load();
|
|
169
|
+
const entries = [];
|
|
170
|
+
for (const dir of resolveTargets(options.target)) {
|
|
171
|
+
if (!fs.existsSync(dir)) continue;
|
|
172
|
+
const json = await native.cache(this.handleFor(), false, JSON.stringify({ cacheDir: dir }));
|
|
173
|
+
const listed = JSON.parse(json);
|
|
174
|
+
for (const entry of listed) entries.push({
|
|
175
|
+
...entry,
|
|
176
|
+
dir
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return entries;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Removes what the options select. With no options, everything.
|
|
183
|
+
*
|
|
184
|
+
* The three filters compose, and `glob` is applied here rather than in the
|
|
185
|
+
* engine: the entries are listed, their URLs are matched, and the ones that
|
|
186
|
+
* matched are what the engine is asked to remove. That keeps the pattern
|
|
187
|
+
* dialect in the layer whose users have opinions about pattern dialects, and
|
|
188
|
+
* keeps the engine's interface to exact URLs.
|
|
189
|
+
*
|
|
190
|
+
* Removal goes through the cache backend, never through the filesystem.
|
|
191
|
+
* Deleting the files directly would leave the backend's index naming entries
|
|
192
|
+
* that are no longer there, and the next process to open the directory
|
|
193
|
+
* either rebuilds the index from disk or, having found it inconsistent,
|
|
194
|
+
* discards it. That is the difference between clearing a cache and
|
|
195
|
+
* corrupting one.
|
|
196
|
+
*/
|
|
197
|
+
async clear(options = {}) {
|
|
198
|
+
const native = load();
|
|
199
|
+
const patterns = (options.glob ?? []).map(globToRegExp);
|
|
200
|
+
const results = [];
|
|
201
|
+
if (patterns.length === 0 && !options.maxAge && !options.maxSize && !this.handleFor()) {
|
|
202
|
+
for (const dir of resolveTargets(options.target)) {
|
|
203
|
+
const before = directorySize(dir);
|
|
204
|
+
fs.rmSync(dir, {
|
|
205
|
+
recursive: true,
|
|
206
|
+
force: true
|
|
207
|
+
});
|
|
208
|
+
results.push({
|
|
209
|
+
removed: -1,
|
|
210
|
+
bytesBefore: before,
|
|
211
|
+
bytesAfter: 0,
|
|
212
|
+
dir
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return results;
|
|
216
|
+
}
|
|
217
|
+
for (const dir of resolveTargets(options.target)) {
|
|
218
|
+
if (!fs.existsSync(dir)) continue;
|
|
219
|
+
const request = { cacheDir: dir };
|
|
220
|
+
if (patterns.length > 0) {
|
|
221
|
+
const json = await native.cache(this.handleFor(), false, JSON.stringify({ cacheDir: dir }));
|
|
222
|
+
const urls = JSON.parse(json).filter((entry) => matchesAny(entry.url, patterns)).map((entry) => entry.url);
|
|
223
|
+
if (urls.length === 0 && options.maxAge === void 0 && options.maxSize === void 0) {
|
|
224
|
+
results.push({
|
|
225
|
+
removed: 0,
|
|
226
|
+
bytesBefore: 0,
|
|
227
|
+
bytesAfter: 0,
|
|
228
|
+
dir
|
|
229
|
+
});
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
request.urls = urls;
|
|
233
|
+
}
|
|
234
|
+
if (options.maxAge) request.unusedSinceMs = Date.now() - options.maxAge * 1e3;
|
|
235
|
+
if (options.maxSize) request.maxBytes = options.maxSize;
|
|
236
|
+
const json = await native.cache(this.handleFor(), true, JSON.stringify(request));
|
|
237
|
+
results.push({
|
|
238
|
+
...JSON.parse(json),
|
|
239
|
+
dir
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The engine handle, when there is an engine.
|
|
246
|
+
*
|
|
247
|
+
* Passed for every directory and not only the engine's own. It is never
|
|
248
|
+
* wrong to pass it -- the engine's thread can open any directory, and for
|
|
249
|
+
* the one it already has open, borrowing its backend is the only thing that
|
|
250
|
+
* returns. It is passing `null` while an engine is up that hangs, which is
|
|
251
|
+
* why this is conditional on neither the directory asked for nor on whether
|
|
252
|
+
* the engine is currently accepting captures.
|
|
253
|
+
*/
|
|
254
|
+
handleFor() {
|
|
255
|
+
return this.engineHandle();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
9
258
|
|
|
259
|
+
//#endregion
|
|
10
260
|
//#region src/lib/client.ts
|
|
11
261
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
12
262
|
const DAEMON_MAIN = path.join(HERE, "daemon_main.js");
|
|
@@ -63,7 +313,11 @@ var DaemonClient = class extends EventEmitter {
|
|
|
63
313
|
header,
|
|
64
314
|
image: header.path ? null : payload
|
|
65
315
|
});
|
|
66
|
-
else
|
|
316
|
+
else {
|
|
317
|
+
const error = new Error(header.error || "shotium: request failed");
|
|
318
|
+
if (header.stats) error.stats = header.stats;
|
|
319
|
+
pending.reject(error);
|
|
320
|
+
}
|
|
67
321
|
}
|
|
68
322
|
failAll(error) {
|
|
69
323
|
for (const [, pending] of this.pending) pending.reject(error);
|
|
@@ -86,16 +340,23 @@ var DaemonClient = class extends EventEmitter {
|
|
|
86
340
|
}), "utf8")));
|
|
87
341
|
});
|
|
88
342
|
}
|
|
89
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* One screenshot, and what taking it cost.
|
|
345
|
+
*
|
|
346
|
+
* The same shape the in-process engine returns, so that moving a program
|
|
347
|
+
* between the two is an import change and nothing else.
|
|
348
|
+
*/
|
|
90
349
|
async screenshot(options) {
|
|
91
350
|
const request = toRequest(options);
|
|
92
|
-
const
|
|
93
|
-
return (await this.send({
|
|
351
|
+
const result = await this.send({
|
|
94
352
|
op: "screenshot",
|
|
95
353
|
request,
|
|
96
|
-
timeout: timeoutFor(options)
|
|
97
|
-
|
|
98
|
-
|
|
354
|
+
timeout: timeoutFor(options)
|
|
355
|
+
});
|
|
356
|
+
return {
|
|
357
|
+
image: result.image,
|
|
358
|
+
stats: result.header.stats ?? emptyStats()
|
|
359
|
+
};
|
|
99
360
|
}
|
|
100
361
|
async status() {
|
|
101
362
|
const { header } = await this.send({ op: "status" });
|
|
@@ -141,10 +402,9 @@ function resolveDaemonOptions(options = {}) {
|
|
|
141
402
|
}
|
|
142
403
|
function spawnDaemon(options) {
|
|
143
404
|
const config = {
|
|
144
|
-
binary: options.binary,
|
|
145
|
-
workers: options.workers,
|
|
146
405
|
cacheDir: options.cacheDir,
|
|
147
|
-
|
|
406
|
+
userAgent: options.userAgent,
|
|
407
|
+
resourceDir: options.resourceDir,
|
|
148
408
|
endpoint: options.endpoint,
|
|
149
409
|
idleTimeoutMs: options.idleTimeoutMs,
|
|
150
410
|
prewarm: options.prewarm
|
|
@@ -196,7 +456,7 @@ async function connect(options = {}) {
|
|
|
196
456
|
const { client } = await ensureClient(options);
|
|
197
457
|
return client;
|
|
198
458
|
}
|
|
199
|
-
async function start(options = {}) {
|
|
459
|
+
async function start$1(options = {}) {
|
|
200
460
|
const { client, spawned, endpoint } = await ensureClient(options);
|
|
201
461
|
try {
|
|
202
462
|
return {
|
|
@@ -208,7 +468,7 @@ async function start(options = {}) {
|
|
|
208
468
|
client.close();
|
|
209
469
|
}
|
|
210
470
|
}
|
|
211
|
-
async function status(options = {}) {
|
|
471
|
+
async function status$1(options = {}) {
|
|
212
472
|
const resolved = resolveDaemonOptions(options);
|
|
213
473
|
let client;
|
|
214
474
|
try {
|
|
@@ -228,7 +488,7 @@ async function status(options = {}) {
|
|
|
228
488
|
client.close();
|
|
229
489
|
}
|
|
230
490
|
}
|
|
231
|
-
async function stop(options = {}) {
|
|
491
|
+
async function stop$1(options = {}) {
|
|
232
492
|
const resolved = resolveDaemonOptions(options);
|
|
233
493
|
let client;
|
|
234
494
|
try {
|
|
@@ -262,90 +522,182 @@ async function screenshot$1(options) {
|
|
|
262
522
|
//#endregion
|
|
263
523
|
//#region src/index.ts
|
|
264
524
|
/**
|
|
265
|
-
* The
|
|
525
|
+
* The engine, and its lifecycle, in this process.
|
|
526
|
+
*
|
|
527
|
+
* import shotium from '@shotkit/shotium';
|
|
528
|
+
*
|
|
529
|
+
* shotium.start();
|
|
530
|
+
* const {image, stats} = await shotium.screenshot({
|
|
531
|
+
* file: 'https://example.com',
|
|
532
|
+
* });
|
|
533
|
+
* await shotium.stop();
|
|
534
|
+
*
|
|
535
|
+
* `start` and `stop` are explicit because starting Blink is the expensive part
|
|
536
|
+
* -- tens of milliseconds and a working set that stays resident -- and only
|
|
537
|
+
* the caller knows whether the next screenshot is coming in a moment or never.
|
|
538
|
+
* Neither call is required: a screenshot starts the engine if it is not up.
|
|
539
|
+
* What they buy is control over when that cost is paid, and the certainty that
|
|
540
|
+
* it has been given back.
|
|
266
541
|
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
542
|
+
* Neither is rationed, either. They may be called in any order and as often as
|
|
543
|
+
* a program likes: `stop()` stands the engine down and `start()` picks the
|
|
544
|
+
* same one back up, warm cache and all. What cannot happen is a *second*
|
|
545
|
+
* engine -- Blink is initialised once per process and there is no undo -- but
|
|
546
|
+
* that is a fact about how many there are, not about how many times the one
|
|
547
|
+
* may be asked for.
|
|
270
548
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
549
|
+
* The methods are on the module rather than under a `runtime` namespace, which
|
|
550
|
+
* they were until 0.3. There was never anything else to start, so the word
|
|
551
|
+
* carried nothing; and `runtime.cache` would have been the wrong place for the
|
|
552
|
+
* cache besides, since a cache directory outlives every engine that writes to
|
|
553
|
+
* it and can be read when no engine is running at all.
|
|
554
|
+
*
|
|
555
|
+
* `Runtime` is still exported for a caller who wants to own a lifecycle rather
|
|
556
|
+
* than share the module's. It is a lifecycle and not an engine: there is one
|
|
557
|
+
* engine per process, and a second Runtime that starts adopts the same one
|
|
558
|
+
* rather than building another. Parallelism is more processes, not more
|
|
559
|
+
* Runtimes.
|
|
560
|
+
*
|
|
561
|
+
* `daemon` is the same engine in a process of its own, behind a socket, for
|
|
562
|
+
* callers whose own process does not live long enough to be worth starting
|
|
563
|
+
* one.
|
|
274
564
|
*/
|
|
275
|
-
var Runtime = class
|
|
276
|
-
|
|
565
|
+
var Runtime = class {
|
|
566
|
+
engine = new Engine();
|
|
567
|
+
/**
|
|
568
|
+
* The HTTP cache: where it is, what is in it, and how to empty it.
|
|
569
|
+
*
|
|
570
|
+
* On the Runtime as well as on the module because a caller holding their own
|
|
571
|
+
* Runtime needs the engine handle to reach a directory that engine has open:
|
|
572
|
+
* within one process a directory has one backend, so borrowing is the only
|
|
573
|
+
* way in.
|
|
574
|
+
*/
|
|
575
|
+
cache = new Cache(() => this.engine.nativeHandle);
|
|
277
576
|
get running() {
|
|
278
|
-
return this.
|
|
577
|
+
return this.engine.running;
|
|
279
578
|
}
|
|
280
579
|
/**
|
|
281
|
-
* Starts the
|
|
282
|
-
*
|
|
580
|
+
* Starts the engine, or picks the running one back up.
|
|
581
|
+
*
|
|
582
|
+
* Callable as often as you like, in any order with `stop()`; library code
|
|
583
|
+
* can call it defensively. The first call in a process builds the engine and
|
|
584
|
+
* every later one adopts it -- the same engine, the same warm cache. The one
|
|
585
|
+
* thing it will refuse is a *different* configuration: the options below are
|
|
586
|
+
* fixed when the engine is built, and there is no second build, so naming
|
|
587
|
+
* one that disagrees with what is running throws rather than rendering with
|
|
588
|
+
* a value you did not ask for.
|
|
589
|
+
*
|
|
590
|
+
* Every option has a default. `cacheDir` is the HTTP disk cache and defaults
|
|
591
|
+
* to a per-project directory under `~/.shotium/cache`, and not under the
|
|
592
|
+
* temporary directory, which is defined by not surviving. `null` turns it
|
|
593
|
+
* off. `resourceDir` is where `shotium_data.pak` and
|
|
594
|
+
* `shotium_strings.pak` are, and defaults to the directory the engine was
|
|
595
|
+
* loaded from, which is where they ship.
|
|
283
596
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
597
|
+
* The return value is worth reading once. `cacheActive: false` with a
|
|
598
|
+
* `cacheDir` set means the directory could not be opened and this engine is
|
|
599
|
+
* running without a cache -- correctly, silently, and a round trip slower on
|
|
600
|
+
* everything.
|
|
288
601
|
*/
|
|
289
602
|
start(options = {}) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.
|
|
310
|
-
await pool.stop();
|
|
603
|
+
return this.engine.start(options);
|
|
604
|
+
}
|
|
605
|
+
/** What `start()` returned, asked again. */
|
|
606
|
+
status() {
|
|
607
|
+
return this.engine.status();
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Stands the engine down, after whatever is queued.
|
|
611
|
+
*
|
|
612
|
+
* The queue drains, the memory the engine can rebuild goes back to the OS,
|
|
613
|
+
* and `running` becomes false. Blink itself stays initialised, because there
|
|
614
|
+
* is no way to un-initialise it -- so the disk cache stays where it is, and
|
|
615
|
+
* `start()` or the next `screenshot()` picks the same engine back up.
|
|
616
|
+
*
|
|
617
|
+
* Which makes this a caller saying they are done for now rather than a
|
|
618
|
+
* destructor. It does the same work as `releaseMemory({releaseWorkingSet:
|
|
619
|
+
* true})` and additionally stops accepting captures.
|
|
620
|
+
*/
|
|
621
|
+
stop() {
|
|
622
|
+
return this.engine.stop();
|
|
311
623
|
}
|
|
312
624
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
625
|
+
* Hands back what the engine is holding but can rebuild: Blink's heap,
|
|
626
|
+
* skia's caches, PartitionAlloc's free lists. Worth calling when a batch has
|
|
627
|
+
* ended and the next one may be a while away.
|
|
628
|
+
*
|
|
629
|
+
* This is memory and nothing else. It was called `purge()` until 0.3, which
|
|
630
|
+
* next to `cache.clear()` read as though it emptied the HTTP cache; it does
|
|
631
|
+
* not touch the disk at all.
|
|
315
632
|
*/
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
633
|
+
releaseMemory(options = {}) {
|
|
634
|
+
this.engine.releaseMemory(options);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Renders one screenshot, and reports what it cost.
|
|
638
|
+
*
|
|
639
|
+
* `image` is the encoded bytes, or `null` when `path` was given and the
|
|
640
|
+
* engine wrote the file itself. `stats` says how many resources were
|
|
641
|
+
* fetched, how many came from the cache, and where the milliseconds went --
|
|
642
|
+
* which for an `https:` URL is usually the answer to "why did this take so
|
|
643
|
+
* long", because a cold connection costs more than the render does.
|
|
644
|
+
*/
|
|
645
|
+
screenshot(options) {
|
|
646
|
+
return this.engine.screenshot(options);
|
|
324
647
|
}
|
|
325
648
|
};
|
|
326
|
-
/** The shared
|
|
649
|
+
/** The shared engine: one per process, started on first use. */
|
|
327
650
|
const runtime = new Runtime();
|
|
328
|
-
/** One screenshot through the shared
|
|
651
|
+
/** One screenshot through the shared engine, starting it if it is not up. */
|
|
329
652
|
const screenshot = (options) => runtime.screenshot(options);
|
|
653
|
+
const start = (options) => runtime.start(options);
|
|
654
|
+
const status = () => runtime.status();
|
|
655
|
+
const stop = () => runtime.stop();
|
|
656
|
+
const releaseMemory = (options) => runtime.releaseMemory(options);
|
|
330
657
|
/**
|
|
331
|
-
* The
|
|
658
|
+
* The HTTP cache.
|
|
659
|
+
*
|
|
660
|
+
* At the top level rather than under the engine because it outlives one: the
|
|
661
|
+
* directory is on disk whether or not anything is running, `getDir()` answers
|
|
662
|
+
* before the first `start()`, and clearing it is something a program may want
|
|
663
|
+
* to do without bringing Blink up at all. When an engine *is* up, these
|
|
664
|
+
* borrow its cache backend, because within one process a directory has one
|
|
665
|
+
* backend and that is the only way in.
|
|
666
|
+
*/
|
|
667
|
+
const cache = runtime.cache;
|
|
668
|
+
/**
|
|
669
|
+
* The resident engine: a process that outlives the one that started it,
|
|
332
670
|
* reachable over a named pipe on Windows and a unix socket elsewhere. For
|
|
333
671
|
* callers that are short-lived themselves. See lib/daemon.ts.
|
|
672
|
+
*
|
|
673
|
+
* It has no `cache` of its own. A daemon's cache directory is reported by
|
|
674
|
+
* `daemon.status()`, and clearing it is done by pointing `cache.clear()` at
|
|
675
|
+
* that directory or by stopping the daemon -- a cross-process cache protocol
|
|
676
|
+
* would be a second implementation of this module for something nobody does on
|
|
677
|
+
* a request path.
|
|
334
678
|
*/
|
|
335
679
|
const daemon = {
|
|
336
680
|
connect,
|
|
337
681
|
screenshot: screenshot$1,
|
|
338
|
-
start,
|
|
339
|
-
status,
|
|
340
|
-
stop
|
|
682
|
+
start: start$1,
|
|
683
|
+
status: status$1,
|
|
684
|
+
stop: stop$1
|
|
341
685
|
};
|
|
342
686
|
var src_default = {
|
|
343
687
|
Runtime,
|
|
688
|
+
cache,
|
|
689
|
+
daemon,
|
|
690
|
+
releaseMemory,
|
|
344
691
|
runtime,
|
|
345
692
|
screenshot,
|
|
346
|
-
|
|
693
|
+
start,
|
|
694
|
+
status,
|
|
695
|
+
stop,
|
|
696
|
+
get running() {
|
|
697
|
+
return runtime.running;
|
|
698
|
+
}
|
|
347
699
|
};
|
|
348
700
|
|
|
349
701
|
//#endregion
|
|
350
|
-
export { Runtime, daemon, src_default as default, runtime, screenshot };
|
|
702
|
+
export { Cache, Runtime, cache, daemon, src_default as default, releaseMemory, runtime, screenshot, start, status, stop };
|
|
351
703
|
//# sourceMappingURL=index.js.map
|