@osqd/bothandlerjs 0.3.0 → 0.5.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/CHANGELOG.md +157 -1
- package/README.md +1 -1
- package/dist/challenge/index.d.ts +18 -0
- package/dist/challenge/interaction.d.ts +215 -0
- package/dist/challenge/page.d.ts +18 -0
- package/dist/cli.cjs +829 -71
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +829 -71
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/core.d.ts +15 -0
- package/dist/corpus/index.cjs +20 -0
- package/dist/corpus/index.cjs.map +1 -1
- package/dist/corpus/index.js +20 -0
- package/dist/corpus/index.js.map +1 -1
- package/dist/dashboard/client/actions.d.ts +17 -0
- package/dist/dashboard/client/actor.d.ts +5 -0
- package/dist/dashboard/client/api.d.ts +15 -0
- package/dist/dashboard/client/app.d.ts +37 -0
- package/dist/dashboard/client/bars.d.ts +4 -0
- package/dist/dashboard/client/boot.d.ts +5 -0
- package/dist/dashboard/client/charts.d.ts +25 -0
- package/dist/dashboard/client/css.d.ts +11 -0
- package/dist/dashboard/client/dom.d.ts +55 -0
- package/dist/dashboard/client/draft.d.ts +46 -0
- package/dist/dashboard/client/feed.d.ts +22 -0
- package/dist/dashboard/client/format.d.ts +37 -0
- package/dist/dashboard/client/guard.d.ts +3 -0
- package/dist/dashboard/client/index.d.ts +1 -0
- package/dist/dashboard/client/outcome.d.ts +33 -0
- package/dist/dashboard/client/pager.d.ts +32 -0
- package/dist/dashboard/client/panels.d.ts +40 -0
- package/dist/dashboard/client/policy.d.ts +25 -0
- package/dist/dashboard/client/query.d.ts +42 -0
- package/dist/dashboard/client/ranges.d.ts +2 -0
- package/dist/dashboard/client/registry.d.ts +4 -0
- package/dist/dashboard/client/replay.d.ts +14 -0
- package/dist/dashboard/client/result.d.ts +10 -0
- package/dist/dashboard/client/store.d.ts +128 -0
- package/dist/dashboard/client/stream.d.ts +14 -0
- package/dist/dashboard/client/tester.d.ts +1 -0
- package/dist/dashboard/client/types.d.ts +69 -0
- package/dist/dashboard/client.generated.d.ts +1 -1
- package/dist/dashboard/page.d.ts +25 -0
- package/dist/dashboard/sections.d.ts +92 -0
- package/dist/dashboard/types.d.ts +2 -82
- package/dist/detectors/crawler-verification.d.ts +34 -1
- package/dist/detectors/index.d.ts +4 -1
- package/dist/detectors/known-bots.d.ts +49 -1
- package/dist/element/config.d.ts +135 -0
- package/dist/element/index.cjs +5150 -0
- package/dist/element/index.cjs.map +1 -0
- package/dist/element/index.d.ts +137 -0
- package/dist/element/index.js +5131 -0
- package/dist/element/index.js.map +1 -0
- package/dist/index.cjs +820 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +811 -71
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts +27 -0
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/presets.d.ts +47 -0
- package/dist/state.d.ts +1 -1
- package/docs/challenge/index.md +1 -0
- package/docs/challenge/interaction.md +325 -0
- package/docs/course/11-the-challenge.md +36 -1
- package/docs/course/13-operating-it.md +20 -2
- package/docs/detection/signatures.md +36 -0
- package/docs/index.md +2 -0
- package/docs/operations/dashboard.md +28 -0
- package/docs/operations/embedding.md +348 -0
- package/docs/operations/index.md +1 -0
- package/docs/policy/presets.md +53 -0
- package/docs/start/choosing-a-policy.md +1 -0
- package/package.json +8 -3
package/dist/dashboard/page.d.ts
CHANGED
|
@@ -48,4 +48,29 @@ export interface DashboardPageOptions {
|
|
|
48
48
|
* it. The HTML is a few tens of kilobytes and identical on every request; rebuilding
|
|
49
49
|
* it per view would be pure waste.
|
|
50
50
|
*/
|
|
51
|
+
/**
|
|
52
|
+
* What the page is told about itself.
|
|
53
|
+
*
|
|
54
|
+
* Built here rather than inline in the renderer so that the embeddable element can be
|
|
55
|
+
* served the identical object from `/api/bootstrap`. The page carries it stamped into its
|
|
56
|
+
* one nonced script; the element has to ask, because it is rendered into somebody else's
|
|
57
|
+
* document and there is nothing to stamp.
|
|
58
|
+
*/
|
|
59
|
+
export declare function bootFor(options: DashboardPageOptions): Record<string, unknown>;
|
|
51
60
|
export declare function renderDashboardPage(options: DashboardPageOptions): (nonce: string) => string;
|
|
61
|
+
/**
|
|
62
|
+
* The dashboard's stylesheet, on its own.
|
|
63
|
+
*
|
|
64
|
+
* Separated from the document so the same rules can be adopted into a shadow root by the
|
|
65
|
+
* embeddable element. The selectors are written `:root, :host` throughout for that
|
|
66
|
+
* reason: `:root` matches the document element on the standalone page and matches nothing
|
|
67
|
+
* inside a shadow tree, where `:host` is the element the tokens have to hang off.
|
|
68
|
+
*/
|
|
69
|
+
export declare const DASHBOARD_CSS: string;
|
|
70
|
+
/**
|
|
71
|
+
* Everything between `<body>` and the boot script.
|
|
72
|
+
*
|
|
73
|
+
* The standalone page and the embedded element render identical markup; only what they
|
|
74
|
+
* render it *into* differs.
|
|
75
|
+
*/
|
|
76
|
+
export declare const DASHBOARD_MARKUP: string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which parts of the dashboard exist, as plain booleans and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* Its own module because it is the one dashboard type the browser half needs: the
|
|
5
|
+
* embeddable element's `hide` option is this shape, so this file ends up in the published
|
|
6
|
+
* `.d.ts` that a front-end application reads. `types.ts` imports `node:http` — it has to,
|
|
7
|
+
* it describes a request handler — and a browser project that type-checks its dependencies
|
|
8
|
+
* strictly would have been asked to install `@types/node` to configure which panels it
|
|
9
|
+
* shows. Nothing here refers to anything outside itself, so nothing follows it in.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Which parts of the dashboard exist on this listener.
|
|
13
|
+
*
|
|
14
|
+
* Everything defaults to on, and turning something off removes it from the page *and*
|
|
15
|
+
* from the server: the tab is gone, the panel is gone, the endpoint behind it answers
|
|
16
|
+
* 403, and fields a switched-off section would have shown are dropped before the data
|
|
17
|
+
* leaves the process. A viewer with devtools open sees exactly what the page sees.
|
|
18
|
+
*
|
|
19
|
+
* **There is no role model in here, deliberately.** `controls` says what a viewer may
|
|
20
|
+
* *do*; this says what a viewer may *see*; and both are fixed when the listener
|
|
21
|
+
* starts, which is what makes them cheap to reason about — no per-request evaluation,
|
|
22
|
+
* no session store, no second copy of your user table quietly disagreeing with the
|
|
23
|
+
* first. Roles are yours: run a listener per role, put your own `auth.authorize`
|
|
24
|
+
* predicate in front of each, and give each the sections and controls that role should
|
|
25
|
+
* have. `npm run demo:roles` is that arrangement, working.
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* // An analyst sees traffic, not the reasons behind it — the evidence panel names
|
|
29
|
+
* // the exact signal that fired, which is a tuning guide for whoever is scraping you.
|
|
30
|
+
* await botHandler.serveDashboard({
|
|
31
|
+
* port: 9684,
|
|
32
|
+
* auth: { authorize: (req) => roleOf(req) === "analyst" },
|
|
33
|
+
* sections: { evidence: false, policy: false },
|
|
34
|
+
* redact: { maskIp: true },
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface DashboardSections {
|
|
39
|
+
/** The Live feed tab, and the `/api/feed` and `/api/stream` endpoints behind it. Default true. */
|
|
40
|
+
feed?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The case for each verdict: the evidence list with its written basis, the request
|
|
43
|
+
* headers, the query parameters, the detector failures, and the buttons that turn a
|
|
44
|
+
* row into a replay line or a corpus case. Default true.
|
|
45
|
+
*
|
|
46
|
+
* The one worth thinking about before sharing a dashboard widely. This is the half
|
|
47
|
+
* of the page that says *which detector fired and why*, which is precisely what
|
|
48
|
+
* somebody building a scraper against you needs in order to know what to fix next.
|
|
49
|
+
* Switching it off leaves the feed — what happened, to whom, and what was done —
|
|
50
|
+
* and drops the evidence from the wire, not just from the screen.
|
|
51
|
+
*/
|
|
52
|
+
evidence?: boolean;
|
|
53
|
+
/** The actor drill-down, the busiest-actors panel, and per-request actor history. Default true. */
|
|
54
|
+
actors?: boolean;
|
|
55
|
+
/** The Statistics tab and the counter tiles above it. Default true. */
|
|
56
|
+
statistics?: boolean;
|
|
57
|
+
/** The traffic audit panel — the window against its baseline. Default true. */
|
|
58
|
+
audit?: boolean;
|
|
59
|
+
/** The notices panel: startup warnings, audit anomalies, detector errors. Default true. */
|
|
60
|
+
notices?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* The changes panel: what was applied at runtime, when, and by whom. Default true.
|
|
63
|
+
*
|
|
64
|
+
* The same list the traffic timeline marks. It is an audit trail rather than a log —
|
|
65
|
+
* bounded and in memory, gone with the process — so treat the panel as the convenient
|
|
66
|
+
* copy and `policy-change`, `guard-change`, `range-change` and `actor-change` as the
|
|
67
|
+
* durable one.
|
|
68
|
+
*/
|
|
69
|
+
changes?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The Actors screen: the busiest actors the *registry* is holding, which is a far
|
|
72
|
+
* larger population than the feed's ring. Default true, and it follows `actors`.
|
|
73
|
+
*/
|
|
74
|
+
registry?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The request tester: paste a User-Agent, a curl command or a raw header block and
|
|
77
|
+
* see what the engine would make of it. Default true.
|
|
78
|
+
*
|
|
79
|
+
* It runs a dry-run assessment — nothing is recorded, no counter moves — so it
|
|
80
|
+
* answers "why is this client being challenged?" without waiting for that client to
|
|
81
|
+
* come back. It shows evidence, so it follows the `evidence` section too.
|
|
82
|
+
*/
|
|
83
|
+
tester?: boolean;
|
|
84
|
+
/** The Policy tab: the rules, the preview, the editor, the settings export. Default true. */
|
|
85
|
+
policy?: boolean;
|
|
86
|
+
/** The range sets panel, and the allowlist controls on it. Default true. */
|
|
87
|
+
ranges?: boolean;
|
|
88
|
+
/** The guard panel inside the Policy tab. Default true; see {@link DashboardControls.editGuard}. */
|
|
89
|
+
guard?: boolean;
|
|
90
|
+
/** The `robots.txt` the policy implies. Default true. */
|
|
91
|
+
robots?: boolean;
|
|
92
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type { ActionName } from "../policy/types.js";
|
|
3
|
+
import type { DashboardSections } from "./sections.js";
|
|
3
4
|
import type { Assessment, BotClass, Certainty, EvidenceDirection, Verdict } from "../types.js";
|
|
5
|
+
export type { DashboardSections };
|
|
4
6
|
/**
|
|
5
7
|
* How a viewer proves they are allowed to read the dashboard.
|
|
6
8
|
*
|
|
@@ -186,88 +188,6 @@ export interface DashboardControls {
|
|
|
186
188
|
*/
|
|
187
189
|
reset?: boolean;
|
|
188
190
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Which parts of the dashboard exist on this listener.
|
|
191
|
-
*
|
|
192
|
-
* Everything defaults to on, and turning something off removes it from the page *and*
|
|
193
|
-
* from the server: the tab is gone, the panel is gone, the endpoint behind it answers
|
|
194
|
-
* 403, and fields a switched-off section would have shown are dropped before the data
|
|
195
|
-
* leaves the process. A viewer with devtools open sees exactly what the page sees.
|
|
196
|
-
*
|
|
197
|
-
* **There is no role model in here, deliberately.** `controls` says what a viewer may
|
|
198
|
-
* *do*; this says what a viewer may *see*; and both are fixed when the listener
|
|
199
|
-
* starts, which is what makes them cheap to reason about — no per-request evaluation,
|
|
200
|
-
* no session store, no second copy of your user table quietly disagreeing with the
|
|
201
|
-
* first. Roles are yours: run a listener per role, put your own `auth.authorize`
|
|
202
|
-
* predicate in front of each, and give each the sections and controls that role should
|
|
203
|
-
* have. `npm run demo:roles` is that arrangement, working.
|
|
204
|
-
*
|
|
205
|
-
* ```ts
|
|
206
|
-
* // An analyst sees traffic, not the reasons behind it — the evidence panel names
|
|
207
|
-
* // the exact signal that fired, which is a tuning guide for whoever is scraping you.
|
|
208
|
-
* await botHandler.serveDashboard({
|
|
209
|
-
* port: 9684,
|
|
210
|
-
* auth: { authorize: (req) => roleOf(req) === "analyst" },
|
|
211
|
-
* sections: { evidence: false, policy: false },
|
|
212
|
-
* redact: { maskIp: true },
|
|
213
|
-
* });
|
|
214
|
-
* ```
|
|
215
|
-
*/
|
|
216
|
-
export interface DashboardSections {
|
|
217
|
-
/** The Live feed tab, and the `/api/feed` and `/api/stream` endpoints behind it. Default true. */
|
|
218
|
-
feed?: boolean;
|
|
219
|
-
/**
|
|
220
|
-
* The case for each verdict: the evidence list with its written basis, the request
|
|
221
|
-
* headers, the query parameters, the detector failures, and the buttons that turn a
|
|
222
|
-
* row into a replay line or a corpus case. Default true.
|
|
223
|
-
*
|
|
224
|
-
* The one worth thinking about before sharing a dashboard widely. This is the half
|
|
225
|
-
* of the page that says *which detector fired and why*, which is precisely what
|
|
226
|
-
* somebody building a scraper against you needs in order to know what to fix next.
|
|
227
|
-
* Switching it off leaves the feed — what happened, to whom, and what was done —
|
|
228
|
-
* and drops the evidence from the wire, not just from the screen.
|
|
229
|
-
*/
|
|
230
|
-
evidence?: boolean;
|
|
231
|
-
/** The actor drill-down, the busiest-actors panel, and per-request actor history. Default true. */
|
|
232
|
-
actors?: boolean;
|
|
233
|
-
/** The Statistics tab and the counter tiles above it. Default true. */
|
|
234
|
-
statistics?: boolean;
|
|
235
|
-
/** The traffic audit panel — the window against its baseline. Default true. */
|
|
236
|
-
audit?: boolean;
|
|
237
|
-
/** The notices panel: startup warnings, audit anomalies, detector errors. Default true. */
|
|
238
|
-
notices?: boolean;
|
|
239
|
-
/**
|
|
240
|
-
* The changes panel: what was applied at runtime, when, and by whom. Default true.
|
|
241
|
-
*
|
|
242
|
-
* The same list the traffic timeline marks. It is an audit trail rather than a log —
|
|
243
|
-
* bounded and in memory, gone with the process — so treat the panel as the convenient
|
|
244
|
-
* copy and `policy-change`, `guard-change`, `range-change` and `actor-change` as the
|
|
245
|
-
* durable one.
|
|
246
|
-
*/
|
|
247
|
-
changes?: boolean;
|
|
248
|
-
/**
|
|
249
|
-
* The Actors screen: the busiest actors the *registry* is holding, which is a far
|
|
250
|
-
* larger population than the feed's ring. Default true, and it follows `actors`.
|
|
251
|
-
*/
|
|
252
|
-
registry?: boolean;
|
|
253
|
-
/**
|
|
254
|
-
* The request tester: paste a User-Agent, a curl command or a raw header block and
|
|
255
|
-
* see what the engine would make of it. Default true.
|
|
256
|
-
*
|
|
257
|
-
* It runs a dry-run assessment — nothing is recorded, no counter moves — so it
|
|
258
|
-
* answers "why is this client being challenged?" without waiting for that client to
|
|
259
|
-
* come back. It shows evidence, so it follows the `evidence` section too.
|
|
260
|
-
*/
|
|
261
|
-
tester?: boolean;
|
|
262
|
-
/** The Policy tab: the rules, the preview, the editor, the settings export. Default true. */
|
|
263
|
-
policy?: boolean;
|
|
264
|
-
/** The range sets panel, and the allowlist controls on it. Default true. */
|
|
265
|
-
ranges?: boolean;
|
|
266
|
-
/** The guard panel inside the Policy tab. Default true; see {@link DashboardControls.editGuard}. */
|
|
267
|
-
guard?: boolean;
|
|
268
|
-
/** The `robots.txt` the policy implies. Default true. */
|
|
269
|
-
robots?: boolean;
|
|
270
|
-
}
|
|
271
191
|
/** How much of each request the dashboard is allowed to show. */
|
|
272
192
|
export interface DashboardRedaction {
|
|
273
193
|
/**
|
|
@@ -1,5 +1,38 @@
|
|
|
1
|
-
import type { Detector } from "./types.js";
|
|
1
|
+
import type { Detector, DetectionContext } from "./types.js";
|
|
2
|
+
import type { BotSignature } from "./known-bots.js";
|
|
3
|
+
/**
|
|
4
|
+
* What an operator's own check concluded about a claimed identity.
|
|
5
|
+
*
|
|
6
|
+
* Three answers, and the third is not a formality. "I could not tell" has to be
|
|
7
|
+
* expressible and has to mean *silence* — a verifier that returned false for both "this
|
|
8
|
+
* is a forgery" and "my key server timed out" would turn an outage into an accusation.
|
|
9
|
+
*/
|
|
10
|
+
export type VerificationOutcome = "verified" | "refuted" | "unknown";
|
|
11
|
+
/**
|
|
12
|
+
* Your own answer to "is this really who it says it is".
|
|
13
|
+
*
|
|
14
|
+
* Called with the same context the built-in checks get, for one claimed signature. It
|
|
15
|
+
* may be async: the natural implementations are a lookup or a signature check.
|
|
16
|
+
*/
|
|
17
|
+
export type CrawlerVerifier = (ctx: DetectionContext, signature: BotSignature) => VerificationOutcome | Promise<VerificationOutcome>;
|
|
2
18
|
export interface CrawlerVerificationOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Verifiers of your own, by signature id — `{ googlebot: ..., gptbot: ... }`.
|
|
21
|
+
*
|
|
22
|
+
* Most of this database cannot be checked from inside a request: the operator
|
|
23
|
+
* publishes no DNS proof and no range list, and the claim is simply unfalsifiable.
|
|
24
|
+
* That is most bots, and until now it meant the library had nothing to offer an
|
|
25
|
+
* operator who *could* check — because their CDN had already verified the crawler and
|
|
26
|
+
* said so in a header, because the bot signs its requests, or because they hold the
|
|
27
|
+
* ASN data. Writing a whole detector to say so meant reimplementing the confirm and
|
|
28
|
+
* refute semantics in this file, including the part where an inconclusive answer must
|
|
29
|
+
* stay silent.
|
|
30
|
+
*
|
|
31
|
+
* A verifier here runs before the built-in check for that signature and a definite
|
|
32
|
+
* answer settles it, which also means no DNS lookup. `unknown` falls through to
|
|
33
|
+
* whatever this library can do on its own.
|
|
34
|
+
*/
|
|
35
|
+
verifiers?: Readonly<Record<string, CrawlerVerifier>>;
|
|
3
36
|
/**
|
|
4
37
|
* Treat an address with no PTR record as a forged claim. Default true.
|
|
5
38
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CrawlerVerificationOptions } from "./crawler-verification.js";
|
|
1
2
|
import type { Detector } from "./types.js";
|
|
2
3
|
export type { DetectionContext, Detector, DetectorResult } from "./types.js";
|
|
3
4
|
export { evidence, absenceIsMeaningful } from "./types.js";
|
|
@@ -53,4 +54,6 @@ export type { BotSignature, BotCategory, Verification } from "./known-bots.js";
|
|
|
53
54
|
* `clearanceDetector` is not here either, because it needs the challenge service —
|
|
54
55
|
* the engine adds it automatically once `challenge.secrets` is configured.
|
|
55
56
|
*/
|
|
56
|
-
export declare function defaultDetectors(
|
|
57
|
+
export declare function defaultDetectors(options?: {
|
|
58
|
+
crawlerVerification?: CrawlerVerificationOptions;
|
|
59
|
+
}): Detector[];
|
|
@@ -16,7 +16,35 @@ import { MultiPatternMatcher } from "../internal/matcher.js";
|
|
|
16
16
|
*/
|
|
17
17
|
export type BotCategory = "search" | "ai" | "seo" | "social" | "monitoring" | "archive" | "feed" | "security" | "advertising" | "library" | "headless"
|
|
18
18
|
/** A real browser engine embedded in a desktop application, with a person driving it. */
|
|
19
|
-
| "embedded"
|
|
19
|
+
| "embedded"
|
|
20
|
+
/**
|
|
21
|
+
* Price, stock and catalogue collection: comparison shopping, marketplace feeds,
|
|
22
|
+
* repricing tools.
|
|
23
|
+
*
|
|
24
|
+
* Its own category because it is the one kind of crawling a shop has a commercial
|
|
25
|
+
* opinion about rather than a technical one. It is not `seo` — nothing here is
|
|
26
|
+
* auditing your site for you — and it is not `scraper`, which is a *behavioural*
|
|
27
|
+
* verdict this library reaches on its own. This is a client that says what it is.
|
|
28
|
+
*/
|
|
29
|
+
| "commerce"
|
|
30
|
+
/**
|
|
31
|
+
* Accessibility auditing: contrast, landmarks, ARIA, WCAG conformance.
|
|
32
|
+
*
|
|
33
|
+
* Separated from `monitoring` because the answer is almost always different. A site
|
|
34
|
+
* owner who blocks uptime probes still wants the tool their accessibility team runs
|
|
35
|
+
* to reach the page, and frequently does not know it is arriving as a bot at all.
|
|
36
|
+
*/
|
|
37
|
+
| "accessibility"
|
|
38
|
+
/**
|
|
39
|
+
* Research and measurement: universities, internet-measurement projects, plagiarism
|
|
40
|
+
* and citation indexes.
|
|
41
|
+
*
|
|
42
|
+
* Distinct from `ai` on purpose. Both read the whole page and neither sends a person,
|
|
43
|
+
* but the decision differs: an operator refusing to feed a commercial model may be
|
|
44
|
+
* perfectly happy to appear in a citation index, and folding the two together forces
|
|
45
|
+
* one answer onto two questions.
|
|
46
|
+
*/
|
|
47
|
+
| "academic" | "other";
|
|
20
48
|
/** Every category, for anything that has to enumerate them — a rule editor, a report. */
|
|
21
49
|
export declare const BOT_CATEGORIES: readonly BotCategory[];
|
|
22
50
|
/**
|
|
@@ -40,6 +68,26 @@ export type Verification = {
|
|
|
40
68
|
} | {
|
|
41
69
|
kind: "ip-ranges";
|
|
42
70
|
publishedAt?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The operator publishes a proof this library cannot check by itself, and you can.
|
|
74
|
+
*
|
|
75
|
+
* A signed request under [Web Bot Auth](https://www.rfc-editor.org/rfc/rfc9421), a
|
|
76
|
+
* CDN that has already verified the crawler and says so in a header it adds, an ASN
|
|
77
|
+
* lookup against data you hold — all of them are conclusive, and none of them are
|
|
78
|
+
* something a detection library should be doing on its own: two need a network
|
|
79
|
+
* dependency and the third needs a key it has no business fetching.
|
|
80
|
+
*
|
|
81
|
+
* So the claim is marked verifiable-by-you, and stays *unverified* until you supply a
|
|
82
|
+
* verifier for it in `crawlerVerification.verifiers`. Marked and unsupplied behaves
|
|
83
|
+
* exactly like `none`: neither confirmed nor accused.
|
|
84
|
+
*
|
|
85
|
+
* `via` names the mechanism, for the operator reading this table to know what they
|
|
86
|
+
* would have to write.
|
|
87
|
+
*/
|
|
88
|
+
| {
|
|
89
|
+
kind: "proof";
|
|
90
|
+
via: string;
|
|
43
91
|
} | {
|
|
44
92
|
kind: "none";
|
|
45
93
|
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element's decisions, with no document in sight.
|
|
3
|
+
*
|
|
4
|
+
* `src/element/` is excluded from the coverage report for the same reason
|
|
5
|
+
* `src/dashboard/client/` is — it needs a document and a custom-element registry, and a
|
|
6
|
+
* number collected from a runner that cannot execute it would describe the runner. That
|
|
7
|
+
* exclusion carries a second half in the client's case, and it was missing here: the pure
|
|
8
|
+
* modules inside an excluded directory still get unit tests, they are simply not counted.
|
|
9
|
+
* This is that module for the element.
|
|
10
|
+
*
|
|
11
|
+
* Everything below answers a question with an argument rather than by looking at the page:
|
|
12
|
+
* which screens survive a config, what a status code means, where the handler is mounted,
|
|
13
|
+
* whether two configs describe the same thing. It is also where the fiddly parts live —
|
|
14
|
+
* `tabs` and `hide` naming different vocabularies, a screen the server withheld — which is
|
|
15
|
+
* exactly the logic that had bugs in it and no test able to reach them.
|
|
16
|
+
*/
|
|
17
|
+
import type { DashboardSections } from "../dashboard/sections.js";
|
|
18
|
+
export type BotDashboardTabId = "live" | "actors" | "stats" | "policy";
|
|
19
|
+
export interface BotDashboardTab {
|
|
20
|
+
id: BotDashboardTabId;
|
|
21
|
+
/** Shown in the tab strip. Defaults to the built-in name. */
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
/** A panel of your own, rendered beside the built-in ones. */
|
|
25
|
+
export interface BotDashboardPanel {
|
|
26
|
+
id: string;
|
|
27
|
+
/** Which screen it appears on. */
|
|
28
|
+
screen: BotDashboardTabId;
|
|
29
|
+
title: string;
|
|
30
|
+
/**
|
|
31
|
+
* Where its rows come from: a URL returning `{ rows: [{ label, value, note? }] }`, or a
|
|
32
|
+
* function returning the same shape.
|
|
33
|
+
*
|
|
34
|
+
* Rendered as text, always — the same rule the rest of the client follows, and for the
|
|
35
|
+
* same reason. Nothing here interprets markup, so a value that happens to contain a tag
|
|
36
|
+
* appears as that tag rather than becoming one.
|
|
37
|
+
*/
|
|
38
|
+
source: string | (() => Promise<BotDashboardRows> | BotDashboardRows);
|
|
39
|
+
/** How often to refresh, ms. Omit for once on load. */
|
|
40
|
+
refreshMs?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface BotDashboardRows {
|
|
43
|
+
rows: ReadonlyArray<{
|
|
44
|
+
label: string;
|
|
45
|
+
value: string | number;
|
|
46
|
+
note?: string;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
export interface BotDashboardTheme {
|
|
50
|
+
/**
|
|
51
|
+
* Token overrides, by custom property name without the leading dashes:
|
|
52
|
+
* `{ accent: "#7c3aed", surface: "#fff" }`.
|
|
53
|
+
*
|
|
54
|
+
* Applied to the host, so they cascade into the shadow root the same way the built-in
|
|
55
|
+
* tokens do. Every token the stylesheet defines can be replaced; the contrast
|
|
56
|
+
* guarantees the built-in palette was measured against are yours to keep once you do.
|
|
57
|
+
*/
|
|
58
|
+
tokens?: Record<string, string>;
|
|
59
|
+
/** Force a scheme instead of following the host page and the OS. */
|
|
60
|
+
scheme?: "light" | "dark";
|
|
61
|
+
/** `compact` tightens the row and panel padding. */
|
|
62
|
+
density?: "comfortable" | "compact";
|
|
63
|
+
}
|
|
64
|
+
export interface BotDashboardConfig {
|
|
65
|
+
/** Where the dashboard handler is mounted. Also settable as the `src` attribute. */
|
|
66
|
+
src?: string;
|
|
67
|
+
/** Which screens appear, in which order, under which labels. */
|
|
68
|
+
tabs?: readonly BotDashboardTab[];
|
|
69
|
+
/** Panels of your own. */
|
|
70
|
+
panels?: readonly BotDashboardPanel[];
|
|
71
|
+
theme?: BotDashboardTheme;
|
|
72
|
+
/**
|
|
73
|
+
* Hide parts of the page the *server* is still serving.
|
|
74
|
+
*
|
|
75
|
+
* Cosmetic, and worth being clear about: a section switched off here is removed from
|
|
76
|
+
* the screen and stays on the wire. The `sections` option on the handler is the one
|
|
77
|
+
* that stops the data leaving the process, and it is the one to use when the point is
|
|
78
|
+
* that somebody should not have it.
|
|
79
|
+
*/
|
|
80
|
+
hide?: DashboardSections;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The section flag that decides whether each screen exists.
|
|
84
|
+
*
|
|
85
|
+
* Omitting a tab has to be expressed this way rather than by deleting the element: the
|
|
86
|
+
* client builds its tab list from the sections it was booted with and then looks each one
|
|
87
|
+
* up, so a missing element is a crash rather than a smaller strip. Saying it in the boot
|
|
88
|
+
* object means the client removes the screen itself, the way it already does for a
|
|
89
|
+
* section the server switched off.
|
|
90
|
+
*/
|
|
91
|
+
export declare const TAB_SECTION: Record<BotDashboardTabId, keyof DashboardSections>;
|
|
92
|
+
/**
|
|
93
|
+
* Which sections the client should be booted with, and what to say about the config.
|
|
94
|
+
*
|
|
95
|
+
* Three inputs settle it, in this order: what the server offered, what `hide` removes, and
|
|
96
|
+
* what `tabs` narrows to. The warnings are returned rather than logged because the reasons
|
|
97
|
+
* a screen goes missing are the interesting part and the caller is the only one that knows
|
|
98
|
+
* whether it has already said them.
|
|
99
|
+
*/
|
|
100
|
+
export declare function resolveSections(fromServer: Readonly<Record<string, boolean>>, config: Pick<BotDashboardConfig, "hide" | "tabs">): {
|
|
101
|
+
sections: Record<string, boolean>;
|
|
102
|
+
warnings: string[];
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Where the handler is mounted, from a raw `src`.
|
|
106
|
+
*
|
|
107
|
+
* A trailing slash is dropped, and so are a query string and a fragment — the element
|
|
108
|
+
* appends `/api/bootstrap` to this, so anything after the path cannot survive that
|
|
109
|
+
* concatenation and never could. Left in, `src="/_bots?token=x"` failed with "it answered
|
|
110
|
+
* text/html — is createDashboardHandler mounted at /_bots?token=x?", which sends somebody
|
|
111
|
+
* to check the one thing that was right.
|
|
112
|
+
*/
|
|
113
|
+
export declare function parseMount(raw: string): {
|
|
114
|
+
base: string;
|
|
115
|
+
warning?: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* What a refusal from the handler actually means to whoever has to fix it.
|
|
119
|
+
*
|
|
120
|
+
* "it answered 401" is true and useless, and 401 is not an exotic case: the documented
|
|
121
|
+
* setup is a dashboard with `auth` set, embedded in an admin page, and a background fetch
|
|
122
|
+
* cannot put up the sign-in prompt that a browser would show for a navigation. So the
|
|
123
|
+
* dashboard is simply blank with a number on it, and the number does not say that the
|
|
124
|
+
* credentials the operator already has are the answer.
|
|
125
|
+
*/
|
|
126
|
+
export declare function explainStatus(status: number, base: string): string;
|
|
127
|
+
/**
|
|
128
|
+
* What of a config actually decides the rendered screens: which tabs, and which panels
|
|
129
|
+
* where. Deliberately not the panel `source` functions — a framework rebuilds those
|
|
130
|
+
* closures on every render, and a comparison that counted them would report a change on
|
|
131
|
+
* every render regardless of whether anything meaningful differed.
|
|
132
|
+
*/
|
|
133
|
+
export declare function shapeOf(config: BotDashboardConfig): string;
|
|
134
|
+
/** A panel cell, as text and never as markup, and never longer than a cell should be. */
|
|
135
|
+
export declare function cellText(value: unknown): string;
|