Become a GraphQL expert

We're launching a brand new course. Pre-sale is now live.

View course

Overstacked

Mon Jun 17 2024

Pending Requests for GraphQL Network Inspector

A technical overview of using the Chrome API to gather request data

cover image

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.

Pending requests in a table view in GraphQL Network Inspector

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


You can get more actionable ideas in my popular email newsletter. Each week, I share deep dives like this, plus the latest product updates. Join over 80,000 developers using my products and tools. Enter your email and don't miss an update.

You'll stay in the loop with my latest updates. Unsubscribe at any time.

© Copyright 2024 Overstacked. All rights reserved.

Created by Warren Day