import { ApolloClient, ApolloLink, createHttpLink, from, InMemoryCache, split, } from '@apollo/client/core' import { WebSocketLink } from '@apollo/client/link/ws' import { setContext } from '@apollo/client/link/context' import { getMainDefinition } from '@apollo/client/utilities' import { SentryLink } from 'apollo-link-sentry' import * as Sentry from '@sentry/browser' import { type GetTokenSilentlyOptions } from '@auth0/auth0-spa-js' import { DefaultApolloClient, provideApolloClient } from '@vue/apollo-composable' import type { Auth0VueClient } from '@auth0/auth0-vue' import { defineNuxtPlugin, useNuxtApp } from '#app' import { envConfig } from '~/utils/environment' // The side effect of patching fetch() has to occur before configuring Apollo Client // https://github.com/getsentry/sentry-javascript/issues/2860#issuecomment-684514367 import './sentry' const apiUrl = envConfig(window.location.hostname).apiUrl const wsUrl = apiUrl.replace(/^http/, 'ws') const cache = new InMemoryCache({ typePolicies: { }, }) const getToken = async (options: GetTokenSilentlyOptions) => { const nuxtApp = useNuxtApp() const auth0: Auth0VueClient = nuxtApp.$auth0 as Auth0VueClient return await auth0.getAccessTokenSilently(options).catch((err) => { Sentry.captureException(err, { extra: { function: 'getTokenSilently', }, }) return undefined }) } const httpLink = createHttpLink({ uri: apiUrl, }) const wsLink = new WebSocketLink({ uri: wsUrl, options: { reconnect: true, lazy: true, connectionParams: () => { return getToken({}).then((token) => ({ authToken: token, })) }, }, }) const authLink = setContext(async (_, { headers }) => { return await getToken({}).then((token) => ({ headers: { ...headers, authorization: token ? `Bearer ${token}` : '', }, })) }) const traceHeaders = setContext((_, { headers }) => { const th = Sentry.getCurrentHub().traceHeaders() return { headers: { ...headers, ...th, }, } }) const sentryTransactionLink = new ApolloLink((operation, forward) => { const existingTransaction = Sentry.getCurrentHub() .getScope() ?.getTransaction() // Create a transaction if one does not exist in order to work around // https://github.com/getsentry/sentry-javascript/issues/3169 // https://github.com/getsentry/sentry-javascript/issues/4072 const transaction = existingTransaction ?? Sentry.startTransaction({ name: `Apollo Request: ${operation.operationName}`, }) Sentry.getCurrentHub().configureScope((scope) => scope.setSpan(transaction)) operation.setContext({ tracing: { transaction } }) return forward(operation).map((data) => { operation.getContext().tracing?.transaction?.finish() return data }) }) const link = sentryTransactionLink.concat( traceHeaders.concat( from([ new SentryLink({}), split( ({ query }) => { const definition = getMainDefinition(query) return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ) }, authLink.concat(wsLink), authLink.concat(httpLink), ), ]), ), ) const instance = new ApolloClient({ connectToDevTools: true, link, cache, defaultOptions: { query: { fetchPolicy: 'cache-first', }, watchQuery: { fetchPolicy: 'cache-and-network', }, }, }) export default defineNuxtPlugin((nuxtApp) => { nuxtApp.provide(DefaultApolloClient[Symbol.toStringTag], instance) provideApolloClient(instance) })