From c6f883dedeae2f8d8c2f2bb3ca13ec0d7f6c8fe4 Mon Sep 17 00:00:00 2001
From: lean <1337lean@users.noreply.github.com>
Date: Fri, 10 Jul 2026 12:54:04 -0400
Subject: [PATCH] Add privacy-safe BufferDash product events
---
.env.example | 3 ++-
README.md | 2 +-
app/privacy/page.tsx | 2 +-
components/tools/ToolExperience.tsx | 20 +++++++++++++++++++-
4 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/.env.example b/.env.example
index 330ef31..21f9722 100644
--- a/.env.example
+++ b/.env.example
@@ -2,7 +2,8 @@
NEXT_PUBLIC_DOCS_URL=
# Optional. Enables the private BufferDash analytics tracker when both values are set.
-# Create the site in BufferDash, then use the generated public site key here.
+# Create the site with domain buffer.lol in BufferDash, then use its public site key here.
+# BufferDash rejects browser tracking requests from other origins by default.
BUFFERDASH_URL=
BUFFERDASH_SITE_ID=
diff --git a/README.md b/README.md
index 005f688..3f4850c 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ The worker container needs `NET_RAW` for low-level network diagnostics. See `dia
## BufferDash Analytics
-buffer.lol can send page views, outbound clicks, sessions, and custom product events to a private BufferDash instance.
+buffer.lol can send page views, outbound clicks, sessions, and custom product events to a private BufferDash instance. Server-backed and browser-latency product events contain only the tool slug, outcome, and request duration; tool inputs are never included.
1. Deploy BufferDash at a private dashboard URL, such as `https://dash.buffer.lol`.
2. In BufferDash, open `/sites`, create a site for `buffer.lol`, and copy the generated public site key.
diff --git a/app/privacy/page.tsx b/app/privacy/page.tsx
index 9766d5e..b9ba356 100644
--- a/app/privacy/page.tsx
+++ b/app/privacy/page.tsx
@@ -21,7 +21,7 @@ export default function PrivacyPage() {
- Input used in browser-ready developer tools is processed on your device and is not submitted to buffer.lol.
- Server-backed network and IP tools process the target you submit, the request metadata needed to complete the check, and short-lived diagnostic results.
- - Private analytics may collect page paths, referrers, session identifiers, outbound clicks, browser metadata, user agent, IP-derived signals, and timing information so we can understand usage and abuse patterns.
+ - Private analytics may collect page paths, referrers, session identifiers, outbound clicks, browser metadata, user agent, IP-derived signals, and timing information. Server-backed and browser latency/stability checks may also record the tool slug, outcome, and duration, but not the submitted target or browser-local tool input.
- Your email address and message content if you contact us.
- Basic technical metadata collected by the hosting platform, such as request time, IP address, user agent, and abuse-prevention signals.
diff --git a/components/tools/ToolExperience.tsx b/components/tools/ToolExperience.tsx
index 6f6a535..f5b9046 100644
--- a/components/tools/ToolExperience.tsx
+++ b/components/tools/ToolExperience.tsx
@@ -4,6 +4,18 @@ import { useMemo, useState, useSyncExternalStore } from "react";
import type { Tool } from "@/data/tools";
import { ResultPanel } from "./ResultPanel";
+type BufferDashWindow = Window & {
+ bufferdash?: {
+ track: (type: string, metadata?: Record) => void;
+ };
+};
+
+function trackToolUse(tool: string, outcome: "success" | "error", durationMs?: number) {
+ const metadata: Record = { tool, outcome };
+ if (typeof durationMs === "number") metadata.durationMs = Math.round(durationMs);
+ (window as BufferDashWindow).bufferdash?.track("tool_used", metadata);
+}
+
export function ToolExperience({ tool }: { tool: Tool }) {
switch (tool.slug) {
case "ping": return ;
@@ -518,9 +530,12 @@ function BrowserLatencyTool({ mode }: { mode: "ping" | "stability" }) {
if (index < sampleCount - 1) await wait(160);
}
- setResult({ kind: "success", samples, summary: summarizeLatencySamples(samples) });
+ const summary = summarizeLatencySamples(samples);
+ setResult({ kind: "success", samples, summary });
+ trackToolUse(mode, summary.received > 0 ? "success" : "error", summary.avgMs ?? undefined);
} catch (error) {
setResult({ kind: "error", message: error instanceof Error ? error.message : "The latency test failed." });
+ trackToolUse(mode, "error");
}
}
@@ -703,6 +718,7 @@ function BackendPlaceholder({ tool }: { tool: Tool }) {
durationMs: payload.durationMs,
requestId: payload.requestId
});
+ trackToolUse(tool.slug, "error", payload.durationMs);
return;
}
@@ -712,11 +728,13 @@ function BackendPlaceholder({ tool }: { tool: Tool }) {
durationMs: payload.durationMs,
requestId: payload.requestId
});
+ trackToolUse(tool.slug, "success", payload.durationMs);
} catch (error) {
setResult({
kind: "error",
message: error instanceof Error ? error.message : "The backend request failed."
});
+ trackToolUse(tool.slug, "error");
}
}