@opencraw/driver 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 +11 -5
- package/dist/index.esm.js +292 -45
- package/dist/main.esm.js +256 -35
- package/dist/src/callout-bridge/client-hooks.client.d.ts +30 -2
- package/dist/src/callout-bridge/index.d.ts +2 -2
- package/dist/src/index.d.ts +8 -4
- package/dist/src/protocol/driver-message.contract.d.ts +243 -2
- package/dist/src/protocol/index.d.ts +2 -2
- package/dist/src/record-files/diff-record-files.use-case.d.ts +10 -0
- package/dist/src/record-files/export-record-files.use-case.d.ts +11 -0
- package/dist/src/record-files/index.d.ts +3 -0
- package/dist/src/session/index.d.ts +1 -1
- package/dist/src/session/run-session.use-case.d.ts +11 -11
- package/dist/src/site-access/index.d.ts +3 -0
- package/dist/src/site-access/resolve-site-access.use-case.d.ts +26 -0
- package/dist/src/site-probe/index.d.ts +2 -0
- package/dist/src/site-probe/probe-site.use-case.d.ts +13 -0
- package/dist/src/work-bridge/client-work-source.client.d.ts +17 -0
- package/dist/src/work-bridge/index.d.ts +2 -0
- package/package.json +3 -3
- package/schemas/driver-protocol.schema.json +566 -1
package/dist/main.esm.js
CHANGED
|
@@ -6,13 +6,20 @@ import { dirname, join } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
// src/server/serve-driver.use-case.ts
|
|
9
|
-
import { once } from "node:events";
|
|
9
|
+
import { once as once2 } from "node:events";
|
|
10
10
|
|
|
11
11
|
// src/protocol/driver-message.contract.ts
|
|
12
12
|
import { calloutRequestSchema, calloutResponseSchema } from "@opencraw/core";
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
var PROTOCOL_VERSION = "1.
|
|
14
|
+
var PROTOCOL_VERSION = "1.2.0";
|
|
15
15
|
var recipeObject = z.record(z.string(), z.unknown());
|
|
16
|
+
var coreObject = z.record(z.string(), z.unknown());
|
|
17
|
+
var accessFields = {
|
|
18
|
+
accessConfig: z.string().optional(),
|
|
19
|
+
access: z.string().optional(),
|
|
20
|
+
/** Access plugins the client serves: a profile `{ "kind": "plugin", "name": … }` naming one leases through a `callout` to the client. */
|
|
21
|
+
accessPlugins: z.array(z.string()).optional()
|
|
22
|
+
};
|
|
16
23
|
var recipeSource = {
|
|
17
24
|
paths: z.array(z.string()).min(1).optional(),
|
|
18
25
|
recipes: z.array(recipeObject).min(1).optional()
|
|
@@ -48,17 +55,86 @@ var runParamsSchema = z.strictObject({
|
|
|
48
55
|
parallel: z.number().int().min(1).max(16).optional(),
|
|
49
56
|
browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
|
|
50
57
|
/** The hooks the client answers itself: each becomes a `callout` request to the client when a recipe calls it. */
|
|
51
|
-
hooks: z.array(z.string()).optional()
|
|
58
|
+
hooks: z.array(z.string()).optional(),
|
|
59
|
+
/** Captcha solvers the client serves, by name: a challenge goes to the client as a `captcha` callout. */
|
|
60
|
+
captchaSolvers: z.array(z.string()).optional(),
|
|
61
|
+
...accessFields
|
|
52
62
|
});
|
|
53
63
|
var runResultSchema = z.strictObject({ runId: z.string() });
|
|
64
|
+
var windowsSchema = z.union([z.number().int().min(1).max(64), z.record(z.string(), z.unknown())]);
|
|
65
|
+
var workParamsSchema = z.strictObject({
|
|
66
|
+
...recipeSource,
|
|
67
|
+
only: z.array(z.string()).optional(),
|
|
68
|
+
out: z.string().optional(),
|
|
69
|
+
append: z.boolean().optional(),
|
|
70
|
+
browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
|
|
71
|
+
hooks: z.array(z.string()).optional(),
|
|
72
|
+
captchaSolvers: z.array(z.string()).optional(),
|
|
73
|
+
...accessFields,
|
|
74
|
+
windows: windowsSchema.optional()
|
|
75
|
+
});
|
|
76
|
+
var varValue = z.union([z.string(), z.number(), z.boolean()]);
|
|
77
|
+
var workItemSchema = z.strictObject({
|
|
78
|
+
id: z.string().min(1),
|
|
79
|
+
vars: z.record(z.string(), varValue),
|
|
80
|
+
recipe: z.string().optional()
|
|
81
|
+
});
|
|
54
82
|
var cancelParamsSchema = z.strictObject({ runId: z.string() });
|
|
55
83
|
var cancelResultSchema = z.strictObject({ cancelled: z.boolean() });
|
|
84
|
+
var probeParamsSchema = z.strictObject({
|
|
85
|
+
url: z.string().min(1),
|
|
86
|
+
/** Render the page in Chromium first (for pages built by scripts). */
|
|
87
|
+
browser: z.boolean().optional(),
|
|
88
|
+
browserPath: z.string().optional(),
|
|
89
|
+
insecureTls: z.boolean().optional(),
|
|
90
|
+
userAgent: z.string().optional(),
|
|
91
|
+
...accessFields
|
|
92
|
+
});
|
|
93
|
+
var diffParamsSchema = z.strictObject({
|
|
94
|
+
/** The earlier run's JSON Lines file. */
|
|
95
|
+
previous: z.string().min(1),
|
|
96
|
+
current: z.string().min(1),
|
|
97
|
+
/** The fields that identify a record; default: each line's `_key`. */
|
|
98
|
+
key: z.array(z.string()).optional(),
|
|
99
|
+
/** Fields left out of the comparison. */
|
|
100
|
+
ignore: z.array(z.string()).optional(),
|
|
101
|
+
/** Changes returned at most (default 200); the counts cover all of them. */
|
|
102
|
+
limit: z.number().int().min(0).optional()
|
|
103
|
+
});
|
|
104
|
+
var diffResultSchema = z.strictObject({
|
|
105
|
+
added: z.number(),
|
|
106
|
+
removed: z.number(),
|
|
107
|
+
changed: z.number(),
|
|
108
|
+
unchanged: z.number(),
|
|
109
|
+
/** Set when the current run lost most of the previous run's records. */
|
|
110
|
+
shrunk: z.strictObject({ previous: z.number(), current: z.number() }).optional(),
|
|
111
|
+
/** The report a person reads, one line per change. */
|
|
112
|
+
summary: z.array(z.string()),
|
|
113
|
+
/** The changes, up to `limit`: each `added`, `removed` or `changed` (with each field's before and after). */
|
|
114
|
+
changes: z.array(coreObject),
|
|
115
|
+
/** More changes than `limit` were found. */
|
|
116
|
+
truncated: z.boolean()
|
|
117
|
+
});
|
|
118
|
+
var exportParamsSchema = z.strictObject({
|
|
119
|
+
/** A run's JSON Lines file. */
|
|
120
|
+
records: z.string().min(1),
|
|
121
|
+
/** The output recipe that gives the columns. */
|
|
122
|
+
output: z.string().min(1),
|
|
123
|
+
/** The file to write; its extension names the format, as for `run`. */
|
|
124
|
+
out: z.string().min(1)
|
|
125
|
+
});
|
|
126
|
+
var exportResultSchema = z.strictObject({ written: z.number(), location: z.string() });
|
|
56
127
|
var shutdownResultSchema = z.strictObject({});
|
|
128
|
+
var workNextParamsSchema = z.strictObject({ runId: z.string() });
|
|
129
|
+
var workNextResultSchema = z.strictObject({ item: workItemSchema.nullable() });
|
|
130
|
+
var workRecordSchema = z.strictObject({ key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
|
|
131
|
+
var workDoneParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, records: z.array(workRecordSchema) });
|
|
132
|
+
var workFailedParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, outcome: z.enum(["failure", "neutral"]) });
|
|
57
133
|
var eventParamsSchema = z.strictObject({ runId: z.string(), event: z.record(z.string(), z.unknown()) });
|
|
58
134
|
var recordParamsSchema = z.strictObject({ runId: z.string(), recipeId: z.string(), key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
|
|
59
135
|
var finishedParamsSchema = z.strictObject({
|
|
60
136
|
runId: z.string(),
|
|
61
|
-
/** The crawl report (`CrawlReport`), absent when the run could not start. */
|
|
137
|
+
/** The crawl report (`CrawlReport`), or for `work` the work report (`WorkReport`); absent when the run could not start. */
|
|
62
138
|
report: z.record(z.string(), z.unknown()).optional(),
|
|
63
139
|
/** Why the run could not start, or stopped as a whole. */
|
|
64
140
|
error: z.string().optional()
|
|
@@ -68,19 +144,127 @@ var calloutParamsSchema = z.strictObject({ runId: z.string(), request: calloutRe
|
|
|
68
144
|
// src/protocol/driver-json-schema.mapper.ts
|
|
69
145
|
import { z as z2 } from "zod";
|
|
70
146
|
|
|
147
|
+
// src/record-files/diff-record-files.use-case.ts
|
|
148
|
+
import { diffReport } from "@opencraw/cli";
|
|
149
|
+
import { diffRecords, readRecordsFile } from "@opencraw/core";
|
|
150
|
+
var CHANGE_LIMIT = 200;
|
|
151
|
+
async function diffRecordFiles(params) {
|
|
152
|
+
const [previous, current] = await Promise.all([readRecordsFile(params.previous), readRecordsFile(params.current)]);
|
|
153
|
+
const diff = diffRecords(previous, current, { key: params.key, ignore: params.ignore });
|
|
154
|
+
const limit = params.limit ?? CHANGE_LIMIT;
|
|
155
|
+
return {
|
|
156
|
+
added: diff.added,
|
|
157
|
+
removed: diff.removed,
|
|
158
|
+
changed: diff.changed,
|
|
159
|
+
unchanged: diff.unchanged,
|
|
160
|
+
...diff.shrunk !== void 0 && { shrunk: diff.shrunk },
|
|
161
|
+
summary: diffReport(diff),
|
|
162
|
+
changes: diff.changes.slice(0, limit).map((change) => ({ ...change })),
|
|
163
|
+
truncated: diff.changes.length > limit
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/record-files/export-record-files.use-case.ts
|
|
168
|
+
import { readFile } from "node:fs/promises";
|
|
169
|
+
import { fileSink, outputRecordOf, parseOutputRecipe, readRecordsFile as readRecordsFile2 } from "@opencraw/core";
|
|
170
|
+
async function exportRecordFiles(params) {
|
|
171
|
+
const output = parseOutputRecipe(JSON.parse(await readFile(params.output, "utf8")), params.output);
|
|
172
|
+
const stored = await readRecordsFile2(params.records);
|
|
173
|
+
const sink = fileSink(params.out, { browser: { executablePath: process.env.OPENCRAW_CHROMIUM } });
|
|
174
|
+
await sink.open(output);
|
|
175
|
+
for (const line of stored) await sink.write(outputRecordOf(line));
|
|
176
|
+
const summary = await sink.close();
|
|
177
|
+
return { written: summary.written, location: summary.location ?? params.out };
|
|
178
|
+
}
|
|
179
|
+
|
|
71
180
|
// src/session/run-session.use-case.ts
|
|
72
|
-
import { createCrawler, fileSink, RecipeSet } from "@opencraw/core";
|
|
181
|
+
import { createCrawler, fileSink as fileSink2, RecipeSet } from "@opencraw/core";
|
|
73
182
|
|
|
74
183
|
// src/callout-bridge/client-hooks.client.ts
|
|
75
|
-
import { calloutHook, calloutResponseSchema as calloutResponseSchema2 } from "@opencraw/core";
|
|
76
|
-
function clientTransport(
|
|
184
|
+
import { accessPluginVia, calloutHook, calloutResponseSchema as calloutResponseSchema2, captchaSolverVia } from "@opencraw/core";
|
|
185
|
+
function clientTransport(label, runId, ask) {
|
|
77
186
|
return {
|
|
78
|
-
label
|
|
187
|
+
label,
|
|
79
188
|
call: async (request) => calloutResponseSchema2.parse(await ask("callout", { runId, request }))
|
|
80
189
|
};
|
|
81
190
|
}
|
|
82
191
|
function clientHooks(names, runId, ask) {
|
|
83
|
-
return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(name
|
|
192
|
+
return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(`client hook ${name}`, runId, ask))]));
|
|
193
|
+
}
|
|
194
|
+
function clientCaptchaSolvers(names, runId, ask) {
|
|
195
|
+
return names.map((name) => captchaSolverVia(name, clientTransport(`client captcha solver ${name}`, runId, ask)));
|
|
196
|
+
}
|
|
197
|
+
function clientAccessPlugins(names, runId, ask) {
|
|
198
|
+
return names.map((name) => accessPluginVia(name, clientTransport(`client access plugin ${name}`, runId, ask)));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/site-access/resolve-site-access.use-case.ts
|
|
202
|
+
import { resolveAccess } from "@opencraw/cli";
|
|
203
|
+
async function resolveSiteAccess(params, runId, ask, env = process.env) {
|
|
204
|
+
const file = params.accessConfig ?? env.OPENCRAW_ACCESS;
|
|
205
|
+
if (file === void 0 && params.access !== void 0) throw new Error(`access profile "${params.access}" needs an access config: give "accessConfig", or set OPENCRAW_ACCESS where the driver runs`);
|
|
206
|
+
const access = await resolveAccess({ insecureTls: false, access: file, accessProfile: params.access });
|
|
207
|
+
return { access, accessPlugins: clientAccessPlugins(params.accessPlugins ?? [], runId, ask) };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/work-bridge/client-work-source.client.ts
|
|
211
|
+
import { once } from "node:events";
|
|
212
|
+
function clientWorkSource(runId, ask, notify) {
|
|
213
|
+
const kept = [];
|
|
214
|
+
let ended = false;
|
|
215
|
+
const askClient = async () => {
|
|
216
|
+
const { item } = workNextResultSchema.parse(await ask("work.next", { runId }));
|
|
217
|
+
if (item === null) {
|
|
218
|
+
ended = true;
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
return item;
|
|
222
|
+
};
|
|
223
|
+
const keepLate = async (answer) => {
|
|
224
|
+
try {
|
|
225
|
+
const item = await answer;
|
|
226
|
+
if (item !== void 0) kept.push(item);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
next: async (options) => {
|
|
232
|
+
const waiting = kept.shift();
|
|
233
|
+
if (waiting !== void 0) return waiting;
|
|
234
|
+
if (ended) return;
|
|
235
|
+
const answer = askClient();
|
|
236
|
+
const signal = options?.signal;
|
|
237
|
+
if (signal === void 0) return await answer;
|
|
238
|
+
signal.throwIfAborted();
|
|
239
|
+
const settled = new AbortController();
|
|
240
|
+
const answered = (async () => {
|
|
241
|
+
const item = await answer;
|
|
242
|
+
return { item };
|
|
243
|
+
})();
|
|
244
|
+
const retired = (async () => {
|
|
245
|
+
try {
|
|
246
|
+
await once(signal, "abort", { signal: settled.signal });
|
|
247
|
+
return "retired";
|
|
248
|
+
} catch {
|
|
249
|
+
return "answered";
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
try {
|
|
253
|
+
const first = await Promise.race([answered, retired]);
|
|
254
|
+
if (first !== "retired" && first !== "answered") return first.item;
|
|
255
|
+
void keepLate(answer);
|
|
256
|
+
throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
|
|
257
|
+
} finally {
|
|
258
|
+
settled.abort();
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
done: (item, report, records) => {
|
|
262
|
+
notify("work.done", { runId, item, report, records: records.map((record) => ({ key: record.key, data: record.data })) });
|
|
263
|
+
},
|
|
264
|
+
failed: (item, report, outcome) => {
|
|
265
|
+
notify("work.failed", { runId, item, report, outcome });
|
|
266
|
+
}
|
|
267
|
+
};
|
|
84
268
|
}
|
|
85
269
|
|
|
86
270
|
// src/session/recipe-loading.use-case.ts
|
|
@@ -124,36 +308,53 @@ function streamingSink() {
|
|
|
124
308
|
function runSessions(notify, ask) {
|
|
125
309
|
const running = /* @__PURE__ */ new Map();
|
|
126
310
|
let next = 1;
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
311
|
+
const crawlerFor = async (runId, params) => {
|
|
312
|
+
const { access, accessPlugins } = await resolveSiteAccess(params, runId, ask);
|
|
313
|
+
return createCrawler({
|
|
314
|
+
sink: params.out === void 0 ? streamingSink() : fileSink2(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
|
|
315
|
+
hooks: clientHooks(params.hooks ?? [], runId, ask),
|
|
316
|
+
captchaSolvers: clientCaptchaSolvers(params.captchaSolvers ?? [], runId, ask),
|
|
317
|
+
access,
|
|
318
|
+
accessPlugins,
|
|
319
|
+
profilesDir: process.env.OPENCRAW_PROFILES,
|
|
320
|
+
parallel: params.parallel,
|
|
321
|
+
resume: params.resume,
|
|
322
|
+
debug: params.dryRun,
|
|
323
|
+
browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
|
|
324
|
+
onEvent: (event) => {
|
|
325
|
+
notify("event", { runId, event });
|
|
326
|
+
if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
const track = (runId, crawler, crawling) => {
|
|
331
|
+
running.set(runId, crawler);
|
|
332
|
+
void (async () => {
|
|
333
|
+
try {
|
|
334
|
+
notify("finished", { runId, report: await crawling() });
|
|
335
|
+
} catch (error) {
|
|
336
|
+
notify("finished", { runId, error: error instanceof Error ? error.message : String(error) });
|
|
337
|
+
} finally {
|
|
338
|
+
running.delete(runId);
|
|
339
|
+
await crawler.close();
|
|
340
|
+
}
|
|
341
|
+
})();
|
|
137
342
|
};
|
|
138
343
|
return {
|
|
139
344
|
start: async (params) => {
|
|
140
345
|
const loaded = await loadRecipes(params);
|
|
141
346
|
const set = params.dryRun === true ? new RecipeSet(loaded.output, loaded.inputs.map((input) => ({ ...input, limits: { ...input.limits, maxRecords: 1 } }))) : loaded;
|
|
142
347
|
const runId = `run-${next++}`;
|
|
143
|
-
const crawler =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
running.set(runId, crawler);
|
|
156
|
-
void crawl(runId, crawler, set);
|
|
348
|
+
const crawler = await crawlerFor(runId, params);
|
|
349
|
+
track(runId, crawler, async () => await crawler.run(set));
|
|
350
|
+
return runId;
|
|
351
|
+
},
|
|
352
|
+
startWork: async (params) => {
|
|
353
|
+
const set = await loadRecipes(params);
|
|
354
|
+
const runId = `run-${next++}`;
|
|
355
|
+
const crawler = await crawlerFor(runId, params);
|
|
356
|
+
const windows = params.windows;
|
|
357
|
+
track(runId, crawler, async () => await crawler.work(set, clientWorkSource(runId, ask, notify), { windows }));
|
|
157
358
|
return runId;
|
|
158
359
|
},
|
|
159
360
|
cancel: async (runId) => {
|
|
@@ -173,6 +374,21 @@ function runSessions(notify, ask) {
|
|
|
173
374
|
};
|
|
174
375
|
}
|
|
175
376
|
|
|
377
|
+
// src/site-probe/probe-site.use-case.ts
|
|
378
|
+
import { probeUrl } from "@opencraw/cli";
|
|
379
|
+
async function probeSite(params, probeId, ask) {
|
|
380
|
+
const { access, accessPlugins } = await resolveSiteAccess(params, probeId, ask);
|
|
381
|
+
const result = await probeUrl(params.url, {
|
|
382
|
+
browser: params.browser ?? false,
|
|
383
|
+
browserPath: params.browserPath ?? process.env.OPENCRAW_CHROMIUM,
|
|
384
|
+
insecureTls: params.insecureTls ?? false,
|
|
385
|
+
userAgent: params.userAgent,
|
|
386
|
+
accessConfig: access,
|
|
387
|
+
accessPlugins
|
|
388
|
+
});
|
|
389
|
+
return { ...result };
|
|
390
|
+
}
|
|
391
|
+
|
|
176
392
|
// src/transport/line-channel.client.ts
|
|
177
393
|
import { createInterface } from "node:readline";
|
|
178
394
|
|
|
@@ -279,7 +495,7 @@ function openLineChannel(input, output, handle) {
|
|
|
279
495
|
}
|
|
280
496
|
|
|
281
497
|
// src/server/serve-driver.use-case.ts
|
|
282
|
-
var METHODS = ["initialize", "validate", "run", "cancel", "shutdown"];
|
|
498
|
+
var METHODS = ["initialize", "validate", "run", "work", "cancel", "probe", "diff", "export", "shutdown"];
|
|
283
499
|
var major = (version) => version.split(".", 1)[0];
|
|
284
500
|
function parsed(schema, params) {
|
|
285
501
|
const result = schema.safeParse(params ?? {});
|
|
@@ -289,6 +505,7 @@ function parsed(schema, params) {
|
|
|
289
505
|
async function serveDriver(input, output, identity) {
|
|
290
506
|
let initialized = false;
|
|
291
507
|
const stopping = new AbortController();
|
|
508
|
+
let probes = 0;
|
|
292
509
|
const channel = openLineChannel(input, output, async (method, params) => {
|
|
293
510
|
if (method === "initialize") {
|
|
294
511
|
const hello = parsed(initializeParamsSchema, params);
|
|
@@ -301,7 +518,11 @@ async function serveDriver(input, output, identity) {
|
|
|
301
518
|
if (!initialized) throw new RpcFailure(RPC_ERRORS.server, "call initialize first");
|
|
302
519
|
if (method === "validate") return await validateRecipes(parsed(validateParamsSchema, params));
|
|
303
520
|
if (method === "run") return { runId: await sessions.start(parsed(runParamsSchema, params)) };
|
|
521
|
+
if (method === "work") return { runId: await sessions.startWork(parsed(workParamsSchema, params)) };
|
|
304
522
|
if (method === "cancel") return { cancelled: await sessions.cancel(parsed(cancelParamsSchema, params).runId) };
|
|
523
|
+
if (method === "probe") return await probeSite(parsed(probeParamsSchema, params), `probe-${++probes}`, channel.request);
|
|
524
|
+
if (method === "diff") return await diffRecordFiles(parsed(diffParamsSchema, params));
|
|
525
|
+
if (method === "export") return await exportRecordFiles(parsed(exportParamsSchema, params));
|
|
305
526
|
await sessions.closeAll();
|
|
306
527
|
setImmediate(() => {
|
|
307
528
|
stopping.abort();
|
|
@@ -309,7 +530,7 @@ async function serveDriver(input, output, identity) {
|
|
|
309
530
|
return {};
|
|
310
531
|
});
|
|
311
532
|
const sessions = runSessions(channel.notify, channel.request);
|
|
312
|
-
await Promise.race([channel.closed,
|
|
533
|
+
await Promise.race([channel.closed, once2(stopping.signal, "abort")]);
|
|
313
534
|
await channel.drained();
|
|
314
535
|
await sessions.closeAll();
|
|
315
536
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import type { CalloutTransport, HookMap } from '@opencraw/core';
|
|
1
|
+
import type { AccessPlugin, CalloutTransport, CaptchaSolver, HookMap } from '@opencraw/core';
|
|
2
2
|
/** Sends one request to the client and waits for its answer: the channel's `request`. */
|
|
3
3
|
export type AskClient = (method: string, params: unknown) => Promise<unknown>;
|
|
4
|
+
/** Sends a notification to the client: the channel's `notify`. */
|
|
5
|
+
export type NotifyClient = (method: string, params: unknown) => void;
|
|
4
6
|
/**
|
|
5
7
|
* A transport whose handler is the client: each call is a `callout` request carrying core's own
|
|
6
8
|
* `CalloutRequest`, and the client answers with a `CalloutResponse`, so `pending` and idempotency keys
|
|
7
9
|
* work exactly as they do for a command or an HTTP hook.
|
|
10
|
+
*
|
|
11
|
+
* @param label - What the logs call it (`client hook tidy`).
|
|
12
|
+
* @param runId - The run it belongs to, sent with every callout.
|
|
13
|
+
* @param ask - The channel's request.
|
|
14
|
+
* @returns The transport.
|
|
8
15
|
*/
|
|
9
|
-
export declare function clientTransport(
|
|
16
|
+
export declare function clientTransport(label: string, runId: string, ask: AskClient): CalloutTransport;
|
|
10
17
|
/**
|
|
11
18
|
* The hooks a run's client serves, each answered by the client over the channel.
|
|
12
19
|
*
|
|
@@ -16,4 +23,25 @@ export declare function clientTransport(hookName: string, runId: string, ask: As
|
|
|
16
23
|
* @returns A hook map for `createCrawler`.
|
|
17
24
|
*/
|
|
18
25
|
export declare function clientHooks(names: readonly string[], runId: string, ask: AskClient): HookMap;
|
|
26
|
+
/**
|
|
27
|
+
* The captcha solvers a run's client serves: a challenge goes to the client as a `captcha` callout (its
|
|
28
|
+
* input is core's `CaptchaCalloutInput`), and the answer's `token` or `text` is put on the page.
|
|
29
|
+
*
|
|
30
|
+
* @param names - The solvers the client said it serves.
|
|
31
|
+
* @param runId - The run they belong to.
|
|
32
|
+
* @param ask - The channel's request.
|
|
33
|
+
* @returns Solvers for `createCrawler`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function clientCaptchaSolvers(names: readonly string[], runId: string, ask: AskClient): CaptchaSolver[];
|
|
36
|
+
/**
|
|
37
|
+
* The access plugins a client serves: a profile `{ "kind": "plugin", "name": … }` leases through an
|
|
38
|
+
* `access` callout to the client (`phase: "lease"`), and releases through another (`phase: "release"`)
|
|
39
|
+
* when the client's lease asked for it.
|
|
40
|
+
*
|
|
41
|
+
* @param names - The plugins the client said it serves.
|
|
42
|
+
* @param runId - The run, or the probe, they belong to.
|
|
43
|
+
* @param ask - The channel's request.
|
|
44
|
+
* @returns Plugins for `createCrawler` or a probe.
|
|
45
|
+
*/
|
|
46
|
+
export declare function clientAccessPlugins(names: readonly string[], runId: string, ask: AskClient): AccessPlugin[];
|
|
19
47
|
//# sourceMappingURL=client-hooks.client.d.ts.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { clientHooks, clientTransport } from './client-hooks.client.js';
|
|
2
|
-
export type { AskClient } from './client-hooks.client.js';
|
|
1
|
+
export { clientAccessPlugins, clientCaptchaSolvers, clientHooks, clientTransport } from './client-hooks.client.js';
|
|
2
|
+
export type { AskClient, NotifyClient } from './client-hooks.client.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export { serveDriver } from './server/index.js';
|
|
2
2
|
export type { DriverIdentity } from './server/index.js';
|
|
3
|
-
export { PROTOCOL_VERSION, MESSAGES, driverProtocolJsonSchema, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, } from './protocol/index.js';
|
|
4
|
-
export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, } from './protocol/index.js';
|
|
3
|
+
export { PROTOCOL_VERSION, MESSAGES, driverProtocolJsonSchema, initializeParamsSchema, initializeResultSchema, validateParamsSchema, validateResultSchema, runParamsSchema, runResultSchema, cancelParamsSchema, cancelResultSchema, eventParamsSchema, recordParamsSchema, finishedParamsSchema, calloutParamsSchema, calloutResultSchema, probeParamsSchema, probeResultSchema, diffParamsSchema, diffResultSchema, exportParamsSchema, exportResultSchema, workParamsSchema, workItemSchema, workNextParamsSchema, workNextResultSchema, workDoneParamsSchema, workFailedParamsSchema, } from './protocol/index.js';
|
|
4
|
+
export type { InitializeParams, InitializeResult, ValidateParams, ValidateResult, RunParams, RunResult, CancelParams, CancelResult, EventParams, RecordParams, FinishedParams, CalloutParams, ProbeParams, DiffParams, DiffResult, ExportParams, ExportResult, WorkParams, WorkItemMessage, WorkNextResult, WorkDoneParams, WorkFailedParams, } from './protocol/index.js';
|
|
5
5
|
export { openLineChannel, RPC_ERRORS, RpcFailure } from './transport/index.js';
|
|
6
6
|
export type { LineChannel, RequestHandler, RpcError, RpcMessage } from './transport/index.js';
|
|
7
7
|
export { runSessions, validateRecipes, streamingSink } from './session/index.js';
|
|
8
|
-
export type { RunSessions
|
|
9
|
-
export { clientHooks } from './callout-bridge/index.js';
|
|
8
|
+
export type { RunSessions } from './session/index.js';
|
|
9
|
+
export { clientHooks, clientCaptchaSolvers, clientAccessPlugins } from './callout-bridge/index.js';
|
|
10
|
+
export type { AskClient, NotifyClient } from './callout-bridge/index.js';
|
|
11
|
+
export { clientWorkSource } from './work-bridge/index.js';
|
|
12
|
+
export { probeSite } from './site-probe/index.js';
|
|
13
|
+
export { diffRecordFiles, exportRecordFiles } from './record-files/index.js';
|
|
10
14
|
//# sourceMappingURL=index.d.ts.map
|