apollographql / apollo-feature-requests

🧑‍🚀 Apollo Client Feature Requests | (no 🐛 please).
Other
130 stars 7 forks source link

Add X-APOLLO-OPERATION-NAME and X-APOLLO-OPERATION-ID headers to requests like Android and iOS #318

Open rmelick-vida opened 2 years ago

rmelick-vida commented 2 years ago

We are trying to track down some backend errors, where our Express server that serves graphql is occasionally crashing before returning a response. We haven't been abe to reproduce reliably, so we would like to figure out if these crashes are happening consistently for a certain operation, or if it's independent of operation.

Unfortunately, they seem to only affect our React graphql clients, and not our Android or iOS.

Can the X-APOLLO-OPERATION-NAME and X-APOLLO-OPERATION-ID headers be added to the javascript apollo client requests, just like in Android and iOS?

jpvajda commented 2 years ago

This looks to be done, so closing.

metrue commented 1 year ago

hey @jpvajda @rmelick-vida I searched through the @apollo/client codes, no X-APOLLO-OPERATION-NAME or X-APOLLO-OPERATION-ID found, where can I check to ensure this two headers are included in the web Apollo client?

bignimbus commented 1 year ago

@metrue see https://github.com/apollographql/apollo-client/pull/4154 for more info. Thanks!

metrue commented 11 months ago

Hey @bignimbus , I don't see the reason why X-APOLLO-OPERATION-NAME or X-APOLLO-OPERATION-ID included the headers of request from @apollo/client from the thread, could you help me understand it, thanks a lot.

metal-messiah commented 11 months ago

@bignimbus I am also trying to understand this issue with JS apollo-client. As far as I understand things, the linked issue you provided is referencing the Apollo version and name at runtime, not the operation name and id that should be linked 1:1 with new requests. I came here because the x-apollo-operation-* headers are not being included on my outgoing test apollo requests, which we are planning on using for interception telemetry purposes.

phryneas commented 11 months ago

I'm reopening this - actually, this is not done yet - JV and @bignimbus confused this with the "client awareness headers" apollographql-client-name and apollographql-client-version.

To be honest, we'll have to discuss internally if it makes sense to ship this as part of HttpClient, since most people do not rely on these headers, but they'd add a good chunk of code to the bundle of everyone.

That said, you can already use them today with a custom link - you can just copy/paste this headersLink into your codebase and add it to your link chain.

(I've just written this up without testing, so if I made any mistakes here, please report back :) )

import { type DocumentNode, print } from "graphql";
import { setContext } from "@apollo/client/link/context";
import { getOperationDefinition } from "@apollo/client/utilities";

const operationHashes = new WeakMap<DocumentNode, string | Promise<string>>();
const headersLink = setContext(async (operation, prevContext) => {
  if (!operationHashes.has(operation.query)) { 
    operationHashes.set(operation.query, new Promise<string>((resolve) => {
      const hash = sha256(print(operation.query));
      operationHashes.set(operation.query, hash);
      resolve(hash);
    }));
  }
  const operationId = operationHashes.get(operation.query)!;

  return {
    ...prevContext,
    headers: {
      ...prevContext.headers,
      "x-apollo-operation-id": await operationId,
      "x-apollo-operation-name": operation.operationName,
      "x-apollo-operation-type": getOperationDefinition(operation.query),
    },
  };
});

// from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
async function sha256(message: string) {
  const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
  const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8); // hash the message
  const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
  const hashHex = hashArray
    .map((b) => b.toString(16).padStart(2, "0"))
    .join(""); // convert bytes to hex string
  return hashHex;
}