230 lines
41 KiB
JavaScript
230 lines
41 KiB
JavaScript
|
"use strict";
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const lodash_1 = require("lodash");
|
||
|
const event_provider_1 = __importDefault(require("../request-hooks/event-provider"));
|
||
|
const resource_injector_1 = __importDefault(require("../resource-injector"));
|
||
|
const headers_1 = require("../utils/headers");
|
||
|
const cdp_1 = require("../utils/cdp");
|
||
|
const error_route_1 = __importDefault(require("../error-route"));
|
||
|
const debug_loggers_1 = require("../../utils/debug-loggers");
|
||
|
const testcafe_hammerhead_1 = require("testcafe-hammerhead");
|
||
|
const default_setup_options_1 = __importDefault(require("../default-setup-options"));
|
||
|
const special_handlers_1 = __importDefault(require("./special-handlers"));
|
||
|
const safe_api_1 = require("./safe-api");
|
||
|
const api_base_1 = __importDefault(require("../api-base"));
|
||
|
const resendAuthRequest_1 = require("./resendAuthRequest");
|
||
|
const test_run_bridge_1 = __importDefault(require("./test-run-bridge"));
|
||
|
const context_info_1 = __importDefault(require("./context-info"));
|
||
|
class ProxylessRequestPipeline extends api_base_1.default {
|
||
|
constructor(browserId, client) {
|
||
|
super(browserId, client);
|
||
|
this._testRunBridge = new test_run_bridge_1.default(browserId);
|
||
|
this._contextInfo = new context_info_1.default(this._testRunBridge);
|
||
|
this._specialServiceRoutes = this._getSpecialServiceRoutes();
|
||
|
this.requestHookEventProvider = new event_provider_1.default();
|
||
|
this._resourceInjector = new resource_injector_1.default(this._testRunBridge, this._specialServiceRoutes);
|
||
|
this._options = default_setup_options_1.default;
|
||
|
this._stopped = false;
|
||
|
this._currentFrameTree = null;
|
||
|
this._failedRequestIds = [];
|
||
|
this.restoringStorages = null;
|
||
|
this.contextStorage = null;
|
||
|
}
|
||
|
_getSpecialServiceRoutes() {
|
||
|
const browserConnection = this._testRunBridge.getBrowserConnection();
|
||
|
const proxy = browserConnection.browserConnectionGateway.proxy;
|
||
|
return {
|
||
|
errorPage1: proxy.resolveRelativeServiceUrl(error_route_1.default, proxy.server1Info.domain),
|
||
|
errorPage2: proxy.resolveRelativeServiceUrl(error_route_1.default, proxy.server2Info.domain),
|
||
|
idlePage: browserConnection.idleUrl,
|
||
|
openFileProtocolUrl: browserConnection.openFileProtocolUrl,
|
||
|
};
|
||
|
}
|
||
|
async _handleMockErrorIfNecessary(pipelineContext, event) {
|
||
|
if (!pipelineContext.mock.hasError)
|
||
|
return;
|
||
|
await pipelineContext.handleMockError(this.requestHookEventProvider);
|
||
|
(0, debug_loggers_1.requestPipelineMockLogger)('%s\n%s', event.networkId, pipelineContext.mock.error);
|
||
|
}
|
||
|
async _handleMockResponse(mockedResponse, pipelineContext, event) {
|
||
|
const mockedResponseBodyStr = mockedResponse.getBody().toString();
|
||
|
const fulfillInfo = {
|
||
|
requestId: event.requestId,
|
||
|
responseCode: mockedResponse.statusCode,
|
||
|
responseHeaders: (0, headers_1.convertToHeaderEntries)(mockedResponse.headers),
|
||
|
body: mockedResponseBodyStr,
|
||
|
};
|
||
|
if (pipelineContext.reqOpts.isAjax)
|
||
|
await this._resourceInjector.processNonProxiedContent(fulfillInfo, this._client);
|
||
|
else {
|
||
|
await this._resourceInjector.processHTMLPageContent(fulfillInfo, {
|
||
|
isIframe: false,
|
||
|
contextStorage: this.contextStorage,
|
||
|
}, this._client);
|
||
|
}
|
||
|
(0, debug_loggers_1.requestPipelineMockLogger)(`Mock request ${event.requestId}`);
|
||
|
}
|
||
|
_createContinueResponseRequest(event, modified) {
|
||
|
const continueResponseRequest = {
|
||
|
requestId: event.requestId,
|
||
|
};
|
||
|
if (modified) {
|
||
|
continueResponseRequest.responseHeaders = event.responseHeaders;
|
||
|
continueResponseRequest.responseCode = event.responseStatusCode;
|
||
|
}
|
||
|
return continueResponseRequest;
|
||
|
}
|
||
|
_shouldRedirectToErrorPage(event) {
|
||
|
return event.resourceType === 'Document'
|
||
|
&& !this._isIframe(event.frameId);
|
||
|
}
|
||
|
async _getUserScripts(event) {
|
||
|
const { pipelineContext, eventFactory } = this._contextInfo.getContextData(event);
|
||
|
await pipelineContext.prepareInjectableUserScripts(eventFactory, this._testRunBridge.getUserScripts());
|
||
|
return pipelineContext.injectableUserScripts;
|
||
|
}
|
||
|
async _respondToOtherRequest(event) {
|
||
|
if ((0, testcafe_hammerhead_1.isRedirectStatusCode)(event.responseStatusCode)) {
|
||
|
await (0, safe_api_1.safeContinueResponse)(this._client, { requestId: event.requestId });
|
||
|
return;
|
||
|
}
|
||
|
const resourceInfo = await this._resourceInjector.getDocumentResourceInfo(event, this._client);
|
||
|
if (resourceInfo.error) {
|
||
|
if (this._shouldRedirectToErrorPage(event)) {
|
||
|
await this._resourceInjector.redirectToErrorPage(this._client, resourceInfo.error, event.request.url);
|
||
|
this._contextInfo.dispose((0, cdp_1.getRequestId)(event));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
const modified = await this.requestHookEventProvider.onResponse(event, resourceInfo.body, this._contextInfo, this._client);
|
||
|
if (event.resourceType !== 'Document') {
|
||
|
const continueResponseRequest = this._createContinueResponseRequest(event, modified);
|
||
|
await (0, safe_api_1.safeContinueResponse)(this._client, continueResponseRequest);
|
||
|
this._contextInfo.dispose((0, cdp_1.getRequestId)(event));
|
||
|
}
|
||
|
else {
|
||
|
const fulfillInfo = {
|
||
|
requestId: event.requestId,
|
||
|
responseHeaders: event.responseHeaders,
|
||
|
responseCode: event.responseStatusCode,
|
||
|
body: resourceInfo.body.toString(),
|
||
|
};
|
||
|
// NOTE: Strange behavior of the CDP API:
|
||
|
// if we pass the empty "responseStatusText" value, we get an error 'Invalid status code or phrase'.
|
||
|
if (event.responseStatusText !== '')
|
||
|
fulfillInfo.responsePhrase = event.responseStatusText;
|
||
|
if ((0, cdp_1.isUnauthorized)(event.responseStatusCode))
|
||
|
await this._tryAuthorizeWithHttpBasicAuthCredentials(event, fulfillInfo);
|
||
|
const userScripts = await this._getUserScripts(event);
|
||
|
await this._resourceInjector.processHTMLPageContent(fulfillInfo, {
|
||
|
isIframe: this._isIframe(event.frameId),
|
||
|
url: event.request.url,
|
||
|
restoringStorages: this.restoringStorages,
|
||
|
contextStorage: this.contextStorage,
|
||
|
userScripts,
|
||
|
}, this._client);
|
||
|
this._contextInfo.dispose((0, cdp_1.getRequestId)(event));
|
||
|
this.restoringStorages = null;
|
||
|
}
|
||
|
}
|
||
|
async _tryAuthorizeWithHttpBasicAuthCredentials(event, fulfillInfo) {
|
||
|
const credentials = this._testRun.getAuthCredentials();
|
||
|
if (!credentials)
|
||
|
return;
|
||
|
const authRequest = await (0, resendAuthRequest_1.resendAuthRequest)(event.request, credentials);
|
||
|
if (typeof authRequest !== 'string' && !(0, cdp_1.isUnauthorized)(authRequest.status)) {
|
||
|
fulfillInfo.responseCode = authRequest.status;
|
||
|
fulfillInfo.body = authRequest.body.toString();
|
||
|
fulfillInfo.responsePhrase = authRequest.statusText;
|
||
|
}
|
||
|
}
|
||
|
async _tryRespondToOtherRequest(event) {
|
||
|
try {
|
||
|
await this._respondToOtherRequest(event);
|
||
|
}
|
||
|
catch (err) {
|
||
|
if (event.networkId && this._failedRequestIds.includes(event.networkId)) {
|
||
|
(0, lodash_1.remove)(this._failedRequestIds, event.networkId);
|
||
|
return;
|
||
|
}
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
async _handleOtherRequests(event) {
|
||
|
(0, debug_loggers_1.requestPipelineOtherRequestLogger)('%r', event);
|
||
|
if (!event.responseErrorReason && ((0, cdp_1.isRequest)(event) || (0, testcafe_hammerhead_1.isRedirectStatusCode)(event.responseStatusCode))) {
|
||
|
this._contextInfo.init(event);
|
||
|
await this.requestHookEventProvider.onRequest(event, this._contextInfo);
|
||
|
const pipelineContext = this._contextInfo.getPipelineContext(event.networkId);
|
||
|
if (!pipelineContext || !pipelineContext.mock)
|
||
|
await (0, safe_api_1.safeContinueRequest)(this._client, event);
|
||
|
else {
|
||
|
const mockedResponse = await pipelineContext.getMockResponse();
|
||
|
await this._handleMockErrorIfNecessary(pipelineContext, event);
|
||
|
const mockedResponseEvent = (0, cdp_1.createRequestPausedEventForResponse)(mockedResponse, event);
|
||
|
await this.requestHookEventProvider.onResponse(mockedResponseEvent, mockedResponse.getBody(), this._contextInfo, this._client);
|
||
|
await this._handleMockResponse(mockedResponse, pipelineContext, event);
|
||
|
this._contextInfo.dispose((0, cdp_1.getRequestId)(event));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
await this._tryRespondToOtherRequest(event);
|
||
|
}
|
||
|
_topFrameNavigation(event) {
|
||
|
return event.type === 'Navigation'
|
||
|
&& !event.frame.parentId;
|
||
|
}
|
||
|
async _updateCurrentFrameTree() {
|
||
|
// NOTE: Due to CDP restrictions (it hangs), we can't get the frame tree
|
||
|
// right before injecting service scripts.
|
||
|
// So, we are forced tracking frames tree.
|
||
|
const result = await this._client.Page.getFrameTree();
|
||
|
this._currentFrameTree = result.frameTree;
|
||
|
}
|
||
|
_isIframe(frameId) {
|
||
|
if (!this._currentFrameTree)
|
||
|
return false;
|
||
|
return this._currentFrameTree.frame.id !== frameId;
|
||
|
}
|
||
|
async init(options) {
|
||
|
this._options = options;
|
||
|
this._client.Fetch.on('requestPaused', async (event) => {
|
||
|
if (this._stopped)
|
||
|
return;
|
||
|
const specialRequestHandler = (0, special_handlers_1.default)(event, this._options, this._specialServiceRoutes);
|
||
|
if (specialRequestHandler)
|
||
|
await specialRequestHandler(event, this._client, this._options);
|
||
|
else
|
||
|
await this._handleOtherRequests(event);
|
||
|
});
|
||
|
this._client.Page.on('frameNavigated', async (event) => {
|
||
|
(0, debug_loggers_1.requestPipelineLogger)('%f', event);
|
||
|
if (!this._topFrameNavigation(event)
|
||
|
|| event.frame.url !== testcafe_hammerhead_1.SPECIAL_BLANK_PAGE)
|
||
|
return;
|
||
|
this._contextInfo.init(event);
|
||
|
const userScripts = await this._getUserScripts(event);
|
||
|
await this._resourceInjector.processAboutBlankPage(event, userScripts, this._client);
|
||
|
this._contextInfo.dispose((0, cdp_1.getRequestId)(event));
|
||
|
});
|
||
|
this._client.Page.on('frameStartedLoading', async () => {
|
||
|
await this._updateCurrentFrameTree();
|
||
|
});
|
||
|
this._client.Network.on('loadingFailed', async (event) => {
|
||
|
(0, debug_loggers_1.requestPipelineLogger)('%l', event);
|
||
|
this._failedRequestIds.push(event.requestId);
|
||
|
if (event.requestId)
|
||
|
this._contextInfo.dispose(event.requestId);
|
||
|
});
|
||
|
await this._client.Page.setBypassCSP({ enabled: true });
|
||
|
}
|
||
|
stop() {
|
||
|
this._stopped = true;
|
||
|
}
|
||
|
}
|
||
|
exports.default = ProxylessRequestPipeline;
|
||
|
module.exports = exports.default;
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHJveHlsZXNzL3JlcXVlc3QtcGlwZWxpbmUvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSxtQ0FBZ0M7QUFTaEMscUZBQWdGO0FBQ2hGLDZFQUFvRDtBQUNwRCw4Q0FBMEQ7QUFFMUQsc0NBS3NCO0FBRXRCLGlFQUF5QztBQUV6Qyw2REFJbUM7QUFFbkMsNkRBSzZCO0FBSTdCLHFGQUF1RTtBQUN2RSwwRUFBMEQ7QUFDMUQseUNBQXVFO0FBQ3ZFLDJEQUEyQztBQUMzQywyREFBd0Q7QUFDeEQsd0VBQThDO0FBQzlDLGtFQUF5RDtBQUd6RCxNQUFxQix3QkFBeUIsU0FBUSxrQkFBZ0I7SUFhbEUsWUFBb0IsU0FBaUIsRUFBRSxNQUFtQjtRQUN0RCxLQUFLLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRXpCLElBQUksQ0FBQyxjQUFjLEdBQWEsSUFBSSx5QkFBYSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzdELElBQUksQ0FBQyxZQUFZLEdBQWUsSUFBSSxzQkFBMkIsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDckYsSUFBSSxDQUFDLHFCQUFxQixHQUFNLElBQUksQ0FBQyx3QkFBd0IsRUFBRSxDQUFDO1FBQ2hFLElBQUksQ0FBQyx3QkFBd0IsR0FBRyxJQUFJLHdCQUFpQyxFQUFFLENBQUM7UUFDeEUsSUFBSSxDQUFDLGlCQUFpQixHQUFVLElBQUksMkJBQWdCLENBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxJQUFJLENBQUMscUJBQXFCLENBQUMsQ0FBQztRQUN0RyxJQUFJLENBQUMsUUFBUSxHQUFtQiwrQkFBK0IsQ0FBQztRQUNoRSxJQUFJLENBQUMsUUFBUSxHQUFtQixLQUFLLENBQUM7UUFDdEMsSUFBSSxDQUFDLGlCQUFpQixHQUFVLElBQUksQ0FBQztRQUNyQyxJQUFJLENBQUMsaUJBQWlCLEdBQVUsRUFB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
|