apollographql / apollo-link

:link: Interface for fetching and modifying control flow of GraphQL requests
https://www.apollographql.com/docs/link/
MIT License
1.44k stars 344 forks source link

Apollo-link-context has no effect on apollo-link-ws #446

Open kirill-konshin opened 6 years ago

kirill-konshin commented 6 years ago

I was trying to add context information with headers (like in example https://www.apollographql.com/docs/react/recipes/authentication.html) but in my case I have WS link instead of HTTP.

const authLink = setContext((req, {headers}) => ({
    headers: {
        ...headers,
        authorization: '...'
    }
}));

const client = new ApolloClient({
    link: authLink.concat(wsLink),
    cache: new InMemoryCache()
});

Context is not delivered to server, I checked it frames section in Network tab and in onOperation:

new SubscriptionServer({
  execute,
  subscribe,
  schema,
  onOperation: (message, params, webSocket) => {
    console.log('onOperation', {message, params});
    return params;
  }
}, {
  server: ws,
  path: '/subscriptions',
});

Context is equal to {}.

Moreover, on client I get the following error when using WS:

bluebird.js:1546 Warning: a promise was created in a handler but was not returned from it, see http://goo.gl/rRqMUw

Everything works well with HTTP though.

kirill-konshin commented 6 years ago

I have traced the execution: WebSocketLink.request(operation) -> SubscriptionClient.request(operation), and the problem is that operation contain getContext getter:

{
  extensions: {},
  operationName: "Xxx"
  query: {kind: "Document", definitions: Array(4), loc: {…}}
  variables: {},
  getContext(),
  setContext()
}

Which is never called in SubscriptionClient.

raeesaa commented 6 years ago

Any updates on this?

tsirlucas commented 6 years ago

Any updates?

------------ EDIT:

For googlers, I ended up using the following:

const wsLink = new WebSocketLink({
      uri: `ws://localhost:3004/graphql`,
      options: {
        reconnect: true,
        connectionParams: () => ({
          authorization: Cookie.get('token'),
        }),
      },
    });

Works just like you were setting http headers on graphql playground:

screenshot at aug 02 18-53-45

sulliwane commented 5 years ago

could someone confirm that once the websocket channel has been opened (with Authorization header = token AAA), each subsequent request using the websocket link will always be identified as AAA token.

Or is there a way to send a different Authorization header on each request (other than re-opening another ws channel)?

I'd like to understand what's happening on a low level protocol for ws.

Thank you for you reply!

const wsClient = new SubscriptionClient(
  graphqlEndpoint,
  {
    reconnect: true,
    connectionParams: () => ({
      headers: {
        'Authorization': 'mytokenAAA',
      },
    }),
  },
  ws,
);
const link = new WebSocketLink(wsClient);

makePromise(execute(link, options)); // that's using token AAA
// how to make another query (execute) using token BBB without creating another link ?
tsujp commented 5 years ago

Any update on dynamically pulling JWT tokens for wsLink? I'm getting all kinds of errors or non-response when I try and mirror setups for httpLink.

cowlicks commented 5 years ago

@hito you should be able to set using middleware of the SubscriptionClient. See my gist here: https://gist.github.com/cowlicks/71e766164647f224bf15f086ea34fa52


const subscriptionMiddleware = {
  applyMiddleware: function(options, next) {
    // Get the current context
    const context = options.getContext();
    // set it on the `options` which will be passed to the websocket with Apollo 
    // Server it becomes: `ApolloServer({contetx: ({payload}) => (returns options)
    options.authorization = context.authorization;
    next()
  },
};

const server = new ApolloServer({
  context: ({connection, payload, req}) => {
    // whatever you return here can be accessed from the middleware of the SubscriptionMiddleware with
    // applyMiddleware: (options, next) => options.getContext()
    return {authorization: payload.authorization};
  },
});

const link = new WebSocketLink({
    uri: WS_URL,
    webSocketImpl: WebSocket,
    options: {
        reconnect: true,
    }
});

link.subscriptionClient.use([subscriptionMiddleware]);