UNPKG

@zohodesk/request_interceptor

Version:

Intercept and modify HTTP headers and payloads before sending requests.Implement custom logic to handle requests based on specific conditions

378 lines (334 loc) 16.8 kB
if (!('isRequestInterceptorAlreadyInitialize' in window)) { window.isRequestInterceptorAlreadyInitialize = false; } export var RequestInterceptor = (function() { var _allowTransformFunction = []; // Stores custom modify conditions var _transformFunction = null; // Transforms the payload var _allowDeTransformFunctions = []; // Stores custom modify conditions var _deTransformFunction = null; // Transforms the response var _wafkeyName = "Waf-Encryption-Key"; // Add custom modify conditions function allowTransformFunction(transformCallback) { _allowTransformFunction.push(transformCallback); } // Set the payload transformation function function transformFunction(_function) { _transformFunction = _function; } // Add custom modify conditions function allowDeTransformFunction(deTransformCallback) { _allowDeTransformFunctions.push(deTransformCallback) } // Set the payload transformation function function deTransformFunction(_function) { _deTransformFunction = _function; } // Retrieve query string from URL function _retrieveQueryStringFromURL(url) { // Check if the URL is defined and truthy if (!url || typeof url !== 'string') { return ""; } // Extract the query string from the URL var queryStringIndex = url.indexOf('?'); if (queryStringIndex === -1) { return ""; } // Extract the query string from the URL const queryString = url.substring(queryStringIndex + 1); return queryString; } // Concatenate original URL and transformed query string function _concatenateURL(originalURL, transformedQueryString) { if(typeof transformedQueryString !== "undefined" && transformedQueryString !== ""){ return originalURL && originalURL.split('?')[0] + '?' + transformedQueryString; } return originalURL; } // Create an object to store response headers function _responseHeaderSplitUp(responseHeadersString){ var responseHeadersArray = responseHeadersString.split('\n'); var responseHeadersObject = {}; responseHeadersArray.forEach(function(header) { var parts = header.split(': '); var key = parts[0]; var value = parts[1]; if (key && value) { responseHeadersObject[key] = value; } }); return responseHeadersObject; } function _modifyQueryStringAndPayload({url, payLoad, commonHeaderKey}) { if (typeof _transformFunction === 'function') { return new Promise((resolve, reject)=>{ let queryString = _retrieveQueryStringFromURL(url); _transformFunction({ queryString, payLoad, commonHeaderKey}).then((transformData) => { if (transformData) { resolve(transformData); } }).catch(error => { console.error('Error in _transformFunction:', error); reject(error); }); }); } return Promise.resolve(url); } // Intercept XMLHttpRequest send requests function interceptXMLHttpRequest() { var originalAddEventListener = XMLHttpRequest.prototype.addEventListener; // Override the addEventListener method XMLHttpRequest.prototype.addEventListener = function(event, listener, options) { // Intercept the 'load' event if (event === 'loadend') { var originalListener = listener; // Save reference to original listener function listener = async function(e) { var xhr = this; var responseData = xhr.response ? xhr.response : xhr.responseText; var responseHeaders = _responseHeaderSplitUp(xhr.getAllResponseHeaders()); var shouldAllowDeTransform = _allowDeTransformFunctions.some(function(_fun) { return _fun(responseHeaders); }, this); if(shouldAllowDeTransform){ let deTransformResponse = await _deTransformFunction({payLoad: responseData},responseHeaders); if(deTransformResponse && deTransformResponse["payLoad"]){ Object.defineProperties(this, { response: {value: deTransformResponse["payLoad"], writable: true}, responseText: {value: deTransformResponse["payLoad"], writable: true} }); } if (originalListener) { originalListener.apply(this, arguments); } }else{ originalListener.apply(this, arguments); } }; } // Call the original addEventListener method with the provided arguments originalAddEventListener.call(this, event, listener, options); }; var _payloadIndex = 0; function _sendArgumentsParse(){ let payload = arguments[_payloadIndex]; return { payload }; } function _sendArgumentsSet(args, {payload}){ args[_payloadIndex] = payload; return args; } /**To overwrite send */ var orginalSend = window.XMLHttpRequest.prototype.send; window.XMLHttpRequest.prototype.send = async function() { var self = this; const { payload } = _sendArgumentsParse.apply(this, arguments); // Check if any of the custom modify conditions return true var shouldModify = _allowTransformFunction.some(function(_fun) { return _fun({ _url: this._url }); }, this); // If any condition returns true, do not modify the payload if (!shouldModify) { orginalSend.apply(this, arguments); } if(shouldModify){ // Transform the payload if a payload transformation function is provided if (typeof _transformFunction === 'function') { const _commonHeaderKey = this._commonHeaderKey; const transformPayload = await _modifyQueryStringAndPayload.call(this, {payLoad: payload, commonHeaderKey: _commonHeaderKey}); if (transformPayload && transformPayload.header && transformPayload.payLoad) { _sendArgumentsSet(arguments,{ payload : transformPayload.payLoad }); if(_commonHeaderKey){ //if waf key already set we need to delete it transformPayload && transformPayload.header && delete transformPayload.header[_wafkeyName]; } for (let header in transformPayload.header) { self.setRequestHeader(header, transformPayload.header[header]); } orginalSend.apply(this, [arguments[0], ...arguments]); }else if(transformPayload && transformPayload.header && self._isQueryStringHasEmpty){//If QueryString and Payload both is empty we need to set the empty wafkey for (let header in transformPayload.header) { self.setRequestHeader(header, transformPayload.header[header]); } orginalSend.apply(this, [arguments[0], ...arguments]); }else{ orginalSend.apply(this, arguments); } } else { orginalSend.apply(this, arguments); } } }; var _methodIndex = 0; var _urlIndex = 1; var _asyncIndex = 2; function _openArgumentsParse(){ let url = arguments[_urlIndex]; let method = arguments[_methodIndex]; let async = arguments[_methodIndex]; return { url, method, async }; } function _openArgumentsSet(args, {url}){ args[_urlIndex] = url; return args; } /**To overwrite open */ var orginalOpen = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = async function() { let self = this; const { url, method, async } = _openArgumentsParse.apply(this, arguments); // Check if any of the custom modify conditions return true var shouldModify = _allowTransformFunction.some(function(_fun) { return _fun({ _url: url, _method: method, _async: async }); }, this); // If any condition returns true, do not modify the payload if (!shouldModify) { self._url = url; self._method = method; orginalOpen.apply(this, arguments); } if(shouldModify){ if (typeof _transformFunction === 'function' && url) { let originalObj = {url}; let transformData = await _modifyQueryStringAndPayload.call(this, originalObj); self._url = transformData ? _concatenateURL(url, transformData.queryString) || url : url; self._method = method; if (transformData && transformData.header && transformData.queryString) { _openArgumentsSet(arguments,{ url: self._url }); orginalOpen.apply(this, arguments); for (let header in transformData.header) { self.setRequestHeader(header, transformData.header[header]); } if(transformData.commonKey){ self._commonHeaderKey = transformData.commonKey; } }else{ self._isQueryStringHasEmpty = transformData && typeof transformData.queryString === "undefined"; orginalOpen.apply(this, arguments); } }else{ orginalOpen.apply(this, arguments); } } }; } // intercept fetch requests function interceptFetch() { var _urlIndex = 0; var _otherIndex = 1; var _payLoadText = "body"; let _headerText = "headers"; function _fetchParseArguments(){ let url = arguments[_urlIndex]; url = typeof url === "object" ? url.url : url; let others = arguments && arguments[_otherIndex]; const payLoad = others && others[_payLoadText]; return { url, payLoad }; } function _fetchArgumentsSet(args, {url, payLoad, header}){ //To set url if(url && typeof url !== "undefined"){ args[_urlIndex] = url; } //To set payload let others = args && args[_otherIndex]; if(others && typeof others !== "undefined" && payLoad && typeof payLoad !== "undefined"){ others[_payLoadText] = others && others[_payLoadText] && payLoad; } //To set header if(others && typeof others !== "undefined" && header && typeof header !== "undefined"){ others[_headerText] = Object.assign(others[_headerText], header) } return args; } function _fetchResponseHeaderSplitUp(responseHeaders){ const headersObject = {}; if(typeof responseHeaders !== "undefined"){ const headers = responseHeaders; headers.forEach((value, key) => { headersObject[key] = value; }); } return headersObject; } var originalFetch = window.fetch; window.fetch = async function() { const { url, payLoad } = _fetchParseArguments.apply(this, arguments); //Check if any of the custom modify conditions return true var shouldModify = _allowTransformFunction.some(function(_fun) { return _fun({_url:url}); }); if (!shouldModify) { // If any condition returns true, do not modify the payload return originalFetch.apply(this, arguments); } if(shouldModify){ // Transform the payload if a payload transformation function is provided if (typeof _transformFunction === 'function') { let transformData = await _modifyQueryStringAndPayload.call(this, {url, payLoad}); if (transformData && transformData.header) { let data = {}; if(url && typeof url !== "undefined" && transformData && transformData.queryString){ let transformUrl = transformData ? _concatenateURL(url, transformData.queryString) || url : url; data.url = transformUrl; } if(payLoad && typeof payLoad !== "undefined" && transformData && transformData.payLoad){ data.payLoad = transformData.payLoad; } if(transformData && transformData.header){ data.header = transformData.header; } _fetchArgumentsSet(arguments,data); return originalFetch.apply(this,arguments).then(async function(){ var response = arguments[0]; const resClone = response.clone(); const parseHeaders = _fetchResponseHeaderSplitUp(response.headers); const shouldAllowDeTransform = _allowDeTransformFunctions.some(function(_fun) { return _fun(parseHeaders); }, this); if(shouldAllowDeTransform){ const decryptedResponse = await resClone.text().then(async (responseData) => { let deTransformResponse = await _deTransformFunction({payLoad: responseData},parseHeaders); if(deTransformResponse && deTransformResponse["payLoad"]){ const modifiedResponse = new Response(deTransformResponse["payLoad"] || "", { headers: response.headers, //ok: response.ok, //redirected: response.redirected, status: response.status, statusText: response.statusText, //type: response.type, //url: response.url }); return modifiedResponse; }else{ return response; } }); return decryptedResponse; } return response; }); } else{ return originalFetch.apply(this, arguments); } } } }; } // Public method to initialize the request interception function initialize() { if(!window.isRequestInterceptorAlreadyInitialize){ interceptXMLHttpRequest(); interceptFetch(); window.isRequestInterceptorAlreadyInitialize = true; } } // Expose the initialize method, allowTransformFunction method, and transformFunction method publicly return { initialize: initialize, allowTransformFunction: allowTransformFunction, transformFunction: transformFunction, allowDeTransformFunction: allowDeTransformFunction, deTransformFunction: deTransformFunction }; })();