There are many posts on SO that come up with a search on Apollo Subscription Resolver Never Activates?
, including one from me from June 2017. Since then I've had subscriptions working well, for many months. But that was Apollo 1.x, and now with Apollo 2.x, I'm having a similar anomaly.
The old SO posts don't seem to resolve this anomaly. I've spent the past couple days going through them all and trying to make sure I'm doing everything the docs and articles say to do, but it's not quite working yet.
For completeness, I'm providing all the relevant code.
SERVER SETUP
import { createApolloServer } from "meteor/apollo";
import { makeExecutableSchema } from "graphql-tools";
import merge from "lodash/merge";
import cors from 'cors';
import GoalsSchema from "../../api/goals/Goal.graphql";
import GoalsResolvers from "../../api/goals/resolvers";
import ResolutionsSchema from "../../api/resolutions/Resolutions.graphql";
import ResolutionsResolvers from "../../api/resolutions/resolvers";
import UsersSchema from "../../api/users/User.graphql";
import UsersResolvers from "../../api/users/resolvers";
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
const typeDefs = [GoalsSchema, ResolutionsSchema, UsersSchema];
//must change this line to get changes in .graphql files recognized. afdkk
const resolvers = merge(GoalsResolvers, ResolutionsResolvers, UsersResolvers);
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
createApolloServer({ schema });
const WS_PORT = 3200;
// Create WebSocket listener server
// https://www.apollographql.com/docs/graphql-subscriptions/express.html
const websocketServer = createServer((request, response) => {
response.writeHead(404);
response.end();
});
// Bind it to port and start listening
websocketServer.listen(WS_PORT, () => console.log(
`Websocket Server is now running on ws://localhost:${WS_PORT}`
));
const subscriptionServer = SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server: websocketServer,
path: '/subscriptions',
},
);
CLIENT SETUP
import React from "react";
import {Meteor} from "meteor/meteor";
import {render} from "react-dom";
import {ApolloProvider} from "react-apollo";
import {ApolloLink, from} from "apollo-link";
import {ApolloClient} from "apollo-client";
import {HttpLink} from "apollo-link-http";
import {InMemoryCache} from "apollo-cache-inmemory";
import {onError} from 'apollo-link-error';
import {split} from 'apollo-link';
import {WebSocketLink} from 'apollo-link-ws';
import {getMainDefinition} from 'apollo-utilities';
import {toIdValue} from 'apollo-utilities';
import App from "../../ui/App";
// Create an http link:
const httpLink = new HttpLink({
uri: Meteor.absoluteUrl("graphql"),
credentials: 'same-origin'
})
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: `ws://localhost:3200/subscriptions`,
options: {
reconnect: true
}
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const splitLink = split(
// split based on operation type
({query}) => {
const {kind, operation} = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const authLink = new ApolloLink((operation, forward) => {
const token = Accounts._storedLoginToken();
operation.setContext(() => ({
headers: {
"meteor-login-token": token
}
}));
return forward(operation);
});
const client = new ApolloClient({
link: ApolloLink.from([
onError(({graphQLErrors, networkError}) => {
if (graphQLErrors)
graphQLErrors.map(({message, locations, path}) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
authLink,
splitLink,
]),
cache: new InMemoryCache({})
});
const ApolloApp = () => (
<ApolloProvider client={client}>
<App/>
</ApolloProvider>
);
Meteor.startup(() => {
render(<ApolloApp/>, document.getElementById("app"));
});
TYPES
type Resolution {
_id: String!
name: String!
goals: [Goal]
completed: Boolean
}
type Query {
resolutions: [Resolution]
getResolutionViaId(resolutionId: String!): Resolution
}
type Mutation {
createResolution(name: String!): Resolution
}
type Subscription {
resolutionWasAdded(userId: String!): Resolution
}
QUERIES
let CREATE_RESOLUTION = gql`
mutation createResolution($name: String!) {
createResolution(name: $name) {
__typename
_id
name
...resolutionGoals
completed
}
}
${resolutionQueryFragments.resolutionGoals}
`;
const RESOLUTION_SUBSCRIBE = gql`
subscription resolutionWasAdded($userId: String!){
resolutionWasAdded(userId: $userId){
__typename
_id
name
...resolutionGoals
completed
}
}
${resolutionQueryFragments.resolutionGoals}
`;
RESOLVER
Mutation: {
createResolution(obj, args, {userId}) {
let name = args.name;
if (userId) {
return Promise.resolve()
.then(() => {
const resolutionId = Resolutions.insert({
name,
userId
});
return resolutionId;
})
.then(resolutionId => {
const resAdded = Resolutions.findOne(resolutionId);
return resAdded;
})
.then(resolutionWasAdded => {
pubsub.publish('resolutionWasAdded', {resolutionWasAdded: args})
return resolutionWasAdded;
})
.catch((err) => {
console.log(err);
});
}
throw new Error("Unauthorized");
}
},
Subscription: {
resolutionWasAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator("resolutionWasAdded"),
(payload, variables) => {
debugger;
return true;
})
}
}
}
The line pubsub.publish...
in the Mutation resolver runs, but the Subscription resolver never activates.
What am I missing?
UPDATE
I have to modify how the subscription is set up in my client query component. I'll update this post later when I discover more.