UNPKG

@corrbo/react-native-dev-console

Version:
287 lines (251 loc) 7.95 kB
import XHRInterceptor from './XHRInterceptor'; import NetworkRequestInfo from './NetworkRequestInfo'; import { Headers, RequestMethod, StartNetworkLoggingOptions } from './types'; import extractHost from './utils/extractHost'; import { warn } from './utils/logger'; import debounce from './utils/debounce'; import { LOGGER_REFRESH_RATE, LOGGER_MAX_REQUESTS } from './constant'; let nextXHRId = 0; type XHR = { _index: number; responseHeaders?: Headers; }; export default class Logger { private requests: NetworkRequestInfo[] = []; private pausedRequests: NetworkRequestInfo[] = []; private xhrIdMap: Map<number, () => number> = new Map(); private maxRequests: number = LOGGER_MAX_REQUESTS; private refreshRate: number = LOGGER_REFRESH_RATE; private latestRequestUpdatedAt: number = 0; private ignoredHosts: Set<string> | undefined; private ignoredUrls: Set<string> | undefined; private ignoredPatterns: RegExp[] | undefined; private paused = false; public enabled = false; callback = (_: NetworkRequestInfo[]) => null; isPaused = this.paused; setCallback = (callback: any) => { this.callback = callback; }; debouncedCallback = debounce(() => { if ( !this.latestRequestUpdatedAt || this.requests.some((r) => r.updatedAt > this.latestRequestUpdatedAt) ) { this.latestRequestUpdatedAt = Date.now(); // prevent mutation of requests for all subscribers this.callback([...this.requests]); } }, this.refreshRate); private getRequest = (xhrIndex?: number) => { if (xhrIndex === undefined) return undefined; if (!this.xhrIdMap.has(xhrIndex)) return undefined; const index = this.xhrIdMap.get(xhrIndex)!(); return (this.paused ? this.pausedRequests : this.requests)[index]; }; private updateRequest = ( index: number, update: Partial<NetworkRequestInfo> ) => { const networkInfo = this.getRequest(index); if (!networkInfo) return; networkInfo.update(update); }; private openCallback = (method: RequestMethod, url: string, xhr: XHR) => { if (this.ignoredHosts) { const host = extractHost(url); if (host && this.ignoredHosts.has(host)) { return; } } if (this.ignoredUrls && this.ignoredUrls.has(url)) { return; } if (this.ignoredPatterns) { if ( this.ignoredPatterns.some((pattern) => pattern.test(`${method} ${url}`)) ) { return; } } xhr._index = nextXHRId++; this.xhrIdMap.set(xhr._index, () => { return (this.paused ? this.pausedRequests : this.requests).findIndex( (r) => r.id === `${xhr._index}` ); }); const newRequest = new NetworkRequestInfo( `${xhr._index}`, 'XMLHttpRequest', method, url ); if (this.paused) { const logsLength = this.pausedRequests.length + this.requests.length; if (logsLength > this.maxRequests) { if (this.requests.length > 0) this.requests.pop(); else this.pausedRequests.pop(); } this.pausedRequests.push(newRequest); } else { this.requests.unshift(newRequest); if (this.requests.length > this.maxRequests) { this.requests.pop(); } } }; private requestHeadersCallback = ( header: string, value: string, xhr: XHR ) => { const networkInfo = this.getRequest(xhr._index); if (!networkInfo) return; networkInfo.requestHeaders[header] = value; }; private headerReceivedCallback = ( responseContentType: string, responseSize: number, responseHeaders: Headers, xhr: XHR ) => { this.updateRequest(xhr._index, { responseContentType, responseSize, responseHeaders: xhr.responseHeaders, }); }; private sendCallback = (data: string, xhr: XHR) => { this.updateRequest(xhr._index, { startTime: Date.now(), dataSent: data, }); this.debouncedCallback(); }; private responseCallback = ( status: number, timeout: number, response: string, responseURL: string, responseType: string, xhr: XHR ) => { this.updateRequest(xhr._index, { endTime: Date.now(), status, timeout, response, responseURL, responseType, }); this.debouncedCallback(); }; enableXHRInterception = (options?: StartNetworkLoggingOptions) => { if ( this.enabled || (XHRInterceptor.isInterceptorEnabled() && !options?.forceEnable) ) { if (!this.enabled) { warn( 'network interceptor has not been enabled as another interceptor is already running (e.g. another debugging program). Use option `forceEnable: true` to override this behaviour.' ); } return; } if (options?.maxRequests !== undefined) { if (typeof options.maxRequests !== 'number' || options.maxRequests < 1) { warn( 'maxRequests must be a number greater than 0. The logger has not been started.' ); return; } this.maxRequests = options.maxRequests; } if (options?.ignoredHosts) { if ( !Array.isArray(options.ignoredHosts) || typeof options.ignoredHosts[0] !== 'string' ) { warn( 'ignoredHosts must be an array of strings. The logger has not been started.' ); return; } this.ignoredHosts = new Set(options.ignoredHosts); } if (options?.refreshRate) { if (typeof options.refreshRate !== 'number' || options.refreshRate < 1) { warn( 'refreshRate must be a number greater than 0. The logger has not been started.' ); return; } this.refreshRate = options.refreshRate; } if (options?.ignoredPatterns) { this.ignoredPatterns = options.ignoredPatterns; } if (options?.ignoredUrls) { if ( !Array.isArray(options.ignoredUrls) || typeof options.ignoredUrls[0] !== 'string' ) { warn( 'ignoredUrls must be an array of strings. The logger has not been started.' ); return; } this.ignoredUrls = new Set(options.ignoredUrls); } XHRInterceptor.setOpenCallback(this.openCallback); XHRInterceptor.setRequestHeaderCallback(this.requestHeadersCallback); XHRInterceptor.setHeaderReceivedCallback(this.headerReceivedCallback); XHRInterceptor.setSendCallback(this.sendCallback); XHRInterceptor.setResponseCallback(this.responseCallback); XHRInterceptor.enableInterception(); this.enabled = true; }; getRequests = () => { return this.requests; }; clearRequests = () => { this.requests = []; this.pausedRequests = []; this.latestRequestUpdatedAt = 0; this.debouncedCallback(); }; onPausedChange = (paused: boolean) => { if (!paused) { this.pausedRequests.forEach((request) => { this.requests.unshift(request); if (this.requests.length > this.maxRequests) { this.requests.pop(); } }); this.pausedRequests = []; this.debouncedCallback(); } this.paused = paused; }; disableXHRInterception = () => { if (!this.enabled) return; this.clearRequests(); nextXHRId = 0; this.enabled = false; this.paused = false; this.xhrIdMap.clear(); this.maxRequests = LOGGER_MAX_REQUESTS; this.refreshRate = LOGGER_REFRESH_RATE; this.ignoredHosts = undefined; this.ignoredUrls = undefined; this.ignoredPatterns = undefined; const noop = () => null; // manually reset callbacks even if the XHRInterceptor lib does it for us with 'disableInterception' XHRInterceptor.setOpenCallback(noop); XHRInterceptor.setRequestHeaderCallback(noop); XHRInterceptor.setHeaderReceivedCallback(noop); XHRInterceptor.setSendCallback(noop); XHRInterceptor.setResponseCallback(noop); XHRInterceptor.disableInterception(); }; }