Giter Site home page Giter Site logo

Comments (28)

nemanjam avatar nemanjam commented on August 26, 2024 33

Is there one complete client and server example how to reconnect with new token instead 100 scattered semi-solutions with 5 and 23 likes?

from apollo-link.

dustinboss avatar dustinboss commented on August 26, 2024 18

Since the new WebSocketLink is still creating an instance of subscriptions-transport-ws, I am manually applying my middleware to the Subscription Transport client, rather than to the WebSocketLink.

I'm using the same process as I did in v1, though it feels kind of hacky to not have it as part of the WebSocketLink API.

// create the web socket link
const wsLink = new WebSocketLink({
  uri: 'ws://example.com',
  options: {
    reconnect: true
  }
})
// create my middleware using the applyMiddleware method from subscriptions-transport-ws
const subscriptionMiddleware = {
  applyMiddleware (options, next) {
    options.auth = { ... }
    next()
  }
}
// add the middleware to the web socket link via the Subscription Transport client
wsLink.subscriptionClient.use([subscriptionMiddleware])

Any values that you add to the options object are available in the onOperation callback on the server side.

from apollo-link.

dyst5422 avatar dyst5422 commented on August 26, 2024 11

You can use a function that resolves to an object in the connectionParams.

    const wsLink = new WebSocketLink({
      uri: config.subscriptionURL,
      options: {
        reconnect: true,
        connectionParams: () => ({
          authToken: reduxStore.getState().authentication.token,
        }),
      },
    });

EDIT:

Oh wait, this isn't called after the first time... Maybe concat with another middleware that puts an authentication object on the response that you can parse with onOperation server side?

from apollo-link.

sulliwane avatar sulliwane commented on August 26, 2024 10

could someone confirm that once the websocket channel has been opened (with Authorization header = token AAA), each subsequent request using the websocket link will always be identified as AAA token.

Or is there a way to send a different Authorization header on each request (other than re-opening another ws channel)?

I'd like to understand what's happening on a low level protocol for ws.

Thank you for you reply!

here is my code so far (working correctly with one token):

const wsClient = new SubscriptionClient(
  graphqlEndpoint,
  {
    reconnect: true,
    connectionParams: () => ({
      headers: {
        'Authorization': 'mytokenAAA',
      },
    }),
  },
  ws,
);
const link = new WebSocketLink(wsClient);

makePromise(execute(link, options)); // that's using token AAA
// how to make another query (execute) using token BBB without creating another link ?

from apollo-link.

Libohan12 avatar Libohan12 commented on August 26, 2024 6

subscription this issues

from apollo-link.

ishan123456789 avatar ishan123456789 commented on August 26, 2024 5

Would any one believe me if I say this works:

const wsLink = process.browser
    ? new WebSocketLink({
        uri: webSocketUri,
        options: {
          reconnect: true,
          connectionParams: () => ({
            token: cookie.get('token'),
          }),
        },
      })
    : null;

ie
Just change:

connectionParams: {
            token: cookie.get('token'),
},

TO:

connectionParams: () => ({
            token: cookie.get('token'),
}),

from apollo-link.

jonathanheilmann avatar jonathanheilmann commented on August 26, 2024 4

I've created a solution(? it's woking in my case) a few weeks ago:

A module GraphQLModule is imported in my app.module.ts:

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { Apollo, ApolloModule } from 'apollo-angular';
import { HttpLinkModule } from 'apollo-angular-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';

@NgModule({
  exports: [
    ApolloModule,
    HttpClientModule,
    HttpLinkModule
  ]
})
export class GraphQLModule {
  public subscriptionClient: SubscriptionClient = null;

  constructor(apollo: Apollo) {
    const wssEndpoint = 'wss://your-domain.com/subscriptions';
    // It's required to define connectionParams as function, as otherwise the updated token is not transferred
    const connectionParams = () => {
      const token = sessionStorage.getItem('token');
      return token ? { 'Authorization': 'token ' + token } : {};
    };

    const wsLink = new WebSocketLink({
      uri: wssEndpoint,
      options: {
        connectionParams: connectionParams,
        reconnect: true
      }
    });
    this.subscriptionClient = (<any>wsLink).subscriptionClient;

    const cache = new InMemoryCache({});

    apollo.create({
      link: wsLink,
      cache: cache.restore(window[ '__APOLLO_CLIENT__' ]),
      connectToDevTools: true,
      queryDeduplication: true
    });
  }
}

In my Authentication service, I'm injecting this module in constructor via private graphQLModule: GraphQLModule.
After login or logout I'm doing:

const client = this.graphQLModule.subscriptionClient;
// Close actual connection
client.close(true, true);
// todo: set/unset session token in sessionStorage
// Connect again
(<any>client).connect();

This opens the subscription client on app start and re-inits it completely on login/logout.

from apollo-link.

RyannGalea avatar RyannGalea commented on August 26, 2024 3

Any clean new ideas on this? Love to have a solution here. I create my apollo link on load but once my user logs in id really like to update the token being sent in the context.

from apollo-link.

cowlicks avatar cowlicks commented on August 26, 2024 2

@sulliwane @RyannGalea @jonathanheilmann @helios1138 To dynamically update tokens/connection params you should set it using middleware of the SubscriptionClient. See my gist here: https://gist.github.com/cowlicks/71e766164647f224bf15f086ea34fa52

const subscriptionMiddleware = {
  applyMiddleware: function(options, next) {
    // Get the current context
    const context = options.getContext();
    // set it on the `options` which will be passed to the websocket with Apollo 
    // Server it becomes: `ApolloServer({contetx: ({payload}) => (returns options)
    options.authorization = context.authorization;
    next()
  },
};

const server = new ApolloServer({
  context: ({connection, payload, req}) => {
    // whatever you return here can be accessed from the middleware of the SubscriptionMiddleware with
    // applyMiddleware: (options, next) => options.getContext()
    return {authorization: payload.authorization};
  },
});

const link = new WebSocketLink({
    uri: WS_URL,
    webSocketImpl: WebSocket,
    options: {
        reconnect: true,
    }
});

link.subscriptionClient.use([subscriptionMiddleware]);

from apollo-link.

dorsett85 avatar dorsett85 commented on August 26, 2024 2

I ended up dynamically generating the client. If the user is not logged in, the client does not create the new WebSocket link, otherwise it does. The generateApolloClient function below exposes the SubscriptionClient as well so I can add dispatch calls when it's been connected (e.g., tell redux store that the websocket is connected).

/**
 * Http link
 */
const httpLink = new HttpLink({
  uri: '/graphql'
});

/**
 * Perform actions before each http request
 */
const middlewareLink = new ApolloLink((operation, forward) => {
  operation.setContext({
    headers: {
      authorization: localStorage.getItem('token')
    }
  });
  return forward(operation);
});

const cache = new InMemoryCache();

type ApolloSubscriptionClient<TLoggedIn extends boolean> = TLoggedIn extends true ? SubscriptionClient : undefined;

/**
 * Function to create our apollo client. After login we want to add subscriptions
 * with websocket, so we'll need to recreate the entire client depending on the
 * user being logged in or logged out.
 */
export const generateApolloClient = <TLoggedIn extends boolean>(
  loggedIn: TLoggedIn
): [ApolloClient<NormalizedCacheObject>, ApolloSubscriptionClient<TLoggedIn>] => {
  let link = middlewareLink.concat(httpLink);

  // Only apply our subscription client when the user is logged in
  let subscriptionClient: SubscriptionClient | undefined;
  if (loggedIn) {
    subscriptionClient = new SubscriptionClient('ws://localhost:4001/graphql/ws', {
      reconnect: true,
      connectionParams: () => ({ authorization: localStorage.getItem('token') })
    });
    
    const wsLink = new WebSocketLink(subscriptionClient);

    link = split(
      ({ query }) => {
        const definition = getMainDefinition(query);
        return (
          definition.kind === 'OperationDefinition' &&
          definition.operation === 'subscription'
        );
      },
      wsLink,
      link
    );
  }
  
  const apolloClient = new ApolloClient({ link, cache });

  return [apolloClient, subscriptionClient as ApolloSubscriptionClient<TLoggedIn>];
};

Here is the code in the ApolloProvider component that uses the generateApolloClient function:

const GraphqlProvider: React.FC = ({ children }) => {
  const dispatch = useAppDispatch();
  const loggedIn = useUserLoggedIn();
  const [client, setClient] = useState(generateApolloClient(false)[0]);

  useEffect(() => {
    if (loggedIn) {
      const [apolloClient, subscriptionClient] = generateApolloClient(loggedIn);
      subscriptionClient.onConnected(() => {
        dispatch(setSubscriptionConnected(true));
      })
      setClient(apolloClient);
    }
  }, [dispatch, loggedIn]);

  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

This is working well and I see a new token on the server every time a user logs out and back in again, but seems like a bit of extra work. Thoughts?

from apollo-link.

dyst5422 avatar dyst5422 commented on August 26, 2024 1

Here's what I've got for you, modeled after the way I was doing it with ApolloClient V1

Add a new link middleware to attach the auth token onto the payload

const authMiddleware = new ApolloLink((operation, forward) => {
    // Add the authorization to the headers for HTTP authentication
    operation.setContext({
      headers: {
        authorization: `Bearer ${authToken()}`,
      },
    });

    // Add onto payload for WebSocket authentication
    (operation as Operation & { authToken: string | undefined }).authToken = authToken();


    return (forward as any)(operation);
  });

const myLink = concat(myLink, wsLink);

Then server side you can check it and apply to context using the onOperation hook

function configureDecodeTokenSocketMiddleware(authURL: string) {
  return async function decodeTokenSocketMiddleware<ConnectionParams extends { authToken: string }>(connectionParams: ConnectionParams, operationParams: object) {
    let authPayload;
    try {
      if (typeof connectionParams.authToken === 'string') {
        authPayload = await verifyJWT(authURL, connectionParams.authToken);
      } else {
        throw new Error('Auth Token not available');
      }
    } catch(e) {
      authPayload = {};
    }
    return {
      ...operationParams,
      context: {
        authentication: authPayload,
      },
    };
  };
}

new SubscriptionServer({
      execute,
      subscribe,
      schema,
      onOperation: configureDecodeTokenSocketMiddleware(appConfig.authURL),
    }, {
      server: appConfig.server,
      path: `/${appConfig.subscriptionsEndpoint}`,
    });

from apollo-link.

clayne11 avatar clayne11 commented on August 26, 2024 1

This is the best solution we've been able to come up with: apollographql/apollo-server#1505.

The context is created on every operation and we simply look up the user dynamically.

from apollo-link.

zenflow avatar zenflow commented on August 26, 2024 1

@kamilkisiela Can we just make the wsLink.subscriptionClient member a public member? It's not likely that it will need to change, is it?

from apollo-link.

WangShuXian6 avatar WangShuXian6 commented on August 26, 2024 1

use get to get the last value

const wsLink = new WebSocketLink({
  uri: CLIENT_CONFIG.websocketUrl,
  options: {
    lazy: true,
    reconnect: true,
    connectionParams: {
      get authorization() {
        return user.getAuthorization();
      },
    },
    connectionCallback: (error) => {
      //@ts-ignore
      if (error?.message === "Authentication Failure!") {
        //@ts-ignore
        //wsLink.subscriptionClient.close(false, false);
      }
    },
  },
});

from apollo-link.

yutin1987 avatar yutin1987 commented on August 26, 2024

maybe, you can try to resend this message: https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L507

      const payload: ConnectionParams = typeof this.connectionParams === 'function' ? this.connectionParams() : this.connectionParams;
      this.sendMessage(undefined, MessageTypes.GQL_CONNECTION_INIT, payload);

from apollo-link.

jeremyputeaux avatar jeremyputeaux commented on August 26, 2024

Is there a recommanded way to do this?

from apollo-link.

bogdansoare avatar bogdansoare commented on August 26, 2024

also having this issue
@jbaxleyiii any thoughts ?

from apollo-link.

clayne11 avatar clayne11 commented on August 26, 2024

Ideally this would support a promise returning function as well so if we have to async fetch the token we can.

from apollo-link.

neyosoft avatar neyosoft commented on August 26, 2024

@clayne11 Am not sure it does. I'm trying to use AsyncStorage from React-Native as the connectionParams. But because of the async i added, it keep sending undefined to the server.
I have an issue with this?

UPDATE:
There is a Pull Request that address this but not yet merged into the master branch

from apollo-link.

ujwal-setlur avatar ujwal-setlur commented on August 26, 2024

@jonathanheilmann, your solution is similar to @helios1138 original workaround. However, the connect() API for the subscription client is marked private and ideally should not be used.

I tried doing a subscriptionClient.close(false, false) which is a public API and should have forced a reconnect, and it does, but the subscription still doesn't work. The only thing that is working right now is:

subscriptionClient.close();
subscriptionClient.connect(); // this is a private API :-(

from apollo-link.

j0nd0n7 avatar j0nd0n7 commented on August 26, 2024

This helped to me
#446 (comment)

from apollo-link.

MikaStark avatar MikaStark commented on August 26, 2024

The only solution I found is to manually close the client...

from apollo-link.

clayne11 avatar clayne11 commented on August 26, 2024

I posted a solution 1.5 years ago: apollographql/apollo-server#1505. AFAIK this still works.

from apollo-link.

MikaStark avatar MikaStark commented on August 26, 2024

@clayne11 your solution implies to use a middleware. I think it's great but I work with Angular and Yoga-Graphql and for unknown reason I can't send token through context.

from apollo-link.

HugoLiconV avatar HugoLiconV commented on August 26, 2024

Is there a way to access the token in a middleware in the server?
I'm using Yoga and this is how I access the token in the context

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  middlewares: [permissions],
  resolvers,
  context: ({  connection, ...request }) => {
    if(connection) {
          wsToken: connection.context.authToken
      }
    }
    return ({...request, prisma });
  }
});


const options = {
  port: process.env.PORT || 4000,
  tracing: true,
  subscriptions: {
    path: "/",
    onConnect: async (connectionParams, webSocket) => {
     if (connectionParams.authToken) {
     // I can access this through context
       return {
         authToken: connectionParams.authToken
       };
     }
    }
  }
};


server.express.use(async (req, res, next) => {
  /*
     I want to access authToken here
  */
  next();
});


server.start(options, ({port}) => {
  console.log(`Server is runnning on http://localhost:${port}`);
});

from apollo-link.

DominicTobias-b1 avatar DominicTobias-b1 commented on August 26, 2024

Without closing the socket I get a bunch of apollo network errors (even though it corrects itself we show those errors in a bunch of toast notifications so less than ideal) so I have to close the websocket after updating the token, then reconnect: true ensure it reconnects... except it reconnects in an infinite loop even thought the socket is only closed once 😔

Edit: Fixed by calling SubscriptionClient.client.close() instead of SubscriptionClient.close().... 🤔

from apollo-link.

proua avatar proua commented on August 26, 2024

I install subscriptions like this apolloServer.installSubscriptionHandlers(server);
and I create the ApolloServer
const apolloServer = new ApolloServer({ schema, introspection: true, subscriptions: { onConnect: (connectionParams, webSocket, context) => { }, onDisconnect: (webSocket, context) => {}, ...there is no OnOperation: () => {}!!! },

Is there some other way how to add onOperation? Or I have to manually create SubscriptionServer and do not use apolloServer.installSubscriptionHandlers(server)?

from apollo-link.

Overdozed avatar Overdozed commented on August 26, 2024

Based on @dorsett85 answer It may be a good idea to use react context api to refresh the client for React/NextJS apps.

----------- Context API

export const ApolloClientContext = createContext(undefined);
export const ApolloClientContextProvider: React.FC<{}> = ({ children }) => {
  const [apolloClient, setApolloClient] = useState(client);

  return (
    <ApolloClientContext.Provider value={[apolloClient, setApolloClient]}>{children}</ApolloClientContext.Provider>
  );
};

--------- Apollo Provider

   <ApolloClientContextProvider>
      <ApolloClientContext.Consumer>
        {([apolloClient, setApolloClient]) => {
          return (
            <ApolloProvider client={apolloClient}>
              <Component {...pageProps} />
            </ApolloProvider>
          );
        }}
      </ApolloClientContext.Consumer>
    </ApolloClientContextProvider>

-------- util function generating new Client with auth

export const generateApolloClientWithAuth = (token): ApolloClient<any> => {
 const httpLink = createHttpLink({
  uri: 'http://localhost:5000/graphql',
  fetch,
  credentials: 'include'
});
  const wsLink = process.browser
    ? new WebSocketLink({
        uri: `ws://localhost:5000/graphql`,
        options: {
          reconnect: true,
          connectionParams() {
            console.log('-token', token);
            return {
              authorization: token ? `Bearer ${token}` : ''
            };
          }
        }
      })
    : null;
  const splitLink = process.browser
    ? split(
        ({ query }) => {
          const definition = getMainDefinition(query);
          return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
        },
        wsLink,
        httpLink
      )
    : httpLink;
  return new ApolloClient({
    ssrMode: true,
    link: splitLink,
    connectToDevTools: process.env.NODE_ENV === 'development',
    cache: new InMemoryCache().restore({})
  });
};

----------login component

const [apolloClient, setApolloClient] = useContext(ApolloClientContext);

----------- on login
setApolloClient(generateApolloClientWithAuth(token));

Important note: Provided Apollo Client code uses SSR

from apollo-link.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.