@apifuse/provider-sdk 2.1.0-beta.12 → 2.1.0-beta.14
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 +8 -0
- package/README.md +8 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +436 -0
- package/dist/contract.js +1 -0
- package/dist/define.d.ts +4 -1
- package/dist/define.js +109 -46
- 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/server/serve.js +16 -8
- package/dist/types.d.ts +106 -0
- package/package.json +1 -1
- package/src/auth.ts +786 -0
- package/src/contract.ts +1 -0
- package/src/define.ts +158 -48
- package/src/index.ts +10 -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/server/serve.ts +39 -4
- package/src/types.ts +136 -0
package/dist/contract.js
CHANGED
|
@@ -122,6 +122,7 @@ function extractHealthCheck(value) {
|
|
|
122
122
|
return undefined;
|
|
123
123
|
return compactObject({
|
|
124
124
|
interval: value.interval,
|
|
125
|
+
schedule: toJsonValue(value.schedule),
|
|
125
126
|
timeoutMs: value.timeoutMs,
|
|
126
127
|
degradedThresholdMs: value.degradedThresholdMs,
|
|
127
128
|
requiresConnection: value.requiresConnection,
|
package/dist/define.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, InferSchemaOutput, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderDefinition, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types";
|
|
1
|
+
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, HealthScheduleRandomization, InferSchemaOutput, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderDefinition, ProviderHealthMonitorConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderReviewed, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, SchemaLike, SmsOtpMatcherDefinition, StealthPlatform } from "./types";
|
|
2
2
|
type ProviderImplementationSourceAccess = "official_api" | "private_api" | "browser_flow" | "hybrid";
|
|
3
3
|
type ProviderImplementationCredentialStrategy = "apifuse_managed" | "workspace_secret" | "user_oauth" | "user_session" | "none";
|
|
4
4
|
interface ProviderImplementationProfile {
|
|
@@ -88,7 +88,10 @@ export declare function defineOperation<TInput extends SchemaLike, TOutput exten
|
|
|
88
88
|
export declare function defineStreamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(operation: StreamOperationConfig<TInput, TOutput>): OperationDefinition<TInput, TOutput>;
|
|
89
89
|
export declare function every(interval: string, options?: {
|
|
90
90
|
jitter?: string;
|
|
91
|
+
randomize?: HealthScheduleRandomization;
|
|
91
92
|
}): HealthJourneySchedule;
|
|
93
|
+
export declare function centered(maxOffset: string): HealthScheduleRandomization;
|
|
94
|
+
export declare function delayed(maxDelay: string): HealthScheduleRandomization;
|
|
92
95
|
export declare function defineSmsOtpMatcher(config: Omit<SmsOtpMatcherDefinition, "extractOtp">): SmsOtpMatcherDefinition;
|
|
93
96
|
export declare function defineHealthJourney(config: HealthJourneyDefinition): HealthJourneyDefinition;
|
|
94
97
|
export declare function defineProvider<TOperations extends Record<string, ProviderOperation>, TConfig extends ProviderConfig<TOperations>>(config: TConfig & AuthStartNoInputGuard<TConfig>): ProviderDefinition & {
|
package/dist/define.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ms from "ms";
|
|
1
2
|
import { ProviderError, ValidationError } from "./errors";
|
|
2
3
|
import { safeParseSchemaSync } from "./schema";
|
|
3
4
|
import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types";
|
|
@@ -50,51 +51,23 @@ const VALID_OPERATION_TRANSPORT_KINDS = [
|
|
|
50
51
|
];
|
|
51
52
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
52
53
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
53
|
-
const MS_DURATION_UNITS = new Set([
|
|
54
|
-
"years",
|
|
55
|
-
"year",
|
|
56
|
-
"yrs",
|
|
57
|
-
"yr",
|
|
58
|
-
"y",
|
|
59
|
-
"weeks",
|
|
60
|
-
"week",
|
|
61
|
-
"w",
|
|
62
|
-
"days",
|
|
63
|
-
"day",
|
|
64
|
-
"d",
|
|
65
|
-
"hours",
|
|
66
|
-
"hour",
|
|
67
|
-
"hrs",
|
|
68
|
-
"hr",
|
|
69
|
-
"h",
|
|
70
|
-
"minutes",
|
|
71
|
-
"minute",
|
|
72
|
-
"mins",
|
|
73
|
-
"min",
|
|
74
|
-
"m",
|
|
75
|
-
"seconds",
|
|
76
|
-
"second",
|
|
77
|
-
"secs",
|
|
78
|
-
"sec",
|
|
79
|
-
"s",
|
|
80
|
-
"milliseconds",
|
|
81
|
-
"millisecond",
|
|
82
|
-
"msecs",
|
|
83
|
-
"msec",
|
|
84
|
-
"ms",
|
|
85
|
-
]);
|
|
86
54
|
const MS_DURATION_PATTERN = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*([a-zA-Z]+)?$/;
|
|
87
55
|
function isPositiveMsDurationString(value) {
|
|
88
56
|
if (typeof value !== "string")
|
|
89
57
|
return false;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
58
|
+
return parsePositiveMsDuration(value) !== undefined;
|
|
59
|
+
}
|
|
60
|
+
function msDurationMs(value) {
|
|
61
|
+
return parsePositiveMsDuration(value) ?? 0;
|
|
62
|
+
}
|
|
63
|
+
function parsePositiveMsDuration(value) {
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!MS_DURATION_PATTERN.test(trimmed))
|
|
66
|
+
return undefined;
|
|
67
|
+
const parsed = ms((trimmed.startsWith("+") ? trimmed.slice(1) : trimmed));
|
|
68
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
69
|
+
return undefined;
|
|
70
|
+
return parsed;
|
|
98
71
|
}
|
|
99
72
|
/** Define one provider operation with schema-driven handler inference. */
|
|
100
73
|
export function defineOperation(operation) {
|
|
@@ -141,6 +114,11 @@ function validateProviderShape(config) {
|
|
|
141
114
|
"mode" in auth &&
|
|
142
115
|
typeof auth.mode === "string")
|
|
143
116
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
117
|
+
if (auth && typeof auth === "object" && "exchange" in auth) {
|
|
118
|
+
throw new ProviderError(`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`, {
|
|
119
|
+
fix: "Use the single canonical auth interface: auth.flow. Gateway calls auth.flow.start/continue/poll/abort/refresh only and persists complete turn data.credential as-is, so put login/token/session exchange inside auth.flow.continue.",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
144
122
|
if (auth &&
|
|
145
123
|
typeof auth === "object" &&
|
|
146
124
|
"flow" in auth &&
|
|
@@ -555,6 +533,7 @@ function validateOperationTransports(providerId, operations) {
|
|
|
555
533
|
}
|
|
556
534
|
const HEALTH_CHECK_SUITE_FIELDS = new Set([
|
|
557
535
|
"interval",
|
|
536
|
+
"schedule",
|
|
558
537
|
"timeoutMs",
|
|
559
538
|
"degradedThresholdMs",
|
|
560
539
|
"cases",
|
|
@@ -776,6 +755,21 @@ function validateHealthCheckSuite(providerId, operationName, suite) {
|
|
|
776
755
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.interval must be a positive ms-style duration string such as 30s, 5m, 8h, or 1 day.`, {
|
|
777
756
|
fix: `Set ${fieldPath}.interval to a positive ms-style duration string.`,
|
|
778
757
|
});
|
|
758
|
+
if (s.schedule !== undefined) {
|
|
759
|
+
if (!s.schedule ||
|
|
760
|
+
typeof s.schedule !== "object" ||
|
|
761
|
+
Array.isArray(s.schedule)) {
|
|
762
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule must be an object.`);
|
|
763
|
+
}
|
|
764
|
+
if (Reflect.get(s.schedule, "jitter") !== undefined) {
|
|
765
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule.jitter is not supported for operation healthCheck schedules. Use schedule.randomize instead.`);
|
|
766
|
+
}
|
|
767
|
+
rejectUnknownFields(s.schedule, new Set(["randomize"]), `${fieldPath}.schedule`);
|
|
768
|
+
const randomize = Reflect.get(s.schedule, "randomize");
|
|
769
|
+
if (randomize !== undefined) {
|
|
770
|
+
validateScheduleRandomization(randomize, `Provider "${providerId}" ${fieldPath}.schedule.randomize`, msDurationMs(s.interval));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
779
773
|
if (s.timeoutMs !== undefined) {
|
|
780
774
|
assertBoundedIntegerMs(s.timeoutMs, `Provider "${providerId}" ${fieldPath}.timeoutMs`, {
|
|
781
775
|
min: HEALTH_CHECK_TIMEOUT_MS_MIN,
|
|
@@ -837,7 +831,12 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
837
831
|
"steps",
|
|
838
832
|
"run",
|
|
839
833
|
]);
|
|
840
|
-
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
834
|
+
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
835
|
+
"kind",
|
|
836
|
+
"interval",
|
|
837
|
+
"jitter",
|
|
838
|
+
"randomize",
|
|
839
|
+
]);
|
|
841
840
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
842
841
|
"id",
|
|
843
842
|
"description",
|
|
@@ -953,6 +952,40 @@ function isoDurationMs(value) {
|
|
|
953
952
|
const seconds = Number(/(\d+(?:\.\d+)?)S/.exec(value)?.[1] ?? 0);
|
|
954
953
|
return (days * 86_400_000 + hours * 3_600_000 + minutes * 60_000 + seconds * 1_000);
|
|
955
954
|
}
|
|
955
|
+
function scheduleRandomizationMs(randomize, fieldPath) {
|
|
956
|
+
const mode = Reflect.get(randomize, "mode");
|
|
957
|
+
switch (mode) {
|
|
958
|
+
case "centered": {
|
|
959
|
+
const maxOffset = Reflect.get(randomize, "maxOffset");
|
|
960
|
+
assertIsoDuration(maxOffset, `${fieldPath}.maxOffset`);
|
|
961
|
+
return isoDurationMs(maxOffset);
|
|
962
|
+
}
|
|
963
|
+
case "delayed": {
|
|
964
|
+
const maxDelay = Reflect.get(randomize, "maxDelay");
|
|
965
|
+
assertIsoDuration(maxDelay, `${fieldPath}.maxDelay`);
|
|
966
|
+
return isoDurationMs(maxDelay);
|
|
967
|
+
}
|
|
968
|
+
default:
|
|
969
|
+
throw new ValidationError(`${fieldPath}.mode must be "centered" or "delayed".`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function validateScheduleRandomization(randomize, fieldPath, intervalMs) {
|
|
973
|
+
if (!randomize || typeof randomize !== "object" || Array.isArray(randomize)) {
|
|
974
|
+
throw new ValidationError(`${fieldPath} must be an object.`);
|
|
975
|
+
}
|
|
976
|
+
const mode = Reflect.get(randomize, "mode");
|
|
977
|
+
const allowedFields = mode === "centered"
|
|
978
|
+
? new Set(["mode", "maxOffset"])
|
|
979
|
+
: new Set(["mode", "maxDelay"]);
|
|
980
|
+
rejectUnknownFields(randomize, allowedFields, fieldPath);
|
|
981
|
+
const offsetMs = scheduleRandomizationMs(randomize, fieldPath);
|
|
982
|
+
if (offsetMs <= 0) {
|
|
983
|
+
throw new ValidationError(`${fieldPath} duration must be positive.`);
|
|
984
|
+
}
|
|
985
|
+
if (offsetMs >= intervalMs) {
|
|
986
|
+
throw new ValidationError(`${fieldPath} duration must be shorter than schedule interval.`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
956
989
|
function assertIsoCountry(value, fieldPath) {
|
|
957
990
|
if (typeof value !== "string" || !ISO_COUNTRY_RE.test(value)) {
|
|
958
991
|
throw new ValidationError(`${fieldPath} must be an ISO 3166-1 alpha-2 country code for example KR.`);
|
|
@@ -962,11 +995,18 @@ function normalizeIntervalDuration(input) {
|
|
|
962
995
|
const trimmed = input.trim();
|
|
963
996
|
const shorthand = /^(\d+)(s|m|h|d)$/i.exec(trimmed);
|
|
964
997
|
if (shorthand) {
|
|
965
|
-
const
|
|
998
|
+
const durationMs = msDurationMs(trimmed);
|
|
999
|
+
const unit = shorthand[2]?.toLowerCase();
|
|
1000
|
+
const amount = unit === "s"
|
|
1001
|
+
? durationMs / 1_000
|
|
1002
|
+
: unit === "m"
|
|
1003
|
+
? durationMs / 60_000
|
|
1004
|
+
: unit === "h"
|
|
1005
|
+
? durationMs / 3_600_000
|
|
1006
|
+
: durationMs / 86_400_000;
|
|
966
1007
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
967
1008
|
throw new ValidationError(`Journey schedule interval must be a positive duration.`);
|
|
968
1009
|
}
|
|
969
|
-
const unit = shorthand[2]?.toLowerCase();
|
|
970
1010
|
if (unit === "s")
|
|
971
1011
|
return `PT${amount}S`;
|
|
972
1012
|
if (unit === "m")
|
|
@@ -980,15 +1020,27 @@ function normalizeIntervalDuration(input) {
|
|
|
980
1020
|
return trimmed;
|
|
981
1021
|
}
|
|
982
1022
|
export function every(interval, options = {}) {
|
|
1023
|
+
if (options.jitter !== undefined && options.randomize !== undefined) {
|
|
1024
|
+
throw new ValidationError(`Schedule cannot define both jitter and randomize. Use randomize instead.`);
|
|
1025
|
+
}
|
|
983
1026
|
const schedule = {
|
|
984
1027
|
kind: "interval",
|
|
985
1028
|
interval: normalizeIntervalDuration(interval),
|
|
986
1029
|
};
|
|
1030
|
+
if (options.randomize !== undefined) {
|
|
1031
|
+
schedule.randomize = options.randomize;
|
|
1032
|
+
}
|
|
987
1033
|
if (options.jitter !== undefined) {
|
|
988
1034
|
schedule.jitter = normalizeIntervalDuration(options.jitter);
|
|
989
1035
|
}
|
|
990
1036
|
return schedule;
|
|
991
1037
|
}
|
|
1038
|
+
export function centered(maxOffset) {
|
|
1039
|
+
return { mode: "centered", maxOffset: normalizeIntervalDuration(maxOffset) };
|
|
1040
|
+
}
|
|
1041
|
+
export function delayed(maxDelay) {
|
|
1042
|
+
return { mode: "delayed", maxDelay: normalizeIntervalDuration(maxDelay) };
|
|
1043
|
+
}
|
|
992
1044
|
function countCapturingGroups(pattern) {
|
|
993
1045
|
let count = 0;
|
|
994
1046
|
const source = pattern.source;
|
|
@@ -1140,9 +1192,18 @@ function validateHealthJourneySchedule(providerId, journeyId, schedule) {
|
|
|
1140
1192
|
rejectUnknownFields(schedule, HEALTH_JOURNEY_SCHEDULE_FIELDS, fieldPath);
|
|
1141
1193
|
if (Reflect.get(schedule, "kind") !== "interval")
|
|
1142
1194
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.kind must be "interval".`);
|
|
1143
|
-
|
|
1195
|
+
const interval = Reflect.get(schedule, "interval");
|
|
1196
|
+
assertIsoDuration(interval, `Provider "${providerId}" ${fieldPath}.interval`);
|
|
1197
|
+
const randomize = Reflect.get(schedule, "randomize");
|
|
1198
|
+
if (Reflect.get(schedule, "jitter") !== undefined &&
|
|
1199
|
+
randomize !== undefined) {
|
|
1200
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath} cannot define both jitter and randomize.`);
|
|
1201
|
+
}
|
|
1144
1202
|
if (Reflect.get(schedule, "jitter") !== undefined)
|
|
1145
1203
|
assertIsoDuration(Reflect.get(schedule, "jitter"), `Provider "${providerId}" ${fieldPath}.jitter`);
|
|
1204
|
+
if (randomize !== undefined) {
|
|
1205
|
+
validateScheduleRandomization(randomize, `Provider "${providerId}" ${fieldPath}.randomize`, isoDurationMs(interval));
|
|
1206
|
+
}
|
|
1146
1207
|
}
|
|
1147
1208
|
function validateHealthJourneys(providerId, operations, healthJourneys) {
|
|
1148
1209
|
const covered = new Set();
|
|
@@ -1278,7 +1339,9 @@ export function defineProvider(config) {
|
|
|
1278
1339
|
fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
|
|
1279
1340
|
});
|
|
1280
1341
|
if (Object.keys(config.operations).length === 0)
|
|
1281
|
-
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
1342
|
+
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
1343
|
+
fix: "Add at least one operation to the operations object",
|
|
1344
|
+
});
|
|
1282
1345
|
validateOperationIds(config.id, config.operations);
|
|
1283
1346
|
validateOperationAnnotations(config.id, config.operations);
|
|
1284
1347
|
validateOperationObservability(config.id, config.operations);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
export * from "./auth";
|
|
1
2
|
export * from "./ceremonies";
|
|
2
3
|
export * from "./choice-token";
|
|
3
4
|
export type { ApiFuseConfig, BrowserConfig, ProxyConfig, SessionConfig, } from "./config/loader";
|
|
4
5
|
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
5
6
|
export { canonicalJson, digestProviderContract, extractProviderContract, type JsonPrimitive, type JsonValue, PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot, } from "./contract";
|
|
6
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define";
|
|
7
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define";
|
|
7
8
|
export type { DevServerOptions } from "./dev";
|
|
8
9
|
export { createDevServer, startDevServer } from "./dev";
|
|
9
10
|
export * from "./errors";
|
|
@@ -33,7 +34,7 @@ export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SEN
|
|
|
33
34
|
export { createServerApp, type ServeOptions, serve } from "./server";
|
|
34
35
|
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles";
|
|
35
36
|
export * from "./stream";
|
|
36
|
-
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderHealthMonitorConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types";
|
|
37
|
+
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderHealthMonitorConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types";
|
|
37
38
|
export { DEFAULT_OPERATION_TRANSPORT, HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, PROBE_INTERVALS, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types";
|
|
38
39
|
export * from "./utils/date";
|
|
39
40
|
export * from "./utils/parse";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// @apifuse/provider-sdk
|
|
2
|
+
export * from "./auth";
|
|
2
3
|
export * from "./ceremonies";
|
|
3
4
|
export * from "./choice-token";
|
|
4
5
|
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
5
6
|
export { canonicalJson, digestProviderContract, extractProviderContract, PROVIDER_CONTRACT_SCHEMA_VERSION, } from "./contract";
|
|
6
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define";
|
|
7
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define";
|
|
7
8
|
export { createDevServer, startDevServer } from "./dev";
|
|
8
9
|
export * from "./errors";
|
|
9
10
|
export * from "./i18n";
|
package/dist/lint.d.ts
CHANGED
package/dist/lint.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { lintPublicSchemaFieldNames } from "./public-schema-field-lint";
|
|
2
2
|
import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY, } from "./schema";
|
|
3
|
+
const AUTH_OPERATION_ID_PATTERN = /^(?:auth[-_])?(?:login|exchange|continue|refresh|callback)(?:[-_]|$)/i;
|
|
3
4
|
function lintAllowedHosts(providerId, allowedHosts) {
|
|
4
5
|
const prefix = providerId ? `Provider "${providerId}"` : "Provider";
|
|
5
6
|
if (!allowedHosts) {
|
|
@@ -49,6 +50,9 @@ function lintReviewed(providerId, reviewed) {
|
|
|
49
50
|
},
|
|
50
51
|
];
|
|
51
52
|
}
|
|
53
|
+
function isProviderAuthLike(value) {
|
|
54
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
55
|
+
}
|
|
52
56
|
function hasReusableSecretKeys(keys) {
|
|
53
57
|
if (!keys) {
|
|
54
58
|
return false;
|
|
@@ -99,6 +103,14 @@ function lintAuthModel(provider) {
|
|
|
99
103
|
message: `${providerLabel} must define auth.flow.continue for ${authMode} auth mode.`,
|
|
100
104
|
});
|
|
101
105
|
}
|
|
106
|
+
if (isProviderAuthLike(provider.auth) && "exchange" in provider.auth) {
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
rule: "auth-exchange-unsupported",
|
|
109
|
+
level: "error",
|
|
110
|
+
field: "auth.exchange",
|
|
111
|
+
message: `${providerLabel} must not define auth.exchange. The Provider SDK has one auth interface: auth.flow. Gateway only calls auth.flow.start/continue/poll/abort/refresh and persists complete turn data.credential as-is; put login/token/session exchange inside auth.flow.continue.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
102
114
|
if (authMode === "credentials" && credentialKeys.length === 0) {
|
|
103
115
|
diagnostics.push({
|
|
104
116
|
rule: "credential-keys-required-when-credentials-mode",
|
|
@@ -673,6 +685,21 @@ export function lintProvider(provider, options = {}) {
|
|
|
673
685
|
...lintPlaywrightDirectImports(provider),
|
|
674
686
|
...lintSelfHostedBrowserPatterns(provider, options),
|
|
675
687
|
];
|
|
688
|
+
if (provider.operations) {
|
|
689
|
+
const authMode = provider.auth?.mode;
|
|
690
|
+
if (authMode === "credentials" || authMode === "oauth2") {
|
|
691
|
+
for (const operationKey of Object.keys(provider.operations)) {
|
|
692
|
+
if (AUTH_OPERATION_ID_PATTERN.test(operationKey)) {
|
|
693
|
+
diagnostics.push({
|
|
694
|
+
rule: "auth-operation-unsupported",
|
|
695
|
+
level: "error",
|
|
696
|
+
field: `operations.${operationKey}`,
|
|
697
|
+
message: `Provider "${provider.id ?? "unknown"}" operation "${operationKey}" looks like a login/token/session exchange endpoint. Authenticated providers must expose login through the single auth.flow interface because Gateway persists only auth.flow complete turn data.credential as the connection credential. Move this logic into auth.flow.continue instead of a provider operation.`,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
676
703
|
if (!provider.operations) {
|
|
677
704
|
return diagnostics;
|
|
678
705
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth";
|
|
2
|
+
export type { CredentialsAuthChallengeDefinition, CredentialsAuthChallengeRequest, CredentialsAuthCompleteResult, CredentialsAuthCredential, CredentialsAuthField, CredentialsAuthFields, CredentialsAuthFieldType, CredentialsAuthInput, CredentialsAuthLoginResult, DefineCredentialsAuthOptions, DefinedCredentialsAuth, } from "./auth";
|
|
1
3
|
export { createFormCeremony } from "./ceremonies";
|
|
2
4
|
export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, type ProviderChoiceTokenErrorReason, type ProviderChoiceTokenPayload, parseProviderChoiceToken, } from "./choice-token";
|
|
3
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
5
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
6
|
export { AuthError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors";
|
|
5
7
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n";
|
|
6
8
|
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice";
|
|
7
9
|
export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, z, } from "./schema";
|
|
8
|
-
export type { AuthMode, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types";
|
|
10
|
+
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types";
|
|
9
11
|
export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types";
|
package/dist/provider.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth";
|
|
1
2
|
export { createFormCeremony } from "./ceremonies";
|
|
2
3
|
export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, parseProviderChoiceToken, } from "./choice-token";
|
|
3
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
5
|
export { AuthError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors";
|
|
5
6
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n";
|
|
6
7
|
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextAccessError } from "../errors";
|
|
2
|
+
import { createAuthFlowHelpers } from "../auth";
|
|
2
3
|
import { createUnsupportedSttClient } from "./stt";
|
|
3
4
|
function normalizeAllowedKeys(allowedKeys) {
|
|
4
5
|
return new Set(allowedKeys.filter((key) => key.trim().length > 0));
|
|
@@ -40,5 +41,6 @@ export function createFlowContext(options) {
|
|
|
40
41
|
env: options.env,
|
|
41
42
|
context: createScratchpad(options.allowedKeys, options.initialContext),
|
|
42
43
|
stt: options.stt ?? createUnsupportedSttClient(),
|
|
44
|
+
auth: createAuthFlowHelpers(),
|
|
43
45
|
};
|
|
44
46
|
}
|
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;
|