From 23eae7871a49678a9c9a1eb6245e8073f531478a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 7 May 2024 12:30:51 +0800 Subject: [PATCH 1/2] send client info to the analytics --- packages/api/src/server.ts | 1 + packages/api/src/utils/analytics.ts | 40 ++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 3fa1fdb9e..23791842c 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -74,6 +74,7 @@ export const createApp = (): Express => { if (client) { httpContext.set('client', client) } + // TODO: get client info from user agent next() }) diff --git a/packages/api/src/utils/analytics.ts b/packages/api/src/utils/analytics.ts index 8b3717cb8..d59b1622a 100644 --- a/packages/api/src/utils/analytics.ts +++ b/packages/api/src/utils/analytics.ts @@ -1,4 +1,42 @@ +import httpContext from 'express-http-context2' import { PostHog } from 'posthog-node' import { env } from '../env' -export const analytics = new PostHog(env.posthog.apiKey || 'test') +interface AnalyticEvent { + distinctId: string + event: string + properties?: Record +} + +interface AnalyticClient { + capture: (event: AnalyticEvent) => void + shutdownAsync?: () => Promise +} + +class PostHogClient implements AnalyticClient { + private client: PostHog + + constructor(apiKey: string) { + this.client = new PostHog(apiKey) + } + + capture({ distinctId, event, properties }: AnalyticEvent) { + // get client from request context + const client = httpContext.get('client') || 'other' + + this.client.capture({ + distinctId, + event, + properties: { + ...properties, + client, + }, + }) + } + + async shutdownAsync() { + return this.client.shutdownAsync() + } +} + +export const analytics = new PostHogClient(env.posthog.apiKey || 'test') From 31233d8348488eaf30f75f56248d1638005574a7 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 7 May 2024 14:53:29 +0800 Subject: [PATCH 2/2] get client from user-agent --- packages/api/src/server.ts | 12 ++++++++++-- packages/api/src/utils/helpers.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 23791842c..06d2fbdcb 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -21,6 +21,7 @@ import { articleRouter } from './routers/article_router' import { authRouter } from './routers/auth/auth_router' import { mobileAuthRouter } from './routers/auth/mobile/mobile_auth_router' import { digestRouter } from './routers/digest_router' +import { explainRouter } from './routers/explain_router' import { integrationRouter } from './routers/integration_router' import { localDebugRouter } from './routers/local_debug_router' import { notificationRouter } from './routers/notification_router' @@ -42,9 +43,9 @@ import { userRouter } from './routers/user_router' import { sentryConfig } from './sentry' import { analytics } from './utils/analytics' import { corsConfig } from './utils/corsConfig' +import { getClientFromUserAgent } from './utils/helpers' import { buildLogger, buildLoggerTransport, logger } from './utils/logger' import { apiLimiter, authLimiter } from './utils/rate_limit' -import { explainRouter } from './routers/explain_router' const PORT = process.env.PORT || 4000 @@ -70,11 +71,18 @@ export const createApp = (): Express => { // set client info in the request context app.use(httpContext.middleware) app.use('/api/', (req, res, next) => { + // get client info from header const client = req.header('X-OmnivoreClient') if (client) { httpContext.set('client', client) } - // TODO: get client info from user agent + + // get client info from user agent + const userAgent = req.header('User-Agent') + if (userAgent) { + const client = getClientFromUserAgent(userAgent) + httpContext.set('client', client) + } next() }) diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index 85abc5467..01aaf9e94 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -411,3 +411,15 @@ export const setRecentlySavedItemInRedis = async ( }) } } + +export const getClientFromUserAgent = (userAgent: string): string => { + // for plugins, currently only obsidian and logseq are supported + const plugins = userAgent.match(/(obsidian|logseq)/i) + if (plugins) return plugins[0].toLowerCase() + + // web browser + const browsers = userAgent.match(/(chrome|safari|firefox|edge|opera)/i) + if (browsers) return 'web' + + return 'other' +}