Apollo-client: client.stop() should call close on websocket link

Created on 28 Oct 2020  ·  5Comments  ·  Source: apollographql/apollo-client

Intended outcome:

As per the documentation of the function Client.stop, it should be ready to recycle the client once it is called. Meaning the websocket connection should be closed.

Actual outcome:

Currently, the websocket connection is NOT closed. This is problematic since a very common use case if to recycle the client when the authorization token changes. If a user is not careful, he will end up creating a lot of websocket connections to the backend.

How to reproduce the issue:

  1. Create a client with the WebsocketLink
  2. Call stop on the client
  3. Observe the network tab, the connection is not closed

All 5 comments

@Sytten If you haven't already, could you try the latest @apollo/client beta, which includes a fix for #6985, which seems possibly related to this behavior?

npm i @apollo/client@beta

I reread the code and nothing was added to call the close method on the WS link client.
Here is the client: https://github.com/apollographql/apollo-client/blob/main/src/link/ws/index.ts#L32
And here is the method that needs to be called: https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L160

While we're waiting for an official implementation (and for anyone who's looking at the issue https://github.com/apollographql/apollo-link/issues/197 and still wondering how to refresh connectionParams), here's how my team and I were finally able to do it in React:

import * as React from "react";
import { ApolloClient, HttpLink, InMemoryCache, split } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
import { SubscriptionClient } from "subscriptions-transport-ws";
import { getMainDefinition } from "@apollo/client/utilities";

const client = new ApolloClient({
  cache: new InMemoryCache()
});

export default function useClient(userId: string) {
  const subscriptionClient = React.useRef<SubscriptionClient>(null);

  React.useEffect(() => {
    if (userId) {
      if (subscriptionClient.current) {
        subscriptionClient.current.close();
      }
      subscriptionClient.current = new SubscriptionClient(
        "ws://localhost:3000/graphql",
        {
          reconnect: true,
          connectionParams: { userId }
        }
      );
    }
  }, [userId]);

  const splitLink = React.useMemo(() => {
    const httpLink = new HttpLink({
      uri: `http://localhost:3000//graphql`
    });

    if (userId && subscriptionClient.current) {
      const websocketLink = new WebSocketLink(subscriptionClient.current);

      return split(
        ({ query }) => {
          const definition = getMainDefinition(query);
          return (
            definition.kind === "OperationDefinition" &&
            definition.operation === "subscription"
          );
        },
        websocketLink,
        httpLink
      );
    }

    return httpLink;
  }, [userId]);

  React.useEffect(() => {
    client.setLink(splitLink);
  }, [splitLink]);

  return client;
}

The main trick was to use SubscriptionClient as the parameter to the WebSocketLink instance. This gives us the option to close the connection when we need it, directly using the subscription client. In the above scenario, that's also the mechanism we use in order to refresh our connection params.

Thanks for the contribution @vsylvestre! The state of subscription in the apollo ecosystem is plainly bad from the server to the client implementation. I can't wait for the subscriptions-transport-ws package to be removed.

Hey @Sytten, maybe you can give graphql-ws a spin. 😄

Offering a completely different Protocol you'd have to use the same lib server side too; but, no stress - there are recipes in the readme for using it with Apollo both server and client side!

Was this page helpful?
0 / 5 - 0 ratings