first commit

This commit is contained in:
Frank John Begornia
2025-12-23 01:51:15 +08:00
commit c926590e1d
4137 changed files with 613038 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import type { CdpTarget } from '../context/CdpTarget.js';
import type { EventManager } from '../session/EventManager.js';
import type { NetworkStorage } from './NetworkStorage.js';
/** Maps 1:1 to CdpTarget. */
export declare class NetworkManager {
#private;
private constructor();
/** Returns the CDP Target associated with this NetworkManager instance. */
get cdpTarget(): CdpTarget;
static create(cdpTarget: CdpTarget, eventManager: EventManager, networkStorage: NetworkStorage): NetworkManager;
}

View File

@@ -0,0 +1,110 @@
"use strict";
/*
* Copyright 2023 Google LLC.
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkManager = void 0;
const NetworkRequest_js_1 = require("./NetworkRequest.js");
/** Maps 1:1 to CdpTarget. */
class NetworkManager {
#cdpTarget;
#eventManager;
#networkStorage;
constructor(cdpTarget, eventManager, networkStorage) {
this.#cdpTarget = cdpTarget;
this.#eventManager = eventManager;
this.#networkStorage = networkStorage;
}
/** Returns the CDP Target associated with this NetworkManager instance. */
get cdpTarget() {
return this.#cdpTarget;
}
/**
* Gets the network request with the given ID, if any.
* Otherwise, creates a new network request with the given ID and cdp target.
*/
#getOrCreateNetworkRequest(id, redirectCount) {
let request = this.#networkStorage.getRequest(id);
if (request) {
return request;
}
request = new NetworkRequest_js_1.NetworkRequest(id, this.#eventManager, this.#networkStorage, this.#cdpTarget, redirectCount);
this.#networkStorage.addRequest(request);
return request;
}
static create(cdpTarget, eventManager, networkStorage) {
const networkManager = new NetworkManager(cdpTarget, eventManager, networkStorage);
cdpTarget.browserCdpClient.on('Target.detachedFromTarget', (params) => {
if (cdpTarget.cdpClient.sessionId === params.sessionId) {
networkManager.#networkStorage.disposeRequestMap();
}
});
cdpTarget.cdpClient.on('Network.requestWillBeSent', (params) => {
const request = networkManager.#networkStorage.getRequest(params.requestId);
if (request && request.isRedirecting()) {
request.handleRedirect(params);
networkManager.#networkStorage.deleteRequest(params.requestId);
networkManager
.#getOrCreateNetworkRequest(params.requestId, request.redirectCount + 1)
.onRequestWillBeSentEvent(params);
}
else if (request) {
request.onRequestWillBeSentEvent(params);
}
else {
networkManager
.#getOrCreateNetworkRequest(params.requestId)
.onRequestWillBeSentEvent(params);
}
});
cdpTarget.cdpClient.on('Network.requestWillBeSentExtraInfo', (params) => {
networkManager
.#getOrCreateNetworkRequest(params.requestId)
.onRequestWillBeSentExtraInfoEvent(params);
});
cdpTarget.cdpClient.on('Network.responseReceived', (params) => {
networkManager
.#getOrCreateNetworkRequest(params.requestId)
.onResponseReceivedEvent(params);
});
cdpTarget.cdpClient.on('Network.responseReceivedExtraInfo', (params) => {
networkManager
.#getOrCreateNetworkRequest(params.requestId)
.onResponseReceivedExtraInfoEvent(params);
});
cdpTarget.cdpClient.on('Network.requestServedFromCache', (params) => {
networkManager
.#getOrCreateNetworkRequest(params.requestId)
.onServedFromCache();
});
cdpTarget.cdpClient.on('Network.loadingFailed', (params) => {
networkManager
.#getOrCreateNetworkRequest(params.requestId)
.onLoadingFailedEvent(params);
});
// https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
cdpTarget.cdpClient.on('Fetch.requestPaused', (params) => {
if (params.networkId) {
networkManager
.#getOrCreateNetworkRequest(params.networkId)
.onRequestPaused(params);
}
});
return networkManager;
}
}
exports.NetworkManager = NetworkManager;
//# sourceMappingURL=NetworkManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"NetworkManager.js","sourceRoot":"","sources":["../../../../../src/bidiMapper/domains/network/NetworkManager.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,21 @@
import { Network, type EmptyResult } from '../../../protocol/protocol.js';
import type { BrowsingContextStorage } from '../context/BrowsingContextStorage.js';
import { NetworkStorage } from './NetworkStorage.js';
/** Dispatches Network domain commands. */
export declare class NetworkProcessor {
#private;
constructor(browsingContextStorage: BrowsingContextStorage, networkStorage: NetworkStorage);
addIntercept(params: Network.AddInterceptParameters): Promise<Network.AddInterceptResult>;
continueRequest(params: Network.ContinueRequestParameters): Promise<EmptyResult>;
continueResponse(params: Network.ContinueResponseParameters): Promise<EmptyResult>;
continueWithAuth(params: Network.ContinueWithAuthParameters): Promise<EmptyResult>;
failRequest({ request: networkId, }: Network.FailRequestParameters): Promise<EmptyResult>;
provideResponse(params: Network.ProvideResponseParameters): Promise<EmptyResult>;
removeIntercept(params: Network.RemoveInterceptParameters): Promise<EmptyResult>;
/**
* Attempts to parse the given url.
* Throws an InvalidArgumentException if the url is invalid.
*/
static parseUrlString(url: string): URL;
static parseUrlPatterns(urlPatterns: Network.UrlPattern[]): Network.UrlPattern[];
}

View File

