Subscriptions with Vue Apollo Next

Hey, everyone :wave:

My question regards the usage of Subscriptions with the latest version of Vue Apollo.

When implementing the subscription, we are returned the following error:

WebSocket connection to 'wss://ws.8base.com/' failed
WebSocket connection to 'wss://ws.8base.com/' failed
WebSocket connection to 'wss://ws.8base.com/' failed

I have been following the indications from the following resources:

Here is how is structured the apolloClient.js file :point_down:

// apolloClient.js

import { onError } from "apollo-link-error";
import { setContext } from "apollo-link-context";
import { ApolloClient } from "@apollo/client/core";
import { HttpLink, split } from "@apollo/client/core";
import { InMemoryCache } from "@apollo/client/core";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
/**
 * Source code provided by Sebastien Scholl @8base.com
 *
 *     Github: https://gist.github.com/sebscholl/dc8319ba1db3854cdff72e8dba63814f
 */
/**
 * Import store to handle logout on expired token.
 */
import store from "@/store";
/**
 * A terminating link that fetches GraphQL results from
 * a GraphQL endpoint over an http connection.
 *
 *   docs: https://www.apollographql.com/docs/link/links/http/
 *
 * The 8base workspace endpoint goes here.
 */
const httpLink = new HttpLink({
  uri: process.env.VUE_APP_WORKSPACE_ENDPOINT
});

// Create the subscription websocket link
const wsLink = new WebSocketLink({
  uri: "wss://ws.8base.com/",
  options: {
    reconnect: true,
    connectionParams: {
      /**
       * WorkspaceID MUST be set or the Websocket Endpoint won't be able to
       * map the request to the appropriate workspace
       */
      workspaceId: process.env.VUE_APP_WORKSPACE_ID
    }
  }
});
/**
 * Common error handlers.
 */
/* NOTE DYLAN */
const errorHandlers = {
  /* Logout on expired token */
  TokenExpiredError: () => store.dispatch("session/logout"),
  /* Invalid token supplied */
  InvalidTokenError: ({ message }) =>
    console.log(`[Token error]: Message: ${message}`),
  /* Default error handler */
  default: ({ message, locations, path }) => {
    console.log(
      `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
    );
  }
};
/**
 * Error Link takes a function that is called in the event of an error.
 * This function is called to do some custom logic when a GraphQL or
 * network error happens.
 *
 *   docs: https://www.apollographql.com/docs/link/links/error/
 */
const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.map(args =>
      (errorHandlers[args.code] || errorHandlers.default)(args)
    );
  }

  if (networkError) console.log(`[Network error]: ${networkError}`);
});
/**
 * Takes a function that returns either an object or a promise that
 * returns an object to set the new context of a request.
 *
 *   docs: https://www.apollographql.com/docs/link/links/context/
 *
 * Here we collect the authentication token from the auth module to
 * add required bearer token to the headers.
 */
const authLink = setContext((_, { headers }) => ({
  headers: {
    authorization: `Bearer ${store.state.session.idToken}`,
    ...headers
  }
}));

/**
 * using the ability to split links, you can send data to each link
 * depending on what kind of operation is being sent
 */
//
const link = split(
  // split based on operation type
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  wsLink,
  errorLink.concat(authLink.concat(httpLink)) // Concatenate the many links
);

/**
 * The ApolloClient class is the core API for Apollo, which we're using
 * to handle are GraphQL requests to the API.
 */
const apolloClient = new ApolloClient({
  /* Split link */
  link,
  /* Initialize the cache for helping performance */
  cache: new InMemoryCache(),
  connectToDevTools: true,
  defaultOptions: {
    query: "cache-and-network"
  }
});

export default apolloClient;

Queries and Mutation are working fine. However, subscriptions don’t work.

Can someone help me find a solution?

Thanks :v:
Andréas

Hey.
First of all - check you declare VUE_APP_WORKSPACE_ID right.
The next thing to try - pass your auth token to the payload of WebSocket.

connectionParams: {
      ...
      token: authToken,
    }

Also please provide screenshot of your websocket response log from devtools just after you try to call subscription (Network - WS) like this one:

Hey @Fomich :wave:

Thanks for your answer.

  1. I did check VUE_APP_WORKSPACE_ID was setup correctly

  2. I changed the payload as suggested.

// Create the subscription websocket link
const wsLink = new WebSocketLink({
  uri: process.env.VUE_APP_WEBSOCKET_ENDPOINT,
  options: {
    reconnect: true,
    connectionParams: {
      /**
       * WorkspaceID MUST be set or the Websocket Endpoint won't be able to
       * map the request to the appropriate workspace
       */
      workspaceId: process.env.VUE_APP_WORKSPACE_ID,
      token: `Bearer ${store.state.session.idToken}`
    }
  }
});

However, the problem still persists (see picture below).

Could you help me find a solution to this issue please?

Thank you,

Andréas

Hello Andréas,

Glad to see you again!
Foma will give you a response as soon as possible.

Lada Kokotova | Technical Support Engineer

Awesome! Thanks :slight_smile:

@andreasblondeau
Hello!
Сan you share WebSocket logs from Network when the connection is failed?
image

Hey @Fomich, thanks for your response.

Please, find below some screenshots of the WebSocket logs from Network when the connection is failed.

Hope this helps :crossed_fingers:

Regards,

Andréas

Hey @Fomich :wave:

I also went ahead and discovered the @8base/apollo-links library that extends the Apollo Client and make it easier to work with 8base.

However, I cannot use this library since it comes with a dev dependency on react@^17.0.1.

Would you have a working piece of code that works with Vue.js ?

Let me know,

Thanks

Hello!

Sorry for the long wait. There’re a lot of urgent tasks. @Fomich will answer you as soon as possible.

Lada Kokotova | Technical Support Engineer

Hey Lada,

Thanks for letting me know. Looking forward to hearing from you :slight_smile:

Andréas

Hey Andréas,
There is not enough context for us to debug the problem… Can you send us the whole project zipped?

Hey Fomich,

Thanks for your suggestion ! As you wish.

In my opinion, a piece of code that allows us to set up our Apollo Client with Vue.js in order to handle subscriptions is the only thing we need :slightly_smiling_face:

We just couldn’t find anything in the docs, on this forum, nor in @sebastian.scholl’s tutorials :slight_smile:

I think this option will be easier for you. What do you think?

Regards,
Andréas

Hello Andréas!

 webSocketImpl: class WebSocketWithoutProtocol extends WebSocket {
            constructor(url: string) {
              super(url); // ignore protocol
            }
          },

Maybe you should update the old version of the package WebSocketLink.

As a variant you can add this to your code(webSocketImpl):

  new WebSocketLink({
              uri: process.env.WSS_URL,
              options: {
                connectionParams: () => {
                  return {
                    // token: st.token,
                    workspaceId: 'WS_ID',
                  };
                },
              },
              webSocketImpl: class WebSocketWithoutProtocol extends WebSocket {
                constructor(url: string) {
                  super(url); // ignore protocol
                }
              },

Lada Kokotova | Technical Support Engineer

hey Lada, thanks for your support.

I have updated the apollo-link-wspackage and implemented your suggestion.

Unfortunately, the problem remains the same (see attachments).

Would you happen to have an apollo-client.js snippet that has been implemented in a Vue project and that handles web sockets, please?

Thanks,
Andréas

Well, I need more time to answer your question, but I’ve got your problem. I’ll let you know soon.

Lada Kokotova | Technical Support Engineer

Hello!

Firstly, if you’ve already added webSocketImpl please check the payload connections to the socket.

If you’ll have the same error you should change workspaceId: 'WS_ID on
process.env.VUE_APP_WORKSPACE_ID or any other variable with your workspaceId

Secondly, if the previous point doesn’t work:

// Create the subscription websocket link
const wsLink = new WebSocketLink({
  uri: "wss://ws.8base.com/",
  options: {
    reconnect: true,
    connectionParams: {
      /**
       * WorkspaceID MUST be set or the Websocket Endpoint won't be able to
       * map the request to the appropriate workspace
       */
      workspaceId: process.env.VUE_APP_WORKSPACE_ID
    }
  },
  webSocketImpl: class WebSocketWithoutProtocol extends WebSocket {
    constructor(url) {
      super(url); // ignore protocol
    }
  },
});

It’s a copy of your file with webSocketImpl. Without webSocketImpl - it gives to reproduce your error.

And finally, if it didn’t help can you provide please a minimal runnable reproduction so we can diagnose the issue.

Lada Kokotova | Technical Support Engineer

Hey Lada,

Thanks for your support! I think we are moving forward :slightly_smiling_face:

Here is my current WS log when the following version of the WebSocketLink

// Create the subscription websocket link
const wsLink = new WebSocketLink({
  uri: process.env.VUE_APP_WEBSOCKET_ENDPOINT,
  options: {
    reconnect: true,
    connectionParams: () => ({
      workspaceId: process.env.VUE_APP_WORKSPACE_ID,
      token: `Bearer ${store.state.session.idToken}`
    })
  },
  webSocketImpl: class WebSocketWithoutProtocol extends WebSocket {
    constructor(url) {
      super(url); // ignore protocol
    }
  }
});

However, when setting up the token without Bearer as token: store.state.session.idToken I get returned the following error.

Are we getting closer to a solution ?

Regards,
Andréas

Hello!

Yeah, we’re almost there!

You should change this:

token:Bearer ${store.state.session.idToken}`

to that:

token: store.state.session.idToken

Lada Kokotova | Technical Support Engineer

Hey Lada,

Thanks for your support. I did try to insert token: store.state.session.idToken (see end of the previous message), but it systematically returns “WebSocket is closed before the connection is established”

I am going to put up a reproducible version then :slight_smile:

Regards,
Andreas

Well, at this stage we did our best. I need to see all you project to help you. Could you send me your project as an archive and wotkspaceID on my email, please?

My email: lada.kokotova@8base.com

Lada Kokotova | Technical Support