0

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.

VikR
  • 4,818
  • 8
  • 51
  • 96

1 Answers1

1

I got this working. Here's the code that calls the subscription resolver and handles the response from it.

import React, {Component} from "react";
import gql from "graphql-tag";
import {graphql} from "react-apollo";
import {Mutation} from "react-apollo";
import {withApollo} from "react-apollo";
import {GET_RESOLUTIONS_FOR_MUTATION_COMPONENT, CREATE_RESOLUTION} from '../../imports/api/resolutions/queries';
import {isDuplicateObject} from "../api/resolutions/queries";

const ResolutionForm = () => {
    let input;
    let state = {
        error: null
    };

    return (
        <Mutation
            mutation={CREATE_RESOLUTION}
            update={(cache, {data: {createResolution}}) => {
                const {resolutions} = cache.readQuery({query: GET_RESOLUTIONS_FOR_MUTATION_COMPONENT});
                if (!isDuplicateObject(createResolution, resolutions)) {
                    cache.writeQuery({
                        query: GET_RESOLUTIONS_FOR_MUTATION_COMPONENT,
                        data: {resolutions: resolutions.concat([createResolution])}
                    });
                }
            }}
        >
            {(createResolution, {data}) => (
                <div>
                    <form
                        onSubmit={e => {
                            e.preventDefault();
                            createResolution({
                                variables: {
                                    name: input.value
                                },
                                optimisticResponse: {
                                    __typename: "Mutation",
                                    createResolution: {
                                        __typename: "Resolution",
                                        completed: false,
                                        goals: [],
                                        _id: "012345",
                                        name: input.value
                                    }
                                }
                            });
                            input.value = "";
                        }}
                    >
                        <input
                            ref={node => {
                                input = node;
                            }}
                            placeholder="Enter a Resolution"
                        />
                        <button type="submit">Submit</button>
                    </form>
                </div>
            )}
        </Mutation>
    );
};

export default withApollo(ResolutionForm);
VikR
  • 4,818
  • 8
  • 51
  • 96