@apifuse/provider-sdk 2.1.0-beta.11 → 2.1.0-beta.13
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/cli/create.js +3 -3
- package/dist/define.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +27 -0
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +1 -0
- 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 +95 -0
- package/package.json +1 -1
- package/src/auth.ts +786 -0
- package/src/cli/create.ts +3 -3
- package/src/define.ts +8 -0
- package/src/index.ts +7 -0
- package/src/lint.ts +33 -0
- package/src/provider.ts +24 -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 +122 -0
package/src/cli/create.ts
CHANGED
|
@@ -734,8 +734,8 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
|
|
|
734
734
|
return `{
|
|
735
735
|
mode: "credentials",
|
|
736
736
|
flow: {
|
|
737
|
-
start: async () => ({
|
|
738
|
-
kind: "
|
|
737
|
+
start: async (_ctx) => ({
|
|
738
|
+
kind: "form",
|
|
739
739
|
turnId: crypto.randomUUID(),
|
|
740
740
|
expectedInput: {
|
|
741
741
|
schema: {
|
|
@@ -778,7 +778,7 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
|
|
|
778
778
|
return `{
|
|
779
779
|
mode: "oauth2",
|
|
780
780
|
flow: {
|
|
781
|
-
start: async () => ({
|
|
781
|
+
start: async (_ctx) => ({
|
|
782
782
|
kind: "redirect",
|
|
783
783
|
turnId: crypto.randomUUID(),
|
|
784
784
|
data: {
|
package/src/define.ts
CHANGED
|
@@ -374,6 +374,14 @@ function validateProviderShape(config: unknown): void {
|
|
|
374
374
|
VALID_AUTH_MODES,
|
|
375
375
|
String(config.id),
|
|
376
376
|
);
|
|
377
|
+
if (auth && typeof auth === "object" && "exchange" in auth) {
|
|
378
|
+
throw new ProviderError(
|
|
379
|
+
`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`,
|
|
380
|
+
{
|
|
381
|
+
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.",
|
|
382
|
+
},
|
|
383
|
+
);
|
|
384
|
+
}
|
|
377
385
|
if (
|
|
378
386
|
auth &&
|
|
379
387
|
typeof auth === "object" &&
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @apifuse/provider-sdk
|
|
2
2
|
|
|
3
|
+
export * from "./auth";
|
|
3
4
|
export * from "./ceremonies";
|
|
4
5
|
export * from "./choice-token";
|
|
5
6
|
export type {
|
|
@@ -122,6 +123,12 @@ export type {
|
|
|
122
123
|
Bcp47Locale,
|
|
123
124
|
BrowserEngine,
|
|
124
125
|
BrowserOptions,
|
|
126
|
+
BrowserResourceBody,
|
|
127
|
+
BrowserResourceDecision,
|
|
128
|
+
BrowserResourceMethod,
|
|
129
|
+
BrowserResourcePolicy,
|
|
130
|
+
BrowserResourceRequest,
|
|
131
|
+
BrowserResourceRoute,
|
|
125
132
|
ConnectionMode,
|
|
126
133
|
ContextDeclaration,
|
|
127
134
|
CookieJar,
|
package/src/lint.ts
CHANGED
|
@@ -22,8 +22,12 @@ type ProviderAuthLike = {
|
|
|
22
22
|
abort?: unknown;
|
|
23
23
|
refresh?: unknown;
|
|
24
24
|
};
|
|
25
|
+
exchange?: unknown;
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
const AUTH_OPERATION_ID_PATTERN =
|
|
29
|
+
/^(?:auth[-_])?(?:login|exchange|continue|refresh|callback)(?:[-_]|$)/i;
|
|
30
|
+
|
|
27
31
|
type ProviderContractMetaLike = {
|
|
28
32
|
publicSchemaFieldNames?: "normalized";
|
|
29
33
|
};
|
|
@@ -127,6 +131,10 @@ function lintReviewed(
|
|
|
127
131
|
];
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
function isProviderAuthLike(value: unknown): value is ProviderAuthLike {
|
|
135
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
130
138
|
function hasReusableSecretKeys(keys: readonly string[] | undefined): boolean {
|
|
131
139
|
if (!keys) {
|
|
132
140
|
return false;
|
|
@@ -215,6 +223,15 @@ function lintAuthModel(provider: {
|
|
|
215
223
|
});
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
if (isProviderAuthLike(provider.auth) && "exchange" in provider.auth) {
|
|
227
|
+
diagnostics.push({
|
|
228
|
+
rule: "auth-exchange-unsupported",
|
|
229
|
+
level: "error",
|
|
230
|
+
field: "auth.exchange",
|
|
231
|
+
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.`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
218
235
|
if (authMode === "credentials" && credentialKeys.length === 0) {
|
|
219
236
|
diagnostics.push({
|
|
220
237
|
rule: "credential-keys-required-when-credentials-mode",
|
|
@@ -1006,6 +1023,22 @@ export function lintProvider(
|
|
|
1006
1023
|
...lintSelfHostedBrowserPatterns(provider, options),
|
|
1007
1024
|
];
|
|
1008
1025
|
|
|
1026
|
+
if (provider.operations) {
|
|
1027
|
+
const authMode = provider.auth?.mode;
|
|
1028
|
+
if (authMode === "credentials" || authMode === "oauth2") {
|
|
1029
|
+
for (const operationKey of Object.keys(provider.operations)) {
|
|
1030
|
+
if (AUTH_OPERATION_ID_PATTERN.test(operationKey)) {
|
|
1031
|
+
diagnostics.push({
|
|
1032
|
+
rule: "auth-operation-unsupported",
|
|
1033
|
+
level: "error",
|
|
1034
|
+
field: `operations.${operationKey}`,
|
|
1035
|
+
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.`,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1009
1042
|
if (!provider.operations) {
|
|
1010
1043
|
return diagnostics;
|
|
1011
1044
|
}
|
package/src/provider.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AuthAbortError,
|
|
3
|
+
credentialsAuthChallenge,
|
|
4
|
+
createAuthFlowHelpers,
|
|
5
|
+
defineCredentialsAuth,
|
|
6
|
+
} from "./auth";
|
|
7
|
+
export type {
|
|
8
|
+
CredentialsAuthChallengeDefinition,
|
|
9
|
+
CredentialsAuthChallengeRequest,
|
|
10
|
+
CredentialsAuthCompleteResult,
|
|
11
|
+
CredentialsAuthCredential,
|
|
12
|
+
CredentialsAuthField,
|
|
13
|
+
CredentialsAuthFields,
|
|
14
|
+
CredentialsAuthFieldType,
|
|
15
|
+
CredentialsAuthInput,
|
|
16
|
+
CredentialsAuthLoginResult,
|
|
17
|
+
DefineCredentialsAuthOptions,
|
|
18
|
+
DefinedCredentialsAuth,
|
|
19
|
+
} from "./auth";
|
|
1
20
|
export { createFormCeremony } from "./ceremonies";
|
|
2
21
|
export {
|
|
3
22
|
assertFreshProviderChoiceIssuedAt,
|
|
@@ -50,7 +69,12 @@ export {
|
|
|
50
69
|
z,
|
|
51
70
|
} from "./schema";
|
|
52
71
|
export type {
|
|
72
|
+
AuthAbortData,
|
|
73
|
+
AuthAbortRetry,
|
|
74
|
+
AuthFlowTerminalContext,
|
|
53
75
|
AuthMode,
|
|
76
|
+
AuthSafeData,
|
|
77
|
+
AuthSafeJson,
|
|
54
78
|
FlowContext,
|
|
55
79
|
HealthCheckAssertionContext,
|
|
56
80
|
HealthCheckCase,
|
package/src/runtime/auth-flow.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextAccessError } from "../errors";
|
|
2
|
+
import { createAuthFlowHelpers } from "../auth";
|
|
2
3
|
import type {
|
|
3
4
|
ContextScratchpad,
|
|
4
5
|
EnvContext,
|
|
@@ -70,5 +71,6 @@ export function createFlowContext(options: {
|
|
|
70
71
|
env: options.env,
|
|
71
72
|
context: createScratchpad(options.allowedKeys, options.initialContext),
|
|
72
73
|
stt: options.stt ?? createUnsupportedSttClient(),
|
|
74
|
+
auth: createAuthFlowHelpers(),
|
|
73
75
|
};
|
|
74
76
|
}
|
package/src/runtime/browser.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import type { Frame, LaunchOptions, Locator, Page } from "playwright";
|
|
2
|
+
import type { Frame, LaunchOptions, Locator, Page, Request, Route } from "playwright";
|
|
3
3
|
|
|
4
4
|
import { ProviderError } from "../errors";
|
|
5
5
|
import type {
|
|
@@ -11,11 +11,18 @@ import type {
|
|
|
11
11
|
BrowserLocator,
|
|
12
12
|
BrowserOptions,
|
|
13
13
|
BrowserPage,
|
|
14
|
+
BrowserResourceBody,
|
|
15
|
+
BrowserResourceDecision,
|
|
16
|
+
BrowserResourceMethod,
|
|
17
|
+
BrowserResourcePolicy,
|
|
18
|
+
BrowserResourceRequest,
|
|
14
19
|
} from "../types";
|
|
15
20
|
|
|
16
21
|
const require = createRequire(import.meta.url);
|
|
17
22
|
const DEFAULT_WAIT_TIMEOUT_MS = 30_000;
|
|
18
23
|
const SELECTOR_POLL_INTERVAL_MS = 100;
|
|
24
|
+
const RESOURCE_POLICY_ROUTE_PATTERN = "**/*";
|
|
25
|
+
const DEFAULT_RESOURCE_METHODS = ["GET", "HEAD"] as const;
|
|
19
26
|
|
|
20
27
|
type PlaywrightModule = typeof import("playwright");
|
|
21
28
|
type PlaywrightExtraModule = {
|
|
@@ -62,8 +69,153 @@ type CdpFrameTreeNode = {
|
|
|
62
69
|
};
|
|
63
70
|
};
|
|
64
71
|
|
|
72
|
+
type CdpFetchFulfillParams = {
|
|
73
|
+
readonly requestId: string;
|
|
74
|
+
readonly responseCode: number;
|
|
75
|
+
readonly responseHeaders?: readonly {
|
|
76
|
+
readonly name: string;
|
|
77
|
+
readonly value: string;
|
|
78
|
+
}[];
|
|
79
|
+
readonly body?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
65
82
|
type BrowserPageContract = BrowserPage;
|
|
66
83
|
|
|
84
|
+
function toResourceBody(
|
|
85
|
+
body: BrowserResourceBody | undefined,
|
|
86
|
+
): Buffer | string | undefined {
|
|
87
|
+
if (body === undefined || typeof body === "string" || Buffer.isBuffer(body)) {
|
|
88
|
+
return body;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (body instanceof ArrayBuffer) {
|
|
92
|
+
return Buffer.from(new Uint8Array(body));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Buffer.from(body);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isResourceMethod(method: string): method is BrowserResourceMethod {
|
|
99
|
+
return method === "GET" || method === "HEAD";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function toResourceRequest(
|
|
103
|
+
request: Request,
|
|
104
|
+
): Promise<BrowserResourceRequest | null> {
|
|
105
|
+
const method = request.method().toUpperCase();
|
|
106
|
+
if (!isResourceMethod(method)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
headers: await request.allHeaders(),
|
|
112
|
+
method,
|
|
113
|
+
resourceType: request.resourceType(),
|
|
114
|
+
url: request.url(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function toCdpResourceRequest(
|
|
119
|
+
params: unknown,
|
|
120
|
+
): { requestId: string; request: BrowserResourceRequest } | null {
|
|
121
|
+
if (!isRecord(params)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const requestId = params.requestId;
|
|
126
|
+
const rawRequest = params.request;
|
|
127
|
+
if (typeof requestId !== "string" || !isRecord(rawRequest)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const url = rawRequest.url;
|
|
132
|
+
const method = String(rawRequest.method ?? "").toUpperCase();
|
|
133
|
+
if (typeof url !== "string" || !isResourceMethod(method)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
requestId,
|
|
139
|
+
request: {
|
|
140
|
+
headers: toCdpResourceHeaders(rawRequest.headers),
|
|
141
|
+
method,
|
|
142
|
+
resourceType:
|
|
143
|
+
typeof params.resourceType === "string" ? params.resourceType : undefined,
|
|
144
|
+
url,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getCdpPausedRequestId(params: unknown): string | null {
|
|
150
|
+
if (!isRecord(params) || typeof params.requestId !== "string") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return params.requestId;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function toCdpResourceHeaders(value: unknown): Record<string, string> {
|
|
158
|
+
if (!isRecord(value)) {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const headers: Record<string, string> = {};
|
|
163
|
+
for (const [name, headerValue] of Object.entries(value)) {
|
|
164
|
+
if (typeof headerValue === "string") {
|
|
165
|
+
headers[name] = headerValue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return headers;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function matchesResourceRoute(
|
|
173
|
+
match: BrowserResourcePolicy["routes"][number]["match"],
|
|
174
|
+
request: BrowserResourceRequest,
|
|
175
|
+
): boolean {
|
|
176
|
+
if (typeof match === "string") {
|
|
177
|
+
return request.url === match;
|
|
178
|
+
}
|
|
179
|
+
if (match instanceof RegExp) {
|
|
180
|
+
return match.test(request.url);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return match(request);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function toCdpFulfillParams(
|
|
187
|
+
requestId: string,
|
|
188
|
+
decision: Extract<BrowserResourceDecision, { readonly action: "fulfill" }>,
|
|
189
|
+
): CdpFetchFulfillParams {
|
|
190
|
+
const body = toResourceBody(decision.body);
|
|
191
|
+
return {
|
|
192
|
+
...(body === undefined
|
|
193
|
+
? {}
|
|
194
|
+
: { body: Buffer.from(body).toString("base64") }),
|
|
195
|
+
...(decision.headers === undefined
|
|
196
|
+
? {}
|
|
197
|
+
: {
|
|
198
|
+
responseHeaders: Object.entries(decision.headers).map(
|
|
199
|
+
([name, value]) => ({ name, value }),
|
|
200
|
+
),
|
|
201
|
+
}),
|
|
202
|
+
requestId,
|
|
203
|
+
responseCode: decision.status ?? 200,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function fulfillResourceRoute(
|
|
208
|
+
route: Route,
|
|
209
|
+
decision: Extract<BrowserResourceDecision, { readonly action: "fulfill" }>,
|
|
210
|
+
): Promise<void> {
|
|
211
|
+
const body = toResourceBody(decision.body);
|
|
212
|
+
await route.fulfill({
|
|
213
|
+
...(body === undefined ? {} : { body }),
|
|
214
|
+
...(decision.headers === undefined ? {} : { headers: decision.headers }),
|
|
215
|
+
status: decision.status ?? 200,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
67
219
|
export type BrowserClientOptions = BrowserOptions & {
|
|
68
220
|
allowedHosts?: string[];
|
|
69
221
|
cdpUrl?: string;
|
|
@@ -398,6 +550,47 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
398
550
|
async close(): Promise<void> {
|
|
399
551
|
await this.page.close();
|
|
400
552
|
}
|
|
553
|
+
|
|
554
|
+
async withResourcePolicy<T>(
|
|
555
|
+
policy: BrowserResourcePolicy,
|
|
556
|
+
run: () => Promise<T>,
|
|
557
|
+
): Promise<T> {
|
|
558
|
+
const allowedMethods = new Set(
|
|
559
|
+
policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS,
|
|
560
|
+
);
|
|
561
|
+
const handler = async (route: Route): Promise<void> => {
|
|
562
|
+
const request = await toResourceRequest(route.request());
|
|
563
|
+
if (!request || !allowedMethods.has(request.method)) {
|
|
564
|
+
await route.abort("blockedbyclient");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
for (const resourceRoute of policy.routes) {
|
|
569
|
+
if (!matchesResourceRoute(resourceRoute.match, request)) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const decision = await resourceRoute.handle(request);
|
|
574
|
+
switch (decision.action) {
|
|
575
|
+
case "fulfill":
|
|
576
|
+
await fulfillResourceRoute(route, decision);
|
|
577
|
+
return;
|
|
578
|
+
case "block":
|
|
579
|
+
await route.abort("blockedbyclient");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
await route.abort("blockedbyclient");
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
588
|
+
try {
|
|
589
|
+
return await run();
|
|
590
|
+
} finally {
|
|
591
|
+
await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
401
594
|
}
|
|
402
595
|
|
|
403
596
|
class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
@@ -1055,6 +1248,105 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1055
1248
|
}
|
|
1056
1249
|
}
|
|
1057
1250
|
|
|
1251
|
+
async withResourcePolicy<T>(
|
|
1252
|
+
policy: BrowserResourcePolicy,
|
|
1253
|
+
run: () => Promise<T>,
|
|
1254
|
+
): Promise<T> {
|
|
1255
|
+
const allowedMethods = new Set(
|
|
1256
|
+
policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS,
|
|
1257
|
+
);
|
|
1258
|
+
const handlePausedRequest = (params: unknown): void => {
|
|
1259
|
+
void this.handleResourcePolicyPausedRequest(
|
|
1260
|
+
params,
|
|
1261
|
+
policy,
|
|
1262
|
+
allowedMethods,
|
|
1263
|
+
);
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
const unsubscribe = this.pageClient.on(
|
|
1267
|
+
"Fetch.requestPaused",
|
|
1268
|
+
handlePausedRequest,
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
try {
|
|
1272
|
+
await this.pageClient.send("Fetch.enable", {
|
|
1273
|
+
patterns: [{ requestStage: "Request", urlPattern: "*" }],
|
|
1274
|
+
});
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
unsubscribe();
|
|
1277
|
+
throw new ProviderError(
|
|
1278
|
+
"CDP browser target does not support BrowserPage.withResourcePolicy()",
|
|
1279
|
+
{
|
|
1280
|
+
cause: error instanceof Error ? error : undefined,
|
|
1281
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
1282
|
+
fix: "Use a Chromium CDP target with the Fetch domain enabled, or use the local Playwright browser runtime.",
|
|
1283
|
+
},
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
try {
|
|
1288
|
+
return await run();
|
|
1289
|
+
} finally {
|
|
1290
|
+
unsubscribe();
|
|
1291
|
+
await this.pageClient.send("Fetch.disable");
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
private async handleResourcePolicyPausedRequest(
|
|
1296
|
+
params: unknown,
|
|
1297
|
+
policy: BrowserResourcePolicy,
|
|
1298
|
+
allowedMethods: ReadonlySet<BrowserResourceMethod>,
|
|
1299
|
+
): Promise<void> {
|
|
1300
|
+
const requestId = getCdpPausedRequestId(params);
|
|
1301
|
+
if (requestId === null) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
try {
|
|
1306
|
+
const parsed = toCdpResourceRequest(params);
|
|
1307
|
+
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
1308
|
+
await this.failCdpResourceRequest(requestId);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
for (const resourceRoute of policy.routes) {
|
|
1313
|
+
if (!matchesResourceRoute(resourceRoute.match, parsed.request)) {
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const decision = await resourceRoute.handle(parsed.request);
|
|
1318
|
+
switch (decision.action) {
|
|
1319
|
+
case "fulfill":
|
|
1320
|
+
await this.pageClient.send(
|
|
1321
|
+
"Fetch.fulfillRequest",
|
|
1322
|
+
toCdpFulfillParams(parsed.requestId, decision),
|
|
1323
|
+
);
|
|
1324
|
+
return;
|
|
1325
|
+
case "block":
|
|
1326
|
+
await this.failCdpResourceRequest(parsed.requestId);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
await this.failCdpResourceRequest(parsed.requestId);
|
|
1332
|
+
} catch {
|
|
1333
|
+
await this.failCdpResourceRequest(requestId);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
private async failCdpResourceRequest(requestId: string): Promise<void> {
|
|
1338
|
+
try {
|
|
1339
|
+
await this.pageClient.send("Fetch.failRequest", {
|
|
1340
|
+
errorReason: "BlockedByClient",
|
|
1341
|
+
requestId,
|
|
1342
|
+
});
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
if (error instanceof Error) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1058
1350
|
private async initialize(): Promise<void> {
|
|
1059
1351
|
if (this.initialized) {
|
|
1060
1352
|
return;
|
package/src/server/serve.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { Hono } from "hono";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { AuthAbortError, createAuthFlowHelpers } from "../auth";
|
|
6
7
|
import {
|
|
7
8
|
AuthError,
|
|
8
9
|
ProviderError,
|
|
@@ -329,6 +330,7 @@ function createAuthFlowContext(
|
|
|
329
330
|
provider: ProviderDefinition,
|
|
330
331
|
request: AuthFlowRequest,
|
|
331
332
|
options: ProviderServerOptions = {},
|
|
333
|
+
signal?: AbortSignal,
|
|
332
334
|
): {
|
|
333
335
|
context: FlowContext;
|
|
334
336
|
getPatch: () => Record<string, unknown | null> | undefined;
|
|
@@ -383,6 +385,7 @@ function createAuthFlowContext(
|
|
|
383
385
|
credential,
|
|
384
386
|
context: flowContextStore.context,
|
|
385
387
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
388
|
+
auth: createAuthFlowHelpers({ signal }),
|
|
386
389
|
},
|
|
387
390
|
getPatch: flowContextStore.getPatch,
|
|
388
391
|
};
|
|
@@ -1291,6 +1294,7 @@ async function handleAuthFlow(
|
|
|
1291
1294
|
request: AuthFlowRequest,
|
|
1292
1295
|
route: AuthRoute,
|
|
1293
1296
|
options: ProviderServerOptions = {},
|
|
1297
|
+
signal?: AbortSignal,
|
|
1294
1298
|
): Promise<Response | AuthFlowResponse> {
|
|
1295
1299
|
const flow = provider.auth?.flow;
|
|
1296
1300
|
if (!flow) {
|
|
@@ -1303,6 +1307,7 @@ async function handleAuthFlow(
|
|
|
1303
1307
|
provider,
|
|
1304
1308
|
request,
|
|
1305
1309
|
options,
|
|
1310
|
+
signal,
|
|
1306
1311
|
);
|
|
1307
1312
|
try {
|
|
1308
1313
|
const result =
|
|
@@ -1336,6 +1341,11 @@ async function handleAuthFlow(
|
|
|
1336
1341
|
? materializeAuthFlowTurn(provider, request, result)
|
|
1337
1342
|
: result;
|
|
1338
1343
|
return toAuthFlowResponse(materializedResult, getPatch());
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
if (error instanceof AuthAbortError) {
|
|
1346
|
+
return toAuthFlowResponse(error.turn, getPatch());
|
|
1347
|
+
}
|
|
1348
|
+
throw error;
|
|
1339
1349
|
} finally {
|
|
1340
1350
|
context.stealth.close?.();
|
|
1341
1351
|
}
|
|
@@ -1450,7 +1460,13 @@ export function createServerApp(
|
|
|
1450
1460
|
AuthFlowRequestSchema.parse(rawBody),
|
|
1451
1461
|
c.req.raw.headers,
|
|
1452
1462
|
);
|
|
1453
|
-
const response = await handleAuthFlow(
|
|
1463
|
+
const response = await handleAuthFlow(
|
|
1464
|
+
provider,
|
|
1465
|
+
body,
|
|
1466
|
+
"start",
|
|
1467
|
+
options,
|
|
1468
|
+
c.req.raw.signal,
|
|
1469
|
+
);
|
|
1454
1470
|
logProviderSuccess(
|
|
1455
1471
|
logger,
|
|
1456
1472
|
provider,
|
|
@@ -1495,6 +1511,7 @@ export function createServerApp(
|
|
|
1495
1511
|
body,
|
|
1496
1512
|
"continue",
|
|
1497
1513
|
options,
|
|
1514
|
+
c.req.raw.signal,
|
|
1498
1515
|
);
|
|
1499
1516
|
logProviderSuccess(
|
|
1500
1517
|
logger,
|
|
@@ -1535,7 +1552,13 @@ export function createServerApp(
|
|
|
1535
1552
|
AuthFlowRequestSchema.parse(rawBody),
|
|
1536
1553
|
c.req.raw.headers,
|
|
1537
1554
|
);
|
|
1538
|
-
const response = await handleAuthFlow(
|
|
1555
|
+
const response = await handleAuthFlow(
|
|
1556
|
+
provider,
|
|
1557
|
+
body,
|
|
1558
|
+
"poll",
|
|
1559
|
+
options,
|
|
1560
|
+
c.req.raw.signal,
|
|
1561
|
+
);
|
|
1539
1562
|
logProviderSuccess(
|
|
1540
1563
|
logger,
|
|
1541
1564
|
provider,
|
|
@@ -1575,7 +1598,13 @@ export function createServerApp(
|
|
|
1575
1598
|
AuthFlowRequestSchema.parse(rawBody),
|
|
1576
1599
|
c.req.raw.headers,
|
|
1577
1600
|
);
|
|
1578
|
-
const response = await handleAuthFlow(
|
|
1601
|
+
const response = await handleAuthFlow(
|
|
1602
|
+
provider,
|
|
1603
|
+
body,
|
|
1604
|
+
"refresh",
|
|
1605
|
+
options,
|
|
1606
|
+
c.req.raw.signal,
|
|
1607
|
+
);
|
|
1579
1608
|
logProviderSuccess(
|
|
1580
1609
|
logger,
|
|
1581
1610
|
provider,
|
|
@@ -1615,7 +1644,13 @@ export function createServerApp(
|
|
|
1615
1644
|
AuthFlowRequestSchema.parse(rawBody),
|
|
1616
1645
|
c.req.raw.headers,
|
|
1617
1646
|
);
|
|
1618
|
-
const response = await handleAuthFlow(
|
|
1647
|
+
const response = await handleAuthFlow(
|
|
1648
|
+
provider,
|
|
1649
|
+
body,
|
|
1650
|
+
"abort",
|
|
1651
|
+
options,
|
|
1652
|
+
c.req.raw.signal,
|
|
1653
|
+
);
|
|
1619
1654
|
logProviderSuccess(
|
|
1620
1655
|
logger,
|
|
1621
1656
|
provider,
|