@apifuse/provider-sdk 2.2.0-beta.27 → 2.2.0-beta.29
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/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +34 -5
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +26 -2
- package/bin/apifuse-perf.ts +2 -4
- package/bin/apifuse-record.ts +39 -6
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +38 -0
- package/dist/ceremonies/index.js +52 -11
- package/dist/config/loader.d.ts +3 -1
- package/dist/config/loader.js +4 -2
- package/dist/index.d.ts +8 -7
- package/dist/index.js +5 -7
- package/dist/provider.d.ts +2 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/auth-flow.js +1 -1
- package/dist/runtime/browser.js +45 -2
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +135 -12
- package/dist/runtime/instrumentation.js +1 -1
- 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 +2 -33
- package/dist/runtime/native-network.js +2 -68
- package/dist/runtime/proxy-telemetry.js +3 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- 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 +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/browser.js +14 -4
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
- package/dist/runtime/resolver-vendors/twocaptcha.js +157 -53
- package/dist/runtime/resolver-vendors/types.d.ts +2 -2
- package/dist/runtime/resolver-vendors/types.js +3 -1
- package/dist/runtime/resolver.d.ts +15 -10
- package/dist/runtime/resolver.js +92 -23
- package/dist/runtime/state.js +5 -115
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +1 -0
- package/dist/runtime/stealth.js +8 -131
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/self-test.d.ts +13 -0
- package/dist/server/self-test.js +124 -46
- package/dist/server/serve-implementation.d.ts +199 -0
- package/dist/server/serve-implementation.js +2072 -0
- package/dist/server/serve.d.ts +1 -187
- package/dist/server/serve.js +1 -1827
- package/dist/stateful/errors.d.ts +5 -0
- package/dist/stateful/errors.js +10 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
- package/dist/stateful/stateful-provider-session-routing.js +2 -10
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/package.json +27 -2
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +78 -0
- package/src/ceremonies/index.ts +68 -18
- package/src/config/loader.ts +8 -2
- package/src/index.ts +18 -24
- package/src/provider.ts +12 -14
- package/src/runtime/auth-flow.ts +1 -1
- package/src/runtime/browser.ts +50 -2
- package/src/runtime/http.ts +155 -11
- package/src/runtime/instrumentation.ts +1 -1
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +16 -97
- package/src/runtime/proxy-telemetry.ts +5 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/browser.ts +14 -4
- package/src/runtime/resolver-vendors/twocaptcha.ts +190 -56
- package/src/runtime/resolver-vendors/types.ts +8 -2
- package/src/runtime/resolver.ts +140 -28
- package/src/runtime/state.ts +5 -144
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +15 -158
- package/src/serve.ts +6 -1
- package/src/server/index.ts +1 -0
- package/src/server/self-test.ts +184 -59
- package/src/server/serve-implementation.ts +3042 -0
- package/src/server/serve.ts +1 -2661
- package/src/stateful/errors.ts +12 -0
- package/src/stateful/stateful-provider-session-routing.ts +2 -11
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +1 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export type StatefulControlPlaneOperation = "resolve" | "acquire" | "renew" | "release";
|
|
2
|
+
export declare class StatefulRoutingDeadlineError extends Error {
|
|
3
|
+
readonly requestId: string;
|
|
4
|
+
readonly deadlineAt: string;
|
|
5
|
+
constructor(requestId: string, deadlineAt: string);
|
|
6
|
+
}
|
|
2
7
|
export declare class StatefulControlPlaneError extends Error {
|
|
3
8
|
readonly code: string;
|
|
4
9
|
readonly operation: StatefulControlPlaneOperation;
|
package/dist/stateful/errors.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export class StatefulRoutingDeadlineError extends Error {
|
|
2
|
+
requestId;
|
|
3
|
+
deadlineAt;
|
|
4
|
+
constructor(requestId, deadlineAt) {
|
|
5
|
+
super(`Stateful operation request ${requestId} deadline has expired.`);
|
|
6
|
+
this.name = "StatefulRoutingDeadlineError";
|
|
7
|
+
this.requestId = requestId;
|
|
8
|
+
this.deadlineAt = deadlineAt;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
1
11
|
export class StatefulControlPlaneError extends Error {
|
|
2
12
|
code;
|
|
3
13
|
operation;
|
|
@@ -40,11 +40,7 @@ export interface StatefulSessionRouterOptions {
|
|
|
40
40
|
readonly clock?: () => Date;
|
|
41
41
|
readonly metricEmitter?: StatefulProviderMetricEmitter;
|
|
42
42
|
}
|
|
43
|
-
export
|
|
44
|
-
readonly requestId: string;
|
|
45
|
-
readonly deadlineAt: string;
|
|
46
|
-
constructor(requestId: string, deadlineAt: string);
|
|
47
|
-
}
|
|
43
|
+
export { StatefulRoutingDeadlineError } from "./errors.js";
|
|
48
44
|
export declare class StatefulRoutingOwnershipError extends Error {
|
|
49
45
|
readonly requestId: string;
|
|
50
46
|
readonly sessionKey: SessionKey;
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
+
import { StatefulRoutingDeadlineError } from "./errors.js";
|
|
1
2
|
import { NOOP_STATEFUL_PROVIDER_METRIC_EMITTER, } from "./stateful-provider-observability.js";
|
|
2
3
|
export { forwardingContextFromStatefulRuntimeContext, isStatefulOperationRuntimeContext, providerContextFromStatefulRuntimeContext, statefulForwardingContextFromProviderRequest, withStatefulLocalProviderContext, } from "./stateful-provider-runtime-context.js";
|
|
3
|
-
export
|
|
4
|
-
requestId;
|
|
5
|
-
deadlineAt;
|
|
6
|
-
constructor(requestId, deadlineAt) {
|
|
7
|
-
super(`Stateful operation request ${requestId} deadline has expired.`);
|
|
8
|
-
this.name = "StatefulRoutingDeadlineError";
|
|
9
|
-
this.requestId = requestId;
|
|
10
|
-
this.deadlineAt = deadlineAt;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
4
|
+
export { StatefulRoutingDeadlineError } from "./errors.js";
|
|
13
5
|
export class StatefulRoutingOwnershipError extends Error {
|
|
14
6
|
requestId;
|
|
15
7
|
sessionKey;
|
package/dist/stream.js
CHANGED
|
@@ -41,16 +41,22 @@ function sseFieldValue(value, field) {
|
|
|
41
41
|
}
|
|
42
42
|
export async function* readableBytes(body) {
|
|
43
43
|
const reader = body.getReader();
|
|
44
|
+
let completed = false;
|
|
44
45
|
try {
|
|
45
46
|
for (;;) {
|
|
46
47
|
const { value, done } = await reader.read();
|
|
47
|
-
if (done)
|
|
48
|
+
if (done) {
|
|
49
|
+
completed = true;
|
|
48
50
|
return;
|
|
51
|
+
}
|
|
49
52
|
if (value)
|
|
50
53
|
yield value;
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
finally {
|
|
57
|
+
if (!completed) {
|
|
58
|
+
await reader.cancel().catch(() => undefined);
|
|
59
|
+
}
|
|
54
60
|
reader.releaseLock();
|
|
55
61
|
}
|
|
56
62
|
}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { describeTransform, snapshotTransform, toMatchShape } from "./helpers.js";
|
|
2
|
+
export { resetProviderCacheForTests } from "../runtime/cache.js";
|
|
2
3
|
export { runStandardTests, type StandardTestsManifest, type StandardTestsOptions, type StandardTestsResult, type StandardTestsUpstreamCall, type StandardTestsUpstreamResponse, type StandardTestsUpstreamStub, } from "./run.js";
|
package/dist/testing/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.2.0-beta.
|
|
2
|
+
"version": "2.2.0-beta.29",
|
|
3
3
|
"name": "@apifuse/provider-sdk",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
@@ -61,6 +61,31 @@
|
|
|
61
61
|
"import": "./dist/server/index.js",
|
|
62
62
|
"default": "./dist/server/index.js"
|
|
63
63
|
},
|
|
64
|
+
"./runtime/browser": {
|
|
65
|
+
"types": "./dist/runtime/browser.d.ts",
|
|
66
|
+
"import": "./dist/runtime/browser.js",
|
|
67
|
+
"default": "./dist/runtime/browser.js"
|
|
68
|
+
},
|
|
69
|
+
"./runtime/native-network": {
|
|
70
|
+
"types": "./dist/runtime/native-network.d.ts",
|
|
71
|
+
"import": "./dist/runtime/native-network.js",
|
|
72
|
+
"default": "./dist/runtime/native-network.js"
|
|
73
|
+
},
|
|
74
|
+
"./runtime/prevalidate": {
|
|
75
|
+
"types": "./dist/runtime/prevalidate.d.ts",
|
|
76
|
+
"import": "./dist/runtime/prevalidate.js",
|
|
77
|
+
"default": "./dist/runtime/prevalidate.js"
|
|
78
|
+
},
|
|
79
|
+
"./runtime/resolver": {
|
|
80
|
+
"types": "./dist/runtime/resolver-public.d.ts",
|
|
81
|
+
"import": "./dist/runtime/resolver-public.js",
|
|
82
|
+
"default": "./dist/runtime/resolver-public.js"
|
|
83
|
+
},
|
|
84
|
+
"./runtime/stealth": {
|
|
85
|
+
"types": "./dist/runtime/stealth.d.ts",
|
|
86
|
+
"import": "./dist/runtime/stealth.js",
|
|
87
|
+
"default": "./dist/runtime/stealth.js"
|
|
88
|
+
},
|
|
64
89
|
"./stateful": {
|
|
65
90
|
"types": "./dist/stateful/index.d.ts",
|
|
66
91
|
"import": "./dist/stateful/index.js",
|
|
@@ -97,6 +122,7 @@
|
|
|
97
122
|
"@biomejs/biome": "^2.5.0",
|
|
98
123
|
"@types/bun": "latest",
|
|
99
124
|
"@types/node": "^25.9.3",
|
|
125
|
+
"ajv": "^8.17",
|
|
100
126
|
"typescript": "6.0.3"
|
|
101
127
|
},
|
|
102
128
|
"dependencies": {
|
|
@@ -104,7 +130,6 @@
|
|
|
104
130
|
"@rgrove/parse-xml": "4.2.2",
|
|
105
131
|
"@types/ms": "^2.1.0",
|
|
106
132
|
"acorn": "^8.17.0",
|
|
107
|
-
"ajv": "^8.17",
|
|
108
133
|
"hono": "^4.12.25",
|
|
109
134
|
"ioredis": "^5.11.1",
|
|
110
135
|
"ms": "^2.1.3",
|
package/src/auth-turn/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export const AUTH_TURN_SCHEMA_ARTIFACT_PATH = "dist/auth-turn/auth-turn.v1.schem
|
|
|
24
24
|
*
|
|
25
25
|
* This is the exact codification of the runtime validation the SDK applies to
|
|
26
26
|
* ceremony outputs (see `validateCeremonyOutput` in `src/ceremonies`), which
|
|
27
|
-
*
|
|
27
|
+
* evaluates this same document. `kind` is an OPEN string on the wire: the known
|
|
28
28
|
* kinds in {@link TURN_KINDS} are tooling metadata, never a wire constraint.
|
|
29
29
|
*
|
|
30
30
|
* The committed artifact at `src/auth-turn/auth-turn.v1.schema.json` (shipped
|
package/src/auth.ts
CHANGED
|
@@ -296,6 +296,25 @@ export interface DefineCredentialsAuthOptions<
|
|
|
296
296
|
):
|
|
297
297
|
| CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>
|
|
298
298
|
| Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
|
|
299
|
+
/**
|
|
300
|
+
* Optional re-mint of an expired session from the stored credential, wired
|
|
301
|
+
* to `auth.flow.refresh`.
|
|
302
|
+
*
|
|
303
|
+
* Credential-auth upstreams routinely invalidate a session well before the
|
|
304
|
+
* `expiresAt` the provider advertised, which leaves every operation failing
|
|
305
|
+
* with a reauth error until a human repeats the whole interactive login.
|
|
306
|
+
* Implement this to re-establish the session from what is already stored on
|
|
307
|
+
* the connection; the result is resolved exactly like `login`, so it may
|
|
308
|
+
* also raise a challenge when the upstream demands one. Omit it when the
|
|
309
|
+
* upstream has no non-interactive path and re-authentication genuinely
|
|
310
|
+
* requires the user.
|
|
311
|
+
*/
|
|
312
|
+
refresh?(
|
|
313
|
+
ctx: FlowContext,
|
|
314
|
+
input: Partial<CredentialsAuthInput<TFields>>,
|
|
315
|
+
):
|
|
316
|
+
| CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>
|
|
317
|
+
| Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
|
|
299
318
|
}
|
|
300
319
|
|
|
301
320
|
export interface DefinedCredentialsAuth {
|
|
@@ -376,6 +395,27 @@ function normalizeInput<TFields extends CredentialsAuthFields>(
|
|
|
376
395
|
return result as CredentialsAuthInput<TFields>;
|
|
377
396
|
}
|
|
378
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Refresh variant of {@link normalizeInput}. `login` coerces every declared
|
|
400
|
+
* field to a string because the interactive turn has already enforced that they
|
|
401
|
+
* are present; refresh runs with no user present, so an absent field is omitted
|
|
402
|
+
* rather than turned into an empty string. That keeps "the user did not supply
|
|
403
|
+
* this" distinguishable from "the user supplied an empty value".
|
|
404
|
+
*/
|
|
405
|
+
function normalizePartialInput<TFields extends CredentialsAuthFields>(
|
|
406
|
+
fields: TFields,
|
|
407
|
+
input: Record<string, unknown> | undefined,
|
|
408
|
+
): Partial<CredentialsAuthInput<TFields>> {
|
|
409
|
+
const result: Record<string, string> = {};
|
|
410
|
+
for (const name of Object.keys(fields)) {
|
|
411
|
+
const value = input?.[name];
|
|
412
|
+
if (typeof value === "string") {
|
|
413
|
+
result[name] = value;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return result as Partial<CredentialsAuthInput<TFields>>;
|
|
417
|
+
}
|
|
418
|
+
|
|
379
419
|
function assertCredentialKeys<TCredentialKeys extends readonly string[]>(
|
|
380
420
|
credentialKeys: TCredentialKeys,
|
|
381
421
|
credential: Record<string, unknown>,
|
|
@@ -716,6 +756,44 @@ export function defineCredentialsAuth<
|
|
|
716
756
|
completeTurnId,
|
|
717
757
|
);
|
|
718
758
|
},
|
|
759
|
+
// Only advertise refresh when the provider implements it: the
|
|
760
|
+
// protocol treats the hook's presence as "this connection can be
|
|
761
|
+
// re-established without the user", and exposing a stub that
|
|
762
|
+
// cannot actually re-mint would turn a recoverable expiry into a
|
|
763
|
+
// silent failure.
|
|
764
|
+
...(options.refresh
|
|
765
|
+
? {
|
|
766
|
+
refresh: async (ctx: FlowContext, rawInput?: Record<string, unknown>) => {
|
|
767
|
+
// A pending challenge belongs to the interactive flow that
|
|
768
|
+
// raised it; finish it there rather than restarting.
|
|
769
|
+
const pending = getPendingChallenge(ctx);
|
|
770
|
+
if (pending) {
|
|
771
|
+
return await continuePendingChallenge(
|
|
772
|
+
ctx,
|
|
773
|
+
options.credentialKeys,
|
|
774
|
+
challenges,
|
|
775
|
+
pending,
|
|
776
|
+
rawInput,
|
|
777
|
+
completeTurnId,
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Refresh runs without user input, so fields are optional
|
|
782
|
+
// here — unlike `continue`, missing ones are not a retry.
|
|
783
|
+
const result = await options.refresh!(
|
|
784
|
+
ctx,
|
|
785
|
+
normalizePartialInput(options.fields, rawInput),
|
|
786
|
+
);
|
|
787
|
+
return await resolveAuthResult(
|
|
788
|
+
ctx,
|
|
789
|
+
options.credentialKeys,
|
|
790
|
+
challenges,
|
|
791
|
+
result,
|
|
792
|
+
completeTurnId,
|
|
793
|
+
);
|
|
794
|
+
},
|
|
795
|
+
}
|
|
796
|
+
: {}),
|
|
719
797
|
},
|
|
720
798
|
},
|
|
721
799
|
credential: {
|
package/src/ceremonies/index.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
import Ajv2020 from "ajv/dist/2020.js";
|
|
4
|
-
|
|
5
3
|
import { AUTH_TURN_SCHEMA, type KnownAuthTurnKind } from "../auth-turn/index.js";
|
|
6
4
|
import {
|
|
7
5
|
FlowExpiredError,
|
|
@@ -16,13 +14,14 @@ type TurnKind = KnownAuthTurnKind;
|
|
|
16
14
|
type CeremonyHandler = AuthFlowInputHandler;
|
|
17
15
|
|
|
18
16
|
type JsonObject = Record<string, unknown>;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
type JsonSchemaNode = {
|
|
18
|
+
readonly additionalProperties?: boolean;
|
|
19
|
+
readonly minimum?: number;
|
|
20
|
+
readonly minLength?: number;
|
|
21
|
+
readonly properties?: Readonly<Record<string, JsonSchemaNode>>;
|
|
22
|
+
readonly required?: readonly string[];
|
|
23
|
+
readonly type?: "number" | "object" | "string";
|
|
24
|
+
};
|
|
26
25
|
|
|
27
26
|
const OAUTH2_STATE_KEY = "__oauth2_state";
|
|
28
27
|
const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
|
|
@@ -45,6 +44,56 @@ function isRecord(value: unknown): value is JsonObject {
|
|
|
45
44
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
function validateSchemaNode(
|
|
48
|
+
value: unknown,
|
|
49
|
+
schema: JsonSchemaNode,
|
|
50
|
+
path: string,
|
|
51
|
+
errors: string[],
|
|
52
|
+
): void {
|
|
53
|
+
if (schema.type === "object") {
|
|
54
|
+
if (!isRecord(value)) {
|
|
55
|
+
errors.push(`${path} must be object`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const properties = schema.properties ?? {};
|
|
59
|
+
for (const required of schema.required ?? []) {
|
|
60
|
+
if (!Object.hasOwn(value, required) || value[required] === undefined) {
|
|
61
|
+
errors.push(`${path} must have required property '${required}'`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (schema.additionalProperties === false) {
|
|
65
|
+
for (const key of Object.keys(value)) {
|
|
66
|
+
if (!Object.hasOwn(properties, key)) {
|
|
67
|
+
errors.push(`${path} must NOT have additional property '${key}'`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
72
|
+
if (Object.hasOwn(value, key) && value[key] !== undefined) {
|
|
73
|
+
validateSchemaNode(value[key], childSchema, `${path}/${key}`, errors);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (schema.type === "string") {
|
|
80
|
+
if (typeof value !== "string") {
|
|
81
|
+
errors.push(`${path} must be string`);
|
|
82
|
+
} else if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
83
|
+
errors.push(`${path} must NOT have fewer than ${schema.minLength} characters`);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (schema.type === "number") {
|
|
89
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
90
|
+
errors.push(`${path} must be number`);
|
|
91
|
+
} else if (schema.minimum !== undefined && value < schema.minimum) {
|
|
92
|
+
errors.push(`${path} must be >= ${schema.minimum}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
48
97
|
function ensureRecord(value: unknown): JsonObject {
|
|
49
98
|
return isRecord(value) ? value : {};
|
|
50
99
|
}
|
|
@@ -162,14 +211,17 @@ function withDeclaredFormFieldOrder(expectedInput: JsonObject): JsonObject {
|
|
|
162
211
|
}
|
|
163
212
|
|
|
164
213
|
export function validateCeremonyOutput(turn: unknown): AuthTurn {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
214
|
+
// Evaluate the exact exported versioned schema without eagerly initializing
|
|
215
|
+
// a general-purpose JSON Schema compiler in every provider process. Contract
|
|
216
|
+
// parity tests compare this focused evaluator with AJV over all fixtures and
|
|
217
|
+
// edge probes, so the runtime and shipped document remain locked together.
|
|
218
|
+
const errors: string[] = [];
|
|
219
|
+
validateSchemaNode(turn, AUTH_TURN_SCHEMA, "", errors);
|
|
220
|
+
if (errors.length > 0) {
|
|
221
|
+
throw new TurnValidationError(errors.join("; "));
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
return turn;
|
|
224
|
+
return turn as AuthTurn;
|
|
173
225
|
}
|
|
174
226
|
|
|
175
227
|
export function createOAuth2Ceremony(options: {
|
|
@@ -285,9 +337,7 @@ export function createOAuth2ProxiedStart(
|
|
|
285
337
|
}
|
|
286
338
|
const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
|
|
287
339
|
if (!ctx.flowId) {
|
|
288
|
-
throw new ValidationError(
|
|
289
|
-
"OAuth2 proxied start requires the gateway flow id.",
|
|
290
|
-
);
|
|
340
|
+
throw new ValidationError("OAuth2 proxied start requires the gateway flow id.");
|
|
291
341
|
}
|
|
292
342
|
|
|
293
343
|
const authorizePath = new URL(options.authorizeUrl).pathname;
|
package/src/config/loader.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
5
|
-
import { Redis } from "ioredis";
|
|
6
|
+
import type { Redis } from "ioredis";
|
|
6
7
|
|
|
7
8
|
import type { ProviderProxyPolicy, ProviderProxyProvider, TraceConfig } from "../types.js";
|
|
8
9
|
import {
|
|
@@ -102,8 +103,11 @@ export type SmartproxyAllocatorBodyClass =
|
|
|
102
103
|
| "text_without_proxies"
|
|
103
104
|
| "usable_proxy_endpoints";
|
|
104
105
|
|
|
106
|
+
export type ProxyUserAgentSource = "declared" | "defaulted";
|
|
107
|
+
|
|
105
108
|
export type ProxyResolutionTelemetryEvent = {
|
|
106
109
|
provider: ProxyVendorName;
|
|
110
|
+
userAgentSource?: ProxyUserAgentSource;
|
|
107
111
|
protocol?: ProxyProtocol;
|
|
108
112
|
cacheStatus: ProxyCacheStatus;
|
|
109
113
|
cacheHit: boolean;
|
|
@@ -217,6 +221,7 @@ type ProxyRedisClient = Pick<
|
|
|
217
221
|
>;
|
|
218
222
|
|
|
219
223
|
const proxyCache = new Map<string, CachedProxyPool>();
|
|
224
|
+
const require = createRequire(import.meta.url);
|
|
220
225
|
const proxyInflight = new Map<string, Promise<SmartproxyAllocationResult>>();
|
|
221
226
|
const invalidatedProxyKeys = new Map<string, number>();
|
|
222
227
|
const redisClients = new Map<string, ProxyRedisClient>();
|
|
@@ -276,7 +281,8 @@ function getProxyRedis(): ProxyRedisClient | undefined {
|
|
|
276
281
|
const existing = redisClients.get(redisUrl);
|
|
277
282
|
if (existing) return existing;
|
|
278
283
|
|
|
279
|
-
const
|
|
284
|
+
const { Redis: RedisClient } = require("ioredis") as typeof import("ioredis");
|
|
285
|
+
const redis = new RedisClient(redisUrl, {
|
|
280
286
|
connectTimeout: REDIS_TIMEOUT_MS,
|
|
281
287
|
enableOfflineQueue: false,
|
|
282
288
|
lazyConnect: true,
|
package/src/index.ts
CHANGED
|
@@ -50,13 +50,11 @@ export * from "./recipes/gov-api.js";
|
|
|
50
50
|
export * from "./recipes/rest-api.js";
|
|
51
51
|
export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
|
|
52
52
|
export type { BrowserClientOptions } from "./runtime/browser.js";
|
|
53
|
-
export { BrowserClient, createBrowserClient } from "./runtime/browser.js";
|
|
54
53
|
export {
|
|
55
54
|
APIFUSE__CACHE__KEY_PEPPER_ENV,
|
|
56
55
|
createBypassProviderCache,
|
|
57
56
|
createProviderCache,
|
|
58
57
|
type ProviderCacheOptions,
|
|
59
|
-
resetProviderCacheForTests,
|
|
60
58
|
} from "./runtime/cache.js";
|
|
61
59
|
export {
|
|
62
60
|
type CreateProviderChoiceContextOptions,
|
|
@@ -72,25 +70,23 @@ export { createEnvContext } from "./runtime/env.js";
|
|
|
72
70
|
export { executeOperation } from "./runtime/executor.js";
|
|
73
71
|
export { createHttpClient } from "./runtime/http.js";
|
|
74
72
|
export {
|
|
75
|
-
createNativeNetworkClient,
|
|
76
|
-
createEnvVendorCredentialResolver,
|
|
77
|
-
deriveNativeCredentialAffinityKey,
|
|
78
73
|
NativeEgressGrantExpiredError,
|
|
79
74
|
NativeEgressNotDeclaredError,
|
|
80
75
|
NativeIdleTimeoutError,
|
|
81
76
|
NativeNetworkError,
|
|
82
77
|
NativeProxyExpiredError,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
78
|
+
} from "./runtime/native-network-errors.js";
|
|
79
|
+
export type {
|
|
80
|
+
NativeGatewayProxy,
|
|
81
|
+
NativeGatewayProxyResolutionInput,
|
|
82
|
+
NativeGatewayProxySkipReason,
|
|
83
|
+
NativeGatewayProxySynthesizer,
|
|
84
|
+
NativeGatewayProxySynthesisResult,
|
|
85
|
+
NativeGatewayProxySynthesisInput,
|
|
86
|
+
NativeNetworkClientOptions,
|
|
87
|
+
NativeNetworkErrorCode,
|
|
88
|
+
VendorCredentialLookup,
|
|
89
|
+
VendorCredentialResolver,
|
|
94
90
|
} from "./runtime/native-network.js";
|
|
95
91
|
export type { Insight, InsightSeverity } from "./runtime/insights.js";
|
|
96
92
|
export { generateInsights } from "./runtime/insights.js";
|
|
@@ -99,7 +95,7 @@ export {
|
|
|
99
95
|
type InstrumentedProviderContext,
|
|
100
96
|
wrapWithInstrumentation,
|
|
101
97
|
} from "./runtime/instrumentation.js";
|
|
102
|
-
export {
|
|
98
|
+
export type { PrevalidateResult } from "./runtime/prevalidate.js";
|
|
103
99
|
export { getProviderBaseUrl } from "./runtime/provider.js";
|
|
104
100
|
export {
|
|
105
101
|
APIFUSE__CDP_POOL__URL,
|
|
@@ -107,12 +103,10 @@ export {
|
|
|
107
103
|
APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
|
|
108
104
|
APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
|
|
109
105
|
APIFUSE__RESOLVER__TIMEOUT_MS,
|
|
110
|
-
createResolverClientFromEnv,
|
|
111
|
-
createUnsupportedResolverClient,
|
|
112
106
|
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} from "./runtime/resolver.js";
|
|
107
|
+
} from "./runtime/resolver-config.js";
|
|
108
|
+
export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
|
|
109
|
+
export type { ResolverRuntimeOptions } from "./runtime/resolver.js";
|
|
116
110
|
export type { ResolverVendorTransport } from "./runtime/resolver-vendors/types.js";
|
|
117
111
|
export {
|
|
118
112
|
assertRequiredSecretsPresent,
|
|
@@ -123,7 +117,6 @@ export {
|
|
|
123
117
|
createUnsupportedProviderRuntimeState,
|
|
124
118
|
UnsupportedProviderStateError,
|
|
125
119
|
} from "./runtime/state.js";
|
|
126
|
-
export { createStealthClient } from "./runtime/stealth.js";
|
|
127
120
|
export {
|
|
128
121
|
APIFUSE__OCR__API_KEY_ENV,
|
|
129
122
|
APIFUSE__OCR__BACKEND_ENV,
|
|
@@ -174,10 +167,11 @@ export {
|
|
|
174
167
|
} from "./schema.js";
|
|
175
168
|
export {
|
|
176
169
|
createServerApp,
|
|
170
|
+
createServerAppAsync,
|
|
177
171
|
ERROR_OBSERVABILITY_HEADER,
|
|
178
172
|
type ServeOptions,
|
|
179
173
|
serve,
|
|
180
|
-
} from "./server/
|
|
174
|
+
} from "./server/serve.js";
|
|
181
175
|
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
|
|
182
176
|
export * from "./stream.js";
|
|
183
177
|
export type {
|
package/src/provider.ts
CHANGED
|
@@ -167,25 +167,23 @@ export type {
|
|
|
167
167
|
StateWriteOptions,
|
|
168
168
|
} from "./types.js";
|
|
169
169
|
export {
|
|
170
|
-
createNativeNetworkClient,
|
|
171
|
-
createEnvVendorCredentialResolver,
|
|
172
|
-
deriveNativeCredentialAffinityKey,
|
|
173
170
|
NativeEgressGrantExpiredError,
|
|
174
171
|
NativeEgressNotDeclaredError,
|
|
175
172
|
NativeIdleTimeoutError,
|
|
176
173
|
NativeNetworkError,
|
|
177
174
|
NativeProxyExpiredError,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
175
|
+
} from "./runtime/native-network-errors.js";
|
|
176
|
+
export type {
|
|
177
|
+
NativeGatewayProxy,
|
|
178
|
+
NativeGatewayProxyResolutionInput,
|
|
179
|
+
NativeGatewayProxySkipReason,
|
|
180
|
+
NativeGatewayProxySynthesizer,
|
|
181
|
+
NativeGatewayProxySynthesisResult,
|
|
182
|
+
NativeGatewayProxySynthesisInput,
|
|
183
|
+
NativeNetworkClientOptions,
|
|
184
|
+
NativeNetworkErrorCode,
|
|
185
|
+
VendorCredentialLookup,
|
|
186
|
+
VendorCredentialResolver,
|
|
189
187
|
} from "./runtime/native-network.js";
|
|
190
188
|
export {
|
|
191
189
|
HttpRetryAfterPolicy,
|
package/src/runtime/auth-flow.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
SttContext,
|
|
11
11
|
} from "../types.js";
|
|
12
12
|
import { createUnsupportedOcrClient } from "./ocr.js";
|
|
13
|
-
import { createUnsupportedResolverClient } from "./resolver.js";
|
|
13
|
+
import { createUnsupportedResolverClient } from "./resolver-shared.js";
|
|
14
14
|
import { createUnsupportedSttClient } from "./stt.js";
|
|
15
15
|
|
|
16
16
|
function normalizeAllowedKeys(allowedKeys: string[]): Set<string> {
|
package/src/runtime/browser.ts
CHANGED
|
@@ -290,7 +290,13 @@ function formatExpression<T>(fn: string | (() => T)): string {
|
|
|
290
290
|
|
|
291
291
|
function toLaunchOptions(options: BrowserClientOptions): LaunchOptions {
|
|
292
292
|
return {
|
|
293
|
-
|
|
293
|
+
// `extraArgs` is optional, but playwright-extra's stealth evasions mutate
|
|
294
|
+
// `options.args` unguarded (navigator.webdriver does
|
|
295
|
+
// `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
|
|
296
|
+
// therefore crashes every stealth launch that omits extraArgs with
|
|
297
|
+
// "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
|
|
298
|
+
// Always hand the launcher a concrete array.
|
|
299
|
+
args: options.extraArgs ?? [],
|
|
294
300
|
executablePath: options.executablePath,
|
|
295
301
|
headless: options.headless ?? true,
|
|
296
302
|
proxy: options.proxy ? { server: options.proxy } : undefined,
|
|
@@ -975,6 +981,20 @@ function getCdpExecutionContext(params: unknown): {
|
|
|
975
981
|
};
|
|
976
982
|
}
|
|
977
983
|
|
|
984
|
+
function getCdpDestroyedExecutionContextId(params: unknown): number | undefined {
|
|
985
|
+
if (!isRecord(params)) {
|
|
986
|
+
return undefined;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return typeof params.executionContextId === "number"
|
|
990
|
+
? params.executionContextId
|
|
991
|
+
: undefined;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function isMissingExecutionContextError(error: unknown): boolean {
|
|
995
|
+
return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
|
|
996
|
+
}
|
|
997
|
+
|
|
978
998
|
class CdpBrowserLocator implements BrowserLocator {
|
|
979
999
|
constructor(
|
|
980
1000
|
private readonly frame: {
|
|
@@ -1120,7 +1140,20 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1120
1140
|
async evaluateInFrame<T>(frameId: string, fn: string | (() => T)): Promise<T> {
|
|
1121
1141
|
await this.initialize();
|
|
1122
1142
|
const contextId = await this.getFrameExecutionContextId(frameId);
|
|
1123
|
-
|
|
1143
|
+
try {
|
|
1144
|
+
return await this.evaluateWithContext<T>(fn, contextId);
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
if (!isMissingExecutionContextError(error)) {
|
|
1147
|
+
throw error;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (this.frameExecutionContexts.get(frameId) === contextId) {
|
|
1151
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const refreshedContextId = await this.getFrameExecutionContextId(frameId);
|
|
1155
|
+
return await this.evaluateWithContext<T>(fn, refreshedContextId);
|
|
1156
|
+
}
|
|
1124
1157
|
}
|
|
1125
1158
|
|
|
1126
1159
|
async waitForSelectorInFrame(
|
|
@@ -1384,6 +1417,21 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1384
1417
|
this.frameExecutionContexts.set(context.frameId, context.id);
|
|
1385
1418
|
}
|
|
1386
1419
|
});
|
|
1420
|
+
this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
|
|
1421
|
+
const destroyedContextId = getCdpDestroyedExecutionContextId(params);
|
|
1422
|
+
if (destroyedContextId === undefined) {
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
for (const [frameId, contextId] of this.frameExecutionContexts) {
|
|
1427
|
+
if (contextId === destroyedContextId) {
|
|
1428
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
this.pageClient.on("Runtime.executionContextsCleared", () => {
|
|
1433
|
+
this.frameExecutionContexts.clear();
|
|
1434
|
+
});
|
|
1387
1435
|
await this.pageClient.send("Page.enable");
|
|
1388
1436
|
await this.pageClient.send("Runtime.enable");
|
|
1389
1437
|
this.initialized = true;
|