2020-01-06 18:22:35 -08:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2019-11-26 11:23:13 -08:00
|
|
|
|
2019-11-27 12:44:12 -08:00
|
|
|
import * as frames from './frames';
|
2020-06-25 08:30:56 -07:00
|
|
|
import * as types from './types';
|
2020-08-24 06:51:51 -07:00
|
|
|
import { assert } from '../utils/utils';
|
2021-02-09 14:44:48 -08:00
|
|
|
import { SdkObject } from './instrumentation';
|
2019-11-27 12:44:12 -08:00
|
|
|
|
2020-06-25 08:30:56 -07:00
|
|
|
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
|
2019-11-26 11:23:13 -08:00
|
|
|
const parsedURLs = urls.map(s => new URL(s));
|
|
|
|
// Chromiums's cookies are missing sameSite when it is 'None'
|
|
|
|
return cookies.filter(c => {
|
2021-01-25 16:37:33 -08:00
|
|
|
// Firefox and WebKit can return cookies with empty values.
|
|
|
|
if (!c.value)
|
|
|
|
return false;
|
2019-11-26 11:23:13 -08:00
|
|
|
if (!parsedURLs.length)
|
|
|
|
return true;
|
|
|
|
for (const parsedURL of parsedURLs) {
|
2021-01-12 15:56:29 -08:00
|
|
|
let domain = c.domain;
|
|
|
|
if (!domain.startsWith('.'))
|
|
|
|
domain = '.' + domain;
|
|
|
|
if (!('.' + parsedURL.hostname).endsWith(domain))
|
2019-11-26 11:23:13 -08:00
|
|
|
continue;
|
|
|
|
if (!parsedURL.pathname.startsWith(c.path))
|
|
|
|
continue;
|
2021-02-19 11:50:59 -08:00
|
|
|
if (parsedURL.protocol !== 'https:' && c.secure)
|
2019-11-26 11:23:13 -08:00
|
|
|
continue;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
2019-11-27 12:44:12 -08:00
|
|
|
|
2020-06-25 08:30:56 -07:00
|
|
|
export function rewriteCookies(cookies: types.SetNetworkCookieParam[]): types.SetNetworkCookieParam[] {
|
2019-12-02 16:36:46 -08:00
|
|
|
return cookies.map(c => {
|
2019-12-03 10:51:41 -08:00
|
|
|
assert(c.name, 'Cookie should have a name');
|
|
|
|
assert(c.value, 'Cookie should have a value');
|
|
|
|
assert(c.url || (c.domain && c.path), 'Cookie should have a url or a domain/path pair');
|
|
|
|
assert(!(c.url && c.domain), 'Cookie should have either url or domain');
|
|
|
|
assert(!(c.url && c.path), 'Cookie should have either url or domain');
|
2019-12-02 16:36:46 -08:00
|
|
|
const copy = {...c};
|
|
|
|
if (copy.url) {
|
|
|
|
assert(copy.url !== 'about:blank', `Blank page can not have cookie "${c.name}"`);
|
|
|
|
assert(!copy.url.startsWith('data:'), `Data URL page can not have cookie "${c.name}"`);
|
|
|
|
const url = new URL(copy.url);
|
|
|
|
copy.domain = url.hostname;
|
|
|
|
copy.path = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
|
|
|
|
copy.secure = url.protocol === 'https:';
|
|
|
|
}
|
|
|
|
return copy;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-25 14:49:51 -08:00
|
|
|
export function parsedURL(url: string): URL | null {
|
|
|
|
try {
|
|
|
|
return new URL(url);
|
|
|
|
} catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function stripFragmentFromUrl(url: string): string {
|
|
|
|
if (!url.includes('#'))
|
2019-12-18 17:27:20 -07:00
|
|
|
return url;
|
2021-01-25 14:49:51 -08:00
|
|
|
return url.substring(0, url.indexOf('#'));
|
2019-12-18 17:27:20 -07:00
|
|
|
}
|
|
|
|
|
2021-08-27 20:42:45 +02:00
|
|
|
type RequestSizes = {
|
|
|
|
responseBodySize: number;
|
|
|
|
transferSize: number;
|
|
|
|
};
|
|
|
|
|
2021-02-09 09:00:00 -08:00
|
|
|
export class Request extends SdkObject {
|
2019-12-11 16:19:37 -08:00
|
|
|
private _response: Response | null = null;
|
2020-03-16 13:31:06 -07:00
|
|
|
private _redirectedFrom: Request | null;
|
|
|
|
private _redirectedTo: Request | null = null;
|
2019-12-14 08:20:51 -08:00
|
|
|
readonly _documentId?: string;
|
2020-01-17 17:14:39 -08:00
|
|
|
readonly _isFavicon: boolean;
|
2020-06-26 11:51:47 -07:00
|
|
|
_failureText: string | null = null;
|
2019-11-27 12:44:12 -08:00
|
|
|
private _url: string;
|
|
|
|
private _resourceType: string;
|
|
|
|
private _method: string;
|
2020-07-22 15:59:37 -07:00
|
|
|
private _postData: Buffer | null;
|
2020-08-18 15:38:29 -07:00
|
|
|
private _headers: types.HeadersArray;
|
2020-10-26 14:32:07 -07:00
|
|
|
private _headersMap = new Map<string, string>();
|
2020-03-10 11:39:35 -07:00
|
|
|
private _frame: frames.Frame;
|
2020-03-13 08:54:19 -07:00
|
|
|
private _waitForResponsePromise: Promise<Response | null>;
|
|
|
|
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
|
2020-10-21 23:25:57 -07:00
|
|
|
_responseEndTiming = -1;
|
2021-08-27 20:42:45 +02:00
|
|
|
_sizes: RequestSizes = { responseBodySize: 0, transferSize: 0 };
|
2019-11-27 12:44:12 -08:00
|
|
|
|
2021-08-05 08:49:02 -07:00
|
|
|
constructor(frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
|
2020-08-18 15:38:29 -07:00
|
|
|
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) {
|
2021-04-20 23:03:56 -07:00
|
|
|
super(frame, 'request');
|
2020-01-16 16:58:02 -08:00
|
|
|
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
|
2019-11-27 12:44:12 -08:00
|
|
|
this._frame = frame;
|
2020-03-16 13:31:06 -07:00
|
|
|
this._redirectedFrom = redirectedFrom;
|
|
|
|
if (redirectedFrom)
|
|
|
|
redirectedFrom._redirectedTo = this;
|
2019-12-14 08:20:51 -08:00
|
|
|
this._documentId = documentId;
|
2019-12-18 17:27:20 -07:00
|
|
|
this._url = stripFragmentFromUrl(url);
|
2019-11-27 12:44:12 -08:00
|
|
|
this._resourceType = resourceType;
|
|
|
|
this._method = method;
|
|
|
|
this._postData = postData;
|
|
|
|
this._headers = headers;
|
2020-10-26 14:32:07 -07:00
|
|
|
for (const { name, value } of this._headers)
|
|
|
|
this._headersMap.set(name.toLowerCase(), value);
|
2019-12-11 16:19:37 -08:00
|
|
|
this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f);
|
2020-01-17 17:14:39 -08:00
|
|
|
this._isFavicon = url.endsWith('/favicon.ico');
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2019-12-16 16:32:04 -08:00
|
|
|
_setFailureText(failureText: string) {
|
2019-11-27 12:44:12 -08:00
|
|
|
this._failureText = failureText;
|
2020-03-13 08:54:19 -07:00
|
|
|
this._waitForResponsePromiseCallback(null);
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
url(): string {
|
|
|
|
return this._url;
|
|
|
|
}
|
|
|
|
|
|
|
|
resourceType(): string {
|
|
|
|
return this._resourceType;
|
|
|
|
}
|
|
|
|
|
|
|
|
method(): string {
|
|
|
|
return this._method;
|
|
|
|
}
|
|
|
|
|
2020-07-22 15:59:37 -07:00
|
|
|
postDataBuffer(): Buffer | null {
|
2019-11-27 12:44:12 -08:00
|
|
|
return this._postData;
|
|
|
|
}
|
|
|
|
|
2020-08-18 15:38:29 -07:00
|
|
|
headers(): types.HeadersArray {
|
|
|
|
return this._headers;
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2020-10-26 14:32:07 -07:00
|
|
|
headerValue(name: string): string | undefined {
|
|
|
|
return this._headersMap.get(name);
|
|
|
|
}
|
|
|
|
|
2020-03-13 08:54:19 -07:00
|
|
|
response(): Promise<Response | null> {
|
|
|
|
return this._waitForResponsePromise;
|
2019-12-11 17:46:26 -08:00
|
|
|
}
|
|
|
|
|
2020-03-13 08:54:19 -07:00
|
|
|
_existingResponse(): Response | null {
|
|
|
|
return this._response;
|
2019-12-11 16:19:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
_setResponse(response: Response) {
|
|
|
|
this._response = response;
|
|
|
|
this._waitForResponsePromiseCallback(response);
|
|
|
|
}
|
|
|
|
|
2020-03-16 13:31:06 -07:00
|
|
|
_finalRequest(): Request {
|
|
|
|
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
|
|
|
|
}
|
|
|
|
|
2020-03-10 11:39:35 -07:00
|
|
|
frame(): frames.Frame {
|
2019-11-27 12:44:12 -08:00
|
|
|
return this._frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
isNavigationRequest(): boolean {
|
2019-12-14 08:20:51 -08:00
|
|
|
return !!this._documentId;
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2020-03-16 13:31:06 -07:00
|
|
|
redirectedFrom(): Request | null {
|
|
|
|
return this._redirectedFrom;
|
|
|
|
}
|
|
|
|
|
2020-06-26 11:51:47 -07:00
|
|
|
failure(): { errorText: string } | null {
|
2019-12-30 14:05:28 -08:00
|
|
|
if (this._failureText === null)
|
2019-11-27 12:44:12 -08:00
|
|
|
return null;
|
|
|
|
return {
|
|
|
|
errorText: this._failureText
|
|
|
|
};
|
|
|
|
}
|
2019-12-30 14:05:28 -08:00
|
|
|
|
2020-10-28 13:46:05 -07:00
|
|
|
updateWithRawHeaders(headers: types.HeadersArray) {
|
2020-10-22 08:49:16 -07:00
|
|
|
this._headers = headers;
|
2020-10-26 14:32:07 -07:00
|
|
|
this._headersMap.clear();
|
|
|
|
for (const { name, value } of this._headers)
|
|
|
|
this._headersMap.set(name.toLowerCase(), value);
|
2020-10-28 15:58:45 -07:00
|
|
|
if (!this._headersMap.has('host')) {
|
|
|
|
const host = new URL(this._url).host;
|
|
|
|
this._headers.push({ name: 'host', value: host });
|
|
|
|
this._headersMap.set('host', host);
|
|
|
|
}
|
2020-10-22 08:49:16 -07:00
|
|
|
}
|
2021-08-27 20:42:45 +02:00
|
|
|
|
|
|
|
bodySize(): number {
|
|
|
|
return this.postDataBuffer()?.length || 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
headersSize(): number {
|
|
|
|
if (!this._response)
|
|
|
|
return 0;
|
|
|
|
let headersSize = 4; // 4 = 2 spaces + 2 line breaks (GET /path \r\n)
|
|
|
|
headersSize += this.method().length;
|
|
|
|
headersSize += (new URL(this.url())).pathname.length;
|
|
|
|
headersSize += 8; // httpVersion
|
|
|
|
for (const header of this._headers)
|
|
|
|
headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
|
|
|
|
return headersSize;
|
|
|
|
}
|
2021-08-27 22:53:57 +02:00
|
|
|
|
|
|
|
sizes() {
|
|
|
|
return {
|
|
|
|
requestBodySize: this.bodySize(),
|
|
|
|
requestHeadersSize: this.headersSize(),
|
|
|
|
responseBodySize: this._sizes.responseBodySize,
|
|
|
|
responseHeadersSize: this._existingResponse()!.headersSize(),
|
|
|
|
responseTransferSize: this._sizes.transferSize,
|
|
|
|
};
|
|
|
|
}
|
2020-03-13 14:30:40 -07:00
|
|
|
}
|
|
|
|
|
2021-02-09 09:00:00 -08:00
|
|
|
export class Route extends SdkObject {
|
2020-03-13 14:30:40 -07:00
|
|
|
private readonly _request: Request;
|
|
|
|
private readonly _delegate: RouteDelegate;
|
|
|
|
private _handled = false;
|
2021-06-18 11:04:48 -07:00
|
|
|
private _response: InterceptedResponse | null = null;
|
2020-03-13 14:30:40 -07:00
|
|
|
|
|
|
|
constructor(request: Request, delegate: RouteDelegate) {
|
2021-04-20 23:03:56 -07:00
|
|
|
super(request.frame(), 'route');
|
2020-03-13 14:30:40 -07:00
|
|
|
this._request = request;
|
|
|
|
this._delegate = delegate;
|
|
|
|
}
|
|
|
|
|
|
|
|
request(): Request {
|
|
|
|
return this._request;
|
|
|
|
}
|
|
|
|
|
2019-12-30 14:05:28 -08:00
|
|
|
async abort(errorCode: string = 'failed') {
|
2020-03-13 14:30:40 -07:00
|
|
|
assert(!this._handled, 'Route is already handled!');
|
|
|
|
this._handled = true;
|
2020-02-05 16:53:36 -08:00
|
|
|
await this._delegate.abort(errorCode);
|
2019-12-30 14:05:28 -08:00
|
|
|
}
|
|
|
|
|
2021-08-24 11:07:54 -07:00
|
|
|
async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean, useInterceptedResponseBody?: boolean }) {
|
2020-03-13 14:30:40 -07:00
|
|
|
assert(!this._handled, 'Route is already handled!');
|
|
|
|
this._handled = true;
|
2021-06-18 11:04:48 -07:00
|
|
|
let body = overrides.body;
|
|
|
|
let isBase64 = overrides.isBase64 || false;
|
2021-08-11 14:47:05 -07:00
|
|
|
if (body === undefined) {
|
2021-08-24 11:07:54 -07:00
|
|
|
if (this._response && overrides.useInterceptedResponseBody) {
|
2021-08-11 14:47:05 -07:00
|
|
|
body = (await this._delegate.responseBody()).toString('utf8');
|
2021-06-18 11:04:48 -07:00
|
|
|
isBase64 = false;
|
|
|
|
} else {
|
|
|
|
body = '';
|
|
|
|
isBase64 = false;
|
|
|
|
}
|
|
|
|
}
|
2020-08-20 11:25:33 -07:00
|
|
|
await this._delegate.fulfill({
|
2021-08-24 11:07:54 -07:00
|
|
|
status: overrides.status || 200,
|
|
|
|
headers: overrides.headers || [],
|
2021-06-18 11:04:48 -07:00
|
|
|
body,
|
|
|
|
isBase64,
|
2020-08-20 11:25:33 -07:00
|
|
|
});
|
2019-12-30 14:05:28 -08:00
|
|
|
}
|
|
|
|
|
2021-06-18 11:04:48 -07:00
|
|
|
async continue(overrides: types.NormalizedContinueOverrides = {}): Promise<InterceptedResponse|null> {
|
2020-03-13 14:30:40 -07:00
|
|
|
assert(!this._handled, 'Route is already handled!');
|
2021-06-18 11:04:48 -07:00
|
|
|
assert(!this._response, 'Cannot call continue after response interception!');
|
2020-11-16 09:59:00 -08:00
|
|
|
if (overrides.url) {
|
|
|
|
const newUrl = new URL(overrides.url);
|
|
|
|
const oldUrl = new URL(this._request.url());
|
|
|
|
if (oldUrl.protocol !== newUrl.protocol)
|
2021-02-24 15:11:34 -08:00
|
|
|
throw new Error('New URL must have same protocol as overridden URL');
|
2020-11-16 09:59:00 -08:00
|
|
|
}
|
2021-08-05 08:49:02 -07:00
|
|
|
this._response = await this._delegate.continue(this._request, overrides);
|
2021-06-23 11:08:35 +02:00
|
|
|
return this._response;
|
2021-06-18 11:04:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async responseBody(): Promise<Buffer> {
|
|
|
|
assert(!this._handled, 'Route is already handled!');
|
2021-08-11 14:47:05 -07:00
|
|
|
return this._delegate.responseBody();
|
2019-12-30 14:05:28 -08:00
|
|
|
}
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2020-03-13 14:30:40 -07:00
|
|
|
export type RouteHandler = (route: Route, request: Request) => void;
|
|
|
|
|
2020-04-01 14:42:47 -07:00
|
|
|
type GetResponseBodyCallback = () => Promise<Buffer>;
|
2019-11-27 12:44:12 -08:00
|
|
|
|
2020-10-21 23:25:57 -07:00
|
|
|
export type ResourceTiming = {
|
|
|
|
startTime: number;
|
|
|
|
domainLookupStart: number;
|
|
|
|
domainLookupEnd: number;
|
|
|
|
connectStart: number;
|
|
|
|
secureConnectionStart: number;
|
|
|
|
connectEnd: number;
|
|
|
|
requestStart: number;
|
|
|
|
responseStart: number;
|
|
|
|
};
|
|
|
|
|
2021-06-15 00:48:08 -07:00
|
|
|
export type RemoteAddr = {
|
|
|
|
ipAddress: string;
|
|
|
|
port: number;
|
2021-06-23 11:08:35 +02:00
|
|
|
};
|
2021-06-15 00:48:08 -07:00
|
|
|
|
|
|
|
export type SecurityDetails = {
|
|
|
|
protocol?: string;
|
|
|
|
subjectName?: string;
|
|
|
|
issuer?: string;
|
|
|
|
validFrom?: number;
|
|
|
|
validTo?: number;
|
|
|
|
};
|
|
|
|
|
2021-02-09 09:00:00 -08:00
|
|
|
export class Response extends SdkObject {
|
2019-11-27 16:02:31 -08:00
|
|
|
private _request: Request;
|
2020-04-01 14:42:47 -07:00
|
|
|
private _contentPromise: Promise<Buffer> | null = null;
|
2020-07-21 14:40:53 -07:00
|
|
|
_finishedPromise: Promise<{ error?: string }>;
|
|
|
|
private _finishedPromiseCallback: (arg: { error?: string }) => void = () => {};
|
2019-11-27 12:44:12 -08:00
|
|
|
private _status: number;
|
|
|
|
private _statusText: string;
|
|
|
|
private _url: string;
|
2020-08-18 15:38:29 -07:00
|
|
|
private _headers: types.HeadersArray;
|
2020-10-26 14:32:07 -07:00
|
|
|
private _headersMap = new Map<string, string>();
|
2019-11-27 12:44:12 -08:00
|
|
|
private _getResponseBodyCallback: GetResponseBodyCallback;
|
2020-10-21 23:25:57 -07:00
|
|
|
private _timing: ResourceTiming;
|
2021-06-15 00:48:08 -07:00
|
|
|
private _serverAddrPromise: Promise<RemoteAddr|undefined>;
|
|
|
|
private _serverAddrPromiseCallback: (arg?: RemoteAddr) => void = () => {};
|
|
|
|
private _securityDetailsPromise: Promise<SecurityDetails|undefined>;
|
|
|
|
private _securityDetailsPromiseCallback: (arg?: SecurityDetails) => void = () => {};
|
2021-08-27 20:42:45 +02:00
|
|
|
private _httpVersion: string | undefined;
|
2019-11-27 12:44:12 -08:00
|
|
|
|
2021-07-08 18:22:37 +02:00
|
|
|
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback, httpVersion?: string) {
|
2021-04-20 23:03:56 -07:00
|
|
|
super(request.frame(), 'response');
|
2019-11-27 12:44:12 -08:00
|
|
|
this._request = request;
|
2020-10-21 23:25:57 -07:00
|
|
|
this._timing = timing;
|
2019-11-27 12:44:12 -08:00
|
|
|
this._status = status;
|
|
|
|
this._statusText = statusText;
|
|
|
|
this._url = request.url();
|
|
|
|
this._headers = headers;
|
2020-10-26 14:32:07 -07:00
|
|
|
for (const { name, value } of this._headers)
|
|
|
|
this._headersMap.set(name.toLowerCase(), value);
|
2019-11-27 12:44:12 -08:00
|
|
|
this._getResponseBodyCallback = getResponseBodyCallback;
|
2021-06-15 00:48:08 -07:00
|
|
|
this._serverAddrPromise = new Promise(f => {
|
|
|
|
this._serverAddrPromiseCallback = f;
|
|
|
|
});
|
|
|
|
this._securityDetailsPromise = new Promise(f => {
|
|
|
|
this._securityDetailsPromiseCallback = f;
|
|
|
|
});
|
2019-12-11 17:46:26 -08:00
|
|
|
this._finishedPromise = new Promise(f => {
|
|
|
|
this._finishedPromiseCallback = f;
|
2019-11-27 12:44:12 -08:00
|
|
|
});
|
2019-12-11 17:46:26 -08:00
|
|
|
this._request._setResponse(this);
|
2021-07-08 18:22:37 +02:00
|
|
|
this._httpVersion = httpVersion;
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2021-06-15 00:48:08 -07:00
|
|
|
_serverAddrFinished(addr?: RemoteAddr) {
|
|
|
|
this._serverAddrPromiseCallback(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
_securityDetailsFinished(securityDetails?: SecurityDetails) {
|
|
|
|
this._securityDetailsPromiseCallback(securityDetails);
|
|
|
|
}
|
|
|
|
|
2021-08-27 20:42:45 +02:00
|
|
|
_requestFinished(responseEndTiming: number, error?: string) {
|
2020-10-21 23:25:57 -07:00
|
|
|
this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);
|
2020-07-21 14:40:53 -07:00
|
|
|
this._finishedPromiseCallback({ error });
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2021-07-08 18:22:37 +02:00
|
|
|
_setHttpVersion(httpVersion: string) {
|
|
|
|
this._httpVersion = httpVersion;
|
|
|
|
}
|
|
|
|
|
2019-11-27 12:44:12 -08:00
|
|
|
url(): string {
|
|
|
|
return this._url;
|
|
|
|
}
|
|
|
|
|
|
|
|
status(): number {
|
|
|
|
return this._status;
|
|
|
|
}
|
|
|
|
|
|
|
|
statusText(): string {
|
|
|
|
return this._statusText;
|
|
|
|
}
|
|
|
|
|
2020-08-18 15:38:29 -07:00
|
|
|
headers(): types.HeadersArray {
|
|
|
|
return this._headers;
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
|
|
|
|
2020-10-26 14:32:07 -07:00
|
|
|
headerValue(name: string): string | undefined {
|
|
|
|
return this._headersMap.get(name);
|
|
|
|
}
|
|
|
|
|
2020-03-04 15:59:26 -08:00
|
|
|
finished(): Promise<Error | null> {
|
2020-07-21 14:40:53 -07:00
|
|
|
return this._finishedPromise.then(({ error }) => error ? new Error(error) : null);
|
2020-03-04 15:59:26 -08:00
|
|
|
}
|
|
|
|
|
2020-10-21 23:25:57 -07:00
|
|
|
timing(): ResourceTiming {
|
|
|
|
return this._timing;
|
|
|
|
}
|
|
|
|
|
2021-06-15 00:48:08 -07:00
|
|
|
async serverAddr(): Promise<RemoteAddr|null> {
|
|
|
|
return await this._serverAddrPromise || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
async securityDetails(): Promise<SecurityDetails|null> {
|
|
|
|
return await this._securityDetailsPromise || null;
|
|
|
|
}
|
|
|
|
|
2020-04-01 14:42:47 -07:00
|
|
|
body(): Promise<Buffer> {
|
2019-11-27 12:44:12 -08:00
|
|
|
if (!this._contentPromise) {
|
2020-07-21 14:40:53 -07:00
|
|
|
this._contentPromise = this._finishedPromise.then(async ({ error }) => {
|
2019-11-27 12:44:12 -08:00
|
|
|
if (error)
|
2020-07-21 14:40:53 -07:00
|
|
|
throw new Error(error);
|
2019-11-27 12:44:12 -08:00
|
|
|
return this._getResponseBodyCallback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return this._contentPromise;
|
|
|
|
}
|
|
|
|
|
2019-11-27 16:02:31 -08:00
|
|
|
request(): Request {
|
2019-11-27 12:44:12 -08:00
|
|
|
return this._request;
|
|
|
|
}
|
|
|
|
|
2020-03-10 11:39:35 -07:00
|
|
|
frame(): frames.Frame {
|
2019-11-27 12:44:12 -08:00
|
|
|
return this._request.frame();
|
|
|
|
}
|
2021-08-27 20:42:45 +02:00
|
|
|
|
|
|
|
transferSize(): number | undefined {
|
|
|
|
return this._request._sizes.transferSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
bodySize(): number {
|
|
|
|
return this._request._sizes.responseBodySize;
|
|
|
|
}
|
|
|
|
|
|
|
|
httpVersion(): string {
|
|
|
|
if (!this._httpVersion)
|
|
|
|
return 'HTTP/1.1';
|
|
|
|
if (this._httpVersion === 'http/1.1')
|
|
|
|
return 'HTTP/1.1';
|
|
|
|
return this._httpVersion;
|
|
|
|
}
|
|
|
|
|
|
|
|
headersSize(): number {
|
|
|
|
let headersSize = 4; // 4 = 2 spaces + 2 line breaks (HTTP/1.1 200 Ok\r\n)
|
|
|
|
headersSize += 8; // httpVersion;
|
|
|
|
headersSize += 3; // statusCode;
|
|
|
|
headersSize += this.statusText().length;
|
|
|
|
for (const header of this.headers())
|
|
|
|
headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
|
|
|
|
headersSize += 2; // '\r\n'
|
|
|
|
return headersSize;
|
|
|
|
}
|
2019-11-27 12:44:12 -08:00
|
|
|
}
|
2019-12-30 14:05:28 -08:00
|
|
|
|
2021-06-18 11:04:48 -07:00
|
|
|
export class InterceptedResponse extends SdkObject {
|
|
|
|
private readonly _request: Request;
|
|
|
|
private readonly _status: number;
|
|
|
|
private readonly _statusText: string;
|
|
|
|
private readonly _headers: types.HeadersArray;
|
|
|
|
|
|
|
|
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray) {
|
|
|
|
super(request.frame(), 'interceptedResponse');
|
2021-08-05 08:49:02 -07:00
|
|
|
this._request = request._finalRequest();
|
2021-06-18 11:04:48 -07:00
|
|
|
this._status = status;
|
|
|
|
this._statusText = statusText;
|
|
|
|
this._headers = headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
status(): number {
|
|
|
|
return this._status;
|
|
|
|
}
|
|
|
|
|
|
|
|
statusText(): string {
|
|
|
|
return this._statusText;
|
|
|
|
}
|
|
|
|
|
|
|
|
headers(): types.HeadersArray {
|
|
|
|
return this._headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
request(): Request {
|
|
|
|
return this._request;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-09 09:00:00 -08:00
|
|
|
export class WebSocket extends SdkObject {
|
2020-10-26 22:20:43 -07:00
|
|
|
private _url: string;
|
|
|
|
|
|
|
|
static Events = {
|
|
|
|
Close: 'close',
|
2020-11-19 12:09:42 -08:00
|
|
|
SocketError: 'socketerror',
|
2020-10-26 22:20:43 -07:00
|
|
|
FrameReceived: 'framereceived',
|
|
|
|
FrameSent: 'framesent',
|
|
|
|
};
|
|
|
|
|
2021-02-09 09:00:00 -08:00
|
|
|
constructor(parent: SdkObject, url: string) {
|
2021-04-20 23:03:56 -07:00
|
|
|
super(parent, 'ws');
|
2020-10-26 22:20:43 -07:00
|
|
|
this._url = url;
|
|
|
|
}
|
|
|
|
|
|
|
|
url(): string {
|
|
|
|
return this._url;
|
|
|
|
}
|
|
|
|
|
|
|
|
frameSent(opcode: number, data: string) {
|
|
|
|
this.emit(WebSocket.Events.FrameSent, { opcode, data });
|
|
|
|
}
|
|
|
|
|
|
|
|
frameReceived(opcode: number, data: string) {
|
|
|
|
this.emit(WebSocket.Events.FrameReceived, { opcode, data });
|
|
|
|
}
|
|
|
|
|
|
|
|
error(errorMessage: string) {
|
2020-11-19 12:09:42 -08:00
|
|
|
this.emit(WebSocket.Events.SocketError, errorMessage);
|
2020-10-26 22:20:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
closed() {
|
|
|
|
this.emit(WebSocket.Events.Close);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-13 14:30:40 -07:00
|
|
|
export interface RouteDelegate {
|
2019-12-30 14:05:28 -08:00
|
|
|
abort(errorCode: string): Promise<void>;
|
2020-06-29 16:37:38 -07:00
|
|
|
fulfill(response: types.NormalizedFulfillResponse): Promise<void>;
|
2021-08-05 08:49:02 -07:00
|
|
|
continue(request: Request, overrides: types.NormalizedContinueOverrides): Promise<InterceptedResponse|null>;
|
2021-08-11 14:47:05 -07:00
|
|
|
responseBody(): Promise<Buffer>;
|
2019-12-30 14:05:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
|
|
|
|
export const STATUS_TEXTS: { [status: string]: string } = {
|
|
|
|
'100': 'Continue',
|
|
|
|
'101': 'Switching Protocols',
|
|
|
|
'102': 'Processing',
|
|
|
|
'103': 'Early Hints',
|
|
|
|
'200': 'OK',
|
|
|
|
'201': 'Created',
|
|
|
|
'202': 'Accepted',
|
|
|
|
'203': 'Non-Authoritative Information',
|
|
|
|
'204': 'No Content',
|
|
|
|
'205': 'Reset Content',
|
|
|
|
'206': 'Partial Content',
|
|
|
|
'207': 'Multi-Status',
|
|
|
|
'208': 'Already Reported',
|
|
|
|
'226': 'IM Used',
|
|
|
|
'300': 'Multiple Choices',
|
|
|
|
'301': 'Moved Permanently',
|
|
|
|
'302': 'Found',
|
|
|
|
'303': 'See Other',
|
|
|
|
'304': 'Not Modified',
|
|
|
|
'305': 'Use Proxy',
|
|
|
|
'306': 'Switch Proxy',
|
|
|
|
'307': 'Temporary Redirect',
|
|
|
|
'308': 'Permanent Redirect',
|
|
|
|
'400': 'Bad Request',
|
|
|
|
'401': 'Unauthorized',
|
|
|
|
'402': 'Payment Required',
|
|
|
|
'403': 'Forbidden',
|
|
|
|
'404': 'Not Found',
|
|
|
|
'405': 'Method Not Allowed',
|
|
|
|
'406': 'Not Acceptable',
|
|
|
|
'407': 'Proxy Authentication Required',
|
|
|
|
'408': 'Request Timeout',
|
|
|
|
'409': 'Conflict',
|
|
|
|
'410': 'Gone',
|
|
|
|
'411': 'Length Required',
|
|
|
|
'412': 'Precondition Failed',
|
|
|
|
'413': 'Payload Too Large',
|
|
|
|
'414': 'URI Too Long',
|
|
|
|
'415': 'Unsupported Media Type',
|
|
|
|
'416': 'Range Not Satisfiable',
|
|
|
|
'417': 'Expectation Failed',
|
|
|
|
'418': 'I\'m a teapot',
|
|
|
|
'421': 'Misdirected Request',
|
|
|
|
'422': 'Unprocessable Entity',
|
|
|
|
'423': 'Locked',
|
|
|
|
'424': 'Failed Dependency',
|
|
|
|
'425': 'Too Early',
|
|
|
|
'426': 'Upgrade Required',
|
|
|
|
'428': 'Precondition Required',
|
|
|
|
'429': 'Too Many Requests',
|
|
|
|
'431': 'Request Header Fields Too Large',
|
|
|
|
'451': 'Unavailable For Legal Reasons',
|
|
|
|
'500': 'Internal Server Error',
|
|
|
|
'501': 'Not Implemented',
|
|
|
|
'502': 'Bad Gateway',
|
|
|
|
'503': 'Service Unavailable',
|
|
|
|
'504': 'Gateway Timeout',
|
|
|
|
'505': 'HTTP Version Not Supported',
|
|
|
|
'506': 'Variant Also Negotiates',
|
|
|
|
'507': 'Insufficient Storage',
|
|
|
|
'508': 'Loop Detected',
|
|
|
|
'510': 'Not Extended',
|
|
|
|
'511': 'Network Authentication Required',
|
|
|
|
};
|
2020-02-26 12:42:20 -08:00
|
|
|
|
2020-08-18 15:38:29 -07:00
|
|
|
export function singleHeader(name: string, value: string): types.HeadersArray {
|
|
|
|
return [{ name, value }];
|
2020-02-26 12:42:20 -08:00
|
|
|
}
|
|
|
|
|
2020-08-18 15:38:29 -07:00
|
|
|
export function mergeHeaders(headers: (types.HeadersArray | undefined | null)[]): types.HeadersArray {
|
2020-02-26 12:42:20 -08:00
|
|
|
const lowerCaseToValue = new Map<string, string>();
|
|
|
|
const lowerCaseToOriginalCase = new Map<string, string>();
|
|
|
|
for (const h of headers) {
|
|
|
|
if (!h)
|
|
|
|
continue;
|
2020-08-18 15:38:29 -07:00
|
|
|
for (const { name, value } of h) {
|
|
|
|
const lower = name.toLowerCase();
|
|
|
|
lowerCaseToOriginalCase.set(lower, name);
|
|
|
|
lowerCaseToValue.set(lower, value);
|
2020-02-26 12:42:20 -08:00
|
|
|
}
|
|
|
|
}
|
2020-08-18 15:38:29 -07:00
|
|
|
const result: types.HeadersArray = [];
|
2020-02-26 12:42:20 -08:00
|
|
|
for (const [lower, value] of lowerCaseToValue)
|
2020-08-18 15:38:29 -07:00
|
|
|
result.push({ name: lowerCaseToOriginalCase.get(lower)!, value });
|
2020-02-26 12:42:20 -08:00
|
|
|
return result;
|
|
|
|
}
|