Skip to content

Commit 4e7fd0b

Browse files
feat: API event handler to proxy event through Nitro server (#36)
* feat: add API event handler, proxy configuration and documentation - Implement event handling in src/runtime/server/api/event.post.ts - Update playground/nuxt.config.ts to enable proxy - Enhance src/module.ts with proxy options and server handler logic - Add proxy and proxyBaseURL options to README and module.ts * chore: minor updates --------- Co-authored-by: Johann Schopplich <[email protected]>
1 parent a1eb478 commit 4e7fd0b

File tree

5 files changed

+90
-0
lines changed

5 files changed

+90
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ With this setup, you can omit the `plausible` key in your Nuxt configuration.
7878
| `autoPageviews` | `boolean` | `true` | Track the current page and all further pages automatically. Disable this if you want to manually manage pageview tracking. |
7979
| `autoOutboundTracking` | `boolean` | `false` | Track all outbound link clicks automatically. If enabled, a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) automagically detects link nodes throughout the application and binds `click` events to them. |
8080
| `logIgnoredEvents` | `boolean` | `false` | Log events to the console if they are ignored. |
81+
| `proxy` | `boolean` | `false` | Whether to proxy the event endpoint through the current origin. |
82+
| `proxyBaseURL` | `string` | `'/api/event'` | The base URL to proxy the event endpoint through. |
8183

8284
## Composables
8385

playground/nuxt.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ export default defineNuxtConfig({
66
plausible: {
77
autoPageviews: false,
88
autoOutboundTracking: false,
9+
proxy: true,
910
},
1011
})

src/module.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defu } from 'defu'
22
import {
33
addImports,
44
addPlugin,
5+
addServerHandler,
56
createResolver,
67
defineNuxtModule,
78
useLogger,
@@ -90,6 +91,20 @@ export interface ModuleOptions {
9091
* @default false
9192
*/
9293
logIgnoredEvents?: boolean
94+
95+
/**
96+
* Whether to proxy the event endpoint through the current origin.
97+
*
98+
* @default false
99+
*/
100+
proxy?: boolean
101+
102+
/**
103+
* The base URL to proxy the event endpoint through.
104+
*
105+
* @default '/api/event'
106+
*/
107+
proxyBaseURL?: string
93108
}
94109

95110
export default defineNuxtModule<ModuleOptions>({
@@ -112,6 +127,8 @@ export default defineNuxtModule<ModuleOptions>({
112127
autoPageviews: true,
113128
autoOutboundTracking: false,
114129
logIgnoredEvents: false,
130+
proxy: false,
131+
proxyBaseURL: '/api/event',
115132
},
116133
setup(options, nuxt) {
117134
const logger = useLogger('plausible')
@@ -142,6 +159,23 @@ export default defineNuxtModule<ModuleOptions>({
142159
// Transpile runtime
143160
nuxt.options.build.transpile.push(resolve('runtime'))
144161

162+
if (nuxt.options.runtimeConfig.public.plausible.proxy) {
163+
const proxyBaseURL = nuxt.options.runtimeConfig.public.plausible.proxyBaseURL
164+
165+
const hasUserProvidedProxyBaseURL
166+
= nuxt.options.serverHandlers.find(handler => handler.route?.startsWith(proxyBaseURL))
167+
|| nuxt.options.devServerHandlers.find(handler => handler.route?.startsWith(proxyBaseURL))
168+
if (hasUserProvidedProxyBaseURL) {
169+
throw new Error(`The route \`${proxyBaseURL}\` is already in use. Please use the \`proxyBaseURL\` option to change the base URL of the proxy endpoint.`)
170+
}
171+
172+
addServerHandler({
173+
route: proxyBaseURL,
174+
handler: resolve('runtime/server/api/event.post'),
175+
method: 'post',
176+
})
177+
}
178+
145179
addImports(
146180
['useTrackEvent', 'useTrackPageview'].map(name => ({
147181
name,

src/runtime/plugin.client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default defineNuxtPlugin({
1717
...options,
1818
logIgnored: options.logIgnoredEvents,
1919
domain: options.domain || window.location.hostname,
20+
apiHost: options.proxy ? window.location.origin : options.apiHost,
2021
})
2122

2223
return {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { joinURL } from 'ufo'
2+
import { createError, defineEventHandler, getRequestHeader, getRequestIP, readBody } from 'h3'
3+
import type { ModuleOptions } from '../../../module'
4+
import { useRuntimeConfig } from '#imports'
5+
6+
export default defineEventHandler(async (event) => {
7+
const config = useRuntimeConfig(event)
8+
const options = config.public.plausible as Required<ModuleOptions>
9+
10+
try {
11+
if (!options?.apiHost) {
12+
throw createError({
13+
statusCode: 500,
14+
message: 'Plausible API host not configured',
15+
})
16+
}
17+
18+
const target = joinURL(options.apiHost, 'api/event')
19+
const body = await readBody(event)
20+
21+
const headers = new Headers({
22+
'Content-Type': 'application/json',
23+
...Object.fromEntries([
24+
['User-Agent', getRequestHeader(event, 'user-agent')],
25+
['X-Forwarded-For', getRequestIP(event, { xForwardedFor: true })],
26+
].filter(([_, value]) => value != null)),
27+
})
28+
29+
const result = await globalThis.$fetch(target, {
30+
method: 'POST',
31+
headers: headers,
32+
body,
33+
retry: 2,
34+
timeout: 5000,
35+
onRequestError: ({ request, error }) => {
36+
console.error(`Failed to send request to ${request}: ${error.message}`)
37+
},
38+
})
39+
40+
return result
41+
}
42+
catch (error) {
43+
if (error instanceof Error && error.name === 'FetchError') {
44+
throw createError({
45+
statusCode: 502,
46+
message: 'Failed to reach Plausible analytics service',
47+
})
48+
}
49+
50+
throw error
51+
}
52+
})

0 commit comments

Comments
 (0)