apollo-feature-requests icon indicating copy to clipboard operation
apollo-feature-requests copied to clipboard

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

Open rmelick-vida opened this issue 4 years ago • 6 comments
trafficstars

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?

  • android: https://github.com/apollographql/apollo-android/issues/614
  • iOS: https://github.com/apollographql/apollo-ios/issues/375

rmelick-vida avatar Nov 08 '21 23:11 rmelick-vida

This looks to be done, so closing.

jpvajda avatar May 25 '22 00:05 jpvajda

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?

metrue avatar Aug 18 '23 06:08 metrue

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

bignimbus avatar Aug 18 '23 15:08 bignimbus

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.

metrue avatar Sep 22 '23 01:09 metrue

@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.

metal-messiah avatar Oct 06 '23 15:10 metal-messiah

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;
}

phryneas avatar Oct 09 '23 08:10 phryneas