UNPKG

temp-taiko

Version:

An easy to use wrapper over Chrome Remote Interface.

273 lines (252 loc) 8.06 kB
const { isFunction, isString, isObject } = require('../helper'); const eventHandler = require('../eventBus'); const logEvent = require('../logger'); const networkPresets = require('../data/networkConditions'); let requestPromises; const defaultErrorReason = 'Failed'; let interceptors = []; let network; eventHandler.on('createdSession', client => { network = client.Network; requestPromises = {}; network.requestWillBeSent(emitXHREvent); network.loadingFinished(resolveXHREvent); network.loadingFailed(resolveXHREvent); network.responseReceived(responseHandler); registerInterceptHandler(); }); const resetInterceptors = () => { interceptors = []; }; const resetPromises = () => { requestPromises = {}; }; const registerInterceptHandler = () => { network.setCacheDisabled({ cacheDisabled: true }); network.setRequestInterception({ patterns: [{ urlPattern: '*' }] }); network.requestIntercepted(handleInterceptor); }; const emitXHREvent = p => { eventHandler.emit('requestStarted', p); if (!(requestPromises && requestPromises[p.requestId])) { logEvent( `Request started:\t RequestId : ${p.requestId}\tRequest Url : ${p.request.url}`, ); let resolve; eventHandler.emit('xhrEvent', { request: p, promise: new Promise(r => { resolve = r; }), }); requestPromises[p.requestId] = resolve; } }; const resolveXHREvent = p => { if (requestPromises && requestPromises[p.requestId]) { logEvent(`Request resolved:\t RequestId : ${p.requestId}`); requestPromises[p.requestId](); delete requestPromises[p.requestId]; } }; const responseHandler = response => { logEvent(`Response Recieved: Request id: ${response.requestId}`); eventHandler.emit('responseReceived', response); }; const getMatchingInterceptor = (interceptor, url) => { if (interceptor.count === 0) { return false; } const re = new RegExp(interceptor.requestUrl); var matches = url.match(re); if (matches === null || matches.length <= 0) { return decodeURI(url).match(re); } return matches; }; const handleInterceptor = p => { let options = { interceptionId: p.interceptionId }; const matches = interceptors.filter(interceptor => getMatchingInterceptor(interceptor, p.request.url), ); const matchesLen = matches.length; if (matchesLen) { if (matchesLen > 1) { const matchesURL = matches .map(e => `"${e.requestUrl}"`) .join(','); console.warn( `WARNING: More than one intercept [${matchesURL}] found for request "${ p.request.url }".\n Applying: intercept("${ matches[matchesLen - 1].requestUrl }", "${matches[matchesLen - 1].action}")`, ); } const interceptor = matches.pop(); if (interceptor.count) { interceptor.count = interceptor.count - 1; } if (!interceptor.action) { options.errorReason = defaultErrorReason; } else if (isFunction(interceptor.action)) { p.continue = override => overrideRequest(p, override, options); p.respond = mock => { options = mockResponse(mock, options); network.continueInterceptedRequest(options).catch(() => { console.warn( `Could not intercept request ${p.request.url}`, ); }); }; interceptor.action(p); return; } else if (isString(interceptor.action)) { if ( !/^https?:\/\//i.test(interceptor.action) && !/^file/i.test(interceptor.action) ) interceptor.action = 'http://' + interceptor.action; options.url = interceptor.action; } else options = mockResponse(interceptor.action, options); } network.continueInterceptedRequest(options).catch(() => { if (matchesLen) console.warn(`Could not intercept request ${p.request.url}`); }); }; const mockResponse = (response, options) => { let responseBodyJson = isObject(response.body) ? JSON.stringify(response.body) : response.body; const responseBody = Buffer.from(responseBodyJson); const responseHeaders = {}; if (response.headers) { for (const header of Object.keys(response.headers)) responseHeaders[header.toLowerCase()] = response.headers[header]; } if (response.contentType) responseHeaders['content-type'] = response.contentType; if (responseBody && !('content-length' in responseHeaders)) { responseHeaders['content-length'] = Buffer.byteLength( responseBody, ); } const statusCode = response.status || 200; const statusText = statusTexts[statusCode] || ''; const statusLine = `HTTP/1.1 ${statusCode} ${statusText}`; const CRLF = '\r\n'; let text = statusLine + CRLF; for (const header of Object.keys(responseHeaders)) text += header + ': ' + responseHeaders[header] + CRLF; text += CRLF; let responseBuffer = Buffer.from(text, 'utf8'); if (responseBody) responseBuffer = Buffer.concat([responseBuffer, responseBody]); options.rawResponse = responseBuffer.toString('base64'); return options; }; const overrideRequest = (p, override, options) => { for (const key in override) options[key] = override[key]; network.continueInterceptedRequest(options).catch(() => { console.warn(`Could not intercept request ${p.request.url}`); }); }; const setNetworkEmulation = async networkType => { const _networkType = process.env.TAIKO_EMULATE_NETWORK; if (!networkType && _networkType) { networkType = _networkType; } const emulate = networkPresets[networkType]; let networkModes = Object.keys(networkPresets); if (emulate === undefined) throw new Error( `Please set one of the given network types \n${networkModes.join( '\n', )}`, ); await network.emulateNetworkConditions(emulate).catch(err => { console.warn(`Could not emulate network ${err}`); }); }; const addInterceptor = async requestWithAction => { interceptors.push(requestWithAction); }; const statusTexts = { '100': 'Continue', '101': 'Switching Protocols', '102': 'Processing', '200': 'OK', '201': 'Created', '202': 'Accepted', '203': 'Non-Authoritative Information', '204': 'No Content', '206': 'Partial Content', '207': 'Multi-Status', '208': 'Already Reported', '209': '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', '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', }; const resetInterceptor = url => { var originalLength = interceptors.length; interceptors = interceptors.filter( interceptor => !getMatchingInterceptor(interceptor, url), ); return originalLength !== interceptors.length; }; module.exports = { addInterceptor, resetInterceptors, setNetworkEmulation, handleInterceptor, resetPromises, resetInterceptor, };