From 374ec6c8f178e016afdf70c2ba72836fb349f813 Mon Sep 17 00:00:00 2001 From: jwallet Date: Mon, 18 Dec 2023 19:16:08 -0500 Subject: [PATCH 1/2] Debounce new requests changes coming from the callback --- src/Logger.ts | 37 ++++++++++++++++++++++++++------ src/NetworkRequestInfo.ts | 3 +++ src/__tests__/Logger.spec.ts | 9 +++++++- src/components/NetworkLogger.tsx | 10 ++++----- src/constant.ts | 3 +++ src/types.ts | 5 +++++ src/utils/debounce.ts | 17 +++++++++++++++ 7 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 src/constant.ts create mode 100644 src/utils/debounce.ts diff --git a/src/Logger.ts b/src/Logger.ts index 7306f29..f91fafe 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -3,6 +3,8 @@ 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; @@ -14,20 +16,32 @@ type XHR = { export default class Logger { private requests: NetworkRequestInfo[] = []; private xhrIdMap: { [key: number]: number } = {}; - private maxRequests: number = 500; + private maxRequests: number = LOGGER_MAX_REQUESTS; + private refreshRate: number = LOGGER_REFRESH_RATE; + private latestRequestUpdatedAt: number = 0; private ignoredHosts: Set | undefined; private ignoredUrls: Set | undefined; private ignoredPatterns: RegExp[] | undefined; public enabled = false; public paused = false; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - callback = (requests: any[]) => {}; + callback = (_: NetworkRequestInfo[]) => null; 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; const requestIndex = this.requests.length - this.xhrIdMap[xhrIndex] - 1; @@ -113,7 +127,7 @@ export default class Logger { startTime: Date.now(), dataSent: data, }); - this.callback(this.requests); + this.debouncedCallback(); }; private responseCallback = ( @@ -132,7 +146,7 @@ export default class Logger { responseURL, responseType, }); - this.callback(this.requests); + this.debouncedCallback(); }; enableXHRInterception = (options?: StartNetworkLoggingOptions) => { @@ -171,6 +185,16 @@ export default class Logger { 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; } @@ -204,6 +228,7 @@ export default class Logger { clearRequests = () => { this.requests = []; - this.callback(this.requests); + this.latestRequestUpdatedAt = 0; + this.debouncedCallback(); }; } diff --git a/src/NetworkRequestInfo.ts b/src/NetworkRequestInfo.ts index 113c2cd..f415e71 100644 --- a/src/NetworkRequestInfo.ts +++ b/src/NetworkRequestInfo.ts @@ -24,12 +24,14 @@ export default class NetworkRequestInfo { startTime: number = 0; endTime: number = 0; gqlOperation?: string; + updatedAt: number = 0; constructor(id: string, type: string, method: RequestMethod, url: string) { this.id = id; this.type = type; this.method = method; this.url = url; + this.updatedAt = Date.now(); } get duration() { @@ -61,6 +63,7 @@ export default class NetworkRequestInfo { const data = this.parseData(values.dataSent); this.gqlOperation = data?.operationName; } + this.updatedAt = Date.now(); } private escapeQuotes(value: string) { diff --git a/src/__tests__/Logger.spec.ts b/src/__tests__/Logger.spec.ts index f64994b..c98ad2d 100644 --- a/src/__tests__/Logger.spec.ts +++ b/src/__tests__/Logger.spec.ts @@ -1,6 +1,7 @@ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor'; import { warn } from '../utils/logger'; import Logger from '../Logger'; +import { LOGGER_REFRESH_RATE } from '../constant'; jest.mock('react-native/Libraries/Blob/FileReader', () => ({})); jest.mock('react-native/Libraries/Network/XHRInterceptor', () => ({ @@ -25,7 +26,7 @@ beforeEach(() => { describe('enableXHRInterception', () => { it('should do nothing if interceptor has already been enabled', () => { - (warn as jest.Mock).mockImplementationOnce(() => {}); + (warn as jest.Mock).mockImplementationOnce(() => null); const logger = new Logger(); (XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValueOnce( @@ -168,6 +169,8 @@ describe('getRequests', () => { describe('clearRequests', () => { it('should clear the requests', () => { + jest.useFakeTimers(); + const logger = new Logger(); logger.callback = jest.fn(); @@ -177,9 +180,13 @@ describe('clearRequests', () => { logger.clearRequests(); + jest.advanceTimersByTime(LOGGER_REFRESH_RATE); + expect(logger.getRequests()).toEqual([]); expect(logger.callback).toHaveBeenCalledTimes(1); expect(logger.callback).toHaveBeenCalledWith([]); + + jest.useRealTimers(); }); }); diff --git a/src/components/NetworkLogger.tsx b/src/components/NetworkLogger.tsx index 2b66d17..f98fb91 100644 --- a/src/components/NetworkLogger.tsx +++ b/src/components/NetworkLogger.tsx @@ -23,9 +23,7 @@ const sortRequests = (requests: NetworkRequestInfo[], sort: 'asc' | 'desc') => { }; const NetworkLogger: React.FC = ({ theme = 'light', sort = 'desc' }) => { - const [requests, setRequests] = useState( - sortRequests(logger.getRequests(), sort) - ); + const [requests, setRequests] = useState(logger.getRequests()); const [request, setRequest] = useState(); const [showDetails, _setShowDetails] = useState(false); const [mounted, setMounted] = useState(false); @@ -43,7 +41,7 @@ const NetworkLogger: React.FC = ({ theme = 'light', sort = 'desc' }) => { useEffect(() => { logger.setCallback((updatedRequests: NetworkRequestInfo[]) => { - setRequests(sortRequests(updatedRequests, sort)); + setRequests(updatedRequests); }); logger.enableXHRInterception(); @@ -104,8 +102,8 @@ const NetworkLogger: React.FC = ({ theme = 'light', sort = 'desc' }) => { }, [paused, getHar]); const requestsInfo = useMemo(() => { - return requests.map((r) => r.toRow()); - }, [requests]); + return sortRequests(requests, sort).map((r) => r.toRow()); + }, [sort, requests]); return ( diff --git a/src/constant.ts b/src/constant.ts new file mode 100644 index 0000000..1a9ef8c --- /dev/null +++ b/src/constant.ts @@ -0,0 +1,3 @@ +// StartNetworkLoggingOptions +export const LOGGER_MAX_REQUESTS: number = 500; +export const LOGGER_REFRESH_RATE: number = 50; diff --git a/src/types.ts b/src/types.ts index 4f25966..48c9d87 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,6 +25,11 @@ export type StartNetworkLoggingOptions = { * e.g. a dev/debuging program */ forceEnable?: boolean; + /** + * Refresh rate of the logger in milliseconds + * @default 50 + */ + refreshRate?: number; }; export type NetworkRequestInfoRow = Pick< diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts new file mode 100644 index 0000000..3995f52 --- /dev/null +++ b/src/utils/debounce.ts @@ -0,0 +1,17 @@ +// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_debounce +function debounce(func: Function, wait: number, immediate: boolean = false) { + let timeout: ReturnType | undefined; + return function () { + const args = arguments; + clearTimeout(timeout); + // @ts-ignore + if (immediate && !timeout) func.apply(this, args); + timeout = setTimeout(function () { + timeout = undefined; + // @ts-ignore + if (!immediate) func.apply(this, args); + }, wait); + }; +} + +export default debounce; From 226e794365a9e7e155ec3f1fc985e51b91f454eb Mon Sep 17 00:00:00 2001 From: jwallet Date: Tue, 19 Dec 2023 11:00:20 -0500 Subject: [PATCH 2/2] Update src/components/NetworkLogger.tsx --- src/components/NetworkLogger.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NetworkLogger.tsx b/src/components/NetworkLogger.tsx index f98fb91..3fe4a6f 100644 --- a/src/components/NetworkLogger.tsx +++ b/src/components/NetworkLogger.tsx @@ -41,7 +41,7 @@ const NetworkLogger: React.FC = ({ theme = 'light', sort = 'desc' }) => { useEffect(() => { logger.setCallback((updatedRequests: NetworkRequestInfo[]) => { - setRequests(updatedRequests); + setRequests([...updatedRequests]); }); logger.enableXHRInterception();