According to the docs for subscriptions in apollo-server-express 2.0, the return value of onConnect is appended to the context. However, in practice the return value of onConnect is ignored and only the context returned by the context method of ApolloServer config is available in resolvers.
For example, with this code:
const server = new ApolloServer({
// These will be defined for both new or existing servers
typeDefs,
resolvers,
context: async ({ req, connection }) => {
if (connection) {
// check connection for metadata
return {
foo: 'bar'
};
} else {
// check from req
const token = req.headers.authorization || "";
return { token };
}
},
subscriptions: {
onConnect: (connectionParams, webSocket) => {
console.log('Websocket CONNECTED');
return {
hello: 'world'
}
},
onDisconnect: () => console.log('Websocket CONNECTED'),
}
});
You would expect the context for websockets to be:
{foo: 'bar', hello: 'world'}
But in practice the context will be:
{foo: 'bar'}
I have created a basic reproduction of the issue here.
Issue is still present in v2.1.0
I was able to get hello object in the context if I manually add it to the context via connection.context. Like:
context: async ({ req, connection }) => {
if (connection) {
// check connection for metadata
return {
foo: 'bar',
hello: connection.context.hello,
};
} else {
// check from req
const token = req.headers.authorization || "";
return { token };
}
},
subscriptions: {
onConnect: (connectionParams, webSocket) => {
console.log('Websocket CONNECTED');
return {
hello: 'world'
}
},
onDisconnect: () => console.log('Websocket CONNECTED'),
}
Not sure if this is the correct approach though.
Nice one @sabith-th. With that being the case, we can simply use the following as a workaround:
return Object.assign(
{
foo: "bar"
},
connection.context
);
Just curious if anyone would find this approach helpful or have feedback =)
Right now I pass the token in two places:
First in the middleware, so that the token is always fresh on each call and allows for using withFilter with a user context that we know is current. NOTE: you need to have your lookup function inside of the applyMiddleware function, as it will be called on each request. If you declare it at the top of the file, it will be stale.
Second, in the connection params so that onConnect can see it and write it to context so that later onDisconnect can also see it, so that we can rely on those calls to set user online status.
// Client.js
import get from 'lodash/get';
import { WebSocketLink } from 'apollo-link-ws';
const wsLink = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
// Pass the token as a connection param so we can set the online status
// on the onConnect callback in subscriptions
connectionParams: {
authToken: `Bearer ${get(JSON.parse(localStorage.getItem('user')), 'data.token')}`,
},
reconnect: true,
},
});
// Pass the authToken as an option on each socket call so that the user
// data is never stale
const subscriptionMiddleware = {
applyMiddleware: async (options, next) => {
// eslint-disable-next-line no-param-reassign
options.authToken = `Bearer ${get(JSON.parse(localStorage.getItem('user')), 'data.token')}`;
next();
},
};
// add the middleware to the web socket link via the Subscription Transport client
wsLink.subscriptionClient.use([subscriptionMiddleware]);
export default wsLink;
// Server.js
const server = new ApolloServer({
context: async ({ req, payload }) => {
// Allow getting the auth from req or ws payload
const authToken = get(req, 'headers.authorization') || get(payload, 'authToken');
const user = await AuthServices.getUser(authToken);
if (!user) {
return { ...req, pubsub, models };
}
return {
...req,
pubsub,
user,
models,
};
},
formatError,
playground: {
endpoint: '/playground',
},
schema,
subscriptions: {
onConnect: async ({ authToken }) => {
if (authToken) {
const user = await AuthServices.getUser(authToken);
if (user) {
await user.setOnline();
return {
authToken,
};
}
}
return {};
},
onDisconnect: (params, socket) => {
socket.initPromise.then(async ({ authToken }) => {
if (authToken) {
const user = await AuthServices.getUser(authToken);
if (user) {
await user.setOffline();
}
}
});
},
path: '/graphql',
},
});
This is caused by https://github.com/apollographql/apollo-server/blob/master/packages/apollo-server-core/src/ApolloServer.ts#L480 overwriting the context returned by onConnect.
@samalexander the only proper workaround I can see for right now is to fork the ApolloServer.installSubscriptionHandlers function and either make it not call this.context or make it merge the objects from onConnect and this.context.
@sabith-th actually it would be even better to do this:
if (connection) {
return {
...connection.context,
foo: 'bar',
};
What's the current status on this? This issue is open for over a year now and it's still not working
Most helpful comment
I was able to get
helloobject in the context if I manually add it to the context viaconnection.context. Like:Not sure if this is the correct approach though.