338 lines
14 KiB
JavaScript
338 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.BidiNetworkManager = void 0;
|
|
exports.bidiBytesValueToString = bidiBytesValueToString;
|
|
var _eventsHelper = require("../../utils/eventsHelper");
|
|
var network = _interopRequireWildcard(require("../network"));
|
|
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
|
|
var _cookieStore = require("../cookieStore");
|
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
class BidiNetworkManager {
|
|
constructor(bidiSession, page, onNavigationResponseStarted) {
|
|
this._session = void 0;
|
|
this._requests = void 0;
|
|
this._page = void 0;
|
|
this._eventListeners = void 0;
|
|
this._onNavigationResponseStarted = void 0;
|
|
this._userRequestInterceptionEnabled = false;
|
|
this._protocolRequestInterceptionEnabled = false;
|
|
this._credentials = void 0;
|
|
this._intercepId = void 0;
|
|
this._session = bidiSession;
|
|
this._requests = new Map();
|
|
this._page = page;
|
|
this._onNavigationResponseStarted = onNavigationResponseStarted;
|
|
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.beforeRequestSent', this._onBeforeRequestSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseStarted', this._onResponseStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseCompleted', this._onResponseCompleted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.fetchError', this._onFetchError.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.authRequired', this._onAuthRequired.bind(this))];
|
|
}
|
|
dispose() {
|
|
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
|
|
}
|
|
_onBeforeRequestSent(param) {
|
|
if (param.request.url.startsWith('data:')) return;
|
|
const redirectedFrom = param.redirectCount ? this._requests.get(param.request.request) || null : null;
|
|
const frame = redirectedFrom ? redirectedFrom.request.frame() : param.context ? this._page._frameManager.frame(param.context) : null;
|
|
if (!frame) return;
|
|
if (redirectedFrom) this._requests.delete(redirectedFrom._id);
|
|
let route;
|
|
if (param.intercepts) {
|
|
// We do not support intercepting redirects.
|
|
if (redirectedFrom) {
|
|
var _redirectedFrom$_orig;
|
|
this._session.sendMayFail('network.continueRequest', {
|
|
request: param.request.request,
|
|
...(((_redirectedFrom$_orig = redirectedFrom._originalRequestRoute) === null || _redirectedFrom$_orig === void 0 ? void 0 : _redirectedFrom$_orig._alreadyContinuedHeaders) || {})
|
|
});
|
|
} else {
|
|
route = new BidiRouteImpl(this._session, param.request.request);
|
|
}
|
|
}
|
|
const request = new BidiRequest(frame, redirectedFrom, param, route);
|
|
this._requests.set(request._id, request);
|
|
this._page._frameManager.requestStarted(request.request, route);
|
|
}
|
|
_onResponseStarted(params) {
|
|
const request = this._requests.get(params.request.request);
|
|
if (!request) return;
|
|
const getResponseBody = async () => {
|
|
throw new Error(`Response body is not available for requests in Bidi`);
|
|
};
|
|
const timings = params.request.timings;
|
|
const startTime = timings.requestTime;
|
|
function relativeToStart(time) {
|
|
if (!time) return -1;
|
|
return (time - startTime) / 1000;
|
|
}
|
|
const timing = {
|
|
startTime: startTime / 1000,
|
|
requestStart: relativeToStart(timings.requestStart),
|
|
responseStart: relativeToStart(timings.responseStart),
|
|
domainLookupStart: relativeToStart(timings.dnsStart),
|
|
domainLookupEnd: relativeToStart(timings.dnsEnd),
|
|
connectStart: relativeToStart(timings.connectStart),
|
|
secureConnectionStart: relativeToStart(timings.tlsStart),
|
|
connectEnd: relativeToStart(timings.connectEnd)
|
|
};
|
|
const response = new network.Response(request.request, params.response.status, params.response.statusText, fromBidiHeaders(params.response.headers), timing, getResponseBody, false);
|
|
response._serverAddrFinished();
|
|
response._securityDetailsFinished();
|
|
// "raw" headers are the same as "provisional" headers in Bidi.
|
|
response.setRawResponseHeaders(null);
|
|
response.setResponseHeadersSize(params.response.headersSize);
|
|
this._page._frameManager.requestReceivedResponse(response);
|
|
if (params.navigation) this._onNavigationResponseStarted(params);
|
|
}
|
|
_onResponseCompleted(params) {
|
|
const request = this._requests.get(params.request.request);
|
|
if (!request) return;
|
|
const response = request.request._existingResponse();
|
|
// TODO: body size is the encoded size
|
|
response.setTransferSize(params.response.bodySize);
|
|
response.setEncodedBodySize(params.response.bodySize);
|
|
|
|
// Keep redirected requests in the map for future reference as redirectedFrom.
|
|
const isRedirected = response.status() >= 300 && response.status() <= 399;
|
|
const responseEndTime = params.request.timings.responseEnd / 1000 - response.timing().startTime;
|
|
if (isRedirected) {
|
|
response._requestFinished(responseEndTime);
|
|
} else {
|
|
this._requests.delete(request._id);
|
|
response._requestFinished(responseEndTime);
|
|
}
|
|
response._setHttpVersion(params.response.protocol);
|
|
this._page._frameManager.reportRequestFinished(request.request, response);
|
|
}
|
|
_onFetchError(params) {
|
|
const request = this._requests.get(params.request.request);
|
|
if (!request) return;
|
|
this._requests.delete(request._id);
|
|
const response = request.request._existingResponse();
|
|
if (response) {
|
|
response.setTransferSize(null);
|
|
response.setEncodedBodySize(null);
|
|
response._requestFinished(-1);
|
|
}
|
|
request.request._setFailureText(params.errorText);
|
|
// TODO: support canceled flag
|
|
this._page._frameManager.requestFailed(request.request, params.errorText === 'NS_BINDING_ABORTED');
|
|
}
|
|
_onAuthRequired(params) {
|
|
var _params$response$auth;
|
|
const isBasic = (_params$response$auth = params.response.authChallenges) === null || _params$response$auth === void 0 ? void 0 : _params$response$auth.some(challenge => challenge.scheme.startsWith('Basic'));
|
|
const credentials = this._page._browserContext._options.httpCredentials;
|
|
if (isBasic && credentials) {
|
|
this._session.sendMayFail('network.continueWithAuth', {
|
|
request: params.request.request,
|
|
action: 'provideCredentials',
|
|
credentials: {
|
|
type: 'password',
|
|
username: credentials.username,
|
|
password: credentials.password
|
|
}
|
|
});
|
|
} else {
|
|
this._session.sendMayFail('network.continueWithAuth', {
|
|
request: params.request.request,
|
|
action: 'default'
|
|
});
|
|
}
|
|
}
|
|
async setRequestInterception(value) {
|
|
this._userRequestInterceptionEnabled = value;
|
|
await this._updateProtocolRequestInterception();
|
|
}
|
|
async setCredentials(credentials) {
|
|
this._credentials = credentials;
|
|
await this._updateProtocolRequestInterception();
|
|
}
|
|
async _updateProtocolRequestInterception(initial) {
|
|
const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
|
|
if (enabled === this._protocolRequestInterceptionEnabled) return;
|
|
this._protocolRequestInterceptionEnabled = enabled;
|
|
if (initial && !enabled) return;
|
|
const cachePromise = this._session.send('network.setCacheBehavior', {
|
|
cacheBehavior: enabled ? 'bypass' : 'default'
|
|
});
|
|
let interceptPromise = Promise.resolve(undefined);
|
|
if (enabled) {
|
|
interceptPromise = this._session.send('network.addIntercept', {
|
|
phases: [bidi.Network.InterceptPhase.AuthRequired, bidi.Network.InterceptPhase.BeforeRequestSent],
|
|
urlPatterns: [{
|
|
type: 'pattern'
|
|
}]
|
|
// urlPatterns: [{ type: 'string', pattern: '*' }],
|
|
}).then(r => {
|
|
this._intercepId = r.intercept;
|
|
});
|
|
} else if (this._intercepId) {
|
|
interceptPromise = this._session.send('network.removeIntercept', {
|
|
intercept: this._intercepId
|
|
});
|
|
this._intercepId = undefined;
|
|
}
|
|
await Promise.all([cachePromise, interceptPromise]);
|
|
}
|
|
}
|
|
exports.BidiNetworkManager = BidiNetworkManager;
|
|
class BidiRequest {
|
|
constructor(frame, redirectedFrom, payload, route) {
|
|
var _payload$navigation;
|
|
this.request = void 0;
|
|
this._id = void 0;
|
|
this._redirectedTo = void 0;
|
|
// Only first request in the chain can be intercepted, so this will
|
|
// store the first and only Route in the chain (if any).
|
|
this._originalRequestRoute = void 0;
|
|
this._id = payload.request.request;
|
|
if (redirectedFrom) redirectedFrom._redirectedTo = this;
|
|
// TODO: missing in the spec?
|
|
const postDataBuffer = null;
|
|
this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, (_payload$navigation = payload.navigation) !== null && _payload$navigation !== void 0 ? _payload$navigation : undefined, payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers));
|
|
// "raw" headers are the same as "provisional" headers in Bidi.
|
|
this.request.setRawRequestHeaders(null);
|
|
this.request._setBodySize(payload.request.bodySize || 0);
|
|
this._originalRequestRoute = route !== null && route !== void 0 ? route : redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom._originalRequestRoute;
|
|
route === null || route === void 0 || route._setRequest(this.request);
|
|
}
|
|
_finalRequest() {
|
|
let request = this;
|
|
while (request._redirectedTo) request = request._redirectedTo;
|
|
return request;
|
|
}
|
|
}
|
|
class BidiRouteImpl {
|
|
constructor(session, requestId) {
|
|
this._requestId = void 0;
|
|
this._session = void 0;
|
|
this._request = void 0;
|
|
this._alreadyContinuedHeaders = void 0;
|
|
this._session = session;
|
|
this._requestId = requestId;
|
|
}
|
|
_setRequest(request) {
|
|
this._request = request;
|
|
}
|
|
async continue(overrides) {
|
|
// Firefox does not update content-length header.
|
|
let headers = overrides.headers || this._request.headers();
|
|
if (overrides.postData && headers) {
|
|
headers = headers.map(header => {
|
|
if (header.name.toLowerCase() === 'content-length') return {
|
|
name: header.name,
|
|
value: overrides.postData.byteLength.toString()
|
|
};
|
|
return header;
|
|
});
|
|
}
|
|
this._alreadyContinuedHeaders = headers;
|
|
await this._session.sendMayFail('network.continueRequest', {
|
|
request: this._requestId,
|
|
url: overrides.url,
|
|
method: overrides.method,
|
|
...toBidiRequestHeaders(this._alreadyContinuedHeaders),
|
|
body: overrides.postData ? {
|
|
type: 'base64',
|
|
value: Buffer.from(overrides.postData).toString('base64')
|
|
} : undefined
|
|
});
|
|
}
|
|
async fulfill(response) {
|
|
const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
|
await this._session.sendMayFail('network.provideResponse', {
|
|
request: this._requestId,
|
|
statusCode: response.status,
|
|
reasonPhrase: network.statusText(response.status),
|
|
...toBidiResponseHeaders(response.headers),
|
|
body: {
|
|
type: 'base64',
|
|
value: base64body
|
|
}
|
|
});
|
|
}
|
|
async abort(errorCode) {
|
|
await this._session.sendMayFail('network.failRequest', {
|
|
request: this._requestId
|
|
});
|
|
}
|
|
}
|
|
function fromBidiHeaders(bidiHeaders) {
|
|
const result = [];
|
|
for (const {
|
|
name,
|
|
value
|
|
} of bidiHeaders) result.push({
|
|
name,
|
|
value: bidiBytesValueToString(value)
|
|
});
|
|
return result;
|
|
}
|
|
function toBidiRequestHeaders(allHeaders) {
|
|
const bidiHeaders = toBidiHeaders(allHeaders);
|
|
const cookies = bidiHeaders.filter(h => h.name.toLowerCase() === 'cookie');
|
|
const headers = bidiHeaders.filter(h => h.name.toLowerCase() !== 'cookie');
|
|
return {
|
|
cookies,
|
|
headers
|
|
};
|
|
}
|
|
function toBidiResponseHeaders(headers) {
|
|
const setCookieHeaders = headers.filter(h => h.name.toLowerCase() === 'set-cookie');
|
|
const otherHeaders = headers.filter(h => h.name.toLowerCase() !== 'set-cookie');
|
|
const rawCookies = setCookieHeaders.map(h => (0, _cookieStore.parseRawCookie)(h.value));
|
|
const cookies = rawCookies.filter(Boolean).map(c => {
|
|
return {
|
|
...c,
|
|
value: {
|
|
type: 'string',
|
|
value: c.value
|
|
},
|
|
sameSite: toBidiSameSite(c.sameSite)
|
|
};
|
|
});
|
|
return {
|
|
cookies,
|
|
headers: toBidiHeaders(otherHeaders)
|
|
};
|
|
}
|
|
function toBidiHeaders(headers) {
|
|
return headers.map(({
|
|
name,
|
|
value
|
|
}) => ({
|
|
name,
|
|
value: {
|
|
type: 'string',
|
|
value
|
|
}
|
|
}));
|
|
}
|
|
function bidiBytesValueToString(value) {
|
|
if (value.type === 'string') return value.value;
|
|
if (value.type === 'base64') return Buffer.from(value.type, 'base64').toString('binary');
|
|
return 'unknown value type: ' + value.type;
|
|
}
|
|
function toBidiSameSite(sameSite) {
|
|
if (!sameSite) return undefined;
|
|
if (sameSite === 'Strict') return bidi.Network.SameSite.Strict;
|
|
if (sameSite === 'Lax') return bidi.Network.SameSite.Lax;
|
|
return bidi.Network.SameSite.None;
|
|
} |