Skip to content

Instantly share code, notes, and snippets.

@revskill10
Forked from shaxaaa/apollo2.js
Created October 5, 2019 22:38
Show Gist options
  • Save revskill10/90c3be955a80328e0323f40313c08646 to your computer and use it in GitHub Desktop.
Save revskill10/90c3be955a80328e0323f40313c08646 to your computer and use it in GitHub Desktop.
import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import fetch from 'isomorphic-unfetch';
import { ApolloLink, split, Observable } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { WebSocketLink } from 'apollo-link-ws';
import { setContext } from 'apollo-link-context';
import { getMainDefinition } from 'apollo-utilities';
import { ApolloProvider } from '@apollo/react-hooks';
import gql from 'graphql-tag';
import { i18n } from '../i18n';
import { getTokens, removeToken, setTokens, logout } from './auth';
const REFRESH_TOKEN = gql`
mutation($refreshToken: String!) {
refreshToken(refreshToken: $refreshToken) {
token
refreshToken
}
}
`;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export default function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState, null);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
if (process.env.NODE_ENV !== 'production') {
// Find correct display name
const displayName = PageComponent.displayName || PageComponent.name || 'Component';
// Warn if old way of installing apollo is used
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.');
}
// Set correct display name for devtools
WithApollo.displayName = `withApollo(${displayName})`;
// Add some prop types
WithApollo.propTypes = {
// Used for getDataFromTree rendering
apolloClient: PropTypes.object,
// Used for client/server rendering
apolloState: PropTypes.object,
};
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx;
// Run all GraphQL queries in the component tree
// and extract the resulting data
const apolloClient = (ctx.apolloClient = initApolloClient({}, ctx));
const pageProps = PageComponent.getInitialProps
? await PageComponent.getInitialProps(ctx)
: {};
// Only on the server
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return {};
}
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr');
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error);
}
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
let apolloClient = null;
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
*/
function initApolloClient(...args) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(...args);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(...args);
}
return apolloClient;
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
* @param {Object} config
*/
function createApolloClient(initialState = {}, ctx = null) {
let changedToken;
const fetchOptions = {};
// If you are using a https_proxy, add fetchOptions with 'https-proxy-agent' agent instance
// 'https-proxy-agent' is required here because it's a sever-side only module
if (typeof window === 'undefined') {
if (process.env.https_proxy) {
fetchOptions.agent = new (require('https-proxy-agent'))(process.env.https_proxy);
}
}
const httpLink = new HttpLink({
uri: `${process.env.API}`, // Server URL (must be absolute)
credentials: 'same-origin',
fetch,
fetchOptions,
});
const wsLink =
typeof window === 'undefined'
? null
: new WebSocketLink({
uri: `${process.env.WEB_SOCKET_LINK}`,
options: {
reconnect: true,
},
});
const link =
typeof window === 'undefined'
? httpLink
: split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' && definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const authLink = setContext((request, { headers }) => {
const { token } = getTokens(ctx);
const test = typeof changedToken !== 'undefined' ? changedToken : token;
console.log(`token is ${test}`);
console.log(request);
return {
headers: {
...headers,
authorization:
typeof changedToken !== 'undefined' ? changedToken : token ? `Bearer ${token}` : '',
},
};
});
const languageLink = setContext(() => {
const lng = i18n.language || i18n.options.defaultLanguage;
return {
uri: `${process.env.API + lng}/`,
};
});
const errorLink = onError(({ networkError, operation, forward }) => {
if (networkError && networkError.statusCode === 401) {
const { token, refreshToken } = getTokens(ctx);
if (token && refreshToken) {
// Let's refresh token through async request.
return new Observable(observer => {
let subscription;
// Remove the token so that auth header is not sent in the request.
removeToken('token', ctx);
changedToken = '';
client
.mutate({
mutation: REFRESH_TOKEN,
variables: { refreshToken },
})
.then(response => {
// Set new tokens.
setTokens(response.data.refreshToken, ctx);
console.log('refreshing tokens');
console.log(response.data.refreshToken);
changedToken = response.data.refreshToken.token;
})
.then(() => {
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
// Retry last failed request
subscription = forward(operation).subscribe(subscriber);
})
.catch(error => {
// Logout the client.
console.log('refresh token error');
console.log(error);
logout(client, ctx);
changedToken = '';
});
return () => {
if (subscription) subscription.unsubscribe();
};
});
}
}
return null;
});
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
const client = new ApolloClient({
ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once)
link: ApolloLink.from([errorLink, authLink, languageLink, link]),
cache: new InMemoryCache().restore(initialState),
resolvers: {},
});
return client;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment