I am developing a keystone app, where we want to use graphql subscriptions.
Followed the documentation: https://keystonejs.com/docs/config/config#extend-http-server
and the keytone github example for websocket file: https://github.com/keystonejs/keystone/blob/main/examples/extend-graphql-subscriptions/websocket.ts
The created subscriptions appear in the Apollo playground schema.
The websocket connection works as long as I comment out graphql-ws from the code. When I add wsUseServer coming from graphql-ws it gives the following errors on these two tries:
My websocket.ts code which is the same as the example, excepting logs and commented test lines for debugging:
import type http from 'http'
import { useServer as wsUseServer } from 'graphql-ws/lib/use/ws'
import { WebSocketServer } from 'ws'
import { PubSub } from 'graphql-subscriptions'
import { parse } from 'graphql'
import { type Context } from '.keystone/types'
// Setup pubsub as a Global variable in dev so it survives Hot Reloads.
declare global {
let graphqlSubscriptionPubSub: PubSub
}
// The 'graphql-subscriptions' pubsub library is not recommended for production use, but can be useful as an example
// for details see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#the-pubsub-class
export const pubSub = global.graphqlSubscriptionPubSub || new PubSub()
globalThis.graphqlSubscriptionPubSub = pubSub
export function extendHttpServer (
httpServer: http.Server,
commonContext: Context,
) {
console.log('HTTP Server:', httpServer);
console.log(commonContext.graphql.schema)
// Setup WebSocket server using 'ws'
const wss = new WebSocketServer({
server: httpServer,
path: '/api/graphql',
/*handleProtocols: (protocols) => {
return Array.from(protocols).includes('graphql-ws') ? 'graphql-ws' : null;
}*/
})
// Setup the WebSocket to handle GraphQL subscriptions using 'graphql-ws'
wsUseServer(
{
schema: commonContext.graphql.schema,
// run these onSubscribe functions as needed or remove them if you don't need them
onSubscribe: async (ctx: any, msg) => {
const context = await commonContext.withRequest(ctx.extra.request)
// Return the execution args for this subscription passing through the Keystone Context
return {
schema: commonContext.graphql.schema,
operationName: msg.payload.operationName,
document: parse(msg.payload.query),
variableValues: msg.payload.variables,
contextValue: context,
}
},
},
wss
)
//wsUseServer({ schema: commonContext.graphql.schema }, wss); // For testing
//const ws = new WebSocket('ws://localhost:3000/api/graphql', 'graphql-ws'); // For testing
// Optional: Log connection events
wss.on('connection', (ws, request) => {
console.log('New WebSocket connection');
console.log('Requested Protocols:', request.headers['sec-websocket-protocol']);
console.log('Selected Protocol:', ws.protocol); // Log the subprotocol selected by the server
ws.on('close', (error) => {
console.error('WebSocket error:', error);
});
ws.on('message', (message) => {
console.log('Received message:', message);
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
ws.onerror = (evt) => {
console.log('Message received:', evt);
};
});
// Send the time every second as an interval example of pub/sub
/*setInterval(() => {
console.log('TIME', Date.now())
pubSub.publish('TIME', {
time: {
iso: new Date().toISOString(),
},
})
}, 1000)*/
}
I removed wsUseServer (Graphql Subscriptions) and the connection is maintained. But what I want is to use the subscriptions. SetInterval (commented out above) is working upon connection.
I added [handleProtocols] to the websocketserver (commented out above). Didn't solve.
Tested both wscat entries in development and production environment and the behavior is the same on both.
What I expect is to be able to maintain the websocket connection with the graphql-ws wsUseServer, to be able to activate subscriptions.
Thanks.
Figured out the solution. Firstly wscat is not the right tool for testing this. Secondly the right protocol is not graphql-ws, but graphql-transport-ws. It is not necessary to specify, since it is handled automatically.
I tested the subscriptions with the Apollo playground and they showed as listening but wouldn't respond.
The issue was with the way Pubsub was implemented. I was calling it in every file I had to use it, and it would generate conflict. It has to be launched in a single file, and than exported to every file in which one wants to use it.