Skip to content
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

feat: add posthog for product telemetry #811

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/telemetry/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
55 changes: 55 additions & 0 deletions packages/telemetry/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "telemetry",
"version": "0.0.1",
"description": "Shared telemetry package for Upstreet Next.js apps and CLI tools",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./core": {
"import": "./dist/core.js",
"require": "./dist/core.cjs",
"types": "./dist/core.d.ts"
},
"./next": {
"import": "./dist/next/index.js",
"require": "./dist/next/index.cjs",
"types": "./dist/next/index.d.ts"
}
},
"scripts": {
"build": "tsup src --format esm,cjs --dts --out-dir dist",
"lint": "eslint src --ext .ts,.tsx",
"test": "jest",
"prepare": "pnpm build"
},
"dependencies": {
"posthog-js": "^1.203.1"
},
"peerDependencies": {
"cookies-next": "^5.0.2",
"next": "^13.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@types/node": "^22.10.2",
"@types/react": "^17.0.0 || ^18.0.0",
"eslint": "^8.0.0",
"jest": "^29.0.0",
"tsup": "^7.0.0",
"typescript": "^5.0.0"
},
"author": "Upstreet",
"repository": {
"type": "git",
"url": "https://github.com/yourorg/yourrepo.git",
"directory": "packages/telemetry"
},
"engines": {
"node": ">=14.0.0"
}
}
21 changes: 21 additions & 0 deletions packages/telemetry/src/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// packages/telemetry/posthog/core.ts
import posthog from "posthog-js";

export function initTelemetry(apiKey: string, apiHost: string, bootstrapData: Record<string, unknown> = {}) {
posthog.init(apiKey, {
api_host: apiHost,
bootstrap: bootstrapData,
capture_pageview: false,
loaded: (posthog) => {
if (process.env.NODE_ENV === "development") posthog.debug();
},
});
}

export function captureEvent(event: string, properties: Record<string, unknown> = {}) {
posthog.capture(event, properties);
}

export function getPostHog() {
return posthog;
}
3 changes: 3 additions & 0 deletions packages/telemetry/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// packages/telemetry/src/index.ts
export * from "./core";
export * from "./next";
45 changes: 45 additions & 0 deletions packages/telemetry/src/next/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// packages/telemetry/src/next/index.tsx
"use client";

import { useEffect } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { getCookie } from "cookies-next";
import { getPostHog } from "../core";
import { PostHogProvider } from "posthog-js/react";

export function initNextTelemetry() {
const flags = getCookie("bootstrapData");
let bootstrapData = {};
if (flags) {
bootstrapData = JSON.parse(flags as string);
}

const posthog = getPostHog();
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST!,
bootstrap: bootstrapData,
});
}

export function PostHogPageview(): JSX.Element {
const pathname = usePathname();
const searchParams = useSearchParams();

useEffect(() => {
const posthog = getPostHog();
if (pathname && typeof window !== "undefined") {
let url = window.origin + pathname;
if (searchParams && searchParams.toString()) {
url += `?${searchParams.toString()}`;
}
posthog.capture("$pageview", { $current_url: url });
}
}, [pathname, searchParams]);

return <></>;
}

export function PHProvider({ children }: { children: React.ReactNode }) {
const posthog = getPostHog();
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}
46 changes: 46 additions & 0 deletions packages/telemetry/src/next/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// middleware.js
import { NextResponse } from "next/server"

export async function middleware(request: any) {
const ph_project_api_key = process.env.NEXT_PUBLIC_POSTHOG_KEY
const ph_cookie_key = `ph_${process.env.NEXT_PUBLIC_POSTHOG_KEY}_posthog`
const cookie = request.cookies.get(ph_cookie_key)

let distinct_id
if (cookie) {
distinct_id = JSON.parse(cookie.value).distinct_id
} else {
distinct_id = crypto.randomUUID()
}

const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
api_key: ph_project_api_key,
distinct_id: distinct_id,
}),
}

const ph_request = await fetch(
"https://app.posthog.com/decide?v=3", // or eu
requestOptions
)
const data = await ph_request.json()

const bootstrapData = {
distinctId: distinct_id,
featureFlags: data.featureFlags,
}

const response = NextResponse.next()
response.cookies.set("bootstrapData", JSON.stringify(bootstrapData))

return response
}

export const config = {
matcher: ["/((?!api|_next/static|favicon.ico|vercel.svg|next.svg).*)"],
}
23 changes: 23 additions & 0 deletions packages/telemetry/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ESNext", // Use the latest ECMAScript features
"module": "ESNext", // Use ESNext module resolution
"lib": ["DOM", "ESNext"], // Include DOM and latest JS features
"declaration": true, // Emit `.d.ts` files for type definitions
"declarationMap": true, // Emit declaration source maps
"outDir": "dist", // Specify output directory for compiled files
"strict": true, // Enable all strict type-checking options
"esModuleInterop": true, // Allow default imports from CommonJS
"skipLibCheck": true, // Skip type checking of declaration files
"incremental": true, // Enable incremental compilation
"tsBuildInfoFile": "./dist/.tsbuildinfo", // Specify incremental build info file
"moduleResolution": "Node", // Resolve modules using Node.js
"resolveJsonModule": true, // Allow importing JSON files
"isolatedModules": true, // Enforce modularity for tools like Babel
"jsx": "react-jsx", // Use the JSX transform
"allowSyntheticDefaultImports": true // Allow default imports
},
"include": ["src"], // Include all TypeScript files in `src`
"exclude": ["node_modules", "dist"] // Exclude unnecessary directories
}

Loading