import * as Tools from '../tool' type networkConfig = { ignoreUrls?:string[], isCatchResponseContent? :boolean, } class TracerNetwork { private _open:any private _send:any private _setRequestHeader:any private _XMLHttp:any private _fetch:any private handleXhrListers: any[] private networkList: any private ignoreUrls: string[] private isCatchResponseContent:boolean private isCatch:boolean constructor(config:networkConfig) { const { ignoreUrls, isCatchResponseContent } = config this.handleXhrListers = []; this.networkList = {} //config params this.ignoreUrls = ignoreUrls || [] this.isCatchResponseContent = isCatchResponseContent?true:false; this.isCatch = true //begin this.init() } private init = ()=>{ this._XMLHttp = (window).XMLHttpRequest this._open = (window).XMLHttpRequest.prototype.open this._send = (window).XMLHttpRequest.prototype.send this._setRequestHeader = (window).XMLHttpRequest.prototype.setRequestHeader this.patchXhr() this.patchFetch() } private patchXhr = () => { var self = this; //拦截原生open (window).XMLHttpRequest.prototype.open = function() { var XML = this; var args = Tools.toArray(arguments); //定时器 var timer = null; //获取请求唯一ID var id = Tools.getUniqueID(); //获取方法 var method = args[0]; //获取url var url = args[1]; //保存下 在send中使用 XML._url = url; XML._method = method; XML._id = id; //保存下请求头 在拦截请求头处使用 XML._setHead = {} XML._setHead[id] = {}; //拦截处理响应回调 var _onreadystatechange = XML.onreadystatechange || function() {}; // start onreadystatechange var onreadystatechange = function() { var item = self.networkList[id] ? self.networkList[id] : false; //如果不存在 可能略过了send 会导致丢失部分数据 if (!item) { item = {} //保存请求方法 item.method = method var { url, params } = self.handleReqUrl(url); //处理请求url和params item.url = url; item.params = params; } //更新状态 item.status = 0; if (XML.readyState > 1) { item.status = XML.status; } item.responseType = XML.responseType; //判断请求状态 if (XML.readyState == 0) { // 未开始 if (!item.startTime) { item.startTime = (+new Date()); } } else if (XML.readyState == 1) { // 打开 if (!item.startTime) { item.startTime = (+new Date()); } } else if (XML.readyState == 2) { // 发送 } else if (XML.readyState == 3) { //loading } else if (XML.readyState == 4) { //处理响应头 item.responseHead = {}; var header = XML.getAllResponseHeaders() || '', headerArr = header.split("\n"); //提取数据 for (let i = 0; i < headerArr.length; i++) { let line = headerArr[i]; if (!line) { continue; } let arr = line.split(': '); let key = arr[0], value = arr.slice(1).join(': '); item.responseHead[key] = value; } //完成 clearInterval(timer); item.endTime = +new Date(), item.costTime = (item.endTime - (item.startTime || item.endTime)) + 'ms'; //判断是否需要捕获数据 var responseContent = self.isCatchResponseContent?XML.response:'no-catch-responseContent' item.response = responseContent //请求结束完成 setTimeout(function() { self.handleDoneXML(id) }) } else { clearInterval(timer); } return _onreadystatechange.apply(XML, arguments); } XML.onreadystatechange = onreadystatechange; //end onreadystatechange //防止第三方库更改状态 //定时查看请求状态 var preState = -1; timer = setInterval(function() { if (preState != XML.readyState) { preState = XML.readyState; onreadystatechange.call(XML); } }, 10); return self._open.apply(XML, args); }; //拦截原始设置请求头 (window).XMLHttpRequest.prototype.setRequestHeader = function() { var XML = this; var args = Tools.toArray(arguments); if (XML._id && XML._setHead) { var setHead = XML._setHead[XML._id]; var key = args[0] ? args[0] : 'unkownRequestHead'; var value = args[1] ? args[1] : ''; setHead[key] = value XML._setHead[XML._id] = setHead } return self._setRequestHeader.apply(XML, args); }; //拦截原生send (window).XMLHttpRequest.prototype.send = function() { var XML = this; var id = XML._id; var method = XML._method.toUpperCase(); var requestHead = XML._setHead[id]; var url = XML._url; var args = [].slice.call(arguments), data = args[0], saveData = ''; //监听列表中创建一条请求 if (!self.networkList[id]) { self.networkList[id] = {} } //保存请求方法 self.networkList[id].method = method; var { url, params } = self.handleReqUrl(url); //处理请求url和params self.networkList[id].url = url; self.networkList[id].params = params; //保存自定义请求头 if (requestHead) { self.networkList[id].requestHead = Object.assign({},requestHead); delete XML._setHead[id]; } //如果是post数据保存 if (method === 'POST') { if (Tools.isString(data)) { saveData = data; } } self.networkList[id].data = saveData; //开始发送 self.handleSendXML(id) return self._send.apply(XML, args); } } private patchFetch = () =>{ if(!window.fetch){ return false; } var self = this self._fetch = window.fetch //https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/fetch#%E5%8F%82%E6%95%B0 window.fetch = function (input, init=undefined) { var fetchSelf = this var args = arguments var startTime = new Date().getTime() var id = Tools.getUniqueID(); if (!self.networkList[id]) { self.networkList[id] = {} } var { url, params } = self.handleReqUrl(input.toString()); //处理请求url和params self.networkList[id].type = 'fetch' self.networkList[id].url = url; self.networkList[id].params = params; if(init && !Tools.isEmptyObject(init)){ self.networkList[id].method = init.method?init.method:'get'; self.networkList[id].data = init.body?init.body:''; self.networkList[id]._setHead = init.headers?init.headers:undefined }else{ self.networkList[id].method = 'get'; self.networkList[id].data = ''; self.networkList[id]._setHead = undefined } self.handleSendXML(id) return new Promise(function (resolve, reject) { var promise try { promise = self._fetch.apply(fetchSelf, args) } catch (error) { self.networkList[id].costTime = new Date().getTime() - startTime +'ms' self.networkList[id].response = 'fetch error:'+error self.networkList[id].status = 0; self.networkList[id].responseHead = ''; self.networkList[id].responseType = 'error'; setTimeout(function() { self.handleDoneXML(id) }) reject(error) return } promise.then( function (response) { response = response.clone() self.networkList[id].costTime = new Date().getTime() - startTime +'ms' self.networkList[id].response = self.isCatchResponseContent?response.body:'no-catch-responseContent' self.networkList[id].status = response.status; self.networkList[id].responseType = response.type; var headers = Tools.toArray(response.headers.keys()) if(!Tools.isEmptyArray(headers)){ self.networkList[id].responseHead = {}; headers.forEach( key =>{ self.networkList[id].responseHead[key] = response.headers.get(key) }) }else{ self.networkList[id].responseHead = undefined } setTimeout(function() { self.handleDoneXML(id) }) resolve(response) }, function (error) { self.networkList[id].costTime = new Date().getTime() - startTime +'ms' self.networkList[id].response = 'fetch error:'+error self.networkList[id].status = 0; self.networkList[id].responseHead = ''; self.networkList[id].responseType = 'error'; setTimeout(function() { self.handleDoneXML(id) }) reject(error) } ) }) } } private handleJudgeDisbale = (ajaxInfo)=>{ var { ignoreUrls } = this if(Tools.isEmptyObject(ajaxInfo) || !ajaxInfo.url){ return false; } const { url } = ajaxInfo //判断是否是是屏蔽url if (!Tools.isEmptyArray(ignoreUrls)) { var unReport = false; ignoreUrls.forEach(function(item) { if (url.indexOf(item) > -1) { unReport = true; return false; } }); if (unReport) { return false; } } return true; } private handleSendXML = (id)=>{ const ajaxInfo = this.networkList[id] if(!this.isCatch || !this.handleJudgeDisbale(ajaxInfo)){ return false; } this.handleXhrListers.forEach((handleHook)=>{ const { method,params,requestHead,url } = ajaxInfo var type = 'request' var info = { method, params, requestHead, url, ajaxInfo } try{ handleHook(type,info) }catch(e){ console.log(e) } }) } private handleDoneXML = (id)=>{ let ajaxInfo = this.networkList[id] if(!this.isCatch || !this.handleJudgeDisbale(ajaxInfo)){ delete this.networkList[id] return false; } this.handleXhrListers.forEach((handleHook)=>{ const { costTime,response,status,responseHead,url } = ajaxInfo var type = 'response' var info = { costTime, response, status, responseHead, url, ajaxInfo } try{ handleHook(type,info) }catch(e){ console.log(e) } }) delete this.networkList[id] } private handleReqUrl =(url:string)=> { //处理下解码URL url = (window).decodeURIComponent(url); let params,baseUrl,query //判断URL后面是否存在参数 if (url.indexOf('?') === -1) { baseUrl = url; } else { query = url.indexOf('?'); baseUrl = url.substring(0, query); query = url.substring(query + 1, url.length); params = {}; query = query.split('&'); // => ['b=c', 'd=?e'] for (let q of query) { q = q.split('='); params[q[0]] = q[1]; } } return { url: baseUrl, params: params } } public addRequestListenr = (onHandleListener:(type,info)=>any)=>{ this.handleXhrListers.push(onHandleListener) } public stopRequestCatch = function(){ //这种方式会和angular 6的zone 等polyfills.js产生冲突 // (window).XMLHttpRequest.prototype.open = this._open // (window).XMLHttpRequest.prototype.send = this._send // (window).XMLHttpRequest.prototype.setRequestHeader = this._setRequestHeader // this._open = false // this._send = false // this._setRequestHeader = false this.isCatch = false } public resumeRequestCatch = function(){ this.isCatch = true } } export default TracerNetwork