From edcc08e1dfeedaaa7b1b064a79610efb2186287c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Sun, 31 Mar 2024 15:19:03 +0200 Subject: [PATCH] sdk: add express sdk --- apps/api/src/controllers/event.controller.ts | 28 +++++------ packages/sdks/express/index.ts | 50 ++++++++++++++++++++ packages/sdks/express/package.json | 36 ++++++++++++++ packages/sdks/express/tsconfig.json | 8 ++++ packages/sdks/express/tsup.config.ts | 9 ++++ pnpm-lock.yaml | 40 ++++++++++++++++ 6 files changed, 154 insertions(+), 17 deletions(-) create mode 100644 packages/sdks/express/index.ts create mode 100644 packages/sdks/express/package.json create mode 100644 packages/sdks/express/tsconfig.json create mode 100644 packages/sdks/express/tsup.config.ts diff --git a/apps/api/src/controllers/event.controller.ts b/apps/api/src/controllers/event.controller.ts index da814b17..570d2e7f 100644 --- a/apps/api/src/controllers/event.controller.ts +++ b/apps/api/src/controllers/event.controller.ts @@ -52,6 +52,8 @@ function createContextLogger(request: FastifyRequest) { }; } +const GLOBAL_PROPERTIES = ['__path', '__referrer']; + export async function postEvent( request: FastifyRequest<{ Body: PostEventPayload; @@ -84,7 +86,7 @@ export async function postEvent( const origin = request.headers.origin!; const ua = request.headers['user-agent']!; const uaInfo = parseUserAgent(ua); - const salts = await getSalts(); + const [geo, salts] = await Promise.all([parseIp(ip), getSalts()]); const currentDeviceId = generateDeviceId({ salt: salts.current, origin, @@ -114,18 +116,11 @@ export async function postEvent( sessionId: event?.sessionId || '', profileId, projectId, - properties: Object.assign( - {}, - omit(['__path', '__referrer'], properties), - { - hash, - query, - } - ), + properties: Object.assign({}, omit(GLOBAL_PROPERTIES, properties)), createdAt, - country: event?.country ?? '', - city: event?.city ?? '', - region: event?.region ?? '', + country: event?.country || geo.country || '', + city: event?.city || geo.city || '', + region: event?.region || geo.region || '', continent: event?.continent ?? '', os: event?.os ?? '', osVersion: event?.osVersion ?? '', @@ -164,11 +159,10 @@ export async function postEvent( return reply.status(200).send(''); } - const [geo, sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] = + const [sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] = await withTiming( 'Get geo and jobs from queue', Promise.all([ - parseIp(ip), findJobByPrefix( eventsQueue, `sessionEnd:${projectId}:${currentDeviceId}:` @@ -225,9 +219,9 @@ export async function postEvent( profileId, projectId, sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? '', - properties: Object.assign({}, omit(['__path', '__referrer'], properties), { - hash, - query, + properties: Object.assign({}, omit(GLOBAL_PROPERTIES, properties), { + __hash: hash, + __query: query, }), createdAt, country: geo.country, diff --git a/packages/sdks/express/index.ts b/packages/sdks/express/index.ts new file mode 100644 index 00000000..4743a758 --- /dev/null +++ b/packages/sdks/express/index.ts @@ -0,0 +1,50 @@ +import type { NextFunction, Request, Response } from 'express'; +import { getClientIp } from 'request-ip'; + +import type { OpenpanelSdkOptions } from '@openpanel/sdk'; +import { OpenpanelSdk } from '@openpanel/sdk'; + +export * from '@openpanel/sdk'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + export interface Request { + op: OpenpanelSdk; + } + } +} + +export type OpenpanelOptions = OpenpanelSdkOptions & { + trackRequest?: (url: string) => boolean; + getProfileId?: (req: Request) => string; +}; + +export default function createMiddleware(options: OpenpanelOptions) { + return function middleware(req: Request, res: Response, next: NextFunction) { + const sdk = new OpenpanelSdk(options); + const ip = getClientIp(req); + if (ip) { + sdk.api.headers['x-forwarded-for'] = ip; + } + + if (options.getProfileId) { + const profileId = options.getProfileId(req); + if (profileId) { + sdk.setProfileId(profileId); + } + } + + if (options.trackRequest?.(req.url)) { + sdk.event('request', { + url: req.url, + method: req.method, + query: req.query, + }); + } + + req.op = sdk; + + return next(); + }; +} diff --git a/packages/sdks/express/package.json b/packages/sdks/express/package.json new file mode 100644 index 00000000..89cce915 --- /dev/null +++ b/packages/sdks/express/package.json @@ -0,0 +1,36 @@ +{ + "name": "@openpanel/express", + "version": "0.0.1", + "module": "index.ts", + "scripts": { + "build": "rm -rf dist && tsup", + "lint": "eslint .", + "format": "prettier --check \"**/*.{mjs,ts,md,json}\"", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@openpanel/sdk": "workspace:*", + "request-ip": "^3.3.0" + }, + "peerDependencies": { + "express": "^3.0.0 || ^4.0.0" + }, + "devDependencies": { + "@openpanel/eslint-config": "workspace:*", + "@openpanel/prettier-config": "workspace:*", + "@openpanel/tsconfig": "workspace:*", + "@types/express": "^4.17.21", + "@types/request-ip": "^0.0.41", + "eslint": "^8.48.0", + "prettier": "^3.0.3", + "tsup": "^7.2.0", + "typescript": "^5.2.2" + }, + "eslintConfig": { + "root": true, + "extends": [ + "@openpanel/eslint-config/base" + ] + }, + "prettier": "@openpanel/prettier-config" +} diff --git a/packages/sdks/express/tsconfig.json b/packages/sdks/express/tsconfig.json new file mode 100644 index 00000000..fa4341f1 --- /dev/null +++ b/packages/sdks/express/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@openpanel/tsconfig/base.json", + "compilerOptions": { + "incremental": false, + "outDir": "dist" + }, + "exclude": ["dist"] +} diff --git a/packages/sdks/express/tsup.config.ts b/packages/sdks/express/tsup.config.ts new file mode 100644 index 00000000..f27e5617 --- /dev/null +++ b/packages/sdks/express/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'tsup'; + +import config from '@openpanel/tsconfig/tsup.config.json' assert { type: 'json' }; + +export default defineConfig({ + ...(config as any), + entry: ['index.ts', 'cdn.ts'], + format: ['cjs', 'esm', 'iife'], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ceea9fd7..ceb60587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -891,6 +891,46 @@ importers: specifier: ^5.2.2 version: 5.3.3 + packages/sdks/express: + dependencies: + '@openpanel/sdk': + specifier: workspace:* + version: link:../sdk + express: + specifier: ^3.0.0 || ^4.0.0 + version: 4.18.2 + request-ip: + specifier: ^3.3.0 + version: 3.3.0 + devDependencies: + '@openpanel/eslint-config': + specifier: workspace:* + version: link:../../../tooling/eslint + '@openpanel/prettier-config': + specifier: workspace:* + version: link:../../../tooling/prettier + '@openpanel/tsconfig': + specifier: workspace:* + version: link:../../../tooling/typescript + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/request-ip': + specifier: ^0.0.41 + version: 0.0.41 + eslint: + specifier: ^8.48.0 + version: 8.56.0 + prettier: + specifier: ^3.0.3 + version: 3.2.5 + tsup: + specifier: ^7.2.0 + version: 7.3.0(typescript@5.3.3) + typescript: + specifier: ^5.2.2 + version: 5.3.3 + packages/sdks/nextjs: dependencies: '@openpanel/web':