@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50
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 +537 -33
- package/CHANGELOG.md +201 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +5 -5
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +179 -16
- package/dist/config/loader.js +435 -127
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +10 -2
- package/dist/provider.js +4 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +14 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +531 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +72 -0
- package/dist/runtime/resolver.js +771 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +41 -5
- package/dist/runtime/stealth.js +997 -291
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.js +2 -2
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test.d.ts +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +9 -4
- package/dist/stealth/profiles.js +160 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +5 -5
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +690 -163
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +220 -13
- package/src/lint.ts +698 -90
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +161 -3
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +520 -15
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +23 -0
- package/src/runtime/resolver-shared.ts +33 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -0
- package/src/server/types.ts +13 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +202 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +788 -174
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -1,4 +1,61 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
HttpClient,
|
|
3
|
+
RequestOptions,
|
|
4
|
+
RequestParamPrimitive,
|
|
5
|
+
RequestParams,
|
|
6
|
+
RequestParamValue,
|
|
7
|
+
} from "../types.js";
|
|
8
|
+
import { isProviderError } from "../errors.js";
|
|
9
|
+
|
|
10
|
+
export const REDACTED_QUERY_VALUE = "[REDACTED]";
|
|
11
|
+
|
|
12
|
+
/** Internal heuristic shared by diagnostic and fixture structural redaction. */
|
|
13
|
+
export function isSensitiveKey(key: string): boolean {
|
|
14
|
+
return /authorization|token|api[-_]?key|(^|[-_])(code|key|secret|signature|session|state)($|[-_])/i.test(
|
|
15
|
+
key,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const HTTP_REQUEST_METHOD_CONFIG = {
|
|
20
|
+
request: { optionsIndex: 1 },
|
|
21
|
+
get: { optionsIndex: 1 },
|
|
22
|
+
post: { optionsIndex: 2 },
|
|
23
|
+
put: { optionsIndex: 2 },
|
|
24
|
+
delete: { optionsIndex: 1 },
|
|
25
|
+
stream: { optionsIndex: 1 },
|
|
26
|
+
sse: { optionsIndex: 1 },
|
|
27
|
+
} as const satisfies Record<keyof HttpClient, { optionsIndex: number }>;
|
|
28
|
+
|
|
29
|
+
export type HttpRequestMethod = keyof typeof HTTP_REQUEST_METHOD_CONFIG;
|
|
30
|
+
|
|
31
|
+
export type HttpRequestInvocation = {
|
|
32
|
+
[M in HttpRequestMethod]: {
|
|
33
|
+
method: M;
|
|
34
|
+
args: Parameters<HttpClient[M]>;
|
|
35
|
+
};
|
|
36
|
+
}[HttpRequestMethod];
|
|
37
|
+
|
|
38
|
+
export const HTTP_REQUEST_METHOD_NAMES = Object.keys(
|
|
39
|
+
HTTP_REQUEST_METHOD_CONFIG,
|
|
40
|
+
) as HttpRequestMethod[];
|
|
41
|
+
|
|
42
|
+
const HTTP_REQUEST_METHOD_SET = new Set<string>(HTTP_REQUEST_METHOD_NAMES);
|
|
43
|
+
|
|
44
|
+
export function isHttpRequestMethod(method: PropertyKey): method is HttpRequestMethod {
|
|
45
|
+
return typeof method === "string" && HTTP_REQUEST_METHOD_SET.has(method);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type SerializedRequestUrl = {
|
|
49
|
+
requestUrl: string;
|
|
50
|
+
redactedUrl: string;
|
|
51
|
+
sensitiveValues: readonly string[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function normalizeSensitiveParams(
|
|
55
|
+
sensitiveParams?: Record<string, string>,
|
|
56
|
+
): Record<string, string> | undefined {
|
|
57
|
+
return sensitiveParams && Object.keys(sensitiveParams).length > 0 ? sensitiveParams : undefined;
|
|
58
|
+
}
|
|
2
59
|
|
|
3
60
|
function isParamArray(value: RequestParamValue): value is readonly RequestParamPrimitive[] {
|
|
4
61
|
return Array.isArray(value);
|
|
@@ -32,6 +89,628 @@ export function appendQueryParams(url: string, params?: RequestParams): string {
|
|
|
32
89
|
return parsed.toString();
|
|
33
90
|
}
|
|
34
91
|
|
|
92
|
+
function decodeQueryComponent(value: string): string {
|
|
93
|
+
try {
|
|
94
|
+
return decodeURIComponent(value.replaceAll("+", " "));
|
|
95
|
+
} catch {
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Redacts matching query keys without reserializing any other URL bytes. */
|
|
101
|
+
export function redactUrlQueryParams(
|
|
102
|
+
url: string,
|
|
103
|
+
sensitiveParamNames: readonly string[],
|
|
104
|
+
): { redactedUrl: string; sensitiveValues: readonly string[] } {
|
|
105
|
+
if (sensitiveParamNames.length === 0) return { redactedUrl: url, sensitiveValues: [] };
|
|
106
|
+
const names = new Set(sensitiveParamNames);
|
|
107
|
+
const queryStart = url.indexOf("?");
|
|
108
|
+
if (queryStart === -1) return { redactedUrl: url, sensitiveValues: [] };
|
|
109
|
+
const fragmentStart = url.indexOf("#", queryStart);
|
|
110
|
+
const queryEnd = fragmentStart === -1 ? url.length : fragmentStart;
|
|
111
|
+
const query = url.slice(queryStart + 1, queryEnd);
|
|
112
|
+
const sensitiveValues: string[] = [];
|
|
113
|
+
const redactedQuery = query
|
|
114
|
+
.split("&")
|
|
115
|
+
.map((part) => {
|
|
116
|
+
const assignment = part.indexOf("=");
|
|
117
|
+
const encodedKey = assignment === -1 ? part : part.slice(0, assignment);
|
|
118
|
+
if (!names.has(decodeQueryComponent(encodedKey))) return part;
|
|
119
|
+
const encodedValue = assignment === -1 ? "" : part.slice(assignment + 1);
|
|
120
|
+
sensitiveValues.push(decodeQueryComponent(encodedValue));
|
|
121
|
+
return `${encodedKey}=${REDACTED_QUERY_VALUE}`;
|
|
122
|
+
})
|
|
123
|
+
.join("&");
|
|
124
|
+
return {
|
|
125
|
+
redactedUrl: `${url.slice(0, queryStart + 1)}${redactedQuery}${url.slice(queryEnd)}`,
|
|
126
|
+
sensitiveValues,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Serializes public and secret query parameters together while constructing a
|
|
132
|
+
* separate, structurally redacted URL for diagnostics and observability.
|
|
133
|
+
*/
|
|
134
|
+
export function serializeRequestUrl(
|
|
135
|
+
url: string,
|
|
136
|
+
params?: RequestParams,
|
|
137
|
+
sensitiveParams?: Record<string, string>,
|
|
138
|
+
): SerializedRequestUrl {
|
|
139
|
+
const urlWithParams = appendQueryParams(url, params);
|
|
140
|
+
const normalizedSensitiveParams = normalizeSensitiveParams(sensitiveParams);
|
|
141
|
+
if (!normalizedSensitiveParams) {
|
|
142
|
+
return { requestUrl: urlWithParams, redactedUrl: urlWithParams, sensitiveValues: [] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const sensitiveParamNames = Object.keys(normalizedSensitiveParams);
|
|
146
|
+
const existing = redactUrlQueryParams(urlWithParams, sensitiveParamNames);
|
|
147
|
+
const sensitiveValues = new Set(existing.sensitiveValues);
|
|
148
|
+
let requestUrl = urlWithParams;
|
|
149
|
+
|
|
150
|
+
for (const [key, value] of Object.entries(normalizedSensitiveParams)) {
|
|
151
|
+
const serializedValue = String(value);
|
|
152
|
+
requestUrl = appendQueryParams(requestUrl, { [key]: serializedValue });
|
|
153
|
+
sensitiveValues.add(serializedValue);
|
|
154
|
+
}
|
|
155
|
+
const redacted = redactUrlQueryParams(requestUrl, sensitiveParamNames);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
requestUrl,
|
|
159
|
+
redactedUrl: redacted.redactedUrl,
|
|
160
|
+
sensitiveValues: [...sensitiveValues],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const MIN_UNSCOPED_SENSITIVE_VALUE_LENGTH = 4;
|
|
165
|
+
|
|
166
|
+
function normalizePercentEscapes(value: string): string {
|
|
167
|
+
return value.replace(/%[\da-f]{2}/gi, (percentEscape) => percentEscape.toUpperCase());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function sensitiveValueVariants(value: string): string[] {
|
|
171
|
+
if (!value || value === REDACTED_QUERY_VALUE) return [];
|
|
172
|
+
|
|
173
|
+
const formEncoded = new URLSearchParams({ value }).toString().slice("value=".length);
|
|
174
|
+
let componentEncoded: string;
|
|
175
|
+
try {
|
|
176
|
+
componentEncoded = encodeURIComponent(value);
|
|
177
|
+
} catch {
|
|
178
|
+
// encodeURIComponent rejects lone UTF-16 surrogates. The raw value is
|
|
179
|
+
// still safe to scrub, and URLSearchParams supplies its well-formed form.
|
|
180
|
+
componentEncoded = value;
|
|
181
|
+
}
|
|
182
|
+
return [...new Set([value, componentEncoded, formEncoded].map(normalizePercentEscapes))];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
type SensitiveValueVariant = { value: string; requiresTokenBoundary: boolean };
|
|
186
|
+
|
|
187
|
+
function allSensitiveValueVariants(sensitiveValues: readonly string[]): SensitiveValueVariant[] {
|
|
188
|
+
const variants = new Map<string, SensitiveValueVariant>();
|
|
189
|
+
for (const sensitiveValue of sensitiveValues) {
|
|
190
|
+
const requiresTokenBoundary = sensitiveValue.length < MIN_UNSCOPED_SENSITIVE_VALUE_LENGTH;
|
|
191
|
+
for (const value of sensitiveValueVariants(sensitiveValue)) {
|
|
192
|
+
const existing = variants.get(value);
|
|
193
|
+
variants.set(value, {
|
|
194
|
+
value,
|
|
195
|
+
requiresTokenBoundary: existing
|
|
196
|
+
? existing.requiresTokenBoundary && requiresTokenBoundary
|
|
197
|
+
: requiresTokenBoundary,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return [...variants.values()].sort(
|
|
202
|
+
(left, right) =>
|
|
203
|
+
right.value.length - left.value.length || left.value.localeCompare(right.value),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isTokenCharacter(value: string | undefined): boolean {
|
|
208
|
+
return value !== undefined && /[\p{L}\p{N}]/u.test(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function replaceSensitiveVariant(text: string, variant: SensitiveValueVariant): string {
|
|
212
|
+
const pattern = variant.value.replace(/%([\da-f]{2})|[.*+?^${}()|[\]\\]/gi, (match, hex) => {
|
|
213
|
+
if (hex) {
|
|
214
|
+
return `%${[...String(hex)]
|
|
215
|
+
.map((character) =>
|
|
216
|
+
/[a-f]/i.test(character)
|
|
217
|
+
? `[${character.toLowerCase()}${character.toUpperCase()}]`
|
|
218
|
+
: character,
|
|
219
|
+
)
|
|
220
|
+
.join("")}`;
|
|
221
|
+
}
|
|
222
|
+
return `\\${match}`;
|
|
223
|
+
});
|
|
224
|
+
return text.replace(new RegExp(pattern, "gu"), (match, offset: number, source: string) => {
|
|
225
|
+
const end = offset + match.length;
|
|
226
|
+
if (
|
|
227
|
+
variant.requiresTokenBoundary &&
|
|
228
|
+
(isTokenCharacter(source[offset - 1]) || isTokenCharacter(source[end]))
|
|
229
|
+
) {
|
|
230
|
+
return match;
|
|
231
|
+
}
|
|
232
|
+
return REDACTED_QUERY_VALUE;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Scrubs raw and URL-encoded secret values from diagnostic text. */
|
|
237
|
+
export function redactSensitiveText(
|
|
238
|
+
text: string,
|
|
239
|
+
sensitiveValues: readonly string[],
|
|
240
|
+
requestUrl?: string,
|
|
241
|
+
redactedUrl?: string,
|
|
242
|
+
): string {
|
|
243
|
+
let redacted = requestUrl && redactedUrl ? text.replaceAll(requestUrl, redactedUrl) : text;
|
|
244
|
+
for (const variant of allSensitiveValueVariants(sensitiveValues)) {
|
|
245
|
+
// Low-entropy values are still redacted, but only as complete tokens. This
|
|
246
|
+
// covers query values and diagnostic phrases such as "credential api rejected"
|
|
247
|
+
// without corrupting timestamps or words such as "rapid".
|
|
248
|
+
redacted = replaceSensitiveVariant(redacted, variant);
|
|
249
|
+
}
|
|
250
|
+
return redacted;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
type RedactionContext = {
|
|
254
|
+
sensitiveValues: readonly string[];
|
|
255
|
+
requestUrl?: string;
|
|
256
|
+
redactedUrl?: string;
|
|
257
|
+
seen: Map<object, unknown>;
|
|
258
|
+
classificationObjects: Set<object>;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
function isSdkErrorBrand(key: PropertyKey): boolean {
|
|
262
|
+
return (
|
|
263
|
+
typeof key === "symbol" &&
|
|
264
|
+
Symbol.keyFor(key)?.startsWith("@apifuse/provider-sdk/error-") === true
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function redactDiagnosticString(value: string, context: RedactionContext): string {
|
|
269
|
+
return redactSensitiveText(
|
|
270
|
+
value,
|
|
271
|
+
context.sensitiveValues,
|
|
272
|
+
context.requestUrl,
|
|
273
|
+
context.redactedUrl,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function redactClassificationString(value: string, context: RedactionContext): string {
|
|
278
|
+
let redacted =
|
|
279
|
+
context.requestUrl && context.redactedUrl
|
|
280
|
+
? value.replaceAll(context.requestUrl, context.redactedUrl)
|
|
281
|
+
: value;
|
|
282
|
+
for (const variant of allSensitiveValueVariants(context.sensitiveValues)) {
|
|
283
|
+
redacted = replaceSensitiveVariant(redacted, {
|
|
284
|
+
...variant,
|
|
285
|
+
requiresTokenBoundary: false,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return redacted;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function cloneForRedaction(source: object): object {
|
|
292
|
+
let clone: object;
|
|
293
|
+
try {
|
|
294
|
+
clone = Array.isArray(source)
|
|
295
|
+
? []
|
|
296
|
+
: source instanceof DOMException
|
|
297
|
+
? new DOMException(source.message, source.name)
|
|
298
|
+
: Object.create(Object.getPrototypeOf(source));
|
|
299
|
+
if (Array.isArray(source) && Object.getPrototypeOf(source) !== Array.prototype) {
|
|
300
|
+
Object.setPrototypeOf(clone, Object.getPrototypeOf(source));
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
clone = source instanceof Error ? new Error() : {};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
307
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
308
|
+
if (!descriptor) continue;
|
|
309
|
+
if (Array.isArray(source) && key === "length") continue;
|
|
310
|
+
try {
|
|
311
|
+
const preserveSdkBrand = isSdkErrorBrand(key);
|
|
312
|
+
Object.defineProperty(
|
|
313
|
+
clone,
|
|
314
|
+
key,
|
|
315
|
+
preserveSdkBrand
|
|
316
|
+
? descriptor
|
|
317
|
+
: {
|
|
318
|
+
...descriptor,
|
|
319
|
+
configurable: true,
|
|
320
|
+
...(Object.hasOwn(descriptor, "value") ? { writable: true } : {}),
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
} catch {
|
|
324
|
+
// A hostile or exotic diagnostic property must not make redaction fail.
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (source instanceof Error) {
|
|
328
|
+
// DOMException exposes name/message through internal-slot getters. An
|
|
329
|
+
// Object.create clone does not carry those slots, so materialize the
|
|
330
|
+
// classification fields as safe own properties on the replacement.
|
|
331
|
+
for (const key of ["name", "message", "stack"] as const) {
|
|
332
|
+
if (Object.hasOwn(clone, key)) continue;
|
|
333
|
+
const property = readDiagnosticProperty(source, key);
|
|
334
|
+
if (!property.ok) continue;
|
|
335
|
+
const propertyValue =
|
|
336
|
+
key === "stack" && typeof property.value !== "string" ? new Error().stack : property.value;
|
|
337
|
+
try {
|
|
338
|
+
Object.defineProperty(clone, key, {
|
|
339
|
+
value: propertyValue,
|
|
340
|
+
configurable: true,
|
|
341
|
+
enumerable: false,
|
|
342
|
+
writable: true,
|
|
343
|
+
});
|
|
344
|
+
} catch {
|
|
345
|
+
// The generic Error fallback remains classifiable even if an exotic
|
|
346
|
+
// prototype rejects an own diagnostic property.
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return clone;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function setDiagnosticProperty(target: object, key: PropertyKey, value: unknown): boolean {
|
|
354
|
+
try {
|
|
355
|
+
return Reflect.set(target, key, value, target);
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function diagnosticPropertyKeys(value: object): PropertyKey[] {
|
|
362
|
+
const keys = new Set<PropertyKey>(Reflect.ownKeys(value));
|
|
363
|
+
if (value instanceof Error) {
|
|
364
|
+
// Built-in error text and AggregateError.errors are commonly
|
|
365
|
+
// non-enumerable, while SDK options carry serializable details.
|
|
366
|
+
for (const key of ["name", "message", "stack", "cause", "errors", "options"] as const) {
|
|
367
|
+
if (key in value) keys.add(key);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (!(value instanceof Error) && "toJSON" in value) keys.add("toJSON");
|
|
371
|
+
return [...keys];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function requiresCloneBeforeRedaction(value: object, keys: readonly PropertyKey[]): boolean {
|
|
375
|
+
for (const key of keys) {
|
|
376
|
+
if (isSdkErrorBrand(key)) continue;
|
|
377
|
+
let owner: object | null = value;
|
|
378
|
+
while (owner) {
|
|
379
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, key);
|
|
380
|
+
if (descriptor) {
|
|
381
|
+
if (Object.hasOwn(descriptor, "value")) {
|
|
382
|
+
if (owner === value && descriptor.writable === false) return true;
|
|
383
|
+
} else if (descriptor.set === undefined) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
owner = Object.getPrototypeOf(owner);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function readDiagnosticProperty(
|
|
395
|
+
value: object,
|
|
396
|
+
key: PropertyKey,
|
|
397
|
+
): { ok: true; value: unknown } | { ok: false } {
|
|
398
|
+
try {
|
|
399
|
+
return { ok: true, value: Reflect.get(value, key, value) };
|
|
400
|
+
} catch {
|
|
401
|
+
return { ok: false };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// These fields control error identity/classification downstream. Their values
|
|
406
|
+
// are exempt from whole-value replacement, but strings are still scrubbed when
|
|
407
|
+
// they contain an actual declared credential or request URL.
|
|
408
|
+
const DIAGNOSTIC_CLASSIFICATION_FIELDS = new Set([
|
|
409
|
+
"name",
|
|
410
|
+
"code",
|
|
411
|
+
"status",
|
|
412
|
+
"upstreamStatus",
|
|
413
|
+
"category",
|
|
414
|
+
"retryable",
|
|
415
|
+
]);
|
|
416
|
+
|
|
417
|
+
// Underscore-delimited error codes are semantic identities, not diagnostic
|
|
418
|
+
// payload. Preserve the shared SDK/provider code convention without maintaining
|
|
419
|
+
// a second, inevitably partial list of codes declared by individual transports.
|
|
420
|
+
function isSemanticErrorCode(value: string): boolean {
|
|
421
|
+
return /^(?:[A-Za-z][A-Za-z0-9]*_)+[A-Za-z0-9]+$/.test(value);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function isClassificationField(
|
|
425
|
+
context: RedactionContext,
|
|
426
|
+
propertyName: string | undefined,
|
|
427
|
+
parent: object | undefined,
|
|
428
|
+
): boolean {
|
|
429
|
+
return Boolean(
|
|
430
|
+
propertyName &&
|
|
431
|
+
parent &&
|
|
432
|
+
context.classificationObjects.has(parent) &&
|
|
433
|
+
DIAGNOSTIC_CLASSIFICATION_FIELDS.has(propertyName),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function redactedDiagnosticKey(key: string, context: RedactionContext): string {
|
|
438
|
+
return redactClassificationString(key, context);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function hasRedactedOwnKey(value: object, context: RedactionContext): boolean {
|
|
442
|
+
return Object.getOwnPropertyNames(value).some(
|
|
443
|
+
(key) => redactedDiagnosticKey(key, context) !== key,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function collisionSafeDiagnosticKey(target: object, preferredKey: string): string {
|
|
448
|
+
if (!Object.hasOwn(target, preferredKey)) return preferredKey;
|
|
449
|
+
let suffix = 2;
|
|
450
|
+
while (Object.hasOwn(target, `${preferredKey}#${suffix}`)) suffix += 1;
|
|
451
|
+
return `${preferredKey}#${suffix}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function redactDiagnosticValue(
|
|
455
|
+
value: unknown,
|
|
456
|
+
context: RedactionContext,
|
|
457
|
+
propertyName?: string,
|
|
458
|
+
parent?: object,
|
|
459
|
+
): unknown {
|
|
460
|
+
const classificationField = isClassificationField(context, propertyName, parent);
|
|
461
|
+
if (classificationField && typeof value !== "string") return value;
|
|
462
|
+
if (typeof value === "string") {
|
|
463
|
+
if (!classificationField) return redactDiagnosticString(value, context);
|
|
464
|
+
const redacted = redactClassificationString(value, context);
|
|
465
|
+
if (propertyName === "code" && redacted === value && isSemanticErrorCode(value)) return value;
|
|
466
|
+
return redacted;
|
|
467
|
+
}
|
|
468
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
469
|
+
return context.sensitiveValues.includes(String(value)) ? REDACTED_QUERY_VALUE : value;
|
|
470
|
+
}
|
|
471
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null) return value;
|
|
472
|
+
if (value instanceof URL) {
|
|
473
|
+
const redactedUrl = redactDiagnosticString(value.toString(), context);
|
|
474
|
+
if (redactedUrl === value.toString()) return value;
|
|
475
|
+
try {
|
|
476
|
+
return new URL(redactedUrl);
|
|
477
|
+
} catch {
|
|
478
|
+
return redactedUrl;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (value instanceof Headers) {
|
|
482
|
+
const redactedHeaders = new Headers();
|
|
483
|
+
for (const [key, headerValue] of value.entries()) {
|
|
484
|
+
redactedHeaders.append(key, redactDiagnosticString(headerValue, context));
|
|
485
|
+
}
|
|
486
|
+
return redactedHeaders;
|
|
487
|
+
}
|
|
488
|
+
if (value instanceof Request) {
|
|
489
|
+
const redactedUrl = redactDiagnosticString(value.url, context);
|
|
490
|
+
const redactedHeaders = redactDiagnosticValue(value.headers, context) as Headers;
|
|
491
|
+
if (
|
|
492
|
+
redactedUrl === value.url &&
|
|
493
|
+
[...value.headers].every(([key, headerValue]) => {
|
|
494
|
+
return redactedHeaders.get(key) === headerValue;
|
|
495
|
+
})
|
|
496
|
+
) {
|
|
497
|
+
return value;
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
const redactedRequest = new Request(redactedUrl, value);
|
|
501
|
+
for (const key of [...redactedRequest.headers.keys()]) redactedRequest.headers.delete(key);
|
|
502
|
+
for (const [key, headerValue] of redactedHeaders) {
|
|
503
|
+
redactedRequest.headers.append(key, headerValue);
|
|
504
|
+
}
|
|
505
|
+
return redactedRequest;
|
|
506
|
+
} catch {
|
|
507
|
+
const redactedRequest = Object.create(Object.getPrototypeOf(value)) as object;
|
|
508
|
+
Object.defineProperties(redactedRequest, {
|
|
509
|
+
headers: { value: redactedHeaders, enumerable: true },
|
|
510
|
+
url: { value: redactedUrl, enumerable: true },
|
|
511
|
+
});
|
|
512
|
+
return redactedRequest;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (value instanceof Response) {
|
|
516
|
+
const redactedUrl = redactDiagnosticString(value.url, context);
|
|
517
|
+
const redactedHeaders = redactDiagnosticValue(value.headers, context) as Headers;
|
|
518
|
+
if (
|
|
519
|
+
redactedUrl === value.url &&
|
|
520
|
+
[...value.headers].every(([key, headerValue]) => {
|
|
521
|
+
return redactedHeaders.get(key) === headerValue;
|
|
522
|
+
})
|
|
523
|
+
) {
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
const redactedResponse = Object.create(Object.getPrototypeOf(value)) as object;
|
|
527
|
+
Object.defineProperties(redactedResponse, {
|
|
528
|
+
headers: { value: redactedHeaders, enumerable: true },
|
|
529
|
+
ok: { value: value.ok, enumerable: true },
|
|
530
|
+
redirected: { value: value.redirected, enumerable: true },
|
|
531
|
+
status: { value: value.status, enumerable: true },
|
|
532
|
+
statusText: {
|
|
533
|
+
value: redactDiagnosticString(value.statusText, context),
|
|
534
|
+
enumerable: true,
|
|
535
|
+
},
|
|
536
|
+
type: { value: value.type, enumerable: true },
|
|
537
|
+
url: { value: redactedUrl, enumerable: true },
|
|
538
|
+
});
|
|
539
|
+
return redactedResponse;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const previouslySeen = context.seen.get(value);
|
|
543
|
+
if (previouslySeen !== undefined) return previouslySeen;
|
|
544
|
+
if (value instanceof Map) {
|
|
545
|
+
const redactedMap = new Map<unknown, unknown>();
|
|
546
|
+
context.seen.set(value, redactedMap);
|
|
547
|
+
for (const [key, entryValue] of value) {
|
|
548
|
+
redactedMap.set(
|
|
549
|
+
redactDiagnosticValue(key, context),
|
|
550
|
+
redactDiagnosticValue(entryValue, context),
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
return redactedMap;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Clone non-extensible graphs before descending so cycles encountered before
|
|
557
|
+
// a secret-bearing readonly field already point at the eventual replacement.
|
|
558
|
+
// Without this, a frozen `error.cause = error` graph could retain the original
|
|
559
|
+
// secret through the cloned node's earlier self-reference.
|
|
560
|
+
const keys = diagnosticPropertyKeys(value);
|
|
561
|
+
let target: object =
|
|
562
|
+
Object.isExtensible(value) &&
|
|
563
|
+
!requiresCloneBeforeRedaction(value, keys) &&
|
|
564
|
+
!hasRedactedOwnKey(value, context)
|
|
565
|
+
? value
|
|
566
|
+
: cloneForRedaction(value);
|
|
567
|
+
context.seen.set(value, target);
|
|
568
|
+
|
|
569
|
+
for (const key of keys) {
|
|
570
|
+
const current = readDiagnosticProperty(value, key);
|
|
571
|
+
if (!current.ok) continue;
|
|
572
|
+
const propertyName = typeof key === "string" ? key : undefined;
|
|
573
|
+
const redacted =
|
|
574
|
+
key === "toJSON" && typeof current.value === "function"
|
|
575
|
+
? (...args: unknown[]) =>
|
|
576
|
+
redactDiagnosticValue(
|
|
577
|
+
Reflect.apply(current.value as (...args: unknown[]) => unknown, value, args),
|
|
578
|
+
context,
|
|
579
|
+
)
|
|
580
|
+
: redactDiagnosticValue(current.value, context, propertyName, value);
|
|
581
|
+
const ownKey = Object.hasOwn(value, key);
|
|
582
|
+
const preferredKey =
|
|
583
|
+
ownKey && typeof key === "string" ? redactedDiagnosticKey(key, context) : key;
|
|
584
|
+
let targetKey = key;
|
|
585
|
+
if (preferredKey !== key) {
|
|
586
|
+
try {
|
|
587
|
+
Reflect.deleteProperty(target, key);
|
|
588
|
+
} catch {
|
|
589
|
+
// A clone is selected before traversal whenever an own key changes,
|
|
590
|
+
// so this is only a defensive fallback for exotic proxies.
|
|
591
|
+
}
|
|
592
|
+
targetKey = collisionSafeDiagnosticKey(target, preferredKey as string);
|
|
593
|
+
}
|
|
594
|
+
if (targetKey === key && Object.is(redacted, current.value)) continue;
|
|
595
|
+
|
|
596
|
+
if (!setDiagnosticProperty(target, targetKey, redacted)) {
|
|
597
|
+
if (target === value) {
|
|
598
|
+
target = cloneForRedaction(value);
|
|
599
|
+
context.seen.set(value, target);
|
|
600
|
+
}
|
|
601
|
+
if (!setDiagnosticProperty(target, targetKey, redacted)) {
|
|
602
|
+
// cloneForRedaction deliberately makes string-keyed own properties
|
|
603
|
+
// configurable. This is a last-resort path for inherited readonly
|
|
604
|
+
// accessors such as DOMException.message.
|
|
605
|
+
try {
|
|
606
|
+
Object.defineProperty(target, targetKey, {
|
|
607
|
+
value: redacted,
|
|
608
|
+
configurable: true,
|
|
609
|
+
enumerable: targetKey !== "message" && targetKey !== "stack" && targetKey !== "cause",
|
|
610
|
+
writable: true,
|
|
611
|
+
});
|
|
612
|
+
} catch {
|
|
613
|
+
// Redaction is best-effort for inaccessible exotic properties, but
|
|
614
|
+
// it must never mask the original transport failure.
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return target;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Redacts error text and recursively serializable diagnostic metadata.
|
|
625
|
+
* Mutable errors retain identity; readonly/frozen errors may be replaced, so
|
|
626
|
+
* callers must use the returned value.
|
|
627
|
+
*/
|
|
628
|
+
export function redactSensitiveError<T>(
|
|
629
|
+
error: T,
|
|
630
|
+
sensitiveValues: readonly string[],
|
|
631
|
+
requestUrl?: string,
|
|
632
|
+
redactedUrl?: string,
|
|
633
|
+
): T {
|
|
634
|
+
if (sensitiveValues.length === 0 && (!requestUrl || !redactedUrl || requestUrl === redactedUrl)) {
|
|
635
|
+
return error;
|
|
636
|
+
}
|
|
637
|
+
const classificationObjects = new Set<object>();
|
|
638
|
+
if ((typeof error === "object" || typeof error === "function") && error !== null) {
|
|
639
|
+
classificationObjects.add(error);
|
|
640
|
+
if (isProviderError(error)) {
|
|
641
|
+
const options = readDiagnosticProperty(error, "options");
|
|
642
|
+
if (options.ok && typeof options.value === "object" && options.value !== null) {
|
|
643
|
+
classificationObjects.add(options.value);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return redactDiagnosticValue(error, {
|
|
648
|
+
classificationObjects,
|
|
649
|
+
sensitiveValues,
|
|
650
|
+
requestUrl,
|
|
651
|
+
redactedUrl,
|
|
652
|
+
seen: new Map(),
|
|
653
|
+
}) as T;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Redacts failures that occur before a request URL can be fully serialized. */
|
|
657
|
+
export function redactSensitiveRequestError<T>(
|
|
658
|
+
error: T,
|
|
659
|
+
url: string,
|
|
660
|
+
sensitiveParams?: Record<string, string>,
|
|
661
|
+
): T {
|
|
662
|
+
const normalizedSensitiveParams = normalizeSensitiveParams(sensitiveParams);
|
|
663
|
+
const structural = redactUrlQueryParams(url, Object.keys(normalizedSensitiveParams ?? {}));
|
|
664
|
+
return redactSensitiveError(
|
|
665
|
+
error,
|
|
666
|
+
[
|
|
667
|
+
...new Set([
|
|
668
|
+
...Object.values(normalizedSensitiveParams ?? {}).map(String),
|
|
669
|
+
...structural.sensitiveValues,
|
|
670
|
+
]),
|
|
671
|
+
],
|
|
672
|
+
url,
|
|
673
|
+
structural.redactedUrl,
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function isRequestOptions(value: unknown): value is RequestOptions {
|
|
678
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Validates an untyped proxy invocation before narrowing it to the HttpClient contract. */
|
|
682
|
+
export function parseHttpRequestInvocation(
|
|
683
|
+
method: PropertyKey,
|
|
684
|
+
args: unknown[],
|
|
685
|
+
): HttpRequestInvocation | undefined {
|
|
686
|
+
if (!isHttpRequestMethod(method) || typeof args[0] !== "string") return undefined;
|
|
687
|
+
const optionsIndex = HTTP_REQUEST_METHOD_CONFIG[method].optionsIndex;
|
|
688
|
+
const options = args[optionsIndex];
|
|
689
|
+
if (options !== undefined && !isRequestOptions(options)) return undefined;
|
|
690
|
+
|
|
691
|
+
if (method === "post" || method === "put") {
|
|
692
|
+
if (args.length < 2 || args.length > 3) return undefined;
|
|
693
|
+
return { method, args: args as Parameters<HttpClient[typeof method]> };
|
|
694
|
+
}
|
|
695
|
+
if (args.length > 2) return undefined;
|
|
696
|
+
return { method, args: args as Parameters<HttpClient[typeof method]> };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** Internal typed registry for the request-options position of HttpClient calls. */
|
|
700
|
+
export function requestOptionsFromHttpInvocation(
|
|
701
|
+
invocation: HttpRequestInvocation,
|
|
702
|
+
): RequestOptions | undefined {
|
|
703
|
+
const candidate = invocation.args[HTTP_REQUEST_METHOD_CONFIG[invocation.method].optionsIndex];
|
|
704
|
+
return isRequestOptions(candidate) ? candidate : undefined;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export function replaceRequestOptionsInHttpInvocation(
|
|
708
|
+
invocation: HttpRequestInvocation,
|
|
709
|
+
options: RequestOptions,
|
|
710
|
+
): void {
|
|
711
|
+
invocation.args[HTTP_REQUEST_METHOD_CONFIG[invocation.method].optionsIndex] = options;
|
|
712
|
+
}
|
|
713
|
+
|
|
35
714
|
export function normalizeHttpRequestBody(body: unknown): string | Buffer | undefined {
|
|
36
715
|
if (body === undefined) {
|
|
37
716
|
return undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const APIFUSE__RESOLVER__2CAPTCHA__API_KEY = "APIFUSE__RESOLVER__2CAPTCHA__API_KEY";
|
|
2
|
+
export const APIFUSE__RESOLVER__CAPSOLVER__API_KEY = "APIFUSE__RESOLVER__CAPSOLVER__API_KEY";
|
|
3
|
+
export const APIFUSE__RESOLVER__CAPMONSTER__API_KEY = "APIFUSE__RESOLVER__CAPMONSTER__API_KEY";
|
|
4
|
+
export const APIFUSE__RESOLVER__TIMEOUT_MS = "APIFUSE__RESOLVER__TIMEOUT_MS";
|
|
5
|
+
export const APIFUSE__CDP_POOL__URL = "APIFUSE__CDP_POOL__URL";
|
|
6
|
+
export const DEFAULT_RESOLVER_TIMEOUT_MS = 180_000;
|