Skip to content

Commit

Permalink
forward visitor info for plausible (#287)
Browse files Browse the repository at this point in the history
Fixes issue with unique visitors not being counted by forwarded the
client's IP address and user agent in the plausible request to
`/api/event`

Tested on https://dev-plausible-test.cryoet.dev.si.czi.technology/ and
opened the website on 3 different devices (laptop, home computer, and
phone on LTE)

<img width="720" alt="image"
src="https://github.com/chanzuckerberg/cryoet-data-portal/assets/2176050/91cce66f-5aaa-46c2-8623-ef9cb35b5627">
  • Loading branch information
codemonkey800 authored Dec 16, 2023
1 parent a715b42 commit cf33352
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 23 deletions.
22 changes: 18 additions & 4 deletions frontend/packages/data-portal/app/routes/api.event.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { ActionFunctionArgs } from '@remix-run/server-runtime'

export async function action({ request }: ActionFunctionArgs) {
const response = await fetch('https://plausible.io/api/event', {
import { ServerContext } from 'app/types/context'
import { removeNullishValues } from 'app/utils/object'

export async function action({ request, context }: ActionFunctionArgs) {
const { clientIp } = context as ServerContext

const payload = {
body: request.body,
method: request.method,
headers: {
headers: removeNullishValues({
'Content-Type': 'application/json',
},
'user-agent': request.headers.get('user-agent'),
'X-Forwarded-For': clientIp,
}) as HeadersInit,
}

// eslint-disable-next-line no-console
console.log({
message: 'Sending plausible event',
payload,
})

const response = await fetch('https://plausible.io/api/event', payload)
const responseBody = await response.text()
const { status, headers } = response

Expand Down
5 changes: 5 additions & 0 deletions frontend/packages/data-portal/app/types/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AppLoadContext } from '@remix-run/server-runtime'

export interface ServerContext extends AppLoadContext {
clientIp: string
}
35 changes: 16 additions & 19 deletions frontend/packages/data-portal/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import fs from 'fs'
import morgan from 'morgan'
import sourceMapSupport from 'source-map-support'

import { ServerContext } from 'app/types/context'

// patch in Remix runtime globals
installGlobals()
sourceMapSupport.install()
Expand All @@ -25,11 +27,7 @@ async function reimportServer() {
}

// Create a request handler that watches for changes to the server build during development.
async function createDevRequestHandler() {
// We'll make chokidar a dev dependency so it doesn't get bundled in production.
const chokidar =
process.env.NODE_ENV === 'development' ? await import('chokidar') : null

async function getRequestHandler() {
async function handleServerUpdate() {
// 1. re-import the server build
build = await reimportServer()
Expand All @@ -45,17 +43,24 @@ async function createDevRequestHandler() {
broadcastDevReady(build)
}

chokidar
?.watch(WATCH_PATH, { ignoreInitial: true })
.on('add', handleServerUpdate)
.on('change', handleServerUpdate)
if (process.env.NODE_ENV === 'development') {
// We'll make chokidar a dev dependency so it doesn't get bundled in production.
const chokidar =
process.env.NODE_ENV === 'development' ? await import('chokidar') : null

chokidar
?.watch(WATCH_PATH, { ignoreInitial: true })
.on('add', handleServerUpdate)
.on('change', handleServerUpdate)
}

// wrap request handler to make sure its recreated with the latest build for every request
return async (req: Request, res: Response, next: NextFunction) => {
try {
await createRequestHandler({
build,
mode: 'development',
mode: process.env.NODE_ENV,
getLoadContext: () => ({ clientIp: req.ip }) as ServerContext,
})(req, res, next)
} catch (error) {
next(error)
Expand Down Expand Up @@ -85,15 +90,7 @@ async function main() {
app.use(morgan('tiny'))

// Check if the server is running in development mode and use the devBuild to reflect realtime changes in the codebase.
app.all(
'*',
process.env.NODE_ENV === 'development'
? await createDevRequestHandler()
: createRequestHandler({
build,
mode: process.env.NODE_ENV,
}),
)
app.all('*', await getRequestHandler())

const port = process.env.PORT || 8080
app.listen(port, () => {
Expand Down

0 comments on commit cf33352

Please sign in to comment.