import ApolloClient, { FetchPolicy } from "apollo-client" import { HttpLink } from "apollo-link-http" import { InMemoryCache, NormalizedCacheObject } from "apollo-cache-inmemory" // import { setContext } from "apollo-link-context" import ActionCable from "action-cable-react-jwt" import ActionCableLink from "graphql-ruby-client/dist/subscriptions/ActionCableLink" import { ApolloLink } from "apollo-link" import { handleAuthentication, refreshToken } from "utils/oauth" import { Observable } from "apollo-link" import { onError } from "apollo-link-error" import config from "config" const promiseToObservable = (promise: Promise) => new Observable((subscriber: any) => { promise.then( value => { if (subscriber.closed) return subscriber.next(value) subscriber.complete() }, error => subscriber.error(error) ) }) const defaultOptions = { watchQuery: { fetchPolicy: "cache-and-network" as FetchPolicy } } const getTokens = async () => { const token = localStorage.getItem("token") const freshToken = localStorage.getItem("refresh_token") if (token && freshToken) { await refreshToken() } else { await handleAuthentication() } const authorization = token ? `Bearer: ${token}` : "" return token ? { authorization: authorization } : {} } const setTokenForOperation = async (operation: any) => { return operation.setContext({ headers: { // eslint-disable-next-line ...(await getTokens()) } }) } const hasSubscriptionOperation = ({ query: { definitions } }: any) => { return definitions.some(({ kind, operation }: any) => { return kind === "OperationDefinition" && operation === "subscription" }) } const createActionCableLink = () => { const token = localStorage.getItem("token") console.log('createActionCableLink: ', token) const cable = ActionCable.createConsumer("ws://localhost:3004/cable", token) return new ActionCableLink({ cable }) } const createLinkWithToken = () => new ApolloLink( (operation, forward) => new Observable(observer => { let handle: any Promise.resolve(operation) .then(setTokenForOperation) .then(() => { handle = forward(operation).subscribe({ next: observer.next.bind(observer), error: observer.error.bind(observer), complete: observer.complete.bind(observer) }) }) .catch(observer.error.bind(observer)) return () => { if (handle) handle.unsubscribe() } }) ) const createErrorLink = () => onError(({ networkError, operation, forward }: any): any => { if (networkError) { switch (networkError.statusCode) { case 401: const token = localStorage.getItem("token") const freshToken = localStorage.getItem("refresh_token") if (token && freshToken) { return promiseToObservable(refreshToken()).flatMap(() => forward(operation)) } else { return promiseToObservable(handleAuthentication()).flatMap(() => forward(operation)) } default: } } }) const createHttpLink = (fetch = undefined, uri = config.api.baseUrl) => new HttpLink({ fetch, uri }) export const createClient = ({ fetch = undefined, uri = config.api.baseUrl }): ApolloClient => { const cache = new InMemoryCache() const client = new ApolloClient({ link: ApolloLink.from([ createErrorLink(), createLinkWithToken(), ApolloLink.split(hasSubscriptionOperation, createActionCableLink(), createHttpLink(fetch, uri)), ]) , cache, defaultOptions }) return client } export default createClient