@fulldotdev/scan 0.1.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.
Files changed (55) hide show
  1. package/README.md +79 -0
  2. package/bin/fullscan.js +2 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +239 -0
  5. package/dist/engine/analysis.d.ts +2 -0
  6. package/dist/engine/analysis.js +747 -0
  7. package/dist/engine/browser-inspection.d.ts +143 -0
  8. package/dist/engine/browser-inspection.js +567 -0
  9. package/dist/engine/browser.d.ts +22 -0
  10. package/dist/engine/browser.js +629 -0
  11. package/dist/engine/collect.d.ts +6 -0
  12. package/dist/engine/collect.js +359 -0
  13. package/dist/engine/crawl-scope.d.ts +22 -0
  14. package/dist/engine/crawl-scope.js +145 -0
  15. package/dist/engine/env.d.ts +1 -0
  16. package/dist/engine/env.js +3 -0
  17. package/dist/engine/html.d.ts +307 -0
  18. package/dist/engine/html.js +645 -0
  19. package/dist/engine/language.d.ts +13 -0
  20. package/dist/engine/language.js +75 -0
  21. package/dist/engine/lighthouse-evidence.d.ts +36 -0
  22. package/dist/engine/lighthouse-evidence.js +69 -0
  23. package/dist/engine/lighthouse.d.ts +4 -0
  24. package/dist/engine/lighthouse.js +284 -0
  25. package/dist/engine/log.d.ts +1 -0
  26. package/dist/engine/log.js +4 -0
  27. package/dist/engine/network.d.ts +53 -0
  28. package/dist/engine/network.js +296 -0
  29. package/dist/engine/proxy.d.ts +8 -0
  30. package/dist/engine/proxy.js +95 -0
  31. package/dist/engine/run.d.ts +38 -0
  32. package/dist/engine/run.js +202 -0
  33. package/dist/engine/select.d.ts +6 -0
  34. package/dist/engine/select.js +38 -0
  35. package/dist/engine/site.d.ts +186 -0
  36. package/dist/engine/site.js +758 -0
  37. package/dist/engine/srcset.d.ts +1 -0
  38. package/dist/engine/srcset.js +31 -0
  39. package/dist/engine/state.d.ts +30 -0
  40. package/dist/engine/state.js +198 -0
  41. package/dist/engine/structured-data.d.ts +83 -0
  42. package/dist/engine/structured-data.js +331 -0
  43. package/dist/engine/types.d.ts +128 -0
  44. package/dist/engine/types.js +63 -0
  45. package/dist/engine.d.ts +1 -0
  46. package/dist/engine.js +1 -0
  47. package/dist/index.d.ts +8 -0
  48. package/dist/index.js +7 -0
  49. package/dist/report/build.d.ts +377 -0
  50. package/dist/report/build.js +2263 -0
  51. package/dist/report/evidence.d.ts +58 -0
  52. package/dist/report/evidence.js +192 -0
  53. package/dist/report/rules.d.ts +41 -0
  54. package/dist/report/rules.js +301 -0
  55. package/package.json +52 -0
@@ -0,0 +1,75 @@
1
+ // Written-language detection from stop words and language-switch link
2
+ // recognition. Both are heuristics; a result is only reported when it is
3
+ // clear, and never turned into an expectation about currency or shipping.
4
+ const stopWords = {
5
+ nl: "de het een en van is dat op te zijn voor met niet ook aan bij wij je om uw".split(" "),
6
+ en: "the and of to a in is that for with you on are as this our be by or it".split(" "),
7
+ de: "der die und das ist nicht mit sie ein eine für auf den dem wir ich zu auch sich im".split(" "),
8
+ fr: "le la les et des est une un pour dans que qui vous nous sur pas avec ce au du".split(" "),
9
+ es: "el la los las y de que en un una es para con por se del al como más".split(" "),
10
+ it: "il la di che e un una per con non sono del della gli le è come anche".split(" "),
11
+ pt: "o a os as de que e um uma para com não do da em por se mais como".split(" "),
12
+ };
13
+ export function detectLanguage(text) {
14
+ const tokens = text
15
+ .toLowerCase()
16
+ .split(/[^\p{L}]+/u)
17
+ .filter(Boolean);
18
+ if (tokens.length < 50)
19
+ return null;
20
+ const hits = Object.entries(stopWords).map(([lang, words]) => {
21
+ const set = new Set(words);
22
+ return [lang, tokens.filter((t) => set.has(t)).length];
23
+ });
24
+ hits.sort((a, b) => b[1] - a[1]);
25
+ const [best, second] = hits;
26
+ return best[1] >= 10 && best[1] >= second[1] * 2 ? best[0] : null;
27
+ }
28
+ const languageNames = {
29
+ english: "en",
30
+ nederlands: "nl",
31
+ dutch: "nl",
32
+ deutsch: "de",
33
+ german: "de",
34
+ français: "fr",
35
+ francais: "fr",
36
+ french: "fr",
37
+ español: "es",
38
+ espanol: "es",
39
+ spanish: "es",
40
+ italiano: "it",
41
+ italian: "it",
42
+ português: "pt",
43
+ portugues: "pt",
44
+ portuguese: "pt",
45
+ svenska: "sv",
46
+ dansk: "da",
47
+ norsk: "no",
48
+ suomi: "fi",
49
+ polski: "pl",
50
+ čeština: "cs",
51
+ 日本語: "ja",
52
+ 中文: "zh",
53
+ };
54
+ export function languageOfTag(tag) {
55
+ return tag ? tag.toLowerCase().split(/[-_]/)[0] : null;
56
+ }
57
+ // Links that switch language: an hreflang attribute, a language name or a
58
+ // two-letter code as the visible text, or a language-prefixed path from a
59
+ // menu that names the language.
60
+ export function languageLinks(links) {
61
+ return links.flatMap((link) => {
62
+ if (!link.href)
63
+ return [];
64
+ const text = (link.text ?? "").trim().toLowerCase();
65
+ const expected = languageOfTag(link.hreflang) ??
66
+ languageNames[text] ??
67
+ (/^[a-z]{2}(-[a-z]{2})?$/.test(text) &&
68
+ Object.values(languageNames).includes(text.slice(0, 2))
69
+ ? text.slice(0, 2)
70
+ : null);
71
+ return expected
72
+ ? [{ url: link.href, expected, text: link.text, location: link.location }]
73
+ : [];
74
+ });
75
+ }
@@ -0,0 +1,36 @@
1
+ export declare function lighthouseEvidence(lhr: any, detailed?: boolean): {
2
+ failedAudits: any[];
3
+ diagnostics: {
4
+ [k: string]: any;
5
+ } | undefined;
6
+ lcp: {
7
+ element: any;
8
+ checks: any;
9
+ phases: {
10
+ [k: string]: any;
11
+ };
12
+ timingBasis: string;
13
+ source: string;
14
+ };
15
+ };
16
+ export declare function measurementSpread(runs: any[], requested: number, reason: string | null): {
17
+ requested: number;
18
+ completed: number;
19
+ valid: number;
20
+ reason: string | null;
21
+ incomplete: boolean;
22
+ performance: {
23
+ count: number;
24
+ min: number;
25
+ max: number | undefined;
26
+ median: number;
27
+ range: number;
28
+ } | null;
29
+ lcpMs: {
30
+ count: number;
31
+ min: number;
32
+ max: number | undefined;
33
+ median: number;
34
+ range: number;
35
+ } | null;
36
+ };
@@ -0,0 +1,69 @@
1
+ export function lighthouseEvidence(lhr, detailed = true) {
2
+ const audits = lhr.audits ?? {};
3
+ const retained = Object.values(audits).filter((a) => (typeof a.score === "number" && a.score < 1) ||
4
+ a.scoreDisplayMode === "error");
5
+ const diagnostics = Object.fromEntries([
6
+ "lcp-breakdown-insight",
7
+ "lcp-discovery-insight",
8
+ "largest-contentful-paint-element",
9
+ "render-blocking-insight",
10
+ "render-blocking-resources",
11
+ "image-delivery-insight",
12
+ "font-display-insight",
13
+ "layout-shifts-insight",
14
+ "network-dependency-tree-insight",
15
+ ]
16
+ .filter((id) => audits[id])
17
+ .map((id) => [id, audits[id]]));
18
+ const discoveryItems = audits["lcp-discovery-insight"]?.details?.items ?? [];
19
+ const breakdownItems = audits["lcp-breakdown-insight"]?.details?.items ?? [];
20
+ const checks = discoveryItems.find((i) => i.type === "checklist")?.items ?? null;
21
+ const phases = Object.fromEntries((breakdownItems.find((i) => i.type === "table")?.items ?? []).map((i) => [i.subpart, i.duration]));
22
+ return {
23
+ failedAudits: detailed
24
+ ? retained
25
+ : retained.map((a) => ({
26
+ id: a.id,
27
+ title: a.title,
28
+ score: a.score,
29
+ displayValue: a.displayValue,
30
+ errorMessage: a.errorMessage,
31
+ })),
32
+ diagnostics: detailed ? diagnostics : undefined,
33
+ lcp: {
34
+ element: [...discoveryItems, ...breakdownItems].find((i) => i.type === "node") ?? null,
35
+ checks,
36
+ phases,
37
+ timingBasis: "Observed trace timings, not the simulated LCP used in the performance score",
38
+ source: "Lighthouse trace; applies to image and background-image LCP",
39
+ },
40
+ };
41
+ }
42
+ export function measurementSpread(runs, requested, reason) {
43
+ const spread = (values) => {
44
+ const sorted = values.filter(Number.isFinite).sort((a, b) => a - b);
45
+ return sorted.length
46
+ ? {
47
+ count: sorted.length,
48
+ min: sorted[0],
49
+ max: sorted.at(-1),
50
+ median: sorted.length % 2
51
+ ? sorted[Math.floor(sorted.length / 2)]
52
+ : (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2,
53
+ range: sorted.at(-1) - sorted[0],
54
+ }
55
+ : null;
56
+ };
57
+ return {
58
+ requested,
59
+ completed: runs.length,
60
+ valid: runs.filter((r) => !r.runtimeError && !r.auditErrors?.length && !r.missingScores?.length).length,
61
+ reason,
62
+ incomplete: runs.length < requested ||
63
+ runs.some((r) => r.runtimeError || r.auditErrors?.length || r.missingScores?.length),
64
+ performance: spread(runs.map((r) => typeof r.scores?.performance === "number"
65
+ ? r.scores.performance * 100
66
+ : NaN)),
67
+ lcpMs: spread(runs.map((r) => r.metrics?.["largest-contentful-paint"]?.numericValue ?? NaN)),
68
+ };
69
+ }
@@ -0,0 +1,4 @@
1
+ import type { BrowserSession } from "./browser.js";
2
+ import { HttpClient } from "./network.js";
3
+ import { type JobResult, type Scan } from "./types.js";
4
+ export declare function collectLighthouse(scan: Scan, url: string, http: HttpClient, session: BrowserSession): Promise<JobResult>;
@@ -0,0 +1,284 @@
1
+ import { lighthouseEvidence, measurementSpread, } from "./lighthouse-evidence.js";
2
+ import { resolvePublic, userAgent } from "./network.js";
3
+ import { errorMessage } from "./types.js";
4
+ // Lighthouse on the rotating sample: mobile and desktop, repeats in deep
5
+ // scans, the mobile final screenshot as artifact. Separate from the per-page
6
+ // browser pass because two navigations per device make it the most
7
+ // expensive measurement.
8
+ export async function collectLighthouse(scan, url, http, session) {
9
+ await resolvePublic(new URL(url).hostname);
10
+ const robots = await http.getRobots(url);
11
+ if (!robots.allowed ||
12
+ robots.parser?.isAllowed(url, "FulldevScan") === false)
13
+ return {
14
+ observations: [
15
+ {
16
+ kind: "lighthouse-run",
17
+ key: url,
18
+ data: { status: "skipped", reason: "robots" },
19
+ },
20
+ ],
21
+ };
22
+ const { proxy, lighthouse, browser, args, chromeVersion } = session;
23
+ const blockedBefore = proxy.blocked.length;
24
+ const result = { observations: [], artifacts: [] };
25
+ const errors = [];
26
+ const jobStarted = Date.now();
27
+ const budgetSeconds = scan.options.deepScan ? 270 : 210;
28
+ const watchdog = setTimeout(() => {
29
+ errors.push({
30
+ stage: "budget",
31
+ error: `Lighthouse job exceeded ${budgetSeconds} seconds`,
32
+ });
33
+ void browser.close();
34
+ }, budgetSeconds * 1000);
35
+ watchdog.unref();
36
+ try {
37
+ const port = Number(new URL(browser.wsEndpoint()).port);
38
+ for (const device of ["mobile", "desktop"]) {
39
+ const measurements = [];
40
+ const screenshots = new Map();
41
+ let requestedRuns = 1;
42
+ let repeatReason = null;
43
+ for (let measurement = 0; measurement < requestedRuns; measurement++) {
44
+ if (measurement > 0 && Date.now() - jobStarted > 150000)
45
+ break;
46
+ try {
47
+ await http.pace(url);
48
+ const run = await lighthouse(url, {
49
+ port,
50
+ output: "json",
51
+ logLevel: "error",
52
+ onlyCategories: [
53
+ "performance",
54
+ "accessibility",
55
+ "best-practices",
56
+ "seo",
57
+ ],
58
+ maxWaitForLoad: 30000,
59
+ ...(device === "desktop"
60
+ ? {
61
+ formFactor: "desktop",
62
+ screenEmulation: {
63
+ mobile: false,
64
+ width: 1350,
65
+ height: 940,
66
+ deviceScaleFactor: 1,
67
+ disabled: false,
68
+ },
69
+ throttling: {
70
+ rttMs: 40,
71
+ throughputKbps: 10240,
72
+ cpuSlowdownMultiplier: 1,
73
+ requestLatencyMs: 0,
74
+ downloadThroughputKbps: 0,
75
+ uploadThroughputKbps: 0,
76
+ },
77
+ }
78
+ : {}),
79
+ extraHeaders: { "User-Agent": userAgent },
80
+ });
81
+ if (!run)
82
+ throw new Error("Lighthouse did not return a report");
83
+ const lhr = run.lhr;
84
+ const auditErrors = Object.entries(lhr.audits)
85
+ .filter(([, audit]) => audit.scoreDisplayMode === "error")
86
+ .map(([id, audit]) => ({ id, error: audit.errorMessage ?? null }));
87
+ const missingScores = Object.entries(lhr.categories)
88
+ .filter(([, category]) => category.score === null)
89
+ .map(([id]) => id);
90
+ const evidence = lighthouseEvidence(lhr, scan.options.deepScan);
91
+ const audit = (id) => lhr.audits[id] ?? null;
92
+ // Audits with measurable savings, largest first.
93
+ const opportunities = Object.values(lhr.audits)
94
+ .filter((a) => typeof a.score === "number" &&
95
+ a.score < 0.9 &&
96
+ (a.metricSavings ||
97
+ a.details?.overallSavingsMs ||
98
+ a.details?.overallSavingsBytes))
99
+ .map((a) => {
100
+ const savings = [
101
+ a.details?.overallSavingsMs ?? 0,
102
+ ...Object.values(a.metricSavings ?? {}).filter((n) => typeof n === "number"),
103
+ ];
104
+ return {
105
+ id: a.id,
106
+ title: a.title,
107
+ displayValue: a.displayValue ?? null,
108
+ savingsMs: Math.round(Math.max(...savings)) || null,
109
+ savingsBytes: a.details?.overallSavingsBytes
110
+ ? Math.round(a.details.overallSavingsBytes)
111
+ : null,
112
+ // Up to five affected files or elements as evidence.
113
+ items: (a.details?.items ?? [])
114
+ .slice(0, 5)
115
+ .map((item) => ({
116
+ url: item.url ?? item.entity ?? null,
117
+ node: item.node?.selector ?? item.node?.snippet ?? null,
118
+ wastedMs: item.wastedMs ?? null,
119
+ wastedBytes: item.wastedBytes ?? null,
120
+ totalBytes: item.totalBytes ?? null,
121
+ })),
122
+ };
123
+ })
124
+ .filter((o) => (o.savingsMs ?? 0) >= 100 || (o.savingsBytes ?? 0) >= 50000)
125
+ .sort((a, b) => (b.savingsMs ?? 0) - (a.savingsMs ?? 0))
126
+ .slice(0, 8);
127
+ const weight = Object.fromEntries((audit("resource-summary")?.details?.items ?? []).map((item) => [
128
+ item.resourceType,
129
+ {
130
+ bytes: item.transferSize ?? 0,
131
+ requests: item.requestCount ?? 0,
132
+ },
133
+ ]));
134
+ const unsizedImages = audit("unsized-images")?.details?.items?.length ?? 0;
135
+ const screenshot = audit("final-screenshot")?.details?.data;
136
+ if (device === "mobile" && typeof screenshot === "string") {
137
+ const match = /^data:(image\/\w+);base64,(.+)$/.exec(screenshot);
138
+ if (match)
139
+ screenshots.set(lhr.fetchTime, {
140
+ kind: "screenshot",
141
+ key: url,
142
+ body: Buffer.from(match[2], "base64"),
143
+ contentType: match[1],
144
+ });
145
+ }
146
+ measurements.push({
147
+ ...evidence,
148
+ url,
149
+ device,
150
+ lighthouseVersion: lhr.lighthouseVersion,
151
+ chromeVersion,
152
+ fetchTime: lhr.fetchTime,
153
+ requestedUrl: lhr.requestedUrl,
154
+ finalDisplayedUrl: lhr.finalDisplayedUrl,
155
+ runtimeError: lhr.runtimeError ?? null,
156
+ auditErrors,
157
+ missingScores,
158
+ launchArgs: args,
159
+ runWarnings: lhr.runWarnings,
160
+ scores: Object.fromEntries(Object.entries(lhr.categories).map(([k, v]) => [k, v.score])),
161
+ metrics: Object.fromEntries([
162
+ "first-contentful-paint",
163
+ "largest-contentful-paint",
164
+ "cumulative-layout-shift",
165
+ "total-blocking-time",
166
+ "speed-index",
167
+ "interactive",
168
+ ].map((id) => [id, audit(id)])),
169
+ configSettings: lhr.configSettings,
170
+ environment: lhr.environment,
171
+ timing: lhr.timing,
172
+ networkRequests: scan.options.deepScan
173
+ ? audit("network-requests")
174
+ : undefined,
175
+ resourceSummary: scan.options.deepScan
176
+ ? audit("resource-summary")
177
+ : undefined,
178
+ thirdParties: scan.options.deepScan
179
+ ? (audit("third-parties-insight") ?? audit("third-party-summary"))
180
+ : undefined,
181
+ opportunities,
182
+ weight,
183
+ unsizedImages,
184
+ note: scan.options.deepScan
185
+ ? "Deep lab scan through public-egress proxy. Full failed audit details retained per run."
186
+ : "Routine lab scan through public-egress proxy. Compact failed audit summaries; detailed diagnostics require a deep scan.",
187
+ });
188
+ if (measurement === 0 &&
189
+ scan.options.deepScan &&
190
+ (lhr.runtimeError ||
191
+ auditErrors.length ||
192
+ missingScores.length ||
193
+ scan.options.lighthouseRepeatBelow === undefined ||
194
+ (typeof lhr.categories.performance?.score === "number" &&
195
+ lhr.categories.performance.score * 100 <
196
+ scan.options.lighthouseRepeatBelow))) {
197
+ requestedRuns = scan.options.lighthouseMaxRuns;
198
+ repeatReason =
199
+ lhr.runtimeError || auditErrors.length || missingScores.length
200
+ ? "incomplete-audit"
201
+ : scan.options.lighthouseRepeatBelow === undefined
202
+ ? "deep-scan"
203
+ : `performance-below-${scan.options.lighthouseRepeatBelow}`;
204
+ }
205
+ if (lhr.runtimeError)
206
+ errors.push({
207
+ stage: `lighthouse-${device}`,
208
+ error: lhr.runtimeError,
209
+ });
210
+ else if (auditErrors.length || missingScores.length)
211
+ errors.push({
212
+ stage: `lighthouse-${device}`,
213
+ error: "Lighthouse returned incomplete audits or category scores",
214
+ auditErrors,
215
+ missingScores,
216
+ });
217
+ }
218
+ catch (error) {
219
+ if (measurement === 0 && scan.options.deepScan) {
220
+ requestedRuns = scan.options.lighthouseMaxRuns;
221
+ repeatReason = "lighthouse-error";
222
+ }
223
+ measurements.push({
224
+ url,
225
+ device,
226
+ scores: {},
227
+ metrics: {},
228
+ runtimeError: { message: errorMessage(error) },
229
+ });
230
+ errors.push({
231
+ stage: `lighthouse-${device}`,
232
+ error: errorMessage(error),
233
+ });
234
+ }
235
+ }
236
+ if (measurements.length) {
237
+ const spread = measurementSpread(measurements, requestedRuns, repeatReason);
238
+ const sorted = [...measurements]
239
+ .filter((m) => !m.runtimeError && typeof m.scores?.performance === "number")
240
+ .sort((a, b) => a.scores.performance - b.scores.performance);
241
+ const representative = sorted[Math.floor(sorted.length / 2)] ?? measurements[0];
242
+ const screenshot = screenshots.get(representative.fetchTime);
243
+ if (screenshot)
244
+ result.artifacts.push(screenshot);
245
+ result.observations.push({
246
+ kind: "lighthouse",
247
+ key: `${url}|${device}`,
248
+ data: {
249
+ ...representative,
250
+ ...(scan.options.deepScan
251
+ ? {
252
+ failedAudits: undefined,
253
+ diagnostics: undefined,
254
+ networkRequests: undefined,
255
+ resourceSummary: undefined,
256
+ thirdParties: undefined,
257
+ measurements,
258
+ representativeIndex: measurements.indexOf(representative),
259
+ }
260
+ : {}),
261
+ spread,
262
+ representative: "median performance run; all runs retained",
263
+ },
264
+ });
265
+ }
266
+ }
267
+ }
268
+ catch (error) {
269
+ errors.push({ stage: "lighthouse", error: errorMessage(error) });
270
+ }
271
+ finally {
272
+ clearTimeout(watchdog);
273
+ }
274
+ result.observations.push({
275
+ kind: "lighthouse-run",
276
+ key: url,
277
+ data: {
278
+ status: errors.length ? "partial" : "completed",
279
+ errors,
280
+ blockedNetwork: proxy.blocked.slice(blockedBefore),
281
+ },
282
+ });
283
+ return result;
284
+ }
@@ -0,0 +1 @@
1
+ export declare function log(event: string, fields?: Record<string, unknown>): void;
@@ -0,0 +1,4 @@
1
+ // One JSON line per event; Netlify function logs are the only debugging surface.
2
+ export function log(event, fields = {}) {
3
+ console.log(JSON.stringify({ event, time: new Date().toISOString(), ...fields }));
4
+ }
@@ -0,0 +1,53 @@
1
+ import { Agent } from "undici";
2
+ import { type HttpResult, type Options } from "./types.js";
3
+ export declare const userAgent: string;
4
+ declare const robotsParser: (url: string, body: string) => {
5
+ isAllowed: (url: string, agent?: string) => boolean | undefined;
6
+ getCrawlDelay: (agent?: string) => number | undefined;
7
+ };
8
+ export declare function sameSite(a: string, b: string): boolean;
9
+ export declare function normalizeUrl(input: string, base?: string): string;
10
+ export declare function slashTwin(url: string): string | null;
11
+ export declare function setLocalTargetsAllowed(allowed: boolean): void;
12
+ export declare function localTargets(): boolean;
13
+ export declare function publicAddress(address: string): boolean;
14
+ export declare function resolvePublic(hostname: string): Promise<{
15
+ address: string;
16
+ family: number;
17
+ }[]>;
18
+ type Robots = {
19
+ result: HttpResult;
20
+ parser: ReturnType<typeof robotsParser> | null;
21
+ allowed: boolean;
22
+ };
23
+ export declare class HttpSession {
24
+ robots: Map<string, Promise<Robots>>;
25
+ lastRequest: Map<string, number>;
26
+ slowdown: Map<string, number>;
27
+ private agents;
28
+ agent(origin: string, pinned: {
29
+ address: string;
30
+ family: number;
31
+ }, timeout: number): Agent;
32
+ close(): Promise<void>;
33
+ }
34
+ export declare class HttpClient {
35
+ readonly options: Options;
36
+ private resolver;
37
+ readonly deadline: number;
38
+ readonly session: HttpSession;
39
+ constructor(options: Options, resolver?: typeof resolvePublic, deadline?: number, session?: HttpSession);
40
+ get remainingMs(): number;
41
+ getRobots(url: string): Promise<Robots>;
42
+ get(input: string, settings?: {
43
+ accept?: string;
44
+ respectRobots?: boolean;
45
+ retry?: boolean;
46
+ maxBytes?: number;
47
+ }): Promise<HttpResult>;
48
+ private slowDown;
49
+ delayFor(origin: string): number;
50
+ pace(input: string): Promise<void>;
51
+ private request;
52
+ }
53
+ export {};