@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.2
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 +61 -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 +46 -336
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/stealth.d.ts +5 -2
- package/dist/runtime/stealth.js +206 -206
- 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 +38 -10
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +1 -0
- 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 +84 -530
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/stealth.ts +269 -353
- 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 +75 -6
- package/src/server/types.ts +1 -0
- package/src/testing/run.ts +9 -1
- package/src/types.ts +186 -0
package/dist/runtime/stealth.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Impit } from "impit";
|
|
3
3
|
import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, resolveProxyConfigAsync, SMARTPROXY_MAX_POOL_SIZE, } from "../config/loader";
|
|
4
|
-
import {
|
|
4
|
+
import { SDKError, TransportError } from "../errors";
|
|
5
5
|
import { getStealthProfile } from "../stealth/profiles";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors";
|
|
7
|
+
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy";
|
|
8
8
|
import { appendQueryParams } from "./request-options";
|
|
9
9
|
const DEFAULT_PROFILE = "chrome-146";
|
|
10
10
|
const MISSING_PROXY_WARNING = "[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
@@ -14,31 +14,7 @@ const PROXY_CONNECT_FAILURE_CODE = "proxy_connect_failed";
|
|
|
14
14
|
const PROXY_CONNECT_FAILURE_BODY_PATTERN = /\bproxy\b.*\b(non[\s-]?200|connect|tunnel)|\bconnect\b.*\bproxy\b|\btunnel\b/i;
|
|
15
15
|
const PROXY_AUTH_DIAGNOSTIC_URL = "http://example.com/";
|
|
16
16
|
const PROXY_AUTH_DIAGNOSTIC_TIMEOUT_MS = 5_000;
|
|
17
|
-
const
|
|
18
|
-
const DEFAULT_STEALTH_RETRY_ERROR_CODES = [
|
|
19
|
-
PROXY_CONNECT_FAILURE_CODE,
|
|
20
|
-
"transport_network_error",
|
|
21
|
-
"transport_timeout",
|
|
22
|
-
];
|
|
23
|
-
const RATE_LIMIT_STEALTH_RETRY_ERROR_CODES = ["transport_timeout"];
|
|
24
|
-
const KNOWN_STEALTH_RETRY_METHODS = new Set([
|
|
25
|
-
"GET",
|
|
26
|
-
"HEAD",
|
|
27
|
-
"POST",
|
|
28
|
-
"PUT",
|
|
29
|
-
"DELETE",
|
|
30
|
-
"OPTIONS",
|
|
31
|
-
"TRACE",
|
|
32
|
-
"PATCH",
|
|
33
|
-
]);
|
|
34
|
-
const UNSAFE_STEALTH_RETRY_METHODS = new Set([
|
|
35
|
-
"POST",
|
|
36
|
-
"PUT",
|
|
37
|
-
"PATCH",
|
|
38
|
-
"DELETE",
|
|
39
|
-
"TRACE",
|
|
40
|
-
]);
|
|
41
|
-
const MAX_STEALTH_RETRY_ATTEMPTS = 8;
|
|
17
|
+
const STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES = [PROXY_CONNECT_FAILURE_CODE];
|
|
42
18
|
const REMOVED_CHROME_PROFILE_NAMES = new Set([
|
|
43
19
|
"chrome-120",
|
|
44
20
|
"chrome-124",
|
|
@@ -102,11 +78,32 @@ class CookieJarImpl {
|
|
|
102
78
|
getAll() {
|
|
103
79
|
return { ...this.cookies };
|
|
104
80
|
}
|
|
81
|
+
has(name) {
|
|
82
|
+
return Object.hasOwn(this.cookies, name);
|
|
83
|
+
}
|
|
105
84
|
toString() {
|
|
106
85
|
return Object.entries(this.cookies)
|
|
107
86
|
.map(([name, value]) => `${name}=${value}`)
|
|
108
87
|
.join("; ");
|
|
109
88
|
}
|
|
89
|
+
toHeader() {
|
|
90
|
+
return this.toString();
|
|
91
|
+
}
|
|
92
|
+
snapshot() {
|
|
93
|
+
return this.getAll();
|
|
94
|
+
}
|
|
95
|
+
restore(cookies) {
|
|
96
|
+
this.clear();
|
|
97
|
+
for (const [name, value] of Object.entries(cookies)) {
|
|
98
|
+
if (name)
|
|
99
|
+
this.cookies[name] = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
clear() {
|
|
103
|
+
for (const name of Object.keys(this.cookies)) {
|
|
104
|
+
delete this.cookies[name];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
110
107
|
find(predicate) {
|
|
111
108
|
for (const [name, value] of Object.entries(this.cookies)) {
|
|
112
109
|
const cookie = `${name}=${value}`;
|
|
@@ -237,7 +234,7 @@ function splitCombinedSetCookieHeader(headerValue) {
|
|
|
237
234
|
cookieStrings.push(finalCookie);
|
|
238
235
|
return cookieStrings;
|
|
239
236
|
}
|
|
240
|
-
export async function normalizeResponse(response) {
|
|
237
|
+
export async function normalizeResponse(response, requestUrl) {
|
|
241
238
|
const headers = Object.fromEntries(response.headers.entries());
|
|
242
239
|
const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers));
|
|
243
240
|
const bodyBytes = await response.arrayBuffer();
|
|
@@ -245,6 +242,12 @@ export async function normalizeResponse(response) {
|
|
|
245
242
|
return {
|
|
246
243
|
status: response.status,
|
|
247
244
|
ok: response.status >= 200 && response.status < 300,
|
|
245
|
+
...(response.url ? { url: response.url } : {}),
|
|
246
|
+
...(response.redirected !== undefined
|
|
247
|
+
? { redirected: response.redirected }
|
|
248
|
+
: requestUrl && response.url
|
|
249
|
+
? { redirected: response.url !== requestUrl }
|
|
250
|
+
: {}),
|
|
248
251
|
headers,
|
|
249
252
|
rawHeaders: headerEntriesFromHeaders(response.headers),
|
|
250
253
|
body,
|
|
@@ -276,130 +279,8 @@ function isPolicyManagedProxy(options) {
|
|
|
276
279
|
const policy = options.proxyPolicy ?? options.upstream?.proxy;
|
|
277
280
|
return Boolean(policy && typeof policy === "object");
|
|
278
281
|
}
|
|
279
|
-
function isRetrySafeStealthMethod(method) {
|
|
280
|
-
return method === "GET" || method === "HEAD" || method === "OPTIONS";
|
|
281
|
-
}
|
|
282
|
-
function createStealthRetryOptions(preset) {
|
|
283
|
-
switch (preset) {
|
|
284
|
-
case HttpRetryPreset.Off:
|
|
285
|
-
return {
|
|
286
|
-
attempts: 1,
|
|
287
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
288
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
289
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
290
|
-
};
|
|
291
|
-
case HttpRetryPreset.AggressiveRead:
|
|
292
|
-
return {
|
|
293
|
-
attempts: 4,
|
|
294
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
295
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
296
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
297
|
-
};
|
|
298
|
-
case HttpRetryPreset.RateLimitAware:
|
|
299
|
-
return {
|
|
300
|
-
attempts: 3,
|
|
301
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
302
|
-
errorCodes: RATE_LIMIT_STEALTH_RETRY_ERROR_CODES,
|
|
303
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
304
|
-
};
|
|
305
|
-
case HttpRetryPreset.SafeRead:
|
|
306
|
-
case HttpRetryPreset.TransportTransient:
|
|
307
|
-
return {
|
|
308
|
-
attempts: 3,
|
|
309
|
-
methods: DEFAULT_STEALTH_RETRY_METHODS,
|
|
310
|
-
errorCodes: DEFAULT_STEALTH_RETRY_ERROR_CODES,
|
|
311
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
throw new ProviderError(`Unknown stealth retry preset: ${preset}`, {
|
|
315
|
-
code: "retry_invalid_policy",
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
function normalizeStealthRetryOptions(retry) {
|
|
319
|
-
if (retry === undefined)
|
|
320
|
-
return undefined;
|
|
321
|
-
if (retry === false)
|
|
322
|
-
return createStealthRetryOptions(HttpRetryPreset.Off);
|
|
323
|
-
if (retry === true)
|
|
324
|
-
return createStealthRetryOptions(HttpRetryPreset.TransportTransient);
|
|
325
|
-
if (typeof retry === "string") {
|
|
326
|
-
if (!Object.values(HttpRetryPreset).includes(retry)) {
|
|
327
|
-
throw new ProviderError(`Unknown stealth retry preset: ${retry}`, {
|
|
328
|
-
code: "retry_invalid_policy",
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
return createStealthRetryOptions(retry);
|
|
332
|
-
}
|
|
333
|
-
if (typeof retry !== "object" || retry === null || Array.isArray(retry)) {
|
|
334
|
-
throw new ProviderError("Stealth retry policy must be a plain object", {
|
|
335
|
-
code: "retry_invalid_policy",
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
if (retry.unsafeMethodPolicy !== undefined &&
|
|
339
|
-
!Object.values(HttpRetryUnsafeMethodPolicy).includes(retry.unsafeMethodPolicy)) {
|
|
340
|
-
throw new ProviderError(`Unknown stealth retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`, { code: "retry_invalid_policy" });
|
|
341
|
-
}
|
|
342
|
-
if (retry.methods !== undefined) {
|
|
343
|
-
if (!Array.isArray(retry.methods)) {
|
|
344
|
-
throw new ProviderError("Stealth retry methods must be an array", {
|
|
345
|
-
code: "retry_invalid_policy",
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
const unknownMethods = retry.methods
|
|
349
|
-
.map((method) => (typeof method === "string" ? method.toUpperCase() : ""))
|
|
350
|
-
.filter((method) => !KNOWN_STEALTH_RETRY_METHODS.has(method));
|
|
351
|
-
if (unknownMethods.length > 0) {
|
|
352
|
-
throw new ProviderError(`Unknown stealth retry method(s): ${unknownMethods.join(", ")}`, { code: "retry_invalid_policy" });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
if (retry.errorCodes !== undefined) {
|
|
356
|
-
if (!Array.isArray(retry.errorCodes) ||
|
|
357
|
-
retry.errorCodes.some((errorCode) => typeof errorCode !== "string")) {
|
|
358
|
-
throw new ProviderError("Stealth retry errorCodes must contain only strings", { code: "retry_invalid_policy" });
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const base = createStealthRetryOptions(retry.preset ?? HttpRetryPreset.TransportTransient);
|
|
362
|
-
const attempts = retry.attempts === undefined || !Number.isFinite(retry.attempts)
|
|
363
|
-
? base.attempts
|
|
364
|
-
: Math.max(1, Math.min(MAX_STEALTH_RETRY_ATTEMPTS, Math.floor(retry.attempts)));
|
|
365
|
-
const normalized = {
|
|
366
|
-
attempts,
|
|
367
|
-
methods: retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
368
|
-
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
369
|
-
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
370
|
-
};
|
|
371
|
-
if (normalized.unsafeMethodPolicy !==
|
|
372
|
-
HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe) {
|
|
373
|
-
const unsafeMethods = normalized.methods.filter((method) => UNSAFE_STEALTH_RETRY_METHODS.has(method.toUpperCase()));
|
|
374
|
-
if (unsafeMethods.length > 0) {
|
|
375
|
-
throw new ProviderError(`Stealth retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return normalized;
|
|
379
|
-
}
|
|
380
|
-
function isExplicitStealthRetryAllowed(method, error, retryOptions) {
|
|
381
|
-
if (!retryOptions || retryOptions.attempts <= 1)
|
|
382
|
-
return false;
|
|
383
|
-
return (retryOptions.methods.includes(method.toUpperCase()) &&
|
|
384
|
-
retryOptions.errorCodes.includes(proxyAttemptErrorCode(error)));
|
|
385
|
-
}
|
|
386
|
-
function isRetryableProxyTransportError(error) {
|
|
387
|
-
if (error instanceof TransportError) {
|
|
388
|
-
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
return (error.code === PROXY_CONNECT_FAILURE_CODE ||
|
|
392
|
-
error.code === "transport_network_error" ||
|
|
393
|
-
error.code === "transport_timeout");
|
|
394
|
-
}
|
|
395
|
-
if (error instanceof SDKError) {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
-
return /\bproxy\b|\bnon[\s-]?200\b|\bconnect\b|\btunnel\b/i.test(message);
|
|
400
|
-
}
|
|
401
282
|
function isProxyConnectFailureResponse(response, body) {
|
|
402
|
-
return
|
|
283
|
+
return response.status === 0 && PROXY_CONNECT_FAILURE_BODY_PATTERN.test(body ?? "");
|
|
403
284
|
}
|
|
404
285
|
function createProxyConnectFailureError(body, cause) {
|
|
405
286
|
const bodyExcerpt = (body ?? "").trim().slice(0, 1_000);
|
|
@@ -502,8 +383,7 @@ function normalizeStealthTransportError(error) {
|
|
|
502
383
|
return createProxyEdgeAuthRejectedError(error instanceof Error ? error : undefined);
|
|
503
384
|
}
|
|
504
385
|
const proxyTunnelStatus = getProxyTunnelStatus(error);
|
|
505
|
-
if (proxyTunnelStatus !== undefined &&
|
|
506
|
-
isProxyPoolStaleStatus(proxyTunnelStatus)) {
|
|
386
|
+
if (proxyTunnelStatus !== undefined && isProxyPoolStaleStatus(proxyTunnelStatus)) {
|
|
507
387
|
return createProxyPoolStaleError(proxyTunnelStatus, error instanceof Error ? error : undefined);
|
|
508
388
|
}
|
|
509
389
|
if (PROXY_CONNECT_FAILURE_BODY_PATTERN.test(message)) {
|
|
@@ -515,6 +395,9 @@ function normalizeStealthTransportError(error) {
|
|
|
515
395
|
cause: error instanceof Error ? error : undefined,
|
|
516
396
|
});
|
|
517
397
|
}
|
|
398
|
+
function sleep(ms) {
|
|
399
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
400
|
+
}
|
|
518
401
|
function normalizeMethod(method) {
|
|
519
402
|
switch (method.toUpperCase()) {
|
|
520
403
|
case "HEAD":
|
|
@@ -537,6 +420,23 @@ function normalizeMethod(method) {
|
|
|
537
420
|
throw new SDKError(`Unsupported stealth method: ${method}`);
|
|
538
421
|
}
|
|
539
422
|
}
|
|
423
|
+
function isRedirectStatus(status) {
|
|
424
|
+
return [301, 302, 303, 307, 308].includes(status);
|
|
425
|
+
}
|
|
426
|
+
function nextRedirectMethod(status, method) {
|
|
427
|
+
if (status === 303 && method !== "HEAD")
|
|
428
|
+
return "GET";
|
|
429
|
+
if ((status === 301 || status === 302) && method === "POST")
|
|
430
|
+
return "GET";
|
|
431
|
+
return method;
|
|
432
|
+
}
|
|
433
|
+
function locationHeader(headers) {
|
|
434
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
435
|
+
if (name.toLowerCase() === "location")
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
540
440
|
function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
541
441
|
const clients = new Map();
|
|
542
442
|
let closed = false;
|
|
@@ -564,18 +464,16 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
564
464
|
return client;
|
|
565
465
|
}
|
|
566
466
|
async function resolveRequestProxy(options, proxyAttempt) {
|
|
567
|
-
const rawProxyAttemptOffset = options?.proxyAttemptOffset ?? 0;
|
|
568
|
-
const proxyAttemptOffset = Number.isFinite(rawProxyAttemptOffset)
|
|
569
|
-
? Math.max(0, Math.floor(rawProxyAttemptOffset))
|
|
570
|
-
: 0;
|
|
571
467
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
572
468
|
proxy: options?.proxy ?? clientOptions.proxy,
|
|
573
469
|
upstream: clientOptions.upstream,
|
|
574
470
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
575
471
|
affinityKey: clientOptions.affinityKey,
|
|
576
|
-
proxyAttempt:
|
|
577
|
-
|
|
578
|
-
: proxyAttemptOffset
|
|
472
|
+
proxyAttempt: computeProxyAttemptIndex({
|
|
473
|
+
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
474
|
+
proxyAttemptOffset: options?.proxyAttemptOffset,
|
|
475
|
+
retryAttemptOffset: proxyAttempt,
|
|
476
|
+
}),
|
|
579
477
|
telemetry: clientOptions.telemetry,
|
|
580
478
|
});
|
|
581
479
|
if (resolvedProxy.shouldWarn && !hasWarnedMissingProxy) {
|
|
@@ -588,20 +486,32 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
588
486
|
proxyHash: proxyEndpointHash(resolvedProxy.url),
|
|
589
487
|
};
|
|
590
488
|
}
|
|
591
|
-
|
|
489
|
+
const session = {
|
|
592
490
|
async fetch(url, options = {}) {
|
|
593
491
|
const method = normalizeMethod(options.method ?? "GET");
|
|
594
492
|
const hasExplicitRetryPolicy = options.retry !== undefined;
|
|
595
|
-
const stealthRetryOptions =
|
|
493
|
+
const stealthRetryOptions = normalizeProxyTransportRetryOptions(options.retry, {
|
|
494
|
+
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
495
|
+
label: "Stealth",
|
|
496
|
+
}) ??
|
|
497
|
+
(hasExplicitRetryPolicy
|
|
498
|
+
? undefined
|
|
499
|
+
: createDefaultProxyTransportRetryOptions({
|
|
500
|
+
extraErrorCodes: STEALTH_PROXY_TRANSPORT_RETRY_ERROR_CODES,
|
|
501
|
+
label: "Stealth",
|
|
502
|
+
}));
|
|
503
|
+
if (stealthRetryOptions) {
|
|
504
|
+
validateUnsafeProxyTransportRetryMethods(stealthRetryOptions, "Stealth");
|
|
505
|
+
}
|
|
596
506
|
const hasPolicyProxy = isPolicyManagedProxy(clientOptions);
|
|
597
507
|
const usesPolicyAllocator = hasPolicyProxy && !options.proxy && !clientOptions.proxy;
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
508
|
+
const retryAttemptCap = Math.max(1, stealthRetryOptions?.attempts ?? 1);
|
|
509
|
+
const policyProxyAttemptCap = Math.max(1, Math.min(MAX_POLICY_PROXY_RETRY_ATTEMPTS, clientOptions.proxyPolicy?.session?.poolSize ??
|
|
510
|
+
(typeof clientOptions.upstream?.proxy === "object"
|
|
511
|
+
? clientOptions.upstream.proxy.session?.poolSize
|
|
512
|
+
: undefined) ??
|
|
513
|
+
DEFAULT_SMARTPROXY_POOL_SIZE));
|
|
514
|
+
const maxAttempts = usesPolicyAllocator ? policyProxyAttemptCap : retryAttemptCap;
|
|
605
515
|
let lastError;
|
|
606
516
|
for (let refreshAttempt = 0; refreshAttempt <= MAX_POLICY_PROXY_POOL_REFRESHES; refreshAttempt += 1) {
|
|
607
517
|
let stalePoolError;
|
|
@@ -622,9 +532,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
622
532
|
...(attemptProxy?.poolIndex === undefined
|
|
623
533
|
? {}
|
|
624
534
|
: { poolIndex: attemptProxy.poolIndex }),
|
|
625
|
-
...(attemptProxy?.proxyHash
|
|
626
|
-
? { proxyHash: attemptProxy.proxyHash }
|
|
627
|
-
: {}),
|
|
535
|
+
...(attemptProxy?.proxyHash ? { proxyHash: attemptProxy.proxyHash } : {}),
|
|
628
536
|
outcome,
|
|
629
537
|
...(errorCode ? { errorCode } : {}),
|
|
630
538
|
...(status === undefined ? {} : { status }),
|
|
@@ -635,16 +543,14 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
635
543
|
assertNoUnsupportedFingerprintOverrides(options);
|
|
636
544
|
attemptProxy = await resolveRequestProxy(options, attempt);
|
|
637
545
|
proxy = attemptProxy.url;
|
|
638
|
-
if (proxy) {
|
|
546
|
+
if (proxy && usesPolicyAllocator) {
|
|
639
547
|
if (attemptedProxies.has(proxy)) {
|
|
640
548
|
break;
|
|
641
549
|
}
|
|
642
550
|
attemptedProxies.add(proxy);
|
|
643
551
|
}
|
|
644
552
|
const ignoreTlsErrors = Boolean(options.stealth?.insecureSkipVerify ??
|
|
645
|
-
(!hasPolicyProxy &&
|
|
646
|
-
proxy &&
|
|
647
|
-
clientOptions.proxyStealth?.insecureSkipVerify));
|
|
553
|
+
(!hasPolicyProxy && proxy && clientOptions.proxyStealth?.insecureSkipVerify));
|
|
648
554
|
const profileName = options.profile ?? defaultProfile;
|
|
649
555
|
const requestUrl = appendQueryParams(resolveUrl(baseUrl, url), options.params);
|
|
650
556
|
const headers = { ...(options.headers ?? {}) };
|
|
@@ -656,25 +562,22 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
656
562
|
const requestInit = {
|
|
657
563
|
headers: normalizeHeaders(headers),
|
|
658
564
|
method,
|
|
565
|
+
...(options.redirect ? { redirect: options.redirect } : {}),
|
|
659
566
|
...(options.timeout ? { timeout: options.timeout } : {}),
|
|
660
567
|
};
|
|
661
568
|
if (options.body !== undefined) {
|
|
662
569
|
requestInit.body = normalizeBody(options.body);
|
|
663
570
|
}
|
|
664
571
|
const response = await getClient(profileName, proxy, ignoreTlsErrors).fetch(requestUrl, requestInit);
|
|
665
|
-
const normalized = await normalizeResponse(response);
|
|
572
|
+
const normalized = await normalizeResponse(response, requestUrl);
|
|
666
573
|
cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers));
|
|
667
|
-
if (proxy &&
|
|
668
|
-
isProxyConnectFailureResponse(response, normalized.body)) {
|
|
574
|
+
if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
|
|
669
575
|
throw createProxyConnectFailureError(normalized.body);
|
|
670
576
|
}
|
|
671
577
|
if (response.status >= 400) {
|
|
672
578
|
if (proxy &&
|
|
673
579
|
usesPolicyAllocator &&
|
|
674
|
-
isProxyEdgeTlsRejectedResponse(response.status, [
|
|
675
|
-
JSON.stringify(responseHeadersToRecord(response.headers)),
|
|
676
|
-
normalized.body,
|
|
677
|
-
].join("\n"))) {
|
|
580
|
+
isProxyEdgeTlsRejectedResponse(response.status, [JSON.stringify(responseHeadersToRecord(response.headers)), normalized.body].join("\n"))) {
|
|
678
581
|
throw createProxyEdgeTlsRejectedError(response.status);
|
|
679
582
|
}
|
|
680
583
|
if (proxy && isProxyAuthIpDeniedMessage(normalized.body)) {
|
|
@@ -702,9 +605,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
702
605
|
const normalizedError = normalizeStealthTransportError(error);
|
|
703
606
|
recordProxyAttempt("error", proxyAttemptErrorCode(normalizedError), proxyAttemptStatus(normalizedError));
|
|
704
607
|
lastError = normalizedError;
|
|
705
|
-
if (proxy &&
|
|
706
|
-
usesPolicyAllocator &&
|
|
707
|
-
isProxyPoolRefreshableError(normalizedError)) {
|
|
608
|
+
if (proxy && usesPolicyAllocator && isProxyPoolRefreshableError(normalizedError)) {
|
|
708
609
|
stalePoolError = normalizedError;
|
|
709
610
|
if (shouldRunProxyAuthDiagnostic(normalizedError)) {
|
|
710
611
|
stalePoolDiagnosticProxy = proxy;
|
|
@@ -714,15 +615,20 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
714
615
|
}
|
|
715
616
|
break;
|
|
716
617
|
}
|
|
717
|
-
if (
|
|
718
|
-
|
|
719
|
-
(stealthRetryOptions
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
618
|
+
if (attempt + 1 <
|
|
619
|
+
(stealthRetryOptions
|
|
620
|
+
? Math.min(maxAttempts, stealthRetryOptions.attempts)
|
|
621
|
+
: maxAttempts) &&
|
|
622
|
+
shouldRetryProxyTransportAttempt({
|
|
623
|
+
error: normalizedError,
|
|
624
|
+
explicitRetry: hasExplicitRetryPolicy,
|
|
625
|
+
method,
|
|
626
|
+
options: stealthRetryOptions,
|
|
627
|
+
proxyUsed: Boolean(proxy),
|
|
628
|
+
})) {
|
|
629
|
+
if (stealthRetryOptions) {
|
|
630
|
+
await sleep(computeProxyTransportRetryDelayMs(stealthRetryOptions, attempt + 1));
|
|
631
|
+
}
|
|
726
632
|
continue;
|
|
727
633
|
}
|
|
728
634
|
throw normalizedError;
|
|
@@ -758,11 +664,112 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
758
664
|
}
|
|
759
665
|
throw normalizeStealthTransportError(lastError);
|
|
760
666
|
},
|
|
667
|
+
cookies: cookieJar,
|
|
668
|
+
redirects: {
|
|
669
|
+
async run(options) {
|
|
670
|
+
const maxHops = options.maxHops === undefined || !Number.isFinite(options.maxHops)
|
|
671
|
+
? 10
|
|
672
|
+
: Math.max(0, Math.floor(options.maxHops));
|
|
673
|
+
const hops = [];
|
|
674
|
+
let currentUrl = resolveUrl(baseUrl, options.url);
|
|
675
|
+
let method = normalizeMethod(options.method ?? "GET");
|
|
676
|
+
let body = options.body;
|
|
677
|
+
let response;
|
|
678
|
+
const visitedRequests = new Set();
|
|
679
|
+
const { url: _url, maxHops: _maxHops, stopWhen, params, ...fetchOptions } = options;
|
|
680
|
+
for (let hopIndex = 0; hopIndex <= maxHops; hopIndex += 1) {
|
|
681
|
+
visitedRequests.add(`${method} ${currentUrl}`);
|
|
682
|
+
response = await session.fetch(currentUrl, {
|
|
683
|
+
...fetchOptions,
|
|
684
|
+
body,
|
|
685
|
+
method,
|
|
686
|
+
...(hopIndex === 0 && params ? { params } : {}),
|
|
687
|
+
redirect: "manual",
|
|
688
|
+
throwOnHttpError: false,
|
|
689
|
+
});
|
|
690
|
+
if (!isRedirectStatus(response.status)) {
|
|
691
|
+
return {
|
|
692
|
+
final: response,
|
|
693
|
+
hops,
|
|
694
|
+
reason: "completed",
|
|
695
|
+
cookies: cookieJar.snapshot(),
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
const location = locationHeader(response.headers);
|
|
699
|
+
const nextUrl = location
|
|
700
|
+
? new URL(location, response.url ?? currentUrl).toString()
|
|
701
|
+
: undefined;
|
|
702
|
+
const hop = {
|
|
703
|
+
url: response.url ?? currentUrl,
|
|
704
|
+
status: response.status,
|
|
705
|
+
method,
|
|
706
|
+
...(location ? { location } : {}),
|
|
707
|
+
...(nextUrl ? { nextUrl } : {}),
|
|
708
|
+
};
|
|
709
|
+
hops.push(hop);
|
|
710
|
+
if (stopWhen && (await stopWhen(hop))) {
|
|
711
|
+
return {
|
|
712
|
+
final: response,
|
|
713
|
+
hops,
|
|
714
|
+
reason: "stopped",
|
|
715
|
+
cookies: cookieJar.snapshot(),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
if (!nextUrl) {
|
|
719
|
+
return {
|
|
720
|
+
final: response,
|
|
721
|
+
hops,
|
|
722
|
+
reason: "missing_location",
|
|
723
|
+
cookies: cookieJar.snapshot(),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
if (hops.length > maxHops) {
|
|
727
|
+
return {
|
|
728
|
+
final: response,
|
|
729
|
+
hops,
|
|
730
|
+
reason: "max_hops",
|
|
731
|
+
cookies: cookieJar.snapshot(),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
const nextMethod = nextRedirectMethod(response.status, method);
|
|
735
|
+
if (nextMethod !== method) {
|
|
736
|
+
body = undefined;
|
|
737
|
+
}
|
|
738
|
+
if (visitedRequests.has(`${nextMethod} ${nextUrl}`)) {
|
|
739
|
+
return {
|
|
740
|
+
final: response,
|
|
741
|
+
hops,
|
|
742
|
+
reason: "loop",
|
|
743
|
+
cookies: cookieJar.snapshot(),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
method = nextMethod;
|
|
747
|
+
currentUrl = nextUrl;
|
|
748
|
+
}
|
|
749
|
+
if (!response) {
|
|
750
|
+
response = await session.fetch(currentUrl, {
|
|
751
|
+
...fetchOptions,
|
|
752
|
+
body,
|
|
753
|
+
method,
|
|
754
|
+
...(params ? { params } : {}),
|
|
755
|
+
redirect: "manual",
|
|
756
|
+
throwOnHttpError: false,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
final: response,
|
|
761
|
+
hops,
|
|
762
|
+
reason: "max_hops",
|
|
763
|
+
cookies: cookieJar.snapshot(),
|
|
764
|
+
};
|
|
765
|
+
},
|
|
766
|
+
},
|
|
761
767
|
close() {
|
|
762
768
|
closed = true;
|
|
763
769
|
clients.clear();
|
|
764
770
|
},
|
|
765
771
|
};
|
|
772
|
+
return session;
|
|
766
773
|
async function classifyProxyAuthDiagnostic(profileName, proxy) {
|
|
767
774
|
try {
|
|
768
775
|
const response = await getClient(profileName, proxy, false).fetch(PROXY_AUTH_DIAGNOSTIC_URL, {
|
|
@@ -774,10 +781,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
|
|
|
774
781
|
}
|
|
775
782
|
catch (error) {
|
|
776
783
|
const message = error instanceof Error
|
|
777
|
-
? [
|
|
778
|
-
error.message,
|
|
779
|
-
error.cause instanceof Error ? error.cause.message : "",
|
|
780
|
-
]
|
|
784
|
+
? [error.message, error.cause instanceof Error ? error.cause.message : ""]
|
|
781
785
|
.filter(Boolean)
|
|
782
786
|
.join(" ")
|
|
783
787
|
: String(error);
|
|
@@ -805,12 +809,8 @@ function hasHeader(headers, name) {
|
|
|
805
809
|
return Object.keys(headers).some((key) => key.toLowerCase() === needle);
|
|
806
810
|
}
|
|
807
811
|
export function createStealthClient(baseUrl, defaultProfileOrOptions = DEFAULT_PROFILE, clientOptions = {}) {
|
|
808
|
-
const defaultProfile = typeof defaultProfileOrOptions === "string"
|
|
809
|
-
|
|
810
|
-
: DEFAULT_PROFILE;
|
|
811
|
-
const resolvedClientOptions = typeof defaultProfileOrOptions === "string"
|
|
812
|
-
? clientOptions
|
|
813
|
-
: defaultProfileOrOptions;
|
|
812
|
+
const defaultProfile = typeof defaultProfileOrOptions === "string" ? defaultProfileOrOptions : DEFAULT_PROFILE;
|
|
813
|
+
const resolvedClientOptions = typeof defaultProfileOrOptions === "string" ? clientOptions : defaultProfileOrOptions;
|
|
814
814
|
let sharedSession = null;
|
|
815
815
|
function getSharedSession() {
|
|
816
816
|
if (!sharedSession) {
|
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;
|