Attempting to use Datadog RUM with Nuxt in plugin, but fetch is already aliased before RUM can overwrite it.
Environment
"@datadog/browser-rum": "^5.0.0",
"nuxt": "^3.7.4",
Reproduction
Not sure if I can set up an example as this might be a paid product only.
Describe the bug
I have the integration set up as a Nuxt plugin, and datadog attempts to override window.fetch, but at that point, fetch has already been stored as the original function in ofetch, so none of the callbacks get intercepted with appropriate headers. I don't see a manual way to override fetch after the fact. Is there a way to override this fetch in Nuxt?
Additional context
No response
Logs
No response
I also experience this issue.
I've found a workaround for my use case: a small Nuxt app generated to a static SPA with no server-side component.
"@datadog/browser-rum": "^5.4.0",
"nuxt": "^3.9.0",
"ofetch": "^1.3.3",
nuxt.config.ts
export default defineNuxtConfig({
ssr: false,
nitro: {
static: true,
prerender: {
autoSubfolderIndex: false,
routes: ['/index.html', '/success.html'],
},
},
})
plugins/01.datadogRum.ts
import { datadogRum } from '@datadog/browser-rum'
export default defineNuxtPlugin(nuxtApp => {
datadogRum.init({
clientToken: '<CLIENT_TOKEN>',
applicationId: '<APPLICATION_ID>',
site: 'datadoghq.com',
})
return {
provide: {
datadogRum,
},
}
})
Invoking datadogRum.init eventually causes createFetchObservable which causes instrumentMethod.
[!IMPORTANT] To capture RUM resource data on fetch calls, the RUM
instrumentationWrapper(packaged by theinstrumentMethodfunction aswindow.fetch) must be invoked.ofetchinvokes nativefetchdirectly through its stored reference, bypassing Datadog'sinstrumentationWrapper.I wonder if a Datadog Nuxt module could solve this issue as a way to run
datadogRum.initbefore$fetchgets created? 🤔
plugins/02.instrumentedFetch.ts (the workaround)
import { createFetch, type FetchRequest, type FetchOptions } from 'ofetch'
export default defineNuxtPlugin(nuxtApp => {
/**
* Create a new ofetch, passing in the version of fetch that is now the RUM instrumentationWrapper
* put in place by the invocation of datadogRum.init in the 01.datadogRum plugin.
*/
function instrumentedFetch<
T = unknown,
R extends FetchRequest = FetchRequest,
O extends FetchOptions = FetchOptions,
>(request: R, opts: O) {
return <Promise<T>>createFetch({ fetch })(request, opts) // Optionally configure fetch defaults here
}
return {
provide: {
instrumentedFetch,
},
}
})
You can now invoke useNuxtApp().$instrumentedFetch() with mostly the same function signature as $fetch().
[!WARNING] As I mentioned, my application doesn't have a server component. I wonder if
useAsyncDatacould be used with the instrumented fetch? I could be unaware of other server-side functionality this approach lacks from the built-in fetch composables.
That said, I don't think this is actually an issue with ofetch.
The workaround above did break the functionality of the registerEndpoint helper from @nuxt/test-utils v3.9.0.
For some reason in my runtime, only globalThis.$fetch gets replaced with the vitest-environment-nuxt version assigned here (or maybe globalThis.fetch gets overridden by something else after vitest-environment-nuxt while globalThis.$fetch doesn't?).
Whatever the cause, adding this line to a file included in our Vitest setupFiles got our instrumentedFetch helper to work with registerEndpoint.
__tests__/setup.ts
import type { $Fetch } from 'ofetch'
/* Causes instrumentedFetch to use the fetch that works with registerEndpoint */
globalThis.fetch = (globalThis.$fetch as $Fetch).native
vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environment: 'nuxt',
setupFiles: ['__tests__/setup.ts'],
},
})
[!TIP]
This plugin that runs only in our test environment was a convenient way to mock the RUM SDK.
__tests__/__plugins__/mockDatadogRum.ts
import { vi } from 'vitest' import { defineNuxtPlugin } from 'nuxt/app' export default defineNuxtPlugin(() => { vi.mock('@datadog/browser-rum', () => ({ datadogRum: { init: vi.fn(), addAction: vi.fn(), addError: vi.fn(), }, })) })vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config' export default defineVitestConfig({ test: { environment: 'nuxt', environmentOptions: { nuxt: { overrides: { plugins: ['__tests__/__plugins__/mockDatadogRum.ts'], }, }, }, setupFiles: ['__tests__/setup.ts'], }, })