Relay: [DefaultNetworkLayer] Passing a function as a custom header?

Created on 5 Oct 2015  路  11Comments  路  Source: facebook/relay

Hi,

I'm looking to use the DefaultNetworkLayer but I need to set a header (X-Request-ID) with a different value on every request. Right now I'm passing a harcoded value:

Relay.injectNetworkLayer(
  new Relay.DefaultNetworkLayer('http://localhost:9000/v1/graphql', {
    headers: {
       'X-Request-ID': "123e4567-e89as-asdasdads-asdasdasdasdasd"
    }
  })
);

but I was hoping to pass a function so that it get evaluated every time a new request is built, something like this:

Relay.injectNetworkLayer(
  new Relay.DefaultNetworkLayer('http://localhost:9000/v1/graphql', {
    headers: {
       'X-Request-ID': function() { return uuid.v4(); }
    }
  })
);

Is anything like this possible without creating a new NetworkLayer? If not I'm happy to contribute that patch, just let me know.

Thanks!

Most helpful comment

Hi, a bit late but I faced the same problem and used the following solution:

  Relay.injectNetworkLayer(
    new Relay.DefaultNetworkLayer('/graphql', {
      get headers() {
        return {
          Authorization: 'Bearer ' + localStorage.getItem('id_token')
        }
      }
    })
  )

cheers

All 11 comments

RelayDefaultNetworkLayer simply forwards any options to the used fetch-polyfill, so no this won't work. An easy work-around would be to create your own network layer that simply creates a new RelayDefaultNetworkLayer for every request. This could have heavy runtime cost!

@luisobo This seems like a good use-case for a custom network layer. As @clentfort suggested, you can delegate to the default layer internally. I suspect that the runtime cost would be negligible. Let us know how this works out for you!

@luisobo Does this suggestion help? I'll close this issue for now, but feel free to comment with more questions or provide feedback on this approach.

Hi, a bit late but I faced the same problem and used the following solution:

  Relay.injectNetworkLayer(
    new Relay.DefaultNetworkLayer('/graphql', {
      get headers() {
        return {
          Authorization: 'Bearer ' + localStorage.getItem('id_token')
        }
      }
    })
  )

cheers

Example of adding X-Request-ID to request headers (which changes with every request) can be found here https://github.com/nodkz/react-relay-network-layer#example-of-injecting-networklayer-with-middlewares-on-the-client-side

import Relay from 'react-relay';
import { RelayNetworkLayer, urlMiddleware } from 'react-relay-network-layer';

// Be aware `RelayNetworkLayer` is community realization of network layer. (this is not relay default network layer)
Relay.injectNetworkLayer(new RelayNetworkLayer([
  // example of thunk, which generate new url with every request
  urlMiddleware({
    url: (req) => '/graphql?' + Math.random(),
  }),

  // example of the custom inline middleware (add `X-Request-ID` to request headers)
  next => req => {
    req.headers['X-Request-ID'] = uuid.v4();
    return next(req);
  }
]));

@IcanDivideBy0 tokens are implemented via authMiddleware

import Relay from 'react-relay';
import { RelayNetworkLayer, authMiddleware } from 'react-relay-network-layer';

Relay.injectNetworkLayer(new RelayNetworkLayer([
  authMiddleware({
    token: () => localStorage.getItem('id_token'),

    // this promise will be called if server returns 401 status code
    // and made implicitly for Relay re-request with new token to GraphQL server
    tokenRefreshPromise: (req) => { 
      console.log('[client.js] resolve token refresh', req);
      return fetch('/jwt/refresh')
        .then(res => res.json())
        .then(json => {
          const token = json.token;
          localStorage.setItem('id_token', token);
          return token;
        })
        .catch(err => console.log('[client.js] ERROR can not refresh token', err));
    },
  })
]));

@nodkz thanks ! this looks definitly cleaner ;)

@IcanDivideBy0 Clever idea, but that actually didn't work for me. I had to use:

 var self = this;
 Relay.injectNetworkLayer(
   new Relay.DefaultNetworkLayer(`${window.API_URL}/graphql`, {
      headers: {
        get Authorization() { return self.state && self.state.auth }
      }
    })
  );

@miracle2k I finally used the authMiddleware which handle a refresh token function as well. You should try the @nodkz solution too, the getter/setter is definitly some kind of a hack

PS: we still write var self = this; in 2016 ? xD

This is untested, but for relay-modern I believe the solution from @IcanDivideBy0 should still work. The new NetworkLayer interface just needs a function that returns a Promise. Modifed version from their docs:

const {Environment, Network} = require('relay-runtime');

// Define a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
function fetchQuery(
  operation,
  variables,
  cacheConfig,
  uploadables,
) {
  return fetch('/graphql', {
    method: 'POST',
    headers: {
      // Add header(s) here as obj getters
      get Authorization(): 'Bearer ' + localStorage.getItem('id_token'),
    },
    body: JSON.stringify({
      query: operation.text, // GraphQL text from input
      variables,
    }),
  }).then(response => {
    return response.json();
  });
}

// Create a network layer from the fetch function
const network = Network.create(fetchQuery);

source: https://facebook.github.io/relay/docs/network-layer.html

We have this up and running in Relay Modern. Here's our implementation. (Note that this works if additional fetches are called while authentication is in progress, since they all reference the same promise instance.)

async function fetchQuery(
  operation: any,
  variables: any,
  cacheConfig: any,
  uploadables: any,
) {
    if (!_accessTokenPromise)
      _accessTokenPromise = fetchAccessToken();

    const authToken = await this._accessTokenPromise;

  const response = await fetch(
    ...
    {
    method: 'POST',
    headers: {
      'content-type': 'application/json', 
      Authorization: "Bearer " + authToken 
    },
    body: JSON.stringify({
      query: operation.text, // GraphQL text from input
      variables,
    }),
  });

  return await response.json();
}

async fetchAccessToken() {
  // tries to get from local storage, or returns null
  const cachedAccessToken = await getJwtOrNull();  

  if (cachedAccessToken)
    return cachedAccessToken;

return await getTokenFromMyAuthProvider();
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

mike-marcacci picture mike-marcacci  路  3Comments

jstejada picture jstejada  路  3Comments

brad-decker picture brad-decker  路  3Comments

rayronvictor picture rayronvictor  路  3Comments

janicduplessis picture janicduplessis  路  3Comments