Created
September 30, 2019 07:56
-
-
Save shaxaaa/15817f1bcc7b479f3c541383d2e83650 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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