@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.1
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 +134 -0
- package/CHANGELOG.md +56 -0
- package/README.md +21 -9
- package/SUBMISSION.md +10 -11
- package/bin/apifuse-pack-check.ts +22 -0
- package/bin/apifuse-submit-check.ts +1747 -388
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +436 -0
- package/dist/ceremonies/index.js +7 -31
- package/dist/cli/create.js +45 -30
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/contract.js +1 -0
- package/dist/define.d.ts +6 -1
- package/dist/define.js +140 -70
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +27 -0
- package/dist/provider.d.ts +4 -2
- package/dist/provider.js +2 -1
- package/dist/runtime/auth-flow.js +2 -0
- package/dist/runtime/browser.js +203 -0
- package/dist/runtime/http.js +28 -8
- package/dist/runtime/stealth.d.ts +5 -2
- package/dist/runtime/stealth.js +149 -3
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +98 -0
- package/dist/server/self-test.js +555 -0
- package/dist/server/serve.d.ts +6 -0
- package/dist/server/serve.js +33 -8
- package/dist/testing/run.js +5 -1
- package/dist/types.d.ts +150 -0
- package/package.json +9 -2
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +786 -0
- package/src/ceremonies/index.ts +9 -43
- package/src/cli/create.ts +60 -97
- package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/src/contract.ts +1 -0
- package/src/define.ts +198 -71
- package/src/index.ts +16 -0
- package/src/lint.ts +33 -0
- package/src/provider.ts +27 -0
- package/src/runtime/auth-flow.ts +2 -0
- package/src/runtime/browser.ts +293 -1
- package/src/runtime/http.ts +48 -7
- package/src/runtime/stealth.ts +182 -5
- package/src/server/index.ts +36 -0
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +725 -0
- package/src/server/serve.ts +67 -4
- package/src/testing/run.ts +9 -1
- package/src/types.ts +186 -0
package/dist/runtime/browser.js
CHANGED
|
@@ -3,6 +3,106 @@ import { ProviderError } from "../errors";
|
|
|
3
3
|
const require = createRequire(import.meta.url);
|
|
4
4
|
const DEFAULT_WAIT_TIMEOUT_MS = 30_000;
|
|
5
5
|
const SELECTOR_POLL_INTERVAL_MS = 100;
|
|
6
|
+
const RESOURCE_POLICY_ROUTE_PATTERN = "**/*";
|
|
7
|
+
const DEFAULT_RESOURCE_METHODS = ["GET", "HEAD"];
|
|
8
|
+
function toResourceBody(body) {
|
|
9
|
+
if (body === undefined || typeof body === "string" || Buffer.isBuffer(body)) {
|
|
10
|
+
return body;
|
|
11
|
+
}
|
|
12
|
+
if (body instanceof ArrayBuffer) {
|
|
13
|
+
return Buffer.from(new Uint8Array(body));
|
|
14
|
+
}
|
|
15
|
+
return Buffer.from(body);
|
|
16
|
+
}
|
|
17
|
+
function isResourceMethod(method) {
|
|
18
|
+
return method === "GET" || method === "HEAD";
|
|
19
|
+
}
|
|
20
|
+
async function toResourceRequest(request) {
|
|
21
|
+
const method = request.method().toUpperCase();
|
|
22
|
+
if (!isResourceMethod(method)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
headers: await request.allHeaders(),
|
|
27
|
+
method,
|
|
28
|
+
resourceType: request.resourceType(),
|
|
29
|
+
url: request.url(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function toCdpResourceRequest(params) {
|
|
33
|
+
if (!isRecord(params)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const requestId = params.requestId;
|
|
37
|
+
const rawRequest = params.request;
|
|
38
|
+
if (typeof requestId !== "string" || !isRecord(rawRequest)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const url = rawRequest.url;
|
|
42
|
+
const method = String(rawRequest.method ?? "").toUpperCase();
|
|
43
|
+
if (typeof url !== "string" || !isResourceMethod(method)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
requestId,
|
|
48
|
+
request: {
|
|
49
|
+
headers: toCdpResourceHeaders(rawRequest.headers),
|
|
50
|
+
method,
|
|
51
|
+
resourceType: typeof params.resourceType === "string" ? params.resourceType : undefined,
|
|
52
|
+
url,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function getCdpPausedRequestId(params) {
|
|
57
|
+
if (!isRecord(params) || typeof params.requestId !== "string") {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return params.requestId;
|
|
61
|
+
}
|
|
62
|
+
function toCdpResourceHeaders(value) {
|
|
63
|
+
if (!isRecord(value)) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
const headers = {};
|
|
67
|
+
for (const [name, headerValue] of Object.entries(value)) {
|
|
68
|
+
if (typeof headerValue === "string") {
|
|
69
|
+
headers[name] = headerValue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return headers;
|
|
73
|
+
}
|
|
74
|
+
function matchesResourceRoute(match, request) {
|
|
75
|
+
if (typeof match === "string") {
|
|
76
|
+
return request.url === match;
|
|
77
|
+
}
|
|
78
|
+
if (match instanceof RegExp) {
|
|
79
|
+
return match.test(request.url);
|
|
80
|
+
}
|
|
81
|
+
return match(request);
|
|
82
|
+
}
|
|
83
|
+
function toCdpFulfillParams(requestId, decision) {
|
|
84
|
+
const body = toResourceBody(decision.body);
|
|
85
|
+
return {
|
|
86
|
+
...(body === undefined
|
|
87
|
+
? {}
|
|
88
|
+
: { body: Buffer.from(body).toString("base64") }),
|
|
89
|
+
...(decision.headers === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: {
|
|
92
|
+
responseHeaders: Object.entries(decision.headers).map(([name, value]) => ({ name, value })),
|
|
93
|
+
}),
|
|
94
|
+
requestId,
|
|
95
|
+
responseCode: decision.status ?? 200,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function fulfillResourceRoute(route, decision) {
|
|
99
|
+
const body = toResourceBody(decision.body);
|
|
100
|
+
await route.fulfill({
|
|
101
|
+
...(body === undefined ? {} : { body }),
|
|
102
|
+
...(decision.headers === undefined ? {} : { headers: decision.headers }),
|
|
103
|
+
status: decision.status ?? 200,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
6
106
|
function getDefaultCdpPoolUrl(env = process.env) {
|
|
7
107
|
return env.APIFUSE__CDP_POOL__URL;
|
|
8
108
|
}
|
|
@@ -256,6 +356,38 @@ class PlaywrightBrowserPage {
|
|
|
256
356
|
async close() {
|
|
257
357
|
await this.page.close();
|
|
258
358
|
}
|
|
359
|
+
async withResourcePolicy(policy, run) {
|
|
360
|
+
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
361
|
+
const handler = async (route) => {
|
|
362
|
+
const request = await toResourceRequest(route.request());
|
|
363
|
+
if (!request || !allowedMethods.has(request.method)) {
|
|
364
|
+
await route.abort("blockedbyclient");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
for (const resourceRoute of policy.routes) {
|
|
368
|
+
if (!matchesResourceRoute(resourceRoute.match, request)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const decision = await resourceRoute.handle(request);
|
|
372
|
+
switch (decision.action) {
|
|
373
|
+
case "fulfill":
|
|
374
|
+
await fulfillResourceRoute(route, decision);
|
|
375
|
+
return;
|
|
376
|
+
case "block":
|
|
377
|
+
await route.abort("blockedbyclient");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
await route.abort("blockedbyclient");
|
|
382
|
+
};
|
|
383
|
+
await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
384
|
+
try {
|
|
385
|
+
return await run();
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
259
391
|
}
|
|
260
392
|
class PlaywrightBrowserClient {
|
|
261
393
|
options;
|
|
@@ -737,6 +869,77 @@ class CdpPoolBrowserPage {
|
|
|
737
869
|
await this.pageClient.close();
|
|
738
870
|
}
|
|
739
871
|
}
|
|
872
|
+
async withResourcePolicy(policy, run) {
|
|
873
|
+
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
874
|
+
const handlePausedRequest = (params) => {
|
|
875
|
+
void this.handleResourcePolicyPausedRequest(params, policy, allowedMethods);
|
|
876
|
+
};
|
|
877
|
+
const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
|
|
878
|
+
try {
|
|
879
|
+
await this.pageClient.send("Fetch.enable", {
|
|
880
|
+
patterns: [{ requestStage: "Request", urlPattern: "*" }],
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
unsubscribe();
|
|
885
|
+
throw new ProviderError("CDP browser target does not support BrowserPage.withResourcePolicy()", {
|
|
886
|
+
cause: error instanceof Error ? error : undefined,
|
|
887
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
888
|
+
fix: "Use a Chromium CDP target with the Fetch domain enabled, or use the local Playwright browser runtime.",
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
return await run();
|
|
893
|
+
}
|
|
894
|
+
finally {
|
|
895
|
+
unsubscribe();
|
|
896
|
+
await this.pageClient.send("Fetch.disable");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
async handleResourcePolicyPausedRequest(params, policy, allowedMethods) {
|
|
900
|
+
const requestId = getCdpPausedRequestId(params);
|
|
901
|
+
if (requestId === null) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
const parsed = toCdpResourceRequest(params);
|
|
906
|
+
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
907
|
+
await this.failCdpResourceRequest(requestId);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
for (const resourceRoute of policy.routes) {
|
|
911
|
+
if (!matchesResourceRoute(resourceRoute.match, parsed.request)) {
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
const decision = await resourceRoute.handle(parsed.request);
|
|
915
|
+
switch (decision.action) {
|
|
916
|
+
case "fulfill":
|
|
917
|
+
await this.pageClient.send("Fetch.fulfillRequest", toCdpFulfillParams(parsed.requestId, decision));
|
|
918
|
+
return;
|
|
919
|
+
case "block":
|
|
920
|
+
await this.failCdpResourceRequest(parsed.requestId);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
await this.failCdpResourceRequest(parsed.requestId);
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
await this.failCdpResourceRequest(requestId);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async failCdpResourceRequest(requestId) {
|
|
931
|
+
try {
|
|
932
|
+
await this.pageClient.send("Fetch.failRequest", {
|
|
933
|
+
errorReason: "BlockedByClient",
|
|
934
|
+
requestId,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
if (error instanceof Error) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
740
943
|
async initialize() {
|
|
741
944
|
if (this.initialized) {
|
|
742
945
|
return;
|
package/dist/runtime/http.js
CHANGED
|
@@ -396,7 +396,8 @@ function toHttpTransportError(error) {
|
|
|
396
396
|
}
|
|
397
397
|
async function toNativeHttpResponse(response) {
|
|
398
398
|
const headers = Object.fromEntries(response.headers.entries());
|
|
399
|
-
const
|
|
399
|
+
const bodyBytes = new Uint8Array(await response.arrayBuffer());
|
|
400
|
+
const rawText = new TextDecoder().decode(bodyBytes);
|
|
400
401
|
const data = parseHttpData(rawText, headers);
|
|
401
402
|
return {
|
|
402
403
|
data,
|
|
@@ -412,6 +413,8 @@ async function toNativeHttpResponse(response) {
|
|
|
412
413
|
ok: response.status >= 200 && response.status < 300,
|
|
413
414
|
status: response.status,
|
|
414
415
|
text: async () => rawText,
|
|
416
|
+
arrayBuffer: async () => bodyBytes.buffer.slice(bodyBytes.byteOffset, bodyBytes.byteOffset + bodyBytes.byteLength),
|
|
417
|
+
bytes: async () => bodyBytes.slice(0),
|
|
415
418
|
};
|
|
416
419
|
}
|
|
417
420
|
async function drainNativeResponseBody(response) {
|
|
@@ -474,12 +477,18 @@ function isAbsoluteUrl(url) {
|
|
|
474
477
|
function resolveHttpUrl(baseUrl, url) {
|
|
475
478
|
return new URL(url, baseUrl ?? DEFAULT_HTTP_BASE_URL).toString();
|
|
476
479
|
}
|
|
477
|
-
async function resolveNativeProxy(options, clientOptions, warn) {
|
|
480
|
+
async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset = 0) {
|
|
481
|
+
const baseProxyAttempt = clientOptions.proxyAttempt === undefined ||
|
|
482
|
+
!Number.isFinite(clientOptions.proxyAttempt)
|
|
483
|
+
? 0
|
|
484
|
+
: Math.max(0, Math.floor(clientOptions.proxyAttempt));
|
|
478
485
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
479
486
|
proxy: options.proxy ?? clientOptions.proxy,
|
|
480
487
|
upstream: clientOptions.upstream,
|
|
481
488
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
489
|
+
proxyPolicy: clientOptions.proxyPolicy,
|
|
482
490
|
affinityKey: clientOptions.affinityKey,
|
|
491
|
+
proxyAttempt: baseProxyAttempt + proxyAttemptOffset,
|
|
483
492
|
telemetry: clientOptions.telemetry,
|
|
484
493
|
});
|
|
485
494
|
if (resolvedProxy.shouldWarn) {
|
|
@@ -501,14 +510,15 @@ function normalizeNativeFetchBody(body) {
|
|
|
501
510
|
copied.set(normalized);
|
|
502
511
|
return copied.buffer;
|
|
503
512
|
}
|
|
504
|
-
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes) {
|
|
513
|
+
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0) {
|
|
505
514
|
const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
|
|
506
515
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
507
516
|
const timeoutHandle = options.timeout
|
|
508
517
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
509
518
|
: undefined;
|
|
519
|
+
let proxy;
|
|
510
520
|
try {
|
|
511
|
-
|
|
521
|
+
proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
|
|
512
522
|
const requestInit = {
|
|
513
523
|
headers: options.headers,
|
|
514
524
|
method,
|
|
@@ -529,6 +539,7 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
|
|
|
529
539
|
status: response.status,
|
|
530
540
|
headers,
|
|
531
541
|
retryable: statusRetryCodes.includes(response.status),
|
|
542
|
+
proxyUsed: Boolean(proxy),
|
|
532
543
|
};
|
|
533
544
|
}
|
|
534
545
|
if (response.status >= 400 && options.throwOnHttpError !== false) {
|
|
@@ -544,7 +555,9 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
|
|
|
544
555
|
if (error instanceof SyntaxError) {
|
|
545
556
|
throw error;
|
|
546
557
|
}
|
|
547
|
-
|
|
558
|
+
const transportError = toHttpTransportError(error);
|
|
559
|
+
transportError.proxyUsed = Boolean(proxy);
|
|
560
|
+
throw transportError;
|
|
548
561
|
}
|
|
549
562
|
finally {
|
|
550
563
|
if (timeoutHandle)
|
|
@@ -608,20 +621,25 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
608
621
|
assertNoHttpTransportOverrides(options);
|
|
609
622
|
const headersOptions = withClientHeaders(options, clientOptions, options.body);
|
|
610
623
|
const methodName = normalizeHttpMethod(method);
|
|
611
|
-
const
|
|
624
|
+
const explicitRetry = headersOptions.retry !== undefined;
|
|
625
|
+
const retryOptions = normalizeRetryOptions(headersOptions.retry) ??
|
|
626
|
+
(explicitRetry
|
|
627
|
+
? undefined
|
|
628
|
+
: createRetryOptions(HttpRetryPreset.TransportTransient));
|
|
612
629
|
if (retryOptions)
|
|
613
630
|
validateUnsafeRetryMethods(retryOptions);
|
|
614
631
|
const retryEnabled = Boolean(retryOptions &&
|
|
615
632
|
retryOptions.attempts > 1 &&
|
|
616
633
|
isMethodRetryable(methodName, retryOptions));
|
|
617
634
|
const statusRetryEnabled = Boolean(retryEnabled &&
|
|
635
|
+
explicitRetry &&
|
|
618
636
|
retryOptions &&
|
|
619
637
|
retryOptions.statusCodes.length > 0 &&
|
|
620
638
|
headersOptions.throwOnHttpError !== false);
|
|
621
639
|
const attemptOptions = statusRetryEnabled
|
|
622
640
|
? { ...headersOptions, throwOnHttpError: false }
|
|
623
641
|
: headersOptions;
|
|
624
|
-
const executeOnce = () => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined);
|
|
642
|
+
const executeOnce = (proxyAttemptOffset = 0) => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset);
|
|
625
643
|
if (!retryEnabled || !retryOptions) {
|
|
626
644
|
const outcome = await executeOnce();
|
|
627
645
|
if (isHttpStatusOutcome(outcome)) {
|
|
@@ -633,7 +651,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
633
651
|
let lastStatus;
|
|
634
652
|
for (let attempt = 1; attempt <= retryOptions.attempts; attempt += 1) {
|
|
635
653
|
try {
|
|
636
|
-
const outcome = await executeOnce();
|
|
654
|
+
const outcome = await executeOnce(attempt - 1);
|
|
637
655
|
if (isHttpStatusOutcome(outcome)) {
|
|
638
656
|
lastStatus = outcome.status;
|
|
639
657
|
if (outcome.retryable && attempt < retryOptions.attempts) {
|
|
@@ -663,7 +681,9 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
663
681
|
catch (error) {
|
|
664
682
|
lastErrorCode = retryErrorCode(error);
|
|
665
683
|
lastStatus = retryErrorStatus(error);
|
|
684
|
+
const proxyUsed = Boolean(error.proxyUsed);
|
|
666
685
|
if (attempt < retryOptions.attempts &&
|
|
686
|
+
(explicitRetry || proxyUsed) &&
|
|
667
687
|
shouldRetryTransportError(error, retryOptions)) {
|
|
668
688
|
await sleep(computeRetryDelayMs(retryOptions, attempt));
|
|
669
689
|
continue;
|
|
@@ -12,7 +12,10 @@ export type StealthClientOptions = ProxyResolutionOptions & {
|
|
|
12
12
|
insecureSkipVerify?: boolean;
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
|
-
type StealthTransportResponse = Pick<ImpitResponse, "arrayBuffer" | "headers" | "json" | "ok" | "status" | "text"
|
|
16
|
-
|
|
15
|
+
type StealthTransportResponse = Pick<ImpitResponse, "arrayBuffer" | "headers" | "json" | "ok" | "status" | "text"> & {
|
|
16
|
+
url?: string;
|
|
17
|
+
redirected?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function normalizeResponse(response: StealthTransportResponse, requestUrl?: string): Promise<StealthResponse>;
|
|
17
20
|
export declare function createStealthClient(baseUrl: string, defaultProfileOrOptions?: string | StealthClientOptions, clientOptions?: StealthClientOptions): StealthClient;
|
|
18
21
|
export {};
|
package/dist/runtime/stealth.js
CHANGED
|
@@ -102,11 +102,32 @@ class CookieJarImpl {
|
|
|
102
102
|
getAll() {
|
|
103
103
|
return { ...this.cookies };
|
|
104
104
|
}
|
|
105
|
+
has(name) {
|
|
106
|
+
return Object.hasOwn(this.cookies, name);
|
|
107
|
+
}
|
|
105
108
|
toString() {
|
|
106
109
|
return Object.entries(this.cookies)
|
|
107
110
|
.map(([name, value]) => `${name}=${value}`)
|
|
108
111
|
.join("; ");
|
|
109
112
|
}
|
|
113
|
+
toHeader() {
|
|
114
|
+
return this.toString();
|
|
115
|
+
}
|
|
116
|
+
snapshot() {
|
|
117
|
+
return this.getAll();
|
|
118
|
+
}
|
|
119
|
+
restore(cookies) {
|
|
120
|
+
this.clear();
|
|
121
|
+
for (const [name, value] of Object.entries(cookies)) {
|
|
122
|
+
if (name)
|
|
123
|
+
this.cookies[name] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
clear() {
|
|
127
|
+
for (const name of Object.keys(this.cookies)) {
|
|
128
|
+
delete this.cookies[name];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
110
131
|
find(predicate) {
|
|
111
132
|
for (const [name, value] of Object.entries(this.cookies)) {
|
|
112
133
|
const cookie = `${name}=${value}`;
|
|
@@ -237,7 +258,7 @@ function splitCombinedSetCookieHeader(headerValue) {
|
|
|
237
258
|
cookieStrings.push(finalCookie);
|
|
238
259
|
return cookieStrings;
|
|
239
260
|
}
|
|
240
|
-
export async function normalizeResponse(response) {
|
|
261
|
+
export async function normalizeResponse(response, requestUrl) {
|
|
241
262
|
const headers = Object.fromEntries(response.headers.entries());
|
|
242
263
|
const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers));
|
|
243
264
|
const bodyBytes = await response.arrayBuffer();
|
|
@@ -245,6 +266,12 @@ export async function normalizeResponse(response) {
|
|
|
245
266
|
return {
|
|
246
267
|
status: response.status,
|
|
247
268
|
ok: response.status >= 200 && response.status < 300,
|
|
269
|
+
...(response.url ? { url: response.url } : {}),
|
|
270
|
+
...(response.redirected !== undefined
|
|
271
|
+
? { redirected: response.redirected }
|
|
272
|
+
: requestUrl && response.url
|
|
273
|
+
? { redirected: response.url !== requestUrl }
|
|
274
|
+
: {}),
|
|
248
275
|
headers,
|
|
249
276
|
rawHeaders: headerEntriesFromHeaders(response.headers),
|
|
250
277
|
body,
|
|
@@ -537,6 +564,23 @@ function normalizeMethod(method) {
|
|
|
537
564
|
throw new SDKError(`Unsupported stealth method: ${method}`);
|
|
538
565
|
}
|
|
539
566
|
}
|
|
567
|
+
function isRedirectStatus(status) {
|
|
568
|
+
return [301, 302, 303, 307, 308].includes(status);
|
|
569
|
+
}
|
|
570
|
+
function nextRedirectMethod(status, method) {
|
|
571
|
+
if (status === 303 && method !== "HEAD")
|
|
572
|
+
return "GET";
|
|
573
|
+
if ((status === 301 || status === 302) && method === "POST")
|
|
574
|
+
return "GET";
|
|
575
|
+
return method;
|
|
576
|
+
}
|
|
577
|
+
function locationHeader(headers) {
|
|
578
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
579
|
+
if (name.toLowerCase() === "location")
|
|
580
|
+
return value;
|
|
581
|
+
}
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
540
584
|
function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
541
585
|
const clients = new Map();
|
|
542
586
|
let closed = false;
|
|
@@ -588,7 +632,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
588
632
|
proxyHash: proxyEndpointHash(resolvedProxy.url),
|
|
589
633
|
};
|
|
590
634
|
}
|
|
591
|
-
|
|
635
|
+
const session = {
|
|
592
636
|
async fetch(url, options = {}) {
|
|
593
637
|
const method = normalizeMethod(options.method ?? "GET");
|
|
594
638
|
const hasExplicitRetryPolicy = options.retry !== undefined;
|
|
@@ -656,13 +700,14 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
656
700
|
const requestInit = {
|
|
657
701
|
headers: normalizeHeaders(headers),
|
|
658
702
|
method,
|
|
703
|
+
...(options.redirect ? { redirect: options.redirect } : {}),
|
|
659
704
|
...(options.timeout ? { timeout: options.timeout } : {}),
|
|
660
705
|
};
|
|
661
706
|
if (options.body !== undefined) {
|
|
662
707
|
requestInit.body = normalizeBody(options.body);
|
|
663
708
|
}
|
|
664
709
|
const response = await getClient(profileName, proxy, ignoreTlsErrors).fetch(requestUrl, requestInit);
|
|
665
|
-
const normalized = await normalizeResponse(response);
|
|
710
|
+
const normalized = await normalizeResponse(response, requestUrl);
|
|
666
711
|
cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers));
|
|
667
712
|
if (proxy &&
|
|
668
713
|
isProxyConnectFailureResponse(response, normalized.body)) {
|
|
@@ -758,11 +803,112 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
758
803
|
}
|
|
759
804
|
throw normalizeStealthTransportError(lastError);
|
|
760
805
|
},
|
|
806
|
+
cookies: cookieJar,
|
|
807
|
+
redirects: {
|
|
808
|
+
async run(options) {
|
|
809
|
+
const maxHops = options.maxHops === undefined || !Number.isFinite(options.maxHops)
|
|
810
|
+
? 10
|
|
811
|
+
: Math.max(0, Math.floor(options.maxHops));
|
|
812
|
+
const hops = [];
|
|
813
|
+
let currentUrl = resolveUrl(baseUrl, options.url);
|
|
814
|
+
let method = normalizeMethod(options.method ?? "GET");
|
|
815
|
+
let body = options.body;
|
|
816
|
+
let response;
|
|
817
|
+
const visitedRequests = new Set();
|
|
818
|
+
const { url: _url, maxHops: _maxHops, stopWhen, params, ...fetchOptions } = options;
|
|
819
|
+
for (let hopIndex = 0; hopIndex <= maxHops; hopIndex += 1) {
|
|
820
|
+
visitedRequests.add(`${method} ${currentUrl}`);
|
|
821
|
+
response = await session.fetch(currentUrl, {
|
|
822
|
+
...fetchOptions,
|
|
823
|
+
body,
|
|
824
|
+
method,
|
|
825
|
+
...(hopIndex === 0 && params ? { params } : {}),
|
|
826
|
+
redirect: "manual",
|
|
827
|
+
throwOnHttpError: false,
|
|
828
|
+
});
|
|
829
|
+
if (!isRedirectStatus(response.status)) {
|
|
830
|
+
return {
|
|
831
|
+
final: response,
|
|
832
|
+
hops,
|
|
833
|
+
reason: "completed",
|
|
834
|
+
cookies: cookieJar.snapshot(),
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const location = locationHeader(response.headers);
|
|
838
|
+
const nextUrl = location
|
|
839
|
+
? new URL(location, response.url ?? currentUrl).toString()
|
|
840
|
+
: undefined;
|
|
841
|
+
const hop = {
|
|
842
|
+
url: response.url ?? currentUrl,
|
|
843
|
+
status: response.status,
|
|
844
|
+
method,
|
|
845
|
+
...(location ? { location } : {}),
|
|
846
|
+
...(nextUrl ? { nextUrl } : {}),
|
|
847
|
+
};
|
|
848
|
+
hops.push(hop);
|
|
849
|
+
if (stopWhen && (await stopWhen(hop))) {
|
|
850
|
+
return {
|
|
851
|
+
final: response,
|
|
852
|
+
hops,
|
|
853
|
+
reason: "stopped",
|
|
854
|
+
cookies: cookieJar.snapshot(),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (!nextUrl) {
|
|
858
|
+
return {
|
|
859
|
+
final: response,
|
|
860
|
+
hops,
|
|
861
|
+
reason: "missing_location",
|
|
862
|
+
cookies: cookieJar.snapshot(),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
if (hops.length > maxHops) {
|
|
866
|
+
return {
|
|
867
|
+
final: response,
|
|
868
|
+
hops,
|
|
869
|
+
reason: "max_hops",
|
|
870
|
+
cookies: cookieJar.snapshot(),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
const nextMethod = nextRedirectMethod(response.status, method);
|
|
874
|
+
if (nextMethod !== method) {
|
|
875
|
+
body = undefined;
|
|
876
|
+
}
|
|
877
|
+
if (visitedRequests.has(`${nextMethod} ${nextUrl}`)) {
|
|
878
|
+
return {
|
|
879
|
+
final: response,
|
|
880
|
+
hops,
|
|
881
|
+
reason: "loop",
|
|
882
|
+
cookies: cookieJar.snapshot(),
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
method = nextMethod;
|
|
886
|
+
currentUrl = nextUrl;
|
|
887
|
+
}
|
|
888
|
+
if (!response) {
|
|
889
|
+
response = await session.fetch(currentUrl, {
|
|
890
|
+
...fetchOptions,
|
|
891
|
+
body,
|
|
892
|
+
method,
|
|
893
|
+
...(params ? { params } : {}),
|
|
894
|
+
redirect: "manual",
|
|
895
|
+
throwOnHttpError: false,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
final: response,
|
|
900
|
+
hops,
|
|
901
|
+
reason: "max_hops",
|
|
902
|
+
cookies: cookieJar.snapshot(),
|
|
903
|
+
};
|
|
904
|
+
},
|
|
905
|
+
},
|
|
761
906
|
close() {
|
|
762
907
|
closed = true;
|
|
763
908
|
clients.clear();
|
|
764
909
|
},
|
|
765
910
|
};
|
|
911
|
+
return session;
|
|
766
912
|
async function classifyProxyAuthDiagnostic(profileName, proxy) {
|
|
767
913
|
try {
|
|
768
914
|
const response = await getClient(profileName, proxy, false).fetch(PROXY_AUTH_DIAGNOSTIC_URL, {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { createServerApp, type ServeOptions, serve } from "./serve";
|
|
2
|
+
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, type SelfTestAppOptions, type SelfTestCaseResult, type SelfTestCaseStatus, type SelfTestOperationInvoke, type SelfTestRequest, SelfTestRequestSchema, type SelfTestResponse, } from "./self-test";
|
|
3
|
+
export { resolveHealthCheckInputDateTokens } from "./self-test-input-tokens";
|
|
4
|
+
export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction";
|
|
5
|
+
export { DEFAULT_SELF_TEST_PORT, deriveSelfTestToken, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, PROVIDER_RUNTIME_SELF_TEST_PORT_ENV, resolveSelfTestMasterSecrets, type SelfTestMasterSecrets, verifySelfTestAuthorization, } from "./self-test-token";
|
|
2
6
|
export type { AuthFlowRequest, AuthFlowResponse, AuthFlowSuccessResponse, ConnectionMode, OperationConnection, OperationErrorResponse, OperationRequest, OperationResponse, OperationSuccessResponse, } from "./types";
|
|
3
7
|
export { AuthFlowRequestSchema, AuthFlowSuccessResponseSchema, ConnectionModeSchema, ErrorEnvelopeSchema, OperationConnectionSchema, OperationErrorResponseSchema, OperationRequestSchema, OperationSuccessResponseSchema, } from "./types";
|
package/dist/server/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export { createServerApp, serve } from "./serve";
|
|
2
|
+
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, SelfTestRequestSchema, } from "./self-test";
|
|
3
|
+
export { resolveHealthCheckInputDateTokens } from "./self-test-input-tokens";
|
|
4
|
+
export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction";
|
|
5
|
+
export { DEFAULT_SELF_TEST_PORT, deriveSelfTestToken, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, PROVIDER_RUNTIME_SELF_TEST_PORT_ENV, resolveSelfTestMasterSecrets, verifySelfTestAuthorization, } from "./self-test-token";
|
|
2
6
|
export { AuthFlowRequestSchema, AuthFlowSuccessResponseSchema, ConnectionModeSchema, ErrorEnvelopeSchema, OperationConnectionSchema, OperationErrorResponseSchema, OperationRequestSchema, OperationSuccessResponseSchema, } from "./types";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveHealthCheckInputDateTokens(value: unknown, now?: Date): unknown;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relative-KST date token resolution for health-check case inputs, ported from
|
|
3
|
+
* the monorepo health-monitor (`apps/health-monitor/src/lib/health-check-input.ts`)
|
|
4
|
+
* so provider self-tests resolve durable probe inputs identically.
|
|
5
|
+
*
|
|
6
|
+
* Supported token: `+<days>d` or `+<days>d:YYYYMMDD` (1..365 days ahead, KST).
|
|
7
|
+
*/
|
|
8
|
+
const RELATIVE_KST_DATE_TOKEN = /^\+(\d{1,3})d(?::(YYYYMMDD))?$/i;
|
|
9
|
+
function dateFromKstDaysAhead(daysAhead, now = new Date(), format = "YYYY-MM-DD") {
|
|
10
|
+
const kstNow = new Date(now.getTime() + 9 * 60 * 60 * 1000);
|
|
11
|
+
const date = new Date(Date.UTC(kstNow.getUTCFullYear(), kstNow.getUTCMonth(), kstNow.getUTCDate() + daysAhead));
|
|
12
|
+
const isoDate = date.toISOString().slice(0, 10);
|
|
13
|
+
return format === "YYYYMMDD" ? isoDate.replace(/-/g, "") : isoDate;
|
|
14
|
+
}
|
|
15
|
+
export function resolveHealthCheckInputDateTokens(value, now = new Date()) {
|
|
16
|
+
if (typeof value === "string") {
|
|
17
|
+
const relative = value.match(RELATIVE_KST_DATE_TOKEN);
|
|
18
|
+
if (!relative)
|
|
19
|
+
return value;
|
|
20
|
+
const daysAhead = Number(relative[1]);
|
|
21
|
+
if (!Number.isInteger(daysAhead) || daysAhead < 1 || daysAhead > 365) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
const format = relative[2]?.toUpperCase() === "YYYYMMDD" ? "YYYYMMDD" : "YYYY-MM-DD";
|
|
25
|
+
return dateFromKstDaysAhead(daysAhead, now, format);
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((entry) => resolveHealthCheckInputDateTokens(entry, now));
|
|
29
|
+
}
|
|
30
|
+
if (value && typeof value === "object") {
|
|
31
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
32
|
+
key,
|
|
33
|
+
resolveHealthCheckInputDateTokens(entry, now),
|
|
34
|
+
]));
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProviderDefinition } from "../types";
|
|
2
|
+
export declare const SELF_TEST_REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
3
|
+
/** Maximum length of any string echoed in a self-test response. */
|
|
4
|
+
export declare const SELF_TEST_MAX_TEXT_LENGTH = 300;
|
|
5
|
+
/**
|
|
6
|
+
* Collects every secret-shaped value known to the self-test runtime so probe
|
|
7
|
+
* results can be scrubbed before they leave the pod: master secrets, declared
|
|
8
|
+
* provider env secrets, health-probe credential env values, and request-supplied
|
|
9
|
+
* credential inputs.
|
|
10
|
+
*/
|
|
11
|
+
export declare function collectSelfTestSensitiveValues(provider: ProviderDefinition, options?: {
|
|
12
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
13
|
+
credentialInputs?: Readonly<Record<string, string>>;
|
|
14
|
+
}): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Scrubs known secret values and header-shaped credentials from a string and
|
|
17
|
+
* caps its length. Every string embedded in a SelfTestResponse MUST pass
|
|
18
|
+
* through this before serialization.
|
|
19
|
+
*/
|
|
20
|
+
export declare function redactSelfTestText(text: string, sensitiveValues: readonly string[], maxLength?: number): string;
|