@@ -0,0 +1,230 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkProcessor = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const assert_js_1 = require("../../../utils/assert.js");
const NetworkStorage_js_1 = require("./NetworkStorage.js");
const NetworkUtils_js_1 = require("./NetworkUtils.js");
/** Dispatches Network domain commands. */
class NetworkProcessor {
#browsingContextStorage;
#networkStorage;
constructor(browsingContextStorage, networkStorage) {
this.#browsingContextStorage = browsingContextStorage;
this.#networkStorage = networkStorage;
}
async addIntercept(params) {
// If AuthRequired is specified, BeforeRequestSent must also be specified.
// This is a CDP quirk.
if (params.phases.includes("authRequired" /* Network.InterceptPhase.AuthRequired */) &&
!params.phases.includes("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */)) {
params.phases.unshift("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */);
}
const urlPatterns = params.urlPatterns ?? [];
const parsedUrlPatterns = NetworkProcessor.parseUrlPatterns(urlPatterns);
const intercept = this.#networkStorage.addIntercept({
urlPatterns: parsedUrlPatterns,
phases: params.phases,
});
await this.#fetchApply();
return {
intercept,
};
}
async continueRequest(params) {
const networkId = params.request;
const { request: fetchId, phase } = this.#getBlockedRequest(networkId);
if (phase !== "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */) {
throw new protocol_js_1.InvalidArgumentException(`Blocked request for network id '${networkId}' is not in 'BeforeRequestSent' phase`);
}
if (params.url !== undefined) {
NetworkProcessor.parseUrlString(params.url);
}
const { url, method, headers } = params;
// TODO: Set / expand.
// ; Step 9. cookies
// ; Step 10. body
const requestHeaders = (0, NetworkUtils_js_1.cdpFetchHeadersFromBidiNetworkHeaders)(headers);
const request = this.#getRequestOrFail(networkId);
await request.continueRequest(fetchId, url, method, requestHeaders);
this.#networkStorage.removeBlockedRequest(networkId);
return {};
}
async continueResponse(params) {
const networkId = params.request;
const { request: fetchId, phase } = this.#getBlockedRequest(networkId);
if (phase === "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */) {
throw new protocol_js_1.InvalidArgumentException(`Blocked request for network id '${networkId}' is in 'BeforeRequestSent' phase`);
}
const { statusCode, reasonPhrase, headers } = params;
const responseHeaders = (0, NetworkUtils_js_1.cdpFetchHeadersFromBidiNetworkHeaders)(headers);
// TODO: Set / expand.
// ; Step 10. cookies
// ; Step 11. credentials
const request = this.#getRequestOrFail(networkId);
await request.continueResponse(fetchId, statusCode, reasonPhrase, responseHeaders);
this.#networkStorage.removeBlockedRequest(networkId);
return {};
}
async continueWithAuth(params) {
const networkId = params.request;
const { request: fetchId, phase } = this.#getBlockedRequest(networkId);
if (phase !== "authRequired" /* Network.InterceptPhase.AuthRequired */) {
throw new protocol_js_1.InvalidArgumentException(`Blocked request for network id '${networkId}' is not in 'AuthRequired' phase`);
}
const request = this.#getRequestOrFail(networkId);
let username;
let password;
if (params.action === 'provideCredentials') {
const { credentials } = params;
username = credentials.username;
password = credentials.password;
// TODO: This should be invalid argument exception.
// Spec may need to be updated.
(0, assert_js_1.assert)(credentials.type === 'password', `Credentials type ${credentials.type} must be 'password'`);
}
const response = (0, NetworkUtils_js_1.cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction)(params.action);
await request.continueWithAuth(fetchId, response, username, password);
return {};
}
async failRequest({ request: networkId, }) {
const { request: fetchId, phase } = this.#getBlockedRequest(networkId);
if (phase === "authRequired" /* Network.InterceptPhase.AuthRequired */) {
throw new protocol_js_1.InvalidArgumentException(`Blocked request for network id '${networkId}' is in 'AuthRequired' phase`);
}
const request = this.#getRequestOrFail(networkId);
await request.failRequest(fetchId, 'Failed');
this.#networkStorage.removeBlockedRequest(networkId);
return {};
}
async provideResponse(params) {
const { statusCode, reasonPhrase, headers, body, request: networkId, } = params;
const { request: fetchId } = this.#getBlockedRequest(networkId);
// TODO: Step 6
// https://w3c.github.io/webdriver-bidi/#command-network-continueResponse
const responseHeaders = (0, NetworkUtils_js_1.cdpFetchHeadersFromBidiNetworkHeaders)(headers);
// TODO: Set / expand.
// ; Step 10. cookies
// ; Step 11. credentials
const request = this.#getRequestOrFail(networkId);
await request.provideResponse(fetchId, statusCode ?? request.statusCode, reasonPhrase, responseHeaders, body?.value // TODO: Differ base64 / string
);
this.#networkStorage.removeBlockedRequest(networkId);
return {};
}
async removeIntercept(params) {
this.#networkStorage.removeIntercept(params.intercept);
await this.#fetchApply();
return {};
}
/** Applies all existing network intercepts to all CDP targets concurrently. */
async #fetchEnable() {
await Promise.all(this.#browsingContextStorage.getAllContexts().map(async (context) => {
await context.cdpTarget.fetchEnable();
}));
}
/** Removes all existing network intercepts from all CDP targets concurrently. */
async #fetchDisable() {
await Promise.all(this.#browsingContextStorage.getAllContexts().map(async (context) => {
await context.cdpTarget.fetchDisable();
}));
}
/**
* Either enables or disables the Fetch domain.
*
* If enabling, applies all existing network intercepts to all CDP targets.
* If disabling, removes all existing network intercepts from all CDP targets.
*
* Disabling is only performed when there are no remaining intercepts or
* // blocked requests.
*/
async #fetchApply() {
if (this.#networkStorage.hasIntercepts() ||
this.#networkStorage.hasBlockedRequests() ||
this.#networkStorage.hasNetworkRequests()) {
// TODO: Add try/catch. Remove the intercept if CDP Fetch commands fail.
await this.#fetchEnable();
}
else {
// The last intercept has been removed, and there are no pending
// blocked requests.
// Disable the Fetch domain.
await this.#fetchDisable();
}
}
/**
* Returns the blocked request associated with the given network ID.
* If none, throws a NoSuchRequestException.
*/
#getBlockedRequest(networkId) {
const blockedRequest = this.#networkStorage.getBlockedRequest(networkId);
if (!blockedRequest) {
throw new protocol_js_1.NoSuchRequestException(`No blocked request found for network id '${networkId}'`);
}
return blockedRequest;
}
#getRequestOrFail(id) {
const request = this.#networkStorage.getRequest(id);
if (!request) {
throw new protocol_js_1.NoSuchRequestException(`Network request with ID ${id} doesn't exist`);
}
return request;
}
/**
* Attempts to parse the given url.
* Throws an InvalidArgumentException if the url is invalid.
*/
static parseUrlString(url) {
try {
return new URL(url);
}
catch (error) {
throw new protocol_js_1.InvalidArgumentException(`Invalid URL '${url}': ${error}`);
}
}
static parseUrlPatterns(urlPatterns) {
return urlPatterns.map((urlPattern) => {
switch (urlPattern.type) {
case 'string': {
NetworkProcessor.parseUrlString(urlPattern.pattern);
return urlPattern;
}
case 'pattern':
// No params signifies intercept all
if (urlPattern.protocol === undefined &&
urlPattern.hostname === undefined &&
urlPattern.port === undefined &&
urlPattern.pathname === undefined &&
urlPattern.search === undefined) {
return urlPattern;
}
if (urlPattern.protocol === '') {
throw new protocol_js_1.InvalidArgumentException(`URL pattern must specify a protocol`);
}
if (urlPattern.hostname === '') {
throw new protocol_js_1.InvalidArgumentException(`URL pattern must specify a hostname`);
}
if ((urlPattern.hostname?.length ?? 0) > 0) {
if (urlPattern.protocol?.match(/^file/i)) {
throw new protocol_js_1.InvalidArgumentException(`URL pattern protocol cannot be 'file'`);
}
if (urlPattern.hostname?.includes(':')) {
throw new protocol_js_1.InvalidArgumentException(`URL pattern hostname must not contain a colon`);
}
}
if (urlPattern.port === '') {
throw new protocol_js_1.InvalidArgumentException(`URL pattern must specify a port`);
}
try {
new URL(NetworkStorage_js_1.NetworkStorage.buildUrlPatternString(urlPattern));
}
catch (error) {
throw new protocol_js_1.InvalidArgumentException(`${error}`);
}
return urlPattern;
}
});
}
}
exports.NetworkProcessor = NetworkProcessor;
//# sourceMappingURL=NetworkProcessor.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,41 @@
/**
* @fileoverview `NetworkRequest` represents a single network request and keeps
* track of all the related CDP events.
*/
import type { Protocol } from 'devtools-protocol';
import { Network, type JsUint } from '../../../protocol/protocol.js';
import type { CdpTarget } from '../context/CdpTarget.js';
import type { EventManager } from '../session/EventManager.js';
import type { NetworkStorage } from './NetworkStorage.js';
/** Abstracts one individual network request. */
export declare class NetworkRequest {
#private;
constructor(requestId: Network.Request, eventManager: EventManager, networkStorage: NetworkStorage, cdpTarget: CdpTarget, redirectCount?: number);
get requestId(): string;
get url(): string | undefined;
get redirectCount(): number;
get cdpTarget(): CdpTarget;
isRedirecting(): boolean;
handleRedirect(event: Protocol.Network.RequestWillBeSentEvent): void;
onRequestWillBeSentEvent(event: Protocol.Network.RequestWillBeSentEvent): void;
onRequestWillBeSentExtraInfoEvent(event: Protocol.Network.RequestWillBeSentExtraInfoEvent): void;
onResponseReceivedExtraInfoEvent(event: Protocol.Network.ResponseReceivedExtraInfoEvent): void;
onResponseReceivedEvent(event: Protocol.Network.ResponseReceivedEvent): void;
onServedFromCache(): void;
onLoadingFailedEvent(event: Protocol.Network.LoadingFailedEvent): void;
/** Fired whenever a network request interception is hit. */
onRequestPaused(params: Protocol.Fetch.RequestPausedEvent): void;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-failRequest */
failRequest(networkId: Network.Request, errorReason: Protocol.Network.ErrorReason): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest */
continueRequest(cdpFetchRequestId: Protocol.Fetch.RequestId, url?: string, method?: string, headers?: Protocol.Fetch.HeaderEntry[]): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueResponse */
continueResponse(cdpFetchRequestId: Protocol.Fetch.RequestId, responseCode?: JsUint, responsePhrase?: string, responseHeaders?: Protocol.Fetch.HeaderEntry[]): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueWithAuth */
continueWithAuth(cdpFetchRequestId: Protocol.Fetch.RequestId, response: 'Default' | 'CancelAuth' | 'ProvideCredentials', username?: string, password?: string): Promise<void>;
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-provideResponse */
provideResponse(cdpFetchRequestId: Protocol.Fetch.RequestId, responseCode: JsUint, responsePhrase?: string, responseHeaders?: Protocol.Fetch.HeaderEntry[], body?: string): Promise<void>;
dispose(): void;
/** Returns the HTTP status code associated with this request if any. */
get statusCode(): number;
}

View File

@@ -0,0 +1,532 @@
"use strict";
/*
* Copyright 2023 Google LLC.
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkRequest = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const assert_js_1 = require("../../../utils/assert.js");
const Deferred_js_1 = require("../../../utils/Deferred.js");
const NetworkUtils_js_1 = require("./NetworkUtils.js");
/** Abstracts one individual network request. */
class NetworkRequest {
static #unknown = 'UNKNOWN';
/**
* Each network request has an associated request id, which is a string
* uniquely identifying that request.
*
* The identifier for a request resulting from a redirect matches that of the
* request that initiated it.
*/
#requestId;
// TODO: Handle auth required?
/**
* Indicates the network intercept phase, if the request is currently blocked.
* Undefined necessarily implies that the request is not blocked.
*/
#interceptPhase = undefined;
#servedFromCache = false;
#redirectCount;
#eventManager;
#networkStorage;
#request = {};
#response = {};
#beforeRequestSentDeferred = new Deferred_js_1.Deferred();
#responseStartedDeferred = new Deferred_js_1.Deferred();
#responseCompletedDeferred = new Deferred_js_1.Deferred();
#cdpTarget;
constructor(requestId, eventManager, networkStorage, cdpTarget, redirectCount = 0) {
this.#requestId = requestId;
this.#eventManager = eventManager;
this.#networkStorage = networkStorage;
this.#cdpTarget = cdpTarget;
this.#redirectCount = redirectCount;
}
get requestId() {
return this.#requestId;
}
get url() {
return this.#response.info?.url ?? this.#request.info?.request.url;
}
get redirectCount() {
return this.#redirectCount;
}
get cdpTarget() {
return this.#cdpTarget;
}
isRedirecting() {
return Boolean(this.#request.info);
}
handleRedirect(event) {
this.#queueResponseStartedEvent();
this.#queueResponseCompletedEvent();
this.#response.hasExtraInfo = event.redirectHasExtraInfo;
this.#response.info = event.redirectResponse;
this.#emitEventsIfReady(true);
}
#emitEventsIfReady(wasRedirected = false) {
const requestExtraInfoCompleted =
// Flush redirects
wasRedirected ||
Boolean(this.#request.extraInfo) ||
// Requests from cache don't have extra info
this.#servedFromCache ||
// Sometimes there is no extra info and the response
// is the only place we can find out
Boolean(this.#response.info && !this.#response.hasExtraInfo) ||
this.#interceptPhase === "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */;
if (this.#request.info && requestExtraInfoCompleted) {
this.#beforeRequestSentDeferred.resolve({
kind: 'success',
value: undefined,
});
}
const responseExtraInfoCompleted = Boolean(this.#response.extraInfo) ||
// Response from cache don't have extra info
this.#servedFromCache ||
// Don't expect extra info if the flag is false
Boolean(this.#response.info && !this.#response.hasExtraInfo) ||
this.#interceptPhase === "responseStarted" /* Network.InterceptPhase.ResponseStarted */;
if (this.#response.info && responseExtraInfoCompleted) {
this.#responseStartedDeferred.resolve({
kind: 'success',
value: undefined,
});
this.#responseCompletedDeferred.resolve({
kind: 'success',
value: undefined,
});
}
}
onRequestWillBeSentEvent(event) {
this.#request.info = event;
this.#queueBeforeRequestSentEvent();
this.#emitEventsIfReady();
}
onRequestWillBeSentExtraInfoEvent(event) {
this.#request.extraInfo = event;
this.#emitEventsIfReady();
}
onResponseReceivedExtraInfoEvent(event) {
this.#response.extraInfo = event;
this.#emitEventsIfReady();
}
onResponseReceivedEvent(event) {
this.#response.hasExtraInfo = event.hasExtraInfo;
this.#response.info = event.response;
this.#queueResponseStartedEvent();
this.#queueResponseCompletedEvent();
this.#emitEventsIfReady();
}
onServedFromCache() {
this.#servedFromCache = true;
this.#emitEventsIfReady();
}
onLoadingFailedEvent(event) {
this.#beforeRequestSentDeferred.resolve({
kind: 'success',
value: undefined,
});
this.#responseStartedDeferred.resolve({
kind: 'error',
error: new Error('Network event loading failed'),
});
this.#responseCompletedDeferred.resolve({
kind: 'error',
error: new Error('Network event loading failed'),
});
this.#eventManager.registerEvent({
type: 'event',
method: protocol_js_1.ChromiumBidi.Network.EventNames.FetchError,
params: {
...this.#getBaseEventParams(),
errorText: event.errorText,
},
}, this.#context);
}
/** Fired whenever a network request interception is hit. */
onRequestPaused(params) {
if (this.#isIgnoredEvent()) {
void this.continueRequest(params.requestId).catch(() => {
// TODO: Add some logging
});
return;
}
// The stage of the request can be determined by presence of
// responseErrorReason and responseStatusCode -- the request is at
// the response stage if either of these fields is present and in the
// request stage otherwise.
let phase;
if (params.responseErrorReason === undefined &&
params.responseStatusCode === undefined) {
phase = "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */;
}
else if (params.responseStatusCode === 401 &&
params.responseStatusText === 'Unauthorized') {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
phase = "authRequired" /* Network.InterceptPhase.AuthRequired */;
}
else {
phase = "responseStarted" /* Network.InterceptPhase.ResponseStarted */;
}
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpFetchHeaders)(
// TODO: Use params.request.headers if request?
params.responseHeaders);
this.#networkStorage.addBlockedRequest(this.requestId, {
request: params.requestId, // intercept request id
phase,
// TODO: Finish populating response / ResponseData.
response: {
url: params.request.url,
// TODO: populate.
protocol: '',
status: params.responseStatusCode ?? 0,
statusText: params.responseStatusText ?? '',
// TODO: populate.
fromCache: false,
headers,
// TODO: populate.
mimeType: '',
// TODO: populate.
bytesReceived: 0,
headersSize: (0, NetworkUtils_js_1.computeHeadersSize)(headers),
// TODO: consider removing from spec.
bodySize: 0,
// TODO: consider removing from spec.
content: {
size: 0,
},
// TODO: populate.
authChallenge: undefined,
},
});
this.#interceptPhase = phase;
this.#emitEventsIfReady();
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-failRequest */
async failRequest(networkId, errorReason) {
await this.#cdpTarget.cdpClient.sendCommand('Fetch.failRequest', {
requestId: networkId,
errorReason,
});
this.#interceptPhase = undefined;
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest */
async continueRequest(cdpFetchRequestId, url, method, headers) {
// TODO: Expand.
await this.#cdpTarget.cdpClient.sendCommand('Fetch.continueRequest', {
requestId: cdpFetchRequestId,
url,
method,
headers,
// TODO: Set?
// postData:,
// interceptResponse:,
});
this.#interceptPhase = undefined;
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueResponse */
async continueResponse(cdpFetchRequestId, responseCode, responsePhrase, responseHeaders) {
await this.#cdpTarget.cdpClient.sendCommand('Fetch.continueResponse', {
requestId: cdpFetchRequestId,
responseCode,
responsePhrase,
responseHeaders,
});
this.#interceptPhase = undefined;
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueWithAuth */
async continueWithAuth(cdpFetchRequestId, response, username, password) {
await this.#cdpTarget.cdpClient.sendCommand('Fetch.continueWithAuth', {
requestId: cdpFetchRequestId,
authChallengeResponse: {
response,
username,
password,
},
});
this.#interceptPhase = undefined;
}
/** @see https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-provideResponse */
async provideResponse(cdpFetchRequestId, responseCode, responsePhrase, responseHeaders, body) {
await this.#cdpTarget.cdpClient.sendCommand('Fetch.fulfillRequest', {
requestId: cdpFetchRequestId,
responseCode,
responsePhrase,
responseHeaders,
...(body ? { body: btoa(body) } : {}), // TODO: Double-check if btoa usage is correct.
});
this.#interceptPhase = undefined;
}
dispose() {
const result = {
kind: 'error',
error: new Error('Network processor detached'),
};
this.#beforeRequestSentDeferred.resolve(result);
this.#responseStartedDeferred.resolve(result);
this.#responseCompletedDeferred.resolve(result);
}
get #context() {
return this.#request.info?.frameId ?? null;
}
/** Returns the HTTP status code associated with this request if any. */
get statusCode() {
return (this.#response.info?.status ?? this.#response.extraInfo?.statusCode ?? -1 // TODO: Throw an exception or use some other status code?
);
}
#getBaseEventParams(phase) {
// TODO: Set this in terms of intercepts?
const isBlocked = phase !== undefined && phase === this.#interceptPhase;
const intercepts = this.#networkStorage.getNetworkIntercepts(this.#requestId, phase);
return {
isBlocked,
context: this.#context,
navigation: this.#getNavigationId(),
redirectCount: this.#redirectCount,
request: this.#getRequestData(),
// Timestamp should be in milliseconds, while CDP provides it in seconds.
timestamp: Math.round((this.#request.info?.wallTime ?? 0) * 1000),
// XXX: we should return correct types from the function.
intercepts: isBlocked ? intercepts : undefined,
};
}
#getNavigationId() {
if (!this.#request.info ||
!this.#request.info.loaderId ||
// When we navigate all CDP network events have `loaderId`
// CDP's `loaderId` and `requestId` match when
// that request triggered the loading
this.#request.info.loaderId !== this.#request.info.requestId) {
return null;
}
return this.#request.info.loaderId;
}
#getRequestData() {
const cookies = this.#request.extraInfo
? NetworkRequest.#getCookies(this.#request.extraInfo.associatedCookies)
: [];
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#request.info?.request.headers);
return {
request: this.#request.info?.requestId ?? NetworkRequest.#unknown,
url: this.#request.info?.request.url ?? NetworkRequest.#unknown,
method: this.#request.info?.request.method ?? NetworkRequest.#unknown,
headers,
cookies,
headersSize: (0, NetworkUtils_js_1.computeHeadersSize)(headers),
// TODO: implement.
bodySize: 0,
timings: this.#getTimings(),
};
}
// TODO: implement.
#getTimings() {
return {
timeOrigin: 0,
requestTime: 0,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 0,
dnsStart: 0,
dnsEnd: 0,
connectStart: 0,
connectEnd: 0,
tlsStart: 0,
requestStart: 0,
responseStart: 0,
responseEnd: 0,
};
}
#queueBeforeRequestSentEvent() {
if (this.#isIgnoredEvent()) {
return;
}
this.#eventManager.registerPromiseEvent(this.#beforeRequestSentDeferred.then((result) => {
if (result.kind === 'success') {
try {
return {
kind: 'success',
value: Object.assign(this.#getBeforeRequestEvent(), {
type: 'event',
}),
};
}
catch (error) {
return {
kind: 'error',
error: error instanceof Error ? error : new Error('Unknown'),
};
}
}
return result;
}), this.#context, protocol_js_1.ChromiumBidi.Network.EventNames.BeforeRequestSent);
}
#getBeforeRequestEvent() {
(0, assert_js_1.assert)(this.#request.info, 'RequestWillBeSentEvent is not set');
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.BeforeRequestSent,
params: {
...this.#getBaseEventParams("beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */),
initiator: {
type: NetworkRequest.#getInitiatorType(this.#request.info.initiator.type),
},
},
};
}
#queueResponseStartedEvent() {
if (this.#isIgnoredEvent()) {
return;
}
this.#eventManager.registerPromiseEvent(this.#responseStartedDeferred.then((result) => {
if (result.kind === 'success') {
try {
return {
kind: 'success',
value: Object.assign(this.#getResponseStartedEvent(), {
type: 'event',
}),
};
}
catch (error) {
return {
kind: 'error',
error: error instanceof Error ? error : new Error('Unknown'),
};
}
}
return result;
}), this.#context, protocol_js_1.ChromiumBidi.Network.EventNames.ResponseStarted);
}
#getResponseStartedEvent() {
(0, assert_js_1.assert)(this.#request.info, 'RequestWillBeSentEvent is not set');
(0, assert_js_1.assert)(this.#response.info, 'ResponseReceivedEvent is not set');
// Chromium sends wrong extraInfo events for responses served from cache.
// See https://github.com/puppeteer/puppeteer/issues/9965 and
// https://crbug.com/1340398.
if (this.#response.info.fromDiskCache) {
this.#response.extraInfo = undefined;
}
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#response.info.headers);
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.ResponseStarted,
params: {
...this.#getBaseEventParams(),
response: {
url: this.#response.info.url ?? NetworkRequest.#unknown,
protocol: this.#response.info.protocol ?? '',
status: this.statusCode,
statusText: this.#response.info.statusText,
fromCache: this.#response.info.fromDiskCache ||
this.#response.info.fromPrefetchCache ||
this.#servedFromCache,
headers,
mimeType: this.#response.info.mimeType,
bytesReceived: this.#response.info.encodedDataLength,
headersSize: (0, NetworkUtils_js_1.computeHeadersSize)(headers),
// TODO: consider removing from spec.
bodySize: 0,
content: {
// TODO: consider removing from spec.
size: 0,
},
},
},
};
}
#queueResponseCompletedEvent() {
if (this.#isIgnoredEvent()) {
return;
}
this.#eventManager.registerPromiseEvent(this.#responseCompletedDeferred.then((result) => {
if (result.kind === 'success') {
try {
return {
kind: 'success',
value: Object.assign(this.#getResponseReceivedEvent(), {
type: 'event',
}),
};
}
catch (error) {
return {
kind: 'error',
error: error instanceof Error ? error : new Error('Unknown'),
};
}
}
return result;
}), this.#context, protocol_js_1.ChromiumBidi.Network.EventNames.ResponseCompleted);
}
#getResponseReceivedEvent() {
(0, assert_js_1.assert)(this.#request.info, 'RequestWillBeSentEvent is not set');
(0, assert_js_1.assert)(this.#response.info, 'ResponseReceivedEvent is not set');
// Chromium sends wrong extraInfo events for responses served from cache.
// See https://github.com/puppeteer/puppeteer/issues/9965 and
// https://crbug.com/1340398.
if (this.#response.info.fromDiskCache) {
this.#response.extraInfo = undefined;
}
const headers = (0, NetworkUtils_js_1.bidiNetworkHeadersFromCdpNetworkHeaders)(this.#response.info.headers);
return {
method: protocol_js_1.ChromiumBidi.Network.EventNames.ResponseCompleted,
params: {
...this.#getBaseEventParams(),
response: {
url: this.#response.info.url ?? NetworkRequest.#unknown,
protocol: this.#response.info.protocol ?? '',
status: this.statusCode,
statusText: this.#response.info.statusText,
fromCache: this.#response.info.fromDiskCache ||
this.#response.info.fromPrefetchCache ||
this.#servedFromCache,
headers,
mimeType: this.#response.info.mimeType,
bytesReceived: this.#response.info.encodedDataLength,
headersSize: (0, NetworkUtils_js_1.computeHeadersSize)(headers),
// TODO: consider removing from spec.
bodySize: 0,
content: {
// TODO: consider removing from spec.
size: 0,
},
},
},
};
}
#isIgnoredEvent() {
return this.#request.info?.request.url.endsWith('/favicon.ico') ?? false;
}
static #getInitiatorType(initiatorType) {
switch (initiatorType) {
case 'parser':
case 'script':
case 'preflight':
return initiatorType;
default:
return 'other';
}
}
static #getCookies(associatedCookies) {
return associatedCookies
.filter(({ blockedReasons }) => {
return !Array.isArray(blockedReasons) || blockedReasons.length === 0;
})
.map(({ cookie }) => (0, NetworkUtils_js_1.cdpToBiDiCookie)(cookie));
}
}
exports.NetworkRequest = NetworkRequest;
//# sourceMappingURL=NetworkRequest.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,84 @@
/**
* Copyright 2023 Google LLC.
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Protocol } from 'devtools-protocol';
import { Network } from '../../../protocol/protocol.js';
import type { NetworkRequest } from './NetworkRequest.js';
interface NetworkInterception {
urlPatterns: Network.UrlPattern[];
phases: Network.AddInterceptParameters['phases'];
}
export interface BlockedRequest {
request: Protocol.Fetch.RequestId;
phase: Network.InterceptPhase;
response: Network.ResponseData;
}
/** Stores network and intercept maps. */
export declare class NetworkStorage {
#private;
disposeRequestMap(): void;
/**
* Adds the given entry to the intercept map.
* URL patterns are assumed to be parsed.
*
* @return The intercept ID.
*/
addIntercept(value: NetworkInterception): Network.Intercept;
/**
* Removes the given intercept from the intercept map.
* Throws NoSuchInterceptException if the intercept does not exist.
*/
removeIntercept(intercept: Network.Intercept): void;
/** Returns true if there's at least one added intercept. */
hasIntercepts(): boolean;
/** Gets parameters for CDP 'Fetch.enable' command from the intercept map. */
getFetchEnableParams(): Protocol.Fetch.EnableRequest;
getRequest(id: Network.Request): NetworkRequest | undefined;
addRequest(request: NetworkRequest): void;
deleteRequest(id: Network.Request): void;
/** Returns true if there's at least one network request. */
hasNetworkRequests(): boolean;
/** Returns true if there's at least one blocked network request. */
hasBlockedRequests(): boolean;
/** Converts a URL pattern from the spec to a CDP URL pattern. */
static cdpFromSpecUrlPattern(urlPattern: Network.UrlPattern): string;
static buildUrlPatternString({ protocol, hostname, port, pathname, search, }: Network.UrlPatternPattern): string;
/**
* Maps spec Network.InterceptPhase to CDP Fetch.RequestStage.
* AuthRequired has no CDP equivalent..
*/
static requestStageFromPhase(phase: Network.InterceptPhase): Protocol.Fetch.RequestStage;
/**
* Returns true if the given protocol is special.
* Special protocols are those that have a default port.
*
* Example inputs: 'http', 'http:'
*
* @see https://url.spec.whatwg.org/#special-scheme
*/
static isSpecialScheme(protocol: string): boolean;
addBlockedRequest(requestId: Network.Request, value: BlockedRequest): void;
removeBlockedRequest(requestId: Network.Request): void;
/**
* Returns the blocked request associated with the given network ID, if any.
*/
getBlockedRequest(networkId: Network.Request): BlockedRequest | undefined;
/** #@see https://w3c.github.io/webdriver-bidi/#get-the-network-intercepts */
getNetworkIntercepts(requestId: Network.Request, phase?: Network.InterceptPhase): Network.Intercept[];
/** Matches the given URLPattern against the given URL. */
static matchUrlPattern(urlPattern: Network.UrlPattern, url: string | undefined): boolean;
}
export {};

View File

@@ -0,0 +1,217 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkStorage = void 0;
const protocol_js_1 = require("../../../protocol/protocol.js");
const UrlPattern_js_1 = require("../../../utils/UrlPattern.js");
const uuid_js_1 = require("../../../utils/uuid.js");
/** Stores network and intercept maps. */
class NetworkStorage {
/**
* A map from network request ID to Network Request objects.
* Needed as long as information about requests comes from different events.
*/
#requestMap = new Map();
/** A map from intercept ID to track active network intercepts. */
#interceptMap = new Map();
/** A map from network request ID to track actively blocked requests. */
#blockedRequestMap = new Map();
disposeRequestMap() {
for (const request of this.#requestMap.values()) {
request.dispose();
}
this.#requestMap.clear();
}
/**
* Adds the given entry to the intercept map.
* URL patterns are assumed to be parsed.
*
* @return The intercept ID.
*/
addIntercept(value) {
const interceptId = (0, uuid_js_1.uuidv4)();
this.#interceptMap.set(interceptId, value);
return interceptId;
}
/**
* Removes the given intercept from the intercept map.
* Throws NoSuchInterceptException if the intercept does not exist.
*/
removeIntercept(intercept) {
if (!this.#interceptMap.has(intercept)) {
throw new protocol_js_1.NoSuchInterceptException(`Intercept '${intercept}' does not exist.`);
}
this.#interceptMap.delete(intercept);
}
/** Returns true if there's at least one added intercept. */
hasIntercepts() {
return this.#interceptMap.size > 0;
}
/** Gets parameters for CDP 'Fetch.enable' command from the intercept map. */
getFetchEnableParams() {
const patterns = [];
for (const value of this.#interceptMap.values()) {
for (const phase of value.phases) {
const requestStage = NetworkStorage.requestStageFromPhase(phase);
if (value.urlPatterns.length === 0) {
patterns.push({
urlPattern: '*',
requestStage,
});
continue;
}
for (const urlPatternSpec of value.urlPatterns) {
const urlPattern = NetworkStorage.cdpFromSpecUrlPattern(urlPatternSpec);
patterns.push({
urlPattern,
requestStage,
});
}
}
}
return {
patterns,
// If there's at least one intercept that requires auth, enable the
// 'Fetch.authRequired' event.
handleAuthRequests: [...this.#interceptMap.values()].some((param) => {
return param.phases.includes("authRequired" /* Network.InterceptPhase.AuthRequired */);
}),
};
}
getRequest(id) {
return this.#requestMap.get(id);
}
addRequest(request) {
this.#requestMap.set(request.requestId, request);
}
deleteRequest(id) {
const request = this.#requestMap.get(id);
if (request) {
request.dispose();
this.#requestMap.delete(id);
}
}
/** Returns true if there's at least one network request. */
hasNetworkRequests() {
return this.#requestMap.size > 0;
}
/** Returns true if there's at least one blocked network request. */
hasBlockedRequests() {
return this.#blockedRequestMap.size > 0;
}
/** Converts a URL pattern from the spec to a CDP URL pattern. */
static cdpFromSpecUrlPattern(urlPattern) {
switch (urlPattern.type) {
case 'string':
return urlPattern.pattern;
case 'pattern':
return NetworkStorage.buildUrlPatternString(urlPattern);
}
}
static buildUrlPatternString({ protocol, hostname, port, pathname, search, }) {
if (!protocol && !hostname && !port && !pathname && !search) {
return '*';
}
let url = '';
if (protocol) {
url += protocol;
if (!protocol.endsWith(':')) {
url += ':';
}
if (NetworkStorage.isSpecialScheme(protocol)) {
url += '//';
}
}
if (hostname) {
url += hostname;
}
if (port) {
url += `:${port}`;
}
if (pathname) {
if (!pathname.startsWith('/')) {
url += '/';
}
url += pathname;
}
if (search) {
if (!search.startsWith('?')) {
url += '?';
}
url += search;
}
return url;
}
/**
* Maps spec Network.InterceptPhase to CDP Fetch.RequestStage.
* AuthRequired has no CDP equivalent..
*/
static requestStageFromPhase(phase) {
switch (phase) {
case "beforeRequestSent" /* Network.InterceptPhase.BeforeRequestSent */:
return 'Request';
case "responseStarted" /* Network.InterceptPhase.ResponseStarted */:
case "authRequired" /* Network.InterceptPhase.AuthRequired */:
return 'Response';
}
}
/**
* Returns true if the given protocol is special.
* Special protocols are those that have a default port.
*
* Example inputs: 'http', 'http:'
*
* @see https://url.spec.whatwg.org/#special-scheme
*/
static isSpecialScheme(protocol) {
return ['ftp', 'file', 'http', 'https', 'ws', 'wss'].includes(protocol.replace(/:$/, ''));
}
addBlockedRequest(requestId, value) {
this.#blockedRequestMap.set(requestId, value);
}
removeBlockedRequest(requestId) {
this.#blockedRequestMap.delete(requestId);
}
/**
* Returns the blocked request associated with the given network ID, if any.
*/
getBlockedRequest(networkId) {
return this.#blockedRequestMap.get(networkId);
}
/** #@see https://w3c.github.io/webdriver-bidi/#get-the-network-intercepts */
getNetworkIntercepts(requestId, phase) {
const request = this.#requestMap.get(requestId);
if (!request) {
return [];
}
const interceptIds = [];
for (const [interceptId, { phases, urlPatterns },] of this.#interceptMap.entries()) {
if (phase && phases.includes(phase)) {
if (urlPatterns.length === 0) {
interceptIds.push(interceptId);
}
else if (urlPatterns.some((urlPattern) => NetworkStorage.matchUrlPattern(urlPattern, request.url))) {
interceptIds.push(interceptId);
}
}
}
return interceptIds;
}
/** Matches the given URLPattern against the given URL. */
static matchUrlPattern(urlPattern, url) {
switch (urlPattern.type) {
case 'string':
return urlPattern.pattern === url;
case 'pattern': {
return (new UrlPattern_js_1.URLPattern({
protocol: urlPattern.protocol,
hostname: urlPattern.hostname,
port: urlPattern.port,
pathname: urlPattern.pathname,
search: urlPattern.search,
}).exec(url) !== null);
}
}
}
}
exports.NetworkStorage = NetworkStorage;
//# sourceMappingURL=NetworkStorage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
/**
* @fileoverview Utility functions for the Network domain.
*/
import type { Protocol } from 'devtools-protocol';
import { Network, type Storage } from '../../../protocol/protocol.js';
export declare function computeHeadersSize(headers: Network.Header[]): number;
/** Converts from CDP Network domain headers to Bidi network headers. */
export declare function bidiNetworkHeadersFromCdpNetworkHeaders(headers?: Protocol.Network.Headers): Network.Header[];
/** Converts from Bidi network headers to CDP Network domain headers. */
export declare function cdpNetworkHeadersFromBidiNetworkHeaders(headers?: Network.Header[]): Protocol.Network.Headers | undefined;
/** Converts from CDP Fetch domain header entries to Bidi network headers. */
export declare function bidiNetworkHeadersFromCdpFetchHeaders(headers?: Protocol.Fetch.HeaderEntry[]): Network.Header[];
/** Converts from Bidi network headers to CDP Fetch domain header entries. */
export declare function cdpFetchHeadersFromBidiNetworkHeaders(headers?: Network.Header[]): Protocol.Fetch.HeaderEntry[] | undefined;
/** Converts from Bidi auth action to CDP auth challenge response. */
export declare function cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction(action: 'default' | 'cancel' | 'provideCredentials'): "Default" | "CancelAuth" | "ProvideCredentials";
/**
* Converts from CDP Network domain cookie to BiDi network cookie.
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Cookie
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
*/
export declare function cdpToBiDiCookie(cookie: Protocol.Network.Cookie): Network.Cookie;
/**
* Converts from BiDi set network cookie params to CDP Network domain cookie.
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-CookieParam
*/
export declare function bidiToCdpCookie(params: Storage.SetCookieParameters, partitionKey: Storage.PartitionKey): Protocol.Network.CookieParam;

View File

@@ -0,0 +1,199 @@
"use strict";
/*
* Copyright 2023 Google LLC.
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.bidiToCdpCookie = exports.cdpToBiDiCookie = exports.cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction = exports.cdpFetchHeadersFromBidiNetworkHeaders = exports.bidiNetworkHeadersFromCdpFetchHeaders = exports.cdpNetworkHeadersFromBidiNetworkHeaders = exports.bidiNetworkHeadersFromCdpNetworkHeaders = exports.computeHeadersSize = void 0;
const ErrorResponse_1 = require("../../../protocol/ErrorResponse");
function computeHeadersSize(headers) {
const requestHeaders = headers.reduce((acc, header) => {
return `${acc}${header.name}: ${header.value.value}\r\n`;
}, '');
return new TextEncoder().encode(requestHeaders).length;
}
exports.computeHeadersSize = computeHeadersSize;
/** Converts from CDP Network domain headers to Bidi network headers. */
function bidiNetworkHeadersFromCdpNetworkHeaders(headers) {
if (!headers) {
return [];
}
return Object.entries(headers).map(([name, value]) => ({
name,
value: {
type: 'string',
value,
},
}));
}
exports.bidiNetworkHeadersFromCdpNetworkHeaders = bidiNetworkHeadersFromCdpNetworkHeaders;
/** Converts from Bidi network headers to CDP Network domain headers. */
function cdpNetworkHeadersFromBidiNetworkHeaders(headers) {
if (headers === undefined) {
return undefined;
}
return headers.reduce((result, header) => {
// TODO: Distinguish between string and bytes?
result[header.name] = header.value.value;
return result;
}, {});
}
exports.cdpNetworkHeadersFromBidiNetworkHeaders = cdpNetworkHeadersFromBidiNetworkHeaders;
/** Converts from CDP Fetch domain header entries to Bidi network headers. */
function bidiNetworkHeadersFromCdpFetchHeaders(headers) {
if (!headers) {
return [];
}
return headers.map(({ name, value }) => ({
name,
value: {
type: 'string',
value,
},
}));
}
exports.bidiNetworkHeadersFromCdpFetchHeaders = bidiNetworkHeadersFromCdpFetchHeaders;
/** Converts from Bidi network headers to CDP Fetch domain header entries. */
function cdpFetchHeadersFromBidiNetworkHeaders(headers) {
if (headers === undefined) {
return undefined;
}
return headers.map(({ name, value }) => ({
name,
value: value.value,
}));
}
exports.cdpFetchHeadersFromBidiNetworkHeaders = cdpFetchHeadersFromBidiNetworkHeaders;
/** Converts from Bidi auth action to CDP auth challenge response. */
function cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction(action) {
switch (action) {
case 'default':
return 'Default';
case 'cancel':
return 'CancelAuth';
case 'provideCredentials':
return 'ProvideCredentials';
}
}
exports.cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction = cdpAuthChallengeResponseFromBidiAuthContinueWithAuthAction;
/**
* Converts from CDP Network domain cookie to BiDi network cookie.
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Cookie
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
*/
function cdpToBiDiCookie(cookie) {
const result = {
name: cookie.name,
value: { type: 'string', value: cookie.value },
domain: cookie.domain,
path: cookie.path,
size: cookie.size,
httpOnly: cookie.httpOnly,
secure: cookie.secure,
sameSite: cookie.sameSite === undefined
? "none" /* Network.SameSite.None */
: sameSiteCdpToBiDi(cookie.sameSite),
...(cookie.expires >= 0 ? { expiry: cookie.expires } : undefined),
};
// Extending with CDP-specific properties with `goog:` prefix.
result[`goog:session`] = cookie.session;
result[`goog:priority`] = cookie.priority;
result[`goog:sameParty`] = cookie.sameParty;
result[`goog:sourceScheme`] = cookie.sourceScheme;
result[`goog:sourcePort`] = cookie.sourcePort;
if (cookie.partitionKey !== undefined) {
result[`goog:partitionKey`] = cookie.partitionKey;
}
if (cookie.partitionKeyOpaque !== undefined) {
result[`goog:partitionKeyOpaque`] = cookie.partitionKeyOpaque;
}
return result;
}
exports.cdpToBiDiCookie = cdpToBiDiCookie;
/**
* Converts from BiDi set network cookie params to CDP Network domain cookie.
* * https://w3c.github.io/webdriver-bidi/#type-network-Cookie
* * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-CookieParam
*/
function bidiToCdpCookie(params, partitionKey) {
if (params.cookie.value.type !== 'string') {
// CDP supports only string values in cookies.
throw new ErrorResponse_1.UnsupportedOperationException('Only string cookie values are supported');
}
const deserializedValue = params.cookie.value.value;
const result = {
name: params.cookie.name,
value: deserializedValue,
domain: params.cookie.domain,
path: params.cookie.path ?? '/',
secure: params.cookie.secure ?? false,
httpOnly: params.cookie.httpOnly ?? false,
// CDP's `partitionKey` is the BiDi's `partition.sourceOrigin`.
...(partitionKey.sourceOrigin !== undefined && {
partitionKey: partitionKey.sourceOrigin,
}),
...(params.cookie.expiry !== undefined && {
expires: params.cookie.expiry,
}),
...(params.cookie.sameSite !== undefined && {
sameSite: sameSiteBiDiToCdp(params.cookie.sameSite),
}),
};
// Extending with CDP-specific properties with `goog:` prefix.
if (params.cookie[`goog:url`] !== undefined) {
result.url = params.cookie[`goog:url`];
}
if (params.cookie[`goog:priority`] !== undefined) {
result.priority = params.cookie[`goog:priority`];
}
if (params.cookie[`goog:sameParty`] !== undefined) {
result.sameParty = params.cookie[`goog:sameParty`];
}
if (params.cookie[`goog:sourceScheme`] !== undefined) {
result.sourceScheme = params.cookie[`goog:sourceScheme`];
}
if (params.cookie[`goog:sourcePort`] !== undefined) {
result.sourcePort = params.cookie[`goog:sourcePort`];
}
return result;
}
exports.bidiToCdpCookie = bidiToCdpCookie;
function sameSiteCdpToBiDi(sameSite) {
switch (sameSite) {
case 'Strict':
return "strict" /* Network.SameSite.Strict */;
case 'None':
return "none" /* Network.SameSite.None */;
case 'Lax':
return "lax" /* Network.SameSite.Lax */;
default:
// Defaults to `Lax`:
// https://web.dev/articles/samesite-cookies-explained#samesitelax_by_default
return "lax" /* Network.SameSite.Lax */;
}
}
function sameSiteBiDiToCdp(sameSite) {
switch (sameSite) {
case "strict" /* Network.SameSite.Strict */:
return 'Strict';
case "lax" /* Network.SameSite.Lax */:
return 'Lax';
case "none" /* Network.SameSite.None */:
return 'None';
}
throw new ErrorResponse_1.InvalidArgumentException(`Unknown 'sameSite' value ${sameSite}`);
}
//# sourceMappingURL=NetworkUtils.js.map

File diff suppressed because one or more lines are too long