diff --git a/packages/analytics/README.md b/packages/analytics/README.md index 1cb90f697..a66982f78 100644 --- a/packages/analytics/README.md +++ b/packages/analytics/README.md @@ -5,6 +5,7 @@ Report analytics events from your web extension extension. ## Supported Analytics Providers - [Google Analytics 4 (Measurement Protocol)](#google-analytics-4-measurement-protocol) +- [PostHog](#posthog) - [Umami](#umami) ## Install With WXT @@ -120,6 +121,35 @@ export default defineAppConfig({ }); ``` +### PostHog + +[PostHog](https://posthog.com/) is an open source product analytics platform. It supports event tracking, session recording, feature flags, surveys, and more. + +In your PostHog project settings, find your **Project API key** and save it to your `.env` file: + +```dotenv +WXT_POSTHOG_API_KEY='phc_...' +``` + +Then add the `posthog` provider to your `/app.config.ts` file: + +```ts +import { posthog } from '@wxt-dev/analytics/providers/posthog'; + +export default defineAppConfig({ + analytics: { + providers: [ + posthog({ + apiKey: import.meta.env.WXT_POSTHOG_API_KEY, + // apiHost defaults to 'https://us.i.posthog.com'. + // Change to 'https://eu.i.posthog.com' for EU Cloud, or your self-hosted URL. + apiHost: 'https://eu.i.posthog.com', + }), + ], + }, +}); +``` + ### Umami [Umami](https://umami.is/) is a privacy-first, open source analytics platform. diff --git a/packages/analytics/app.config.ts b/packages/analytics/app.config.ts index 160fd015f..56e0c5857 100644 --- a/packages/analytics/app.config.ts +++ b/packages/analytics/app.config.ts @@ -1,5 +1,6 @@ import { defineAppConfig } from 'wxt/utils/define-app-config'; import { googleAnalytics4 } from './modules/analytics/providers/google-analytics-4'; +import { posthog } from './modules/analytics/providers/posthog'; import { umami } from './modules/analytics/providers/umami'; export default defineAppConfig({ @@ -10,6 +11,9 @@ export default defineAppConfig({ apiSecret: '...', measurementId: '...', }), + posthog({ + apiKey: '...', + }), umami({ apiUrl: 'https://umami.aklinker1.io/api', domain: 'analytics.wxt.dev', diff --git a/packages/analytics/modules/analytics/providers/google-analytics-4.ts b/packages/analytics/modules/analytics/providers/google-analytics-4.ts index 55fde52d4..7efe3a824 100644 --- a/packages/analytics/modules/analytics/providers/google-analytics-4.ts +++ b/packages/analytics/modules/analytics/providers/google-analytics-4.ts @@ -18,7 +18,7 @@ export const googleAnalytics4 = eventProperties: Record | undefined, ): Promise => { const url = new URL( - config?.debug ? '/debug/mp/collect' : '/mp/collect', + config.debug ? '/debug/mp/collect' : '/mp/collect', options.apiUrl ?? 'https://www.google-analytics.com', ); if (options.apiSecret) @@ -38,6 +38,12 @@ export const googleAnalytics4 = ]), ); + if (config.debug) { + console.debug( + '[@wxt-dev/analytics] Sending event to Google Analytics 4:', + { eventName, eventProperties }, + ); + } await fetch(url.href, { method: 'POST', body: JSON.stringify({ diff --git a/packages/analytics/modules/analytics/providers/posthog.ts b/packages/analytics/modules/analytics/providers/posthog.ts new file mode 100644 index 000000000..4d5b1c3dc --- /dev/null +++ b/packages/analytics/modules/analytics/providers/posthog.ts @@ -0,0 +1,83 @@ +import { defineAnalyticsProvider } from '../client'; + +export interface PostHogProviderOptions { + /** Your PostHog project API key. */ + apiKey: string; + /** + * PostHog API host URL. + * + * @default 'https://us.i.posthog.com' + */ + apiHost?: string; +} + +export const posthog = defineAnalyticsProvider( + (_, config, options) => { + const apiHost = (options.apiHost ?? 'https://us.i.posthog.com').replace( + /\/$/, + '', + ); + + const capture = async ( + distinctId: string, + event: string, + properties: Record, + ): Promise => { + if (config.debug) { + console.debug('[@wxt-dev/analytics] Sending event to PostHog:', { + event, + properties, + }); + } + const body: PostHogCaptureBody = { + api_key: options.apiKey, + distinct_id: distinctId, + event, + properties, + timestamp: new Date().toISOString(), + }; + await fetch(`${apiHost}/i/v0/e/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + }; + + return { + identify: async (event) => { + await capture(event.user.id, '$identify', { + $set: event.user.properties, + }); + }, + page: async (event) => { + await capture(event.user.id, '$pageview', { + $current_url: event.page.url, + $title: event.page.title, + $session_id: event.meta.sessionId, + $screen: event.meta.screen, + $language: event.meta.language, + $referrer: event.meta.referrer, + $set: event.user.properties, + }); + }, + track: async (event) => { + await capture(event.user.id, event.event.name, { + ...event.event.properties, + $screen: event.meta.screen, + $language: event.meta.language, + $referrer: event.meta.referrer, + $set: event.user.properties, + }); + }, + }; + }, +); + +/** @see https://posthog.com/docs/api/capture */ +interface PostHogCaptureBody { + api_key: string; + distinct_id: string; + event: string; + properties: Record; + timestamp: string; +} diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 1f2b87ca9..a5a7ce45e 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -58,6 +58,10 @@ "./providers/umami": { "types": "./dist/providers/umami.d.mts", "default": "./dist/providers/umami.mjs" + }, + "./providers/posthog": { + "types": "./dist/providers/posthog.d.mts", + "default": "./dist/providers/posthog.mjs" } }, "module": "./dist/index.mjs", diff --git a/packages/analytics/tsdown.config.ts b/packages/analytics/tsdown.config.ts index 1bc3b18c8..68c4119c8 100644 --- a/packages/analytics/tsdown.config.ts +++ b/packages/analytics/tsdown.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ 'providers/google-analytics-4': './modules/analytics/providers/google-analytics-4.ts', 'providers/umami': './modules/analytics/providers/umami.ts', + 'providers/posthog': './modules/analytics/providers/posthog.ts', }, deps: { neverBundle: ['#analytics'],