Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client.stop() should call close on websocket link #7257

Open
Sytten opened this issue Oct 28, 2020 · 7 comments
Open

client.stop() should call close on websocket link #7257

Sytten opened this issue Oct 28, 2020 · 7 comments
Labels
🏓 awaiting-team-response requires input from the apollo team 🌤 has-workaround

Comments

@Sytten
Copy link

Sytten commented Oct 28, 2020

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
@benjamn
Copy link
Member

benjamn commented Oct 29, 2020

@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

@Sytten
Copy link
Author

Sytten commented Oct 31, 2020

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

@vsylvestre
Copy link

vsylvestre commented Nov 13, 2020

While we're waiting for an official implementation (and for anyone who's looking at the issue apollographql/apollo-link#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.

@Sytten
Copy link
Author

Sytten commented Nov 13, 2020

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.

@enisdenjo
Copy link
Contributor

enisdenjo commented Nov 16, 2020

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!

@shriram10567
Copy link

While we're waiting for an official implementation (and for anyone who's looking at the issue apollographql/apollo-link#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.

how do you use this with HOC like next-apollo

@bonnopc
Copy link

bonnopc commented Oct 3, 2021

This method worked like a charm for my next.js project!

@bignimbus bignimbus added 🌤 has-workaround 🏓 awaiting-team-response requires input from the apollo team labels Nov 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏓 awaiting-team-response requires input from the apollo team 🌤 has-workaround
Projects
None yet
Development

No branches or pull requests

7 participants