This week pending requests landed in GraphQL Network Inspector. It's been a frequently requested feature and we're excited to share it with you.
In this post, we'll take a look at how we implemented this feature and how you can use the Chrome API to gather requests yourself.
How requests currently works
We hook into events from the chrome API to listen for request data. Currently that data comes from one API.
chrome.devtools.network.onRequestFinished
This is a common approach for network monitoring tools. We listen for the onRequestFinished
event which provides both the request and response at once.
export const onRequestFinished = (
cb: (e: chrome.devtools.network.Request) => void
) => {
const chrome = chromeProvider()
chrome.devtools.network.onRequestFinished.addListener(cb)
return () => {
chrome.devtools.network.onRequestFinished.removeListener(cb)
}
}
This event only fires when a request is completed. This means that we have no access to requests in-flight.
Adding pending requests
It would be great if we could access an API like chrome.devtools.network.onRequestStarted
and have a common id to associate the request with the response. Unfortunately, this API doesn't exist, and neither does an id.
Instead we need to use a combination of three API's to build up a request/response pair.
chrome.webRequest.onBeforeRequest
https://developer.chrome.com/docs/extensions/reference/api/webRequest#event-onBeforeRequest
First we listen for a request starting. This event fires just before the request is sent and we can access the request body to store for later
export const onBeforeRequest = (
cb: (e: chrome.webRequest.WebRequestBodyDetails) => void
) => {
const chrome = chromeProvider()
const currentTabId = chrome.devtools.inspectedWindow.tabId
chrome.webRequest.onBeforeRequest.addListener(
cb,
{ urls: ['<all_urls>'], tabId: currentTabId },
['requestBody']
)
return () => {
chrome.webRequest.onBeforeRequest.removeListener(cb)
}
}
We then need to grab the requests headers which are not available in the onBeforeRequest
event.
To get request headers we use the chrome.webRequest.onBeforeSendHeaders
event.
export const onBeforeSendHeaders = (
cb: (e: chrome.webRequest.WebRequestHeadersDetails) => void
) => {
const chrome = chromeProvider()
const currentTabId = chrome.devtools.inspectedWindow.tabId
chrome.webRequest.onBeforeSendHeaders.addListener(
cb,
{ urls: ['<all_urls>'], tabId: currentTabId },
['requestHeaders']
)
return () => {
chrome.webRequest.onBeforeSendHeaders.removeListener(cb)
}
}
Data from onBeforeRequest
and onBeforeSendHeaders
contain a shared id. We can use this to associate the two events.
At this point we can display the pending request data within the table view.
Merging the response
The next step is using the original chrome.devtools.network.onRequestFinished
event to connect the response data with the request. As mentioned, this only fires once the response has been received.
Unfortunately, onRequestFinished
doesn't contain the same id from onBeforeRequest
. So we need a different way to match the data.
What we do have though is the request data itself. This means we can use the request data we already have stored to match the request data found within the onRequestFinished
event.
We compare:
- URL
- Method
- Request Headers
- Request Body
If all of these match, we can be confident wwe can merge the response data with our pending request.
export const matchWebAndNetworkRequest = (
networkRequest: chrome.devtools.network.Request,
webRequest: chrome.webRequest.WebRequestBodyDetails,
webRequestHeaders: IHeader[]
): boolean => {
try {
const webRequestBody = getRequestBodyFromWebRequestBodyDetails(
webRequest,
webRequestHeaders
)
const networkRequestBody = getRequestBodyFromNetworkRequest(networkRequest)
const isMethodMatch = webRequest.method === networkRequest.request.method
const isBodyMatch = webRequestBody === networkRequestBody
const isUrlMatch = webRequest.url === networkRequest.request.url
const isHeadersMatch = matchHeaders(
webRequestHeaders,
networkRequest.request.headers
)
return isMethodMatch && isBodyMatch && isUrlMatch && isHeadersMatch
} catch (e) {
return false
}
}
Once matched we merge the data and the table displays the response data. The status code, response headers, and response body are all displayed.
If you're interested in the source code. You can find the github repo and some files of interest here:
https://github.com/warrenday/graphql-network-inspector/blob/master/src/services/networkMonitor.ts
https://github.com/warrenday/graphql-network-inspector/blob/master/src/hooks/useNetworkMonitor.ts