diff --git a/.env.example b/.env.example index 2ff11d4..41cfc68 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,7 @@ GITHUB_TOKEN= # route. Defaults to the canonical SNAP/ETH pair on Ethereum mainnet. # Only change this if the pair migrates or you need to point at a test endpoint. DEXSCREENER_PAIR_URL=https://api.dexscreener.com/latest/dex/pairs/ethereum/0x72a70a747a8390caf1aad3fb1de3564b55871f137539e498d30f02b1167742ea + +# ─── PostHog analytics ─────────────────────────────────────────────────────── +NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=your_posthog_project_token_here +NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com diff --git a/instrumentation-client.ts b/instrumentation-client.ts new file mode 100644 index 0000000..888edc8 --- /dev/null +++ b/instrumentation-client.ts @@ -0,0 +1,21 @@ +import posthog from "posthog-js"; + +const projectToken = process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN; +const host = process.env.NEXT_PUBLIC_POSTHOG_HOST; + +if (!projectToken || !host) { + if (process.env.NODE_ENV !== "production") { + throw new Error( + !projectToken + ? "NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN is configured" + : "NEXT_PUBLIC_POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once NEXT_PUBLIC_POSTHOG_HOST is configured", + ); + } +} else { + posthog.init(projectToken, { + api_host: host, + defaults: "2026-01-30", + capture_exceptions: true, + debug: process.env.NODE_ENV === "development", + }); +} diff --git a/package-lock.json b/package-lock.json index 70dcbff..42b3c6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "lucide-react": "^1.14.0", "next": "^16.2.5", "postcss": "^8.5.15", + "posthog-js": "^1.410.6", "react": "^19.2.4", "react-dom": "^19.2.4", "rehype-raw": "^7.0.0", @@ -1258,6 +1259,31 @@ "node": ">=12.4.0" } }, + "node_modules/@posthog/browser-common": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.3.1.tgz", + "integrity": "sha512-1nhMVY1wnHADTg8tR9yvm+lPAz5ROxznfQlBtLzB2FFo4lp/LU8lk9KyFPsARDkBxCfYachsJfvRjocL1G/AJQ==", + "license": "MIT", + "dependencies": { + "@posthog/core": "^1.46.0", + "@posthog/types": "^1.399.0" + } + }, + "node_modules/@posthog/core": { + "version": "1.46.5", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.46.5.tgz", + "integrity": "sha512-zJr9v4bhV9DRGJENWj/FepD9S+bvsc/bwl0Sb46Md4jV7NoPWYp+2QdH4iskv8t1uZhkmCk1XxHn38RXaak/7A==", + "license": "MIT", + "dependencies": { + "@posthog/types": "^1.400.0" + } + }, + "node_modules/@posthog/types": { + "version": "1.400.0", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.400.0.tgz", + "integrity": "sha512-0VVOXFrkh0TEAfmptoUqFGbhEzygQyWYQZYPlw0v0QYJfXTYlnlhr/QMMF3NAGKakFwOAn9ZZnlpEFeaLhZ/Cg==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1654,6 +1680,13 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -2846,6 +2879,17 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3049,6 +3093,15 @@ "node": ">=0.10.0" } }, + "node_modules/dompurify": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3778,6 +3831,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.9.tgz", + "integrity": "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -6753,6 +6812,41 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/posthog-js": { + "version": "1.410.6", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.410.6.tgz", + "integrity": "sha512-/LRekY3DN0SRDD98xS7DPXeTRGozMhkxO/mi+i9F783c3YnaGRP7UGKyS9In1adjmp0xXKc4PfKkwKHonB2liQ==", + "license": "(Apache-2.0 AND MIT)", + "dependencies": { + "@posthog/browser-common": "^0.3.1", + "@posthog/core": "^1.46.5", + "@posthog/types": "^1.400.0", + "core-js": "^3.49.0", + "dompurify": "^3.3.2", + "fflate": "^0.4.8", + "preact": "^10.29.3", + "query-selector-shadow-dom": "^1.0.1", + "web-vitals": "^5.3.0" + } + }, + "node_modules/preact": { + "version": "10.29.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", + "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6795,6 +6889,12 @@ "node": ">=6" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -8198,6 +8298,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/web-vitals": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.3.0.tgz", + "integrity": "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==", + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 5e8c6d4..5505dad 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "lucide-react": "^1.14.0", "next": "^16.2.5", "postcss": "^8.5.15", + "posthog-js": "^1.410.6", "react": "^19.2.4", "react-dom": "^19.2.4", "rehype-raw": "^7.0.0", diff --git a/src/components/copy-command.tsx b/src/components/copy-command.tsx index 1efe58d..7c1ba90 100644 --- a/src/components/copy-command.tsx +++ b/src/components/copy-command.tsx @@ -1,6 +1,7 @@ "use client"; import { Check, Copy } from "lucide-react"; +import posthog from "posthog-js"; import { useEffect, useRef, useState } from "react"; type CopyStatus = "idle" | "copied" | "failed"; @@ -32,6 +33,12 @@ export function CopyCommand({ value }: { value: string }) { try { await navigator.clipboard.writeText(value); + if ( + process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN && + process.env.NEXT_PUBLIC_POSTHOG_HOST + ) { + posthog.capture("command_copied"); + } setStatus("copied"); } catch { setStatus("failed"); diff --git a/src/components/node-info-checker.tsx b/src/components/node-info-checker.tsx index 057ab42..6d6952a 100644 --- a/src/components/node-info-checker.tsx +++ b/src/components/node-info-checker.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { AlertTriangle, CheckCircle2, Eraser, Search } from "lucide-react"; +import posthog from "posthog-js"; type Tone = "good" | "warn" | "bad" | "idle"; @@ -167,6 +168,12 @@ export function NodeInfoChecker() { }); return; } + if ( + process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN && + process.env.NEXT_PUBLIC_POSTHOG_HOST + ) { + posthog.capture("node_info_analyzed"); + } setResult(classifyInfo(trimmed)); }} type="button"