-
Notifications
You must be signed in to change notification settings - Fork 343
feat: move rrweb event stream to client and query through /api/clickhouse-proxy #755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7406d9b
ca292ba
b5e07d3
ad66255
de10070
68060c8
6801d96
4e2e5bc
4a2c701
fe188f2
5e1bf07
2ee94ae
454f7b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@hyperdx/common-utils": patch | ||
| "@hyperdx/api": patch | ||
| "@hyperdx/app": patch | ||
| --- | ||
|
|
||
| feat: move rrweb event fetching to the client instead of an api route |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import express from 'express'; | ||
| import { z } from 'zod'; | ||
|
|
||
| export function validateRequestHeaders<T extends z.Schema>(schema: T) { | ||
| return function ( | ||
| req: express.Request, | ||
| res: express.Response, | ||
| next: express.NextFunction, | ||
| ) { | ||
| const parsed = schema.safeParse(req.headers); | ||
| if (!parsed.success) { | ||
| return res.status(400).json({ type: 'Headers', errors: parsed.error }); | ||
| } | ||
|
|
||
| return next(); | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,11 @@ | ||||||||||
| import express, { Request, Response } from 'express'; | ||||||||||
| import express, { RequestHandler, Response } from 'express'; | ||||||||||
| import { createProxyMiddleware } from 'http-proxy-middleware'; | ||||||||||
| import { z } from 'zod'; | ||||||||||
| import { validateRequest } from 'zod-express-middleware'; | ||||||||||
|
|
||||||||||
| import { getConnectionById } from '@/controllers/connection'; | ||||||||||
| import { getNonNullUserWithTeam } from '@/middleware/auth'; | ||||||||||
| import { validateRequestHeaders } from '@/middleware/validation'; | ||||||||||
| import { objectIdSchema } from '@/utils/zod'; | ||||||||||
|
|
||||||||||
| const router = express.Router(); | ||||||||||
|
|
@@ -58,17 +59,22 @@ router.post( | |||||||||
| }, | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| router.get( | ||||||||||
| '/*', | ||||||||||
| validateRequest({ | ||||||||||
| query: z.object({ | ||||||||||
| hyperdx_connection_id: objectIdSchema, | ||||||||||
| }), | ||||||||||
| const hasConnectionId = validateRequestHeaders( | ||||||||||
| z.object({ | ||||||||||
| 'x-hyperdx-connection-id': objectIdSchema, | ||||||||||
| }), | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| const getConnection: RequestHandler = | ||||||||||
| // prettier-ignore-next-line | ||||||||||
| async (req, res, next) => { | ||||||||||
| try { | ||||||||||
| const { teamId } = getNonNullUserWithTeam(req); | ||||||||||
| const { hyperdx_connection_id } = req.query; | ||||||||||
| const connection_id = req.headers['x-hyperdx-connection-id']!; // ! because zod already validated | ||||||||||
| delete req.headers['x-hyperdx-connection-id']; | ||||||||||
| const hyperdx_connection_id = Array.isArray(connection_id) | ||||||||||
| ? connection_id.join('') | ||||||||||
| : connection_id; | ||||||||||
|
|
||||||||||
| const connection = await getConnectionById( | ||||||||||
| teamId.toString(), | ||||||||||
|
|
@@ -93,13 +99,15 @@ router.get( | |||||||||
| console.error('Error fetching connection info:', e); | ||||||||||
| next(e); | ||||||||||
| } | ||||||||||
| }, | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const proxyMiddleware: RequestHandler = | ||||||||||
| // prettier-ignore-next-line | ||||||||||
| createProxyMiddleware({ | ||||||||||
| target: '', // doesn't matter. it should be overridden by the router | ||||||||||
| changeOrigin: true, | ||||||||||
| pathFilter: (path, _req) => { | ||||||||||
| // TODO: allow other methods | ||||||||||
| return _req.method === 'GET'; | ||||||||||
| return _req.method === 'GET' || _req.method === 'POST'; | ||||||||||
| }, | ||||||||||
| pathRewrite: { | ||||||||||
| '^/clickhouse-proxy': '', | ||||||||||
|
|
@@ -113,16 +121,20 @@ router.get( | |||||||||
| on: { | ||||||||||
| proxyReq: (proxyReq, _req) => { | ||||||||||
| const newPath = _req.params[0]; | ||||||||||
| // @ts-expect-error _req.query is type ParamQs, which doesn't play nicely with URLSearchParams. TODO: Replace with getting query params from _req.url eventually | ||||||||||
| const qparams = new URLSearchParams(_req.query); | ||||||||||
|
Comment on lines
+124
to
125
|
||||||||||
| // @ts-expect-error _req.query is type ParamQs, which doesn't play nicely with URLSearchParams. TODO: Replace with getting query params from _req.url eventually | |
| const qparams = new URLSearchParams(_req.query); | |
| const url = new URL(_req.url, `http://${_req.headers.host}`); | |
| const qparams = new URLSearchParams(url.search); |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ const DEFAULT_SERVER_URL = `http://127.0.0.1:${process.env.HYPERDX_API_PORT}`; | |
| export const config = { | ||
| api: { | ||
| externalResolver: true, | ||
| bodyParser: true, | ||
| bodyParser: false, | ||
| }, | ||
| }; | ||
|
|
||
|
|
@@ -17,12 +17,6 @@ export default (req: NextApiRequest, res: NextApiResponse) => { | |
| pathRewrite: { '^/api': '' }, | ||
| target: process.env.NEXT_PUBLIC_SERVER_URL || DEFAULT_SERVER_URL, | ||
| autoRewrite: true, | ||
| /** | ||
| * Fix bodyParser | ||
| **/ | ||
| on: { | ||
| proxyReq: fixRequestBody, | ||
| }, | ||
| // ...(IS_DEV && { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function fixRequestBody does not handle Content-Type: 'text/plain', which makes things horribly annoying. Here's what happens:
I filed an issue and PR to fix fixRequestBody. But we don't even need bodyParser here, so just disabling it and forwarding everything works too. |
||
| // logger: console, | ||
| // }), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Header x-hyperdx-connection-id is now in place of the respective queryparam. I chose this based on the clickhouse documentation for a reverse-proxy in front of a clickhouse instance. They do have a section that allows you to send query params as well, but those are all prefixed with 'param_', which quickly gets messy with zod validating either query param 'hyperdx_connection_id' or 'param_hyperdx_connection_id', so I opted to move to using headers.