@apifuse/provider-sdk 2.2.0-beta.67 → 2.2.0-beta.69
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/AUTHORING.md +34 -2
- package/CHANGELOG.md +8 -0
- package/README.md +18 -7
- package/bin/apifuse-record.ts +81 -21
- package/bin/apifuse-submit-check.ts +170 -28
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +138 -23
- package/dist/cli/templates/provider/README.md.tpl +17 -9
- package/dist/config/loader.d.ts +16 -0
- package/dist/config/loader.js +25 -8
- package/dist/error-resolution.d.ts +13 -0
- package/dist/error-resolution.js +72 -35
- package/dist/fixture-sanitization.js +159 -15
- package/dist/lint.d.ts +2 -0
- package/dist/lint.js +549 -11
- package/dist/runtime/diagnostic-redactor.d.ts +6 -1
- package/dist/runtime/diagnostic-redactor.js +16 -14
- package/dist/runtime/executor.js +3 -0
- package/dist/runtime/http.d.ts +2 -0
- package/dist/runtime/http.js +17 -103
- package/dist/runtime/instrumentation.js +4 -1
- package/dist/runtime/provider.d.ts +7 -0
- package/dist/runtime/provider.js +14 -0
- package/dist/runtime/redirect-hop-headers.d.ts +19 -0
- package/dist/runtime/redirect-hop-headers.js +58 -0
- package/dist/runtime/redirects.d.ts +33 -1
- package/dist/runtime/redirects.js +113 -0
- package/dist/runtime/resolver-span-attributes.d.ts +34 -0
- package/dist/runtime/resolver-span-attributes.js +93 -0
- package/dist/runtime/resolver-usage.d.ts +8 -0
- package/dist/runtime/resolver-usage.js +5 -11
- package/dist/runtime/resolver-vendors/browser.js +5 -4
- package/dist/runtime/resolver-vendors/capsolver.js +11 -10
- package/dist/runtime/resolver-vendors/twocaptcha.js +9 -8
- package/dist/runtime/resolver.js +7 -6
- package/dist/runtime/stealth-owned-headers.d.ts +21 -8
- package/dist/runtime/stealth-owned-headers.js +28 -8
- package/dist/runtime/stealth-telemetry.d.ts +1 -1
- package/dist/runtime/stealth.js +158 -46
- package/dist/runtime/trace.js +2 -2
- package/dist/server/index.d.ts +1 -0
- package/dist/server/serve-implementation.d.ts +2 -1
- package/dist/server/serve-implementation.js +45 -30
- package/dist/server/state-backend-report.d.ts +70 -0
- package/dist/server/state-backend-report.js +113 -0
- package/dist/testing/run.js +14 -7
- package/dist/trace-sanitization.js +1 -1
- package/dist/types.d.ts +30 -6
- package/package.json +2 -2
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +138 -23
- package/src/cli/templates/provider/README.md.tpl +17 -9
- package/src/config/loader.ts +49 -12
- package/src/error-resolution.ts +89 -35
- package/src/fixture-sanitization.ts +175 -17
- package/src/lint.ts +595 -10
- package/src/runtime/diagnostic-redactor.ts +19 -13
- package/src/runtime/executor.ts +3 -0
- package/src/runtime/http.ts +24 -112
- package/src/runtime/instrumentation.ts +4 -1
- package/src/runtime/provider.ts +15 -0
- package/src/runtime/redirect-hop-headers.ts +71 -0
- package/src/runtime/redirects.ts +150 -1
- package/src/runtime/resolver-span-attributes.ts +139 -0
- package/src/runtime/resolver-usage.ts +8 -5
- package/src/runtime/resolver-vendors/browser.ts +5 -4
- package/src/runtime/resolver-vendors/capsolver.ts +11 -10
- package/src/runtime/resolver-vendors/twocaptcha.ts +9 -8
- package/src/runtime/resolver.ts +7 -6
- package/src/runtime/stealth-owned-headers.ts +34 -8
- package/src/runtime/stealth-telemetry.ts +6 -1
- package/src/runtime/stealth.ts +180 -58
- package/src/runtime/trace.ts +6 -2
- package/src/server/index.ts +4 -0
- package/src/server/serve-implementation.ts +58 -31
- package/src/server/state-backend-report.ts +185 -0
- package/src/testing/run.ts +15 -2
- package/src/trace-sanitization.ts +1 -1
- package/src/types.ts +30 -6
|
@@ -4,6 +4,14 @@ import type { TraceRecorder } from "./trace.js";
|
|
|
4
4
|
export declare const RESOLVER_PAID_USAGE_SPAN = "resolver.usage";
|
|
5
5
|
export type ResolverPaidUsageOutcome = "success" | "vendor_error" | "timeout" | "abandoned";
|
|
6
6
|
export type ResolverPaidUsageEndpoint = "capsolver:create_task" | "twocaptcha:create_task" | "hyper:ip" | "hyper:sbsd_create";
|
|
7
|
+
/**
|
|
8
|
+
* How one call to the endpoint is counted. `metered` endpoints are the vendors' task or
|
|
9
|
+
* payload creation calls: the SDK counts one unit per call (the vendor ledger stays
|
|
10
|
+
* authoritative for refunds of failed tasks). Hyper's `/ip` reflector is authenticated
|
|
11
|
+
* but has no published price, so it is recorded with 0 units and `billing: "unconfirmed"`
|
|
12
|
+
* rather than invented as a paid unit; the span still proves the call was made.
|
|
13
|
+
*/
|
|
14
|
+
export type ResolverPaidUsageBilling = "metered" | "unconfirmed";
|
|
7
15
|
/**
|
|
8
16
|
* Records one `resolver.usage` span around a paid vendor call. Callers validate credentials
|
|
9
17
|
* and transport first, so a span means a potentially billable request was attempted (a
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
+
import { resolverSpanAttributes } from "./resolver-span-attributes.js";
|
|
1
2
|
export const RESOLVER_PAID_USAGE_SPAN = "resolver.usage";
|
|
2
|
-
/**
|
|
3
|
-
* How one call to the endpoint is counted. `metered` endpoints are the vendors' task or
|
|
4
|
-
* payload creation calls: the SDK counts one unit per call (the vendor ledger stays
|
|
5
|
-
* authoritative for refunds of failed tasks). Hyper's `/ip` reflector is authenticated
|
|
6
|
-
* but has no published price, so it is recorded with 0 units and `billing: "unconfirmed"`
|
|
7
|
-
* rather than invented as a paid unit; the span still proves the call was made.
|
|
8
|
-
*/
|
|
9
3
|
const ENDPOINT_BILLING = {
|
|
10
4
|
"capsolver:create_task": { billable_units: 1, billing: "metered" },
|
|
11
5
|
"twocaptcha:create_task": { billable_units: 1, billing: "metered" },
|
|
@@ -42,7 +36,7 @@ export async function recordPaidResolverCreate(options) {
|
|
|
42
36
|
}
|
|
43
37
|
return options.create();
|
|
44
38
|
}
|
|
45
|
-
const baseAttributes = {
|
|
39
|
+
const baseAttributes = resolverSpanAttributes({
|
|
46
40
|
vendor: options.vendor,
|
|
47
41
|
challenge_kind: options.kind,
|
|
48
42
|
endpoint: options.endpoint,
|
|
@@ -51,10 +45,10 @@ export async function recordPaidResolverCreate(options) {
|
|
|
51
45
|
...(options.usage ? { vendor_index: options.usage.vendorIndex } : {}),
|
|
52
46
|
...(options.round === undefined ? {} : { round: options.round }),
|
|
53
47
|
resolver_identity_scope: options.usage?.resolverIdentityScope,
|
|
54
|
-
};
|
|
48
|
+
});
|
|
55
49
|
return options.traceRecorder.runSpan(RESOLVER_PAID_USAGE_SPAN, options.create, {
|
|
56
50
|
attributes: baseAttributes,
|
|
57
|
-
onSuccess: () => ({ outcome: "success" }),
|
|
58
|
-
onError: (error) => ({ outcome: errorOutcome(error, options.signal) }),
|
|
51
|
+
onSuccess: () => resolverSpanAttributes({ outcome: "success" }),
|
|
52
|
+
onError: (error) => resolverSpanAttributes({ outcome: errorOutcome(error, options.signal) }),
|
|
59
53
|
});
|
|
60
54
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isProviderError } from "../../errors.js";
|
|
2
2
|
import { createBrowserClient } from "../browser.js";
|
|
3
|
+
import { resolverSpanAttributes } from "../resolver-span-attributes.js";
|
|
3
4
|
import { resolverChallengeIssuingIdentity } from "./bindings.js";
|
|
4
5
|
import { assertResolverHostAllowed, normalizedResolverHostname } from "./hosts.js";
|
|
5
6
|
import { ResolverVendorUnavailableError, } from "./types.js";
|
|
@@ -141,15 +142,15 @@ async function runBoundedCleanup(cleanup, timeoutMs, challengeKind, operation, t
|
|
|
141
142
|
}
|
|
142
143
|
await traceRecorder
|
|
143
144
|
.runSpan("resolver.vendor.cleanup", boundedCleanup, {
|
|
144
|
-
attributes: {
|
|
145
|
+
attributes: resolverSpanAttributes({
|
|
145
146
|
vendor: BROWSER_VENDOR_ID,
|
|
146
147
|
challenge_kind: challengeKind,
|
|
147
148
|
operation,
|
|
148
|
-
},
|
|
149
|
+
}),
|
|
149
150
|
onError(error) {
|
|
150
|
-
return {
|
|
151
|
+
return resolverSpanAttributes({
|
|
151
152
|
error_message: error instanceof Error ? error.message : String(error),
|
|
152
|
-
};
|
|
153
|
+
});
|
|
153
154
|
},
|
|
154
155
|
})
|
|
155
156
|
.catch(() => undefined);
|
|
@@ -2,6 +2,7 @@ import { getStealthProfile } from "../../stealth/profiles.js";
|
|
|
2
2
|
import { redactSensitiveText } from "../request-options.js";
|
|
3
3
|
import { DEFAULT_STEALTH_PROFILE } from "../stealth.js";
|
|
4
4
|
import { recordPaidResolverCreate } from "../resolver-usage.js";
|
|
5
|
+
import { resolverSpanAttributes } from "../resolver-span-attributes.js";
|
|
5
6
|
import { assertResolverHostAllowed } from "./hosts.js";
|
|
6
7
|
import { ResolverChallengeVerdictError, ResolverVendorUnavailableError, recordResolverVendorPoll, resolverVendorSupports, } from "./types.js";
|
|
7
8
|
const CAPSOLVER_VENDOR_ID = "capsolver";
|
|
@@ -263,7 +264,7 @@ function endpoint(baseUrl, path) {
|
|
|
263
264
|
}
|
|
264
265
|
function spanErrorAttributes(error, phase) {
|
|
265
266
|
if (error instanceof ResolverVendorUnavailableError) {
|
|
266
|
-
return {
|
|
267
|
+
return resolverSpanAttributes({
|
|
267
268
|
unavailability_reason: error.reason,
|
|
268
269
|
transport_phase: error.phase,
|
|
269
270
|
...(error instanceof CapsolverUnavailableError
|
|
@@ -275,21 +276,21 @@ function spanErrorAttributes(error, phase) {
|
|
|
275
276
|
response_length: error.responseLength,
|
|
276
277
|
}
|
|
277
278
|
: {}),
|
|
278
|
-
};
|
|
279
|
+
});
|
|
279
280
|
}
|
|
280
281
|
if (error instanceof CapsolverVerdictError) {
|
|
281
|
-
return {
|
|
282
|
+
return resolverSpanAttributes({
|
|
282
283
|
verdict_reason: error.reason,
|
|
283
284
|
transport_phase: error.phase,
|
|
284
285
|
vendor_error_code: error.errorCode,
|
|
285
286
|
vendor_error_description: error.errorDescription,
|
|
286
|
-
};
|
|
287
|
+
});
|
|
287
288
|
}
|
|
288
289
|
if (error instanceof CapsolverSolveTimeoutError) {
|
|
289
|
-
return {
|
|
290
|
+
return resolverSpanAttributes({
|
|
290
291
|
unavailability_reason: "timeout",
|
|
291
292
|
transport_phase: phase,
|
|
292
|
-
};
|
|
293
|
+
});
|
|
293
294
|
}
|
|
294
295
|
return undefined;
|
|
295
296
|
}
|
|
@@ -436,10 +437,10 @@ export function createCapsolverResolverVendorAdapter(options) {
|
|
|
436
437
|
};
|
|
437
438
|
const taskId = traceRecorder
|
|
438
439
|
? await traceRecorder.runSpan("resolver.vendor.create_task", createTask, {
|
|
439
|
-
attributes: {
|
|
440
|
+
attributes: resolverSpanAttributes({
|
|
440
441
|
vendor: CAPSOLVER_VENDOR_ID,
|
|
441
442
|
challenge_kind: challenge.kind,
|
|
442
|
-
},
|
|
443
|
+
}),
|
|
443
444
|
onError: (error) => spanErrorAttributes(error, "create_task"),
|
|
444
445
|
})
|
|
445
446
|
: await createTask();
|
|
@@ -506,10 +507,10 @@ export function createCapsolverResolverVendorAdapter(options) {
|
|
|
506
507
|
};
|
|
507
508
|
return traceRecorder
|
|
508
509
|
? await traceRecorder.runSpan("resolver.vendor.poll_result", pollResult, {
|
|
509
|
-
attributes: {
|
|
510
|
+
attributes: resolverSpanAttributes({
|
|
510
511
|
vendor: CAPSOLVER_VENDOR_ID,
|
|
511
512
|
challenge_kind: challenge.kind,
|
|
512
|
-
},
|
|
513
|
+
}),
|
|
513
514
|
onError: (error) => spanErrorAttributes(error, "poll_result"),
|
|
514
515
|
})
|
|
515
516
|
: await pollResult();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { recordPaidResolverCreate } from "../resolver-usage.js";
|
|
2
|
+
import { resolverSpanAttributes } from "../resolver-span-attributes.js";
|
|
2
3
|
import { assertResolverHostAllowed } from "./hosts.js";
|
|
3
4
|
import { ResolverChallengeVerdictError, ResolverVendorUnavailableError, recordResolverVendorPoll, resolverVendorSupports, } from "./types.js";
|
|
4
5
|
const TWOCAPTCHA_VENDOR_ID = "2captcha";
|
|
@@ -198,19 +199,19 @@ function endpoint(baseUrl, path) {
|
|
|
198
199
|
}
|
|
199
200
|
function spanErrorAttributes(error, phase) {
|
|
200
201
|
if (error instanceof ResolverVendorUnavailableError) {
|
|
201
|
-
return {
|
|
202
|
+
return resolverSpanAttributes({
|
|
202
203
|
unavailability_reason: error.reason,
|
|
203
204
|
transport_phase: error.phase,
|
|
204
|
-
};
|
|
205
|
+
});
|
|
205
206
|
}
|
|
206
207
|
// The solve budget raises TwoCaptchaSolveTimeoutError inside the spanned closure and
|
|
207
208
|
// the outer catch converts it only after the span has been finalized, so the span has
|
|
208
209
|
// to recognize it here or slow solves lose exactly the attribution these spans add.
|
|
209
210
|
if (error instanceof TwoCaptchaSolveTimeoutError) {
|
|
210
|
-
return {
|
|
211
|
+
return resolverSpanAttributes({
|
|
211
212
|
unavailability_reason: "timeout",
|
|
212
213
|
transport_phase: phase,
|
|
213
|
-
};
|
|
214
|
+
});
|
|
214
215
|
}
|
|
215
216
|
return undefined;
|
|
216
217
|
}
|
|
@@ -345,10 +346,10 @@ export function createTwoCaptchaResolverVendorAdapter(options) {
|
|
|
345
346
|
};
|
|
346
347
|
const taskId = traceRecorder
|
|
347
348
|
? await traceRecorder.runSpan("resolver.vendor.create_task", createTask, {
|
|
348
|
-
attributes: {
|
|
349
|
+
attributes: resolverSpanAttributes({
|
|
349
350
|
vendor: TWOCAPTCHA_VENDOR_ID,
|
|
350
351
|
challenge_kind: challenge.kind,
|
|
351
|
-
},
|
|
352
|
+
}),
|
|
352
353
|
onError: (error) => spanErrorAttributes(error, "create_task"),
|
|
353
354
|
})
|
|
354
355
|
: await createTask();
|
|
@@ -387,10 +388,10 @@ export function createTwoCaptchaResolverVendorAdapter(options) {
|
|
|
387
388
|
};
|
|
388
389
|
return traceRecorder
|
|
389
390
|
? await traceRecorder.runSpan("resolver.vendor.poll_result", pollResult, {
|
|
390
|
-
attributes: {
|
|
391
|
+
attributes: resolverSpanAttributes({
|
|
391
392
|
vendor: TWOCAPTCHA_VENDOR_ID,
|
|
392
393
|
challenge_kind: challenge.kind,
|
|
393
|
-
},
|
|
394
|
+
}),
|
|
394
395
|
onError: (error) => spanErrorAttributes(error, "poll_result"),
|
|
395
396
|
})
|
|
396
397
|
: await pollResult();
|
package/dist/runtime/resolver.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createTwoCaptchaResolverVendorAdapter } from "./resolver-vendors/twocap
|
|
|
13
13
|
import { RESOLVER_VENDOR_CAPABILITIES, ResolverVendorUnavailableError, attachResolverVendorPollObserver, resolveProviderResolverVendors, resolverVendorSupports, } from "./resolver-vendors/types.js";
|
|
14
14
|
import { createUnsupportedResolverClient, inheritResolverTelemetryBinding, registerResolverTelemetryBinding, RESOLVER_INSTRUMENTATION_METADATA, } from "./resolver-shared.js";
|
|
15
15
|
import { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, kindRequiresClientProfile, } from "./resolver-config.js";
|
|
16
|
+
import { resolverSpanAttributes } from "./resolver-span-attributes.js";
|
|
16
17
|
import { DEFAULT_STEALTH_PROFILE } from "./stealth.js";
|
|
17
18
|
export { createUnsupportedResolverClient, RESOLVER_INSTRUMENTATION_METADATA, } from "./resolver-shared.js";
|
|
18
19
|
export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, } from "./resolver-config.js";
|
|
@@ -356,7 +357,7 @@ function unavailableAttempt(error, forTelemetry = false) {
|
|
|
356
357
|
}
|
|
357
358
|
function unavailableSpanAttributes(error) {
|
|
358
359
|
const attempt = unavailableAttempt(error);
|
|
359
|
-
return {
|
|
360
|
+
return resolverSpanAttributes({
|
|
360
361
|
unavailability_reason: error.reason,
|
|
361
362
|
missing_fields: error.missingFields,
|
|
362
363
|
cause_name: attempt.cause?.name,
|
|
@@ -364,7 +365,7 @@ function unavailableSpanAttributes(error) {
|
|
|
364
365
|
upstream_host: attempt.upstreamHost,
|
|
365
366
|
transport_phase: attempt.phase,
|
|
366
367
|
transport_round: attempt.round,
|
|
367
|
-
};
|
|
368
|
+
});
|
|
368
369
|
}
|
|
369
370
|
function resolverTelemetryPhase(value) {
|
|
370
371
|
return value === "create_task" ||
|
|
@@ -657,8 +658,8 @@ async function invalidateResolverSolutionWithOutcome(resolver, challenge, soluti
|
|
|
657
658
|
return await invalidate();
|
|
658
659
|
}
|
|
659
660
|
return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
|
|
660
|
-
attributes: { challenge_kind: challenge.kind },
|
|
661
|
-
onSuccess: (outcome) => ({ outcome }),
|
|
661
|
+
attributes: resolverSpanAttributes({ challenge_kind: challenge.kind }),
|
|
662
|
+
onSuccess: (outcome) => resolverSpanAttributes({ outcome }),
|
|
662
663
|
});
|
|
663
664
|
}
|
|
664
665
|
/** Remove the cached entry for the exact solution object returned by this resolver. */
|
|
@@ -864,12 +865,12 @@ function createResolverChainClient(options) {
|
|
|
864
865
|
};
|
|
865
866
|
solution = traceRecorder
|
|
866
867
|
? await traceRecorder.runSpan("resolver.vendor.attempt", solveAttempt, {
|
|
867
|
-
attributes: {
|
|
868
|
+
attributes: resolverSpanAttributes({
|
|
868
869
|
vendor: adapter.id,
|
|
869
870
|
challenge_kind: challenge.kind,
|
|
870
871
|
client_profile: options.clientProfile,
|
|
871
872
|
resolver_identity_source: identityResolution.userAgentSource,
|
|
872
|
-
},
|
|
873
|
+
}),
|
|
873
874
|
onError(error) {
|
|
874
875
|
return error instanceof ResolverVendorUnavailableError
|
|
875
876
|
? unavailableSpanAttributes(error)
|
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `ctx.stealth` composes these from the Chrome emulation and the request class,
|
|
5
5
|
* so a caller value is rejected at request time with `SDKError` code
|
|
6
|
-
* `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` (
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` ({@link assertCallerHeadersSupported}).
|
|
7
|
+
* The offline test harness (`runStandardTests`) calls the same function on
|
|
8
|
+
* every `ctx.stealth` request it serves, so a provider test fails exactly where
|
|
9
|
+
* production would, whichever file or variable the header came from. That is
|
|
10
|
+
* the cross-file enforcement.
|
|
11
|
+
*
|
|
12
|
+
* The `browser-version-literal` lint reports a subset of these names — the
|
|
13
|
+
* `sec-fetch-*` and `sec-ch-ua*` families — in files that use `ctx.stealth`
|
|
14
|
+
* (`isReportedOwnedHeaderName` in lint.ts). Both read this module, so the lint
|
|
15
|
+
* cannot claim a rejection the runtime does not perform; it reports
|
|
16
|
+
* deliberately less than the runtime rejects (host, connection,
|
|
12
17
|
* accept-encoding and a non-literal user-agent stay silent — see that
|
|
13
18
|
* predicate for why), so a green `apifuse check` is not proof that no owned
|
|
14
19
|
* header is set.
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
21
|
+
* Kept off the stealth runtime: the only import is `errors.js`, whose own
|
|
22
|
+
* imports are type-only, so the lazily loaded CLI lint path does not pull the
|
|
23
|
+
* stealth runtime in.
|
|
18
24
|
*/
|
|
19
25
|
export declare const SDK_OWNED_EXACT_CHROME_HEADERS: ReadonlySet<string>;
|
|
20
26
|
export declare const SDK_OWNED_CHROME_HEADER_PREFIX = "sec-fetch-";
|
|
@@ -24,3 +30,10 @@ export declare const SDK_OWNED_CHROME_HEADER_PREFIX = "sec-fetch-";
|
|
|
24
30
|
* `sec-fetch-*` name.
|
|
25
31
|
*/
|
|
26
32
|
export declare function isStealthOwnedHeaderName(name: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Throws `SDKError` `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` for the first caller
|
|
35
|
+
* header `ctx.stealth` owns. An `undefined` value is skipped, as the transport
|
|
36
|
+
* drops it before sending. The stealth runtime and the offline test harness
|
|
37
|
+
* both call this, so the two cannot disagree about what is rejected.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertCallerHeadersSupported(headers: Readonly<Record<string, string | readonly string[] | undefined>>): void;
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
import { SDKError } from "../errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* Header names the stealth transport owns.
|
|
3
4
|
*
|
|
4
5
|
* `ctx.stealth` composes these from the Chrome emulation and the request class,
|
|
5
6
|
* so a caller value is rejected at request time with `SDKError` code
|
|
6
|
-
* `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` (
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` ({@link assertCallerHeadersSupported}).
|
|
8
|
+
* The offline test harness (`runStandardTests`) calls the same function on
|
|
9
|
+
* every `ctx.stealth` request it serves, so a provider test fails exactly where
|
|
10
|
+
* production would, whichever file or variable the header came from. That is
|
|
11
|
+
* the cross-file enforcement.
|
|
12
|
+
*
|
|
13
|
+
* The `browser-version-literal` lint reports a subset of these names — the
|
|
14
|
+
* `sec-fetch-*` and `sec-ch-ua*` families — in files that use `ctx.stealth`
|
|
15
|
+
* (`isReportedOwnedHeaderName` in lint.ts). Both read this module, so the lint
|
|
16
|
+
* cannot claim a rejection the runtime does not perform; it reports
|
|
17
|
+
* deliberately less than the runtime rejects (host, connection,
|
|
12
18
|
* accept-encoding and a non-literal user-agent stay silent — see that
|
|
13
19
|
* predicate for why), so a green `apifuse check` is not proof that no owned
|
|
14
20
|
* header is set.
|
|
15
21
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
22
|
+
* Kept off the stealth runtime: the only import is `errors.js`, whose own
|
|
23
|
+
* imports are type-only, so the lazily loaded CLI lint path does not pull the
|
|
24
|
+
* stealth runtime in.
|
|
18
25
|
*/
|
|
19
26
|
export const SDK_OWNED_EXACT_CHROME_HEADERS = new Set([
|
|
20
27
|
"host",
|
|
@@ -35,3 +42,16 @@ export function isStealthOwnedHeaderName(name) {
|
|
|
35
42
|
const lower = name.toLowerCase();
|
|
36
43
|
return (SDK_OWNED_EXACT_CHROME_HEADERS.has(lower) || lower.startsWith(SDK_OWNED_CHROME_HEADER_PREFIX));
|
|
37
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Throws `SDKError` `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` for the first caller
|
|
47
|
+
* header `ctx.stealth` owns. An `undefined` value is skipped, as the transport
|
|
48
|
+
* drops it before sending. The stealth runtime and the offline test harness
|
|
49
|
+
* both call this, so the two cannot disagree about what is rejected.
|
|
50
|
+
*/
|
|
51
|
+
export function assertCallerHeadersSupported(headers) {
|
|
52
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
53
|
+
if (value === undefined || !isStealthOwnedHeaderName(name))
|
|
54
|
+
continue;
|
|
55
|
+
throw new SDKError(`Stealth transport owns the "${name.toLowerCase()}" header; remove it from headers.`, { code: "STEALTH_HEADER_OVERRIDE_UNSUPPORTED" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StealthBrowser, StealthChallengeClassification, StealthOS, StealthProfileDescriptor } from "../types.js";
|
|
2
2
|
import { type ClosedEnum, type TelemetryContributor } from "./request-telemetry.js";
|
|
3
3
|
export type StealthTelemetryErrorCode = "transport_network_error" | "transport_timeout" | "transport_cancelled" | "upstream_http_error" | "response_too_large" | "proxy_connect_failed" | "PROXY_POOL_STALE" | "PROXY_EDGE_AUTH_REJECTED" | "PROXY_AUTH_IP_DENIED" | "PROXY_EDGE_TLS_REJECTED" | "PROXY_REQUIRED" | "other";
|
|
4
|
-
export type StealthTelemetryRequestClass = "navigation" | "script_navigation" | "xhr" | "post";
|
|
4
|
+
export type StealthTelemetryRequestClass = "navigation" | "script_navigation" | "form_post" | "xhr" | "post";
|
|
5
5
|
export type StealthTelemetryAttemptKind = "request" | "resolver" | "proxy_diagnostic";
|
|
6
6
|
export type StealthTelemetrySbsdOutcome = StealthChallengeClassification["outcome"] | "detected" | "refetch_clear";
|
|
7
7
|
export type StealthTelemetryDiagnostics = {
|