Skip to content

Commit

Permalink
feat(apollo): complete own plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
dargmuesli committed Jul 17, 2022
1 parent 18210c6 commit 492a23f
Show file tree
Hide file tree
Showing 4 changed files with 452 additions and 26 deletions.
6 changes: 6 additions & 0 deletions nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@
"apollo-cache-inmemory": "1.6.6",
"apollo-client": "2.6.10",
"apollo-link": "1.2.14",
"apollo-link-context": "1.0.20",
"apollo-link-http": "1.5.17",
"apollo-link-http-common": "0.2.16",
"apollo-link-persisted-queries": "0.2.5",
"apollo-link-state": "0.4.2",
"apollo-upload-client": "13.0.0",
"chart.js": "3.8.0",
"consola": "2.15.3",
"cookie": "0.5.0",
Expand Down Expand Up @@ -108,6 +113,7 @@
"@storybook/core-events": "6.5.9",
"@storybook/theming": "6.5.9",
"@tiptap/core": "2.0.0-beta.182",
"@types/apollo-upload-client": "8.1.3",
"@types/cookie": "0.5.1",
"@types/dompurify": "2.3.3",
"@types/html-to-text": "8.1.0",
Expand Down
23 changes: 23 additions & 0 deletions nuxt/plugins/apollo-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Context } from '@nuxt/types-edge'
import consola from 'consola'

export default ({ store }: Context) => {
return {
httpEndpoint: process.server
? 'http://postgraphile:5000/graphql'
: 'https://postgraphile.' +
(process.env.NUXT_ENV_STACK_DOMAIN || 'maevsi.test') +
'/graphql',
getAuth: (_tokenName: string) => {
const jwt = store.state.jwt

if (jwt) {
consola.trace('Apollo request authenticated with: ' + jwt)
return `Bearer ${jwt}`
} else {
consola.trace('Apollo request without authentication.')
return ''
}
},
}
}
308 changes: 286 additions & 22 deletions nuxt/plugins/apollo.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,312 @@
import { Inject } from '@nuxt/types-edge/app'
import { Context } from '@nuxt/types-edge'
import { ApolloClient } from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient, ApolloClientOptions, Resolvers } from 'apollo-client'
import { ApolloLink, DocumentNode, from } from 'apollo-link'
import { setContext } from 'apollo-link-context'
import { createPersistedQueryLink } from 'apollo-link-persisted-queries'
import { HttpOptions } from 'apollo-link-http-common'
import { withClientState, ClientStateConfig } from 'apollo-link-state'
import {
InMemoryCache,
InMemoryCacheConfig,
NormalizedCacheObject,
} from 'apollo-cache-inmemory'
import { createUploadLink } from 'apollo-upload-client'
import Vue from 'vue'
import VueApollo from 'vue-apollo'
import consola from 'consola'
import { parse } from 'cookie'

import config from './apollo-config'

export interface ApolloClientClientConfig<TCacheShape> {
apollo?: ApolloClientOptions<TCacheShape>
cache?: InMemoryCache | false
clientState?: ClientStateConfig
defaultHttpLink?: boolean
httpEndpoint?: string
httpLinkOptions?: HttpOptions
inMemoryCacheOptions?: InMemoryCacheConfig
link?: ApolloLink
persisting?: boolean
resolvers?: Resolvers | Resolvers[]
ssr?: boolean
tokenName?: string
typeDefs?: string | string[] | DocumentNode | DocumentNode[]
websocketsOnly?: boolean
wsEndpoint?: string
getAuth?: (tokenName: string) => string | void
onCacheInit?: (cache: InMemoryCache) => Promise<void>
validateToken?: (token: string) => boolean
}

Vue.use(VueApollo)

export default ({ app }: Context, inject: Inject) => {
const httpLink = createHttpLink({
uri: process.server
? 'http://postgraphile:5000/graphql'
: 'https://postgraphile.' +
(process.env.NUXT_ENV_STACK_DOMAIN || 'maevsi.test') +
'/graphql',
export default (ctx: Context) => {
const { app, beforeNuxtRender, req } = ctx

const tokenName = 'apollo-token'
// const cookieAttributes = { expires: 7, path: '/', secure: false }
const authenticationType = 'Bearer'
const cookies = req && req.headers.cookie && parse(req.headers.cookie)

const clientConfig = config(ctx) as ApolloClientClientConfig<unknown>
const defaultValidateToken = () => true

function defaultGetAuth() {
const token = cookies && cookies[tokenName]
return token &&
clientConfig.validateToken &&
clientConfig.validateToken(token)
? `${authenticationType} ${token}`
: ''
}

if (!clientConfig.validateToken) {
clientConfig.validateToken = defaultValidateToken
}

const defaultCache = clientConfig.cache
? clientConfig.cache
: new InMemoryCache(
clientConfig.inMemoryCacheOptions
? clientConfig.inMemoryCacheOptions
: undefined
)

if (
!process.server &&
window.__NUXT__ &&
window.__NUXT__.apollo &&
window.__NUXT__.apollo.defaultClient
) {
defaultCache.restore(window.__NUXT__.apollo.defaultClient)
}

if (!clientConfig.getAuth) {
clientConfig.getAuth = defaultGetAuth
}

// if (process.client && clientConfig.browserHttpEndpoint) {
// clientConfig.httpEndpoint = clientConfig.browserHttpEndpoint
// }

clientConfig.ssr = !!process.server
clientConfig.cache = defaultCache
// clientConfig.tokenName = defaultTokenName

if (process.server && req && req.headers && req.headers.cookie) {
if (!clientConfig.httpLinkOptions) {
clientConfig.httpLinkOptions = {}
}
if (!clientConfig.httpLinkOptions.headers) {
clientConfig.httpLinkOptions.headers = {}
}
clientConfig.httpLinkOptions.headers.cookie = req.headers.cookie
}

const defaultApolloCreation = createApolloClient({
...clientConfig,
})
const cache = new InMemoryCache()

if (process.client) {
const vueApolloOptions = {
defaultClient: defaultApolloCreation.apolloClient,
errorHandler(error: Error) {
consola.log(
'%cError',
'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;',
error.message
)
},
}

const apolloProvider = new VueApollo(vueApolloOptions)

app.apolloProvider = apolloProvider

if (process.server) {
const apolloSSR = require('vue-apollo/ssr')
beforeNuxtRender(({ nuxtState }) => {
nuxtState.apollo = apolloSSR.getStates(apolloProvider)
})
}
}

export function createApolloClient<TCacheShape>(
options: ApolloClientClientConfig<TCacheShape>
): {
apolloClient: ApolloClient<NormalizedCacheObject>
stateLink: ApolloLink | undefined
} {
const defaultOptions = {
clientId: 'defaultClient',
tokenName: 'apollo-token',
persisting: false,
ssr: false,
websocketsOnly: false,
preAuthLinks: [],
defaultHttpLink: true,
httpLinkOptions: {},
inMemoryCacheOptions: {},
apollo: {},
getAuth: defaultGetAuth,
}

const optionsMerged = {
...defaultOptions,
...options,
}

let { cache, link, persisting } = optionsMerged

const {
apollo,
clientId,
clientState,
defaultHttpLink,
httpEndpoint,
httpLinkOptions,
inMemoryCacheOptions,
preAuthLinks,
resolvers,
ssr,
tokenName,
typeDefs,
websocketsOnly,
wsEndpoint,
getAuth,
onCacheInit,
} = optionsMerged

let authLink, stateLink
const disableHttp = websocketsOnly && !ssr && wsEndpoint

// Apollo cache
if (!cache) {
cache = new InMemoryCache(inMemoryCacheOptions)
}

if (!disableHttp) {
const httpLink = createUploadLink({
uri: httpEndpoint,
...httpLinkOptions,
})

if (!link) {
link = httpLink
} else if (defaultHttpLink) {
link = from([link, httpLink])
}

// HTTP Auth header injection
authLink = setContext(async (_, { headers }) => {
const Authorization = await getAuth(tokenName)
const authorizationHeader = Authorization ? { Authorization } : {}
return {
headers: {
...headers,
...authorizationHeader,
},
}
})

// Concat all the http link parts
link = authLink.concat(link)

if (preAuthLinks.length) {
link = from(preAuthLinks).concat(authLink)
}
}

// On the server, we don't want WebSockets and Upload links
if (!ssr) {
// If on the client, recover the injected state
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-underscore-dangle
const state = window.__APOLLO_STATE__
if (state) {
cache.restore(state.defaultClient)
if (state && state[clientId]) {
// Restore state
cache.restore(state[clientId])
}
}

if (!disableHttp) {
let persistingOpts = {}
if (typeof persisting === 'object' && persisting != null) {
persistingOpts = persisting
persisting = true
}
if (persisting === true && link) {
link = createPersistedQueryLink(persistingOpts).concat(link)
}
}
}

if (clientState && link) {
consola.warn(
'clientState is deprecated, see https://vue-cli-plugin-apollo.netlify.com/guide/client-state.html'
)
stateLink = withClientState({
cache,
...clientState,
resolvers: undefined,
})
link = from([stateLink, link])
}

const apolloClient = new ApolloClient({
link: httpLink,
link,
cache,
...(process.server
// Additional options
...(ssr
? {
// Set this on the server to optimize queries when SSR
ssrMode: true,
}
: {
// This will temporary disable query force-fetching
ssrForceFetchDelay: 100,
// Apollo devtools
connectToDevTools: process.env.NODE_ENV !== 'production',
}),
})
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
typeDefs,
resolvers,
...apollo,
})

app.apolloProvider = apolloProvider
// Re-write the client state defaults on cache reset
if (stateLink) {
apolloClient.onResetStore(stateLink.writeDefaults)
}

if (onCacheInit) {
onCacheInit(cache)
apolloClient.onResetStore(() => {
if (cache) {
return onCacheInit(cache)
} else {
return Promise.resolve()
}
})
}

return {
apolloClient,
stateLink,
}
}

inject('apolloProvider', apolloProvider)
function defaultGetAuth(tokenName: string) {
if (typeof window !== 'undefined') {
// get the authentication token from local storage if it exists
const token = window.localStorage.getItem(tokenName)
// return the headers to the context so httpLink can read them
return token ? `Bearer ${token}` : ''
}
}

declare global {
interface Window {
__APOLLO_STATE__: Record<string, NormalizedCacheObject>
__NUXT__: Record<string, NormalizedCacheObject>
}
}
Loading

0 comments on commit 492a23f

Please sign in to comment.