[GH-ISSUE #412] Support GraphQL Previewer #407

Open
opened 2026-03-03 19:18:20 +03:00 by kerem · 21 comments
Owner

Originally created by @NghiaTranUIT on GitHub (Feb 25, 2020).
Original GitHub issue: https://github.com/ProxymanApp/Proxyman/issues/412

Originally assigned to: @NghiaTranUIT on GitHub.

🐶 Brief

Currently, it's super difficult to see the content of GraphQL requests even though in pretty JSON Previewer. The original discussion at https://www.reddit.com/r/macapps/comments/f7u7bb/proxyman_modern_and_delightful_web_debugging/fihjlwq?utm_source=share&utm_medium=web2x

Let see how we support it 🙌

👑 Criteria

  • Research how to support GraphQL Previewer
  • Add option to filter particular key in GraphQL request, since those GraphQL URLs are the same
Originally created by @NghiaTranUIT on GitHub (Feb 25, 2020). Original GitHub issue: https://github.com/ProxymanApp/Proxyman/issues/412 Originally assigned to: @NghiaTranUIT on GitHub. ## 🐶 Brief Currently, it's super difficult to see the content of GraphQL requests even though in pretty JSON Previewer. The original discussion at https://www.reddit.com/r/macapps/comments/f7u7bb/proxyman_modern_and_delightful_web_debugging/fihjlwq?utm_source=share&utm_medium=web2x Let see how we support it 🙌 ## 👑 Criteria - [x] Research how to support GraphQL Previewer - [x] Add option to filter particular key in GraphQL request, since those GraphQL URLs are the same
Author
Owner

@atdrago commented on GitHub (Jun 16, 2020):

@NghiaTranUIT Loving Proxyman. I've already recommended it to a couple different people.

I'm curious about this issue. Would it allow Map Local and Map Remote for GraphQL queries and mutations? This is something I've wanted for a long time in Charles, and even sent in feature requests twice for.

In other words, I'd like to map requests to /graphql differently based on the request body. Map Local/Remote are based on path, but since all GraphQL requests typically go to the same path with a different body (operationName, variables, and query) it's impossible to use Map Local/Remote for GraphQL right now, as far as I know. Would that come along with this story or should I file a different issue?

Thank you!!

<!-- gh-comment-id:644846112 --> @atdrago commented on GitHub (Jun 16, 2020): @NghiaTranUIT Loving Proxyman. I've already recommended it to a couple different people. I'm curious about this issue. Would it allow Map Local and Map Remote for GraphQL queries and mutations? This is something I've wanted for a long time in Charles, and even sent in feature requests twice for. In other words, I'd like to map requests to `/graphql` differently based on the request body. Map Local/Remote are based on path, but since all GraphQL requests typically go to the same path with a different body (`operationName`, `variables`, and `query`) it's impossible to use Map Local/Remote for GraphQL right now, as far as I know. Would that come along with this story or should I file a different issue? Thank you!!
Author
Owner

@NghiaTranUIT commented on GitHub (Jun 16, 2020):

@atdrago Thank you for your kind words. Since I've just released the Proxyman v2 - which is rewritten with Swift- NIO (https://github.com/ProxymanApp/Proxyman/releases/tag/2.0.0), I totally free to push up new features.

GraphQL is what I'm looking for since there are not many tools that support debugging GraphQL.


You're right. At the moment, Map Local/Remote is based on the path, so it's impossible to map the content of the GraphQL. On the other hand, mapping base on operationName, variables, and query seems easier to implement.

I'm planning to support this soon 👍

<!-- gh-comment-id:644850584 --> @NghiaTranUIT commented on GitHub (Jun 16, 2020): @atdrago Thank you for your kind words. Since I've just released the Proxyman v2 - which is rewritten with Swift- NIO (https://github.com/ProxymanApp/Proxyman/releases/tag/2.0.0), I totally free to push up new features. GraphQL is what I'm looking for since there are not many tools that support debugging GraphQL. ----------- You're right. At the moment, Map Local/Remote is based on the path, so it's impossible to map the content of the GraphQL. On the other hand, mapping base on `operationName, variables, and query ` seems easier to implement. I'm planning to support this soon 👍
Author
Owner

@atdrago commented on GitHub (Jun 16, 2020):

Thanks for your quick response!

This is so exciting to hear! I actually saw v2 come through. I updated and it inspired me to come check if anyone has requested GraphQL support yet. Keep up the great work!!!

<!-- gh-comment-id:644865969 --> @atdrago commented on GitHub (Jun 16, 2020): Thanks for your quick response! This is so exciting to hear! I actually saw v2 come through. I updated and it inspired me to come check if anyone has requested GraphQL support yet. Keep up the great work!!!
Author
Owner

@nuynait commented on GitHub (Sep 22, 2020):

@atdrago I want to map local one of the graphQL queries.

One of my solution of map local graphQL queries are appending the query name into the URL. So domain.com/api/graphql changed to domain.com/api/graphql?gqlquery=MyQuery. I plan to modify the URL using the script tool.

Once the script tool has change the request url, I can then map local domain.com/api/graphql?gqlquery=MyQuery to change the response of only that query.

However, when I try this solution, I found that the map local is not applied.
Here are some screenshots:

Captured 2020-09-22 at 15 36 34
Captured 2020-09-22 at 15 45 03
Captured 2020-09-22 at 15 46 28

An interesting fact, that if I choose repeat the request, the response is the local response I have in map local. When repeat the request, the URL is already with the gqlquery=MyQuery, that's why map local works.

It seems that map local only recognize the original url before it changed by the script tool.

It would be really cool if this works.

<!-- gh-comment-id:696942785 --> @nuynait commented on GitHub (Sep 22, 2020): @atdrago I want to map local one of the graphQL queries. One of my solution of map local graphQL queries are appending the query name into the URL. So `domain.com/api/graphql` changed to `domain.com/api/graphql?gqlquery=MyQuery`. I plan to modify the URL using the script tool. Once the script tool has change the request url, I can then map local `domain.com/api/graphql?gqlquery=MyQuery` to change the response of only that query. However, when I try this solution, I found that the map local is not applied. Here are some screenshots: ![Captured 2020-09-22 at 15 36 34](https://user-images.githubusercontent.com/2152195/93930220-059e5c80-fceb-11ea-9896-86f8dde1fd22.png) ![Captured 2020-09-22 at 15 45 03](https://user-images.githubusercontent.com/2152195/93930224-07682000-fceb-11ea-9ff9-e0f4f31b21d6.png) ![Captured 2020-09-22 at 15 46 28](https://user-images.githubusercontent.com/2152195/93930229-08994d00-fceb-11ea-953f-23b23448a997.png) An interesting fact, that if I choose repeat the request, the response is the local response I have in map local. When repeat the request, the URL is already with the `gqlquery=MyQuery`, that's why map local works. It seems that map local only recognize the original url before it changed by the script tool. It would be really cool if this works.
Author
Owner

@NghiaTranUIT commented on GitHub (Sep 23, 2020):

Hey @nuynait, at the moment, the Scripting will run after the Map Local. Therefore, it's a expected behavior.

To support GraphQL, I suggest that you can use

How to setup

  • Create a Scripting with URL Rule: domain.com/api/graphql (No query param)
// /Users/<username>/Library/Application Support/com.proxyman.NSProxy/users/B02D96D5.default_message_32E64A5B.json
const file = require("@users/B02D96D5.default_message_32E64A5B.json");

function onRequest(context, url, request) {

  // 1. Extract the queryName from the request
  var queryName = request.body.query.match(/\S+/gi)[1].split('(').shift();

  // 2. Save to sharedState
  sharedState.queryName = queryName

  // Done
  return request;
}

function onResponse(context, url, request, response) {

  // 3. Check if it's the request we need to map
  if (sharedState.queryName == "user") {
    
    // 4. Import the local file by Action Button -> Import
    // Get the local JSON file and set it as a body (like Map Local)
    response.headers["Content-Type"] = "application/json";
    response.body = file;
  }

  // Done
  return response;
}

The above script will achieve the same result that you're trying 👍

<!-- gh-comment-id:697101594 --> @NghiaTranUIT commented on GitHub (Sep 23, 2020): Hey @nuynait, at the moment, the Scripting will run after the Map Local. Therefore, it's a expected behavior. To support GraphQL, I suggest that you can use - Scripting with Import Tool: It allows you to import a local file and set it as a Body Response (Same with Map Local) https://docs.proxyman.io/scripting/snippet-code#json 🙌 - SharedState to share the query from the onRequest to onResponse: https://docs.proxyman.io/scripting/environment-variables#1-shared-state ### How to setup - Create a Scripting with URL Rule: domain.com/api/graphql (No query param) ```js // /Users/<username>/Library/Application Support/com.proxyman.NSProxy/users/B02D96D5.default_message_32E64A5B.json const file = require("@users/B02D96D5.default_message_32E64A5B.json"); function onRequest(context, url, request) { // 1. Extract the queryName from the request var queryName = request.body.query.match(/\S+/gi)[1].split('(').shift(); // 2. Save to sharedState sharedState.queryName = queryName // Done return request; } function onResponse(context, url, request, response) { // 3. Check if it's the request we need to map if (sharedState.queryName == "user") { // 4. Import the local file by Action Button -> Import // Get the local JSON file and set it as a body (like Map Local) response.headers["Content-Type"] = "application/json"; response.body = file; } // Done return response; } ``` The above script will achieve the same result that you're trying 👍
Author
Owner

@NghiaTranUIT commented on GitHub (Sep 23, 2020):

Meanwhile, it's correct that the Scripting Tool should be executed before other tools, such as Map Local, Remote, ...

I will revisit the logic and improve it 👍

<!-- gh-comment-id:697101814 --> @NghiaTranUIT commented on GitHub (Sep 23, 2020): Meanwhile, it's correct that the Scripting Tool should be executed before other tools, such as Map Local, Remote, ... I will revisit the logic and improve it 👍
Author
Owner

@nuynait commented on GitHub (Sep 23, 2020):

Hi @NghiaTranUIT Thanks for the quick and detailed reply. Thanks for all the tips on how to set up scripting so that I can map to the local response for GQL query. That's really helpful to someone who is new to Proxyman like me.

I would also be very interesting to know when you will be improving the logic since using Map Local tool instead of the scripting would be a much clean and interesting solution instead of doing the map in the scripting tool.

<!-- gh-comment-id:697117965 --> @nuynait commented on GitHub (Sep 23, 2020): Hi @NghiaTranUIT Thanks for the quick and detailed reply. Thanks for all the tips on how to set up scripting so that I can map to the local response for GQL query. That's really helpful to someone who is new to Proxyman like me. I would also be very interesting to know when you will be improving the logic since using Map Local tool instead of the scripting would be a much clean and interesting solution instead of doing the map in the scripting tool.
Author
Owner

@NghiaTranUIT commented on GitHub (Sep 24, 2020):

Ultimately, I will find a way to support GraphQL Rule, so we can define which query in the GraphQL body 👍

For now, I would suggest that using the Scripting tool 😄

<!-- gh-comment-id:698073120 --> @NghiaTranUIT commented on GitHub (Sep 24, 2020): Ultimately, I will find a way to support GraphQL Rule, so we can define which query in the GraphQL body 👍 For now, I would suggest that using the Scripting tool 😄
Author
Owner

@TeresaCastle commented on GitHub (Dec 18, 2020):

Hi @NghiaTranUIT. I too am trying to figure out how to map local on graphql when there are multiple calls made, but only one endpoint. I used your script above, but am still not seeing my local file updating when I pull to refresh on the device when the script is running. If I repeat the request, an new request is displayed in the Proxyman and the response is updated with the file I want to replace it with from the script, but the changes do not display on the device. Could you help me figure out what I'm missing? Here is my script:

// /Users/teresa.castle/Library/Application Support/com.proxyman.NSProxy/users/AE397ECE.homepage_article.json
const file = require("@users/AE397ECE.homepage_article.json"); // replace intercepted response with this file

function onRequest(context, url, request) {
var operationName = request.body.query.match(/\S+/gi)[1].split('(').shift();
request.queries["gqlquery"]=operationName // adds operationName parameter
sharedState.operationName = operationName
console.log(sharedState.operationName)
return request;
}

function onResponse(context, url, request, response) {
console.log(sharedState.operationName)
if (sharedState.operationName == "appHomeScreen") {
response.headers["Content-Type"] = "application/json";
response.body = file; // should replace with the file from the const file = require(....) from above
}
return response;
}

<!-- gh-comment-id:748264946 --> @TeresaCastle commented on GitHub (Dec 18, 2020): Hi @NghiaTranUIT. I too am trying to figure out how to map local on graphql when there are multiple calls made, but only one endpoint. I used your script above, but am still not seeing my local file updating when I pull to refresh on the device when the script is running. If I repeat the request, an new request is displayed in the Proxyman and the response is updated with the file I want to replace it with from the script, but the changes do not display on the device. Could you help me figure out what I'm missing? Here is my script: // /Users/teresa.castle/Library/Application Support/com.proxyman.NSProxy/users/AE397ECE.homepage_article.json const file = require("@users/AE397ECE.homepage_article.json"); // replace intercepted response with this file function onRequest(context, url, request) { var operationName = request.body.query.match(/\S+/gi)[1].split('(').shift(); request.queries["gqlquery"]=operationName // adds operationName parameter sharedState.operationName = operationName console.log(sharedState.operationName) return request; } function onResponse(context, url, request, response) { console.log(sharedState.operationName) if (sharedState.operationName == "appHomeScreen") { response.headers["Content-Type"] = "application/json"; response.body = file; // should replace with the file from the const file = require(....) from above } return response; }
Author
Owner

@NghiaTranUIT commented on GitHub (Dec 19, 2020):

It seems correct @TeresaCastle, if you could not see the change, let add some console.log() to make sure it's correct.

For instance,

  1. I see you've already added console.log(sharedState.operationName) -> When making a request, do you see the log is appHomeScreen ?
  2. Do you see the log of console.log(sharedState.operationName) from onResponse() function?

It looks like the expression if (sharedState.operationName == "appHomeScreen") is false, so it the JSON file doesn't replace the Response Body

I suggest adding some console.log and debug to see if what is wrong 👍

<!-- gh-comment-id:748400616 --> @NghiaTranUIT commented on GitHub (Dec 19, 2020): It seems correct @TeresaCastle, if you could not see the change, let add some `console.log()` to make sure it's correct. For instance, 1. I see you've already added `console.log(sharedState.operationName)` -> When making a request, do you see the log is `appHomeScreen` ? 2. Do you see the log of `console.log(sharedState.operationName)` from onResponse() function? It looks like the expression `if (sharedState.operationName == "appHomeScreen") ` is false, so it the JSON file doesn't replace the Response Body I suggest adding some console.log and debug to see if what is wrong 👍
Author
Owner

@TeresaCastle commented on GitHub (Dec 21, 2020):

I was seeing appHomeScreen on all of my console.log(). I think I see what is wrong. I have "appHomeScreen" and the console.log() is returning "AppHomeScreen". I've updated the script and it looks like it is working as expected.

I appreciate you taking the time to respond. I'm a manual tester with little scripting experience, but after reading your response, it took me 5 seconds to figure out what was wrong with what I had been staring at for 8 hours, lol.

Thanks so much!!!

<!-- gh-comment-id:749070399 --> @TeresaCastle commented on GitHub (Dec 21, 2020): I was seeing appHomeScreen on all of my console.log(). I think I see what is wrong. I have "appHomeScreen" and the console.log() is returning "AppHomeScreen". I've updated the script and it looks like it is working as expected. I appreciate you taking the time to respond. I'm a manual tester with little scripting experience, but after reading your response, it took me 5 seconds to figure out what was wrong with what I had been staring at for 8 hours, lol. Thanks so much!!!
Author
Owner

@NghiaTranUIT commented on GitHub (Dec 22, 2020):

Glad to know that it works for you 🎉

<!-- gh-comment-id:749306779 --> @NghiaTranUIT commented on GitHub (Dec 22, 2020): Glad to know that it works for you 🎉
Author
Owner

@NghiaTranUIT commented on GitHub (Mar 15, 2021):

Just a friendly reminder: From build 2.20.0, we can quickly see the Query name of GraphQL requests on the main table.

It's super easier to distinguish graphQL request since the URLs are the same 🎉

CleanShot_2021-03-07_at_10_06_55_2x
<!-- gh-comment-id:799040465 --> @NghiaTranUIT commented on GitHub (Mar 15, 2021): Just a friendly reminder: From build 2.20.0, we can quickly see the Query name of GraphQL requests on the main table. It's super easier to distinguish graphQL request since the URLs are the same 🎉 <img width="1680" alt="CleanShot_2021-03-07_at_10_06_55_2x" src="https://user-images.githubusercontent.com/5878421/110228019-46540000-7f30-11eb-9ebc-c7e7a2929a2d.png">
Author
Owner

@atdrago commented on GitHub (Mar 15, 2021):

This is great and will definitely help identify the current GraphQL operation! Thank you @NghiaTranUIT !

Regarding my original comment wrt mapping local, we now add ?opname=${operationName} to the query string of our /graphql requests (as @nuynait and others have suggested). It solves the Map Local problem and makes Network logs way easier to read

<!-- gh-comment-id:799424313 --> @atdrago commented on GitHub (Mar 15, 2021): This is great and will definitely help identify the current GraphQL operation! Thank you @NghiaTranUIT ! Regarding my original comment wrt mapping local, we now add `?opname=${operationName}` to the query string of our `/graphql` requests (as @nuynait [and others](https://github.com/apollographql/apollo-link/issues/264#issuecomment-348177575) have suggested). It solves the Map Local problem _and_ makes Network logs way easier to read
Author
Owner

@NghiaTranUIT commented on GitHub (Mar 15, 2021):

Glad to know it helps you 😊

<!-- gh-comment-id:799425552 --> @NghiaTranUIT commented on GitHub (Mar 15, 2021): Glad to know it helps you 😊
Author
Owner

@NghiaTranUIT commented on GitHub (Mar 29, 2021):

Hey guys, I'm working on this feature (#840), which allows us to define a rule by QueryName from GraphQL's Request Body. Therefore, you can use any debugging tools with GraphQL too 😄

<!-- gh-comment-id:809033528 --> @NghiaTranUIT commented on GitHub (Mar 29, 2021): Hey guys, I'm working on this feature (#840), which allows us to define a rule by QueryName from GraphQL's Request Body. Therefore, you can use any debugging tools with GraphQL too 😄
Author
Owner

@NghiaTranUIT commented on GitHub (May 26, 2021):

Good news to @atdrago @TeresaCastle @nuynait @Elecweb @erickva and anyone who would like to use Breakpoint, Map Local with GraphQL request 🙌

Screen_Shot_2021-05-26_at_15_48_17 Screen_Shot_2021-05-26_at_15_46_13

If you guys find any problems/bugs, please report here. I appreciate it ❤️

<!-- gh-comment-id:848600748 --> @NghiaTranUIT commented on GitHub (May 26, 2021): Good news to @atdrago @TeresaCastle @nuynait @Elecweb @erickva and anyone who would like to use Breakpoint, Map Local with GraphQL request 🙌 - We've supported matching with GraphQL Query Name. - Please try this beta build: https://proxyman.s3.us-east-2.amazonaws.com/beta/Proxyman_2.26.0_Matching_Rule_For_GraphQL.dmg - You can set define a GraphQL Name, so it would match exactly the GraphQL request. Works well for Breakpoint, Map Local, Scripting, Block List, Allow List ✅ <img width="1218" alt="Screen_Shot_2021-05-26_at_15_48_17" src="https://user-images.githubusercontent.com/5878421/119632628-2fd88900-be3b-11eb-85b9-ef3a791a0520.png"> <img width="845" alt="Screen_Shot_2021-05-26_at_15_46_13" src="https://user-images.githubusercontent.com/5878421/119632730-47b00d00-be3b-11eb-98d6-0a2fd8e16593.png"> -------------- If you guys find any problems/bugs, please report here. I appreciate it ❤️
Author
Owner

@NghiaTranUIT commented on GitHub (Jun 5, 2021):

Debugging with GraphQL feature is available on the latest build (2.27.0). Please update it 😄

Thanks all 🙇

<!-- gh-comment-id:855179245 --> @NghiaTranUIT commented on GitHub (Jun 5, 2021): Debugging with GraphQL feature is available on the latest build (2.27.0). Please update it 😄 Thanks all 🙇
Author
Owner

@erickva commented on GitHub (Jun 5, 2021):

@NghiaTranUIT This is incredible news. I am no longer working on a Project with GraphQL, but it will happen again soon. Great effort! It will be a life saver. ❤️

<!-- gh-comment-id:855287310 --> @erickva commented on GitHub (Jun 5, 2021): @NghiaTranUIT This is incredible news. I am no longer working on a Project with GraphQL, but it will happen again soon. Great effort! It will be a life saver. ❤️
Author
Owner

@cameroncooke commented on GitHub (Jul 9, 2021):

Does this work with mutations as well or just queries?

<!-- gh-comment-id:877139968 --> @cameroncooke commented on GitHub (Jul 9, 2021): Does this work with mutations as well or just queries?
Author
Owner

@NghiaTranUIT commented on GitHub (Jul 9, 2021):

It works with mutation too @cameroncooke

For instance, the QueryName is ViewerNotificationsLastSeenUpdate in this graphQL Body.

{
"query": "mutation ViewerNotificationsLastSeenUpdate{response:viewerNotificationsLastSeenUpdate(input:{}){node{id notificationFeedItemsUnreadCount notificationFeedLastSeenAt __typename}__typename}}"
}
<!-- gh-comment-id:877151465 --> @NghiaTranUIT commented on GitHub (Jul 9, 2021): It works with mutation too @cameroncooke For instance, the QueryName is `ViewerNotificationsLastSeenUpdate` in this graphQL Body. ```json { "query": "mutation ViewerNotificationsLastSeenUpdate{response:viewerNotificationsLastSeenUpdate(input:{}){node{id notificationFeedItemsUnreadCount notificationFeedLastSeenAt __typename}__typename}}" } ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/Proxyman#407
No description provided.