Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function PrivacyPage() {
<ul>
<li>Input used in browser-ready developer tools is processed on your device and is not submitted to buffer.lol.</li>
<li>Server-backed network and IP tools process the target you submit, the request metadata needed to complete the check, and short-lived diagnostic results.</li>
<li>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.</li>
<li>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.</li>
<li>Your email address and message content if you contact us.</li>
<li>Basic technical metadata collected by the hosting platform, such as request time, IP address, user agent, and abuse-prevention signals.</li>
</ul>
Expand Down
20 changes: 19 additions & 1 deletion components/tools/ToolExperience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | number | boolean>) => void;
};
};

function trackToolUse(tool: string, outcome: "success" | "error", durationMs?: number) {
const metadata: Record<string, string | number | boolean> = { 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 <BrowserLatencyTool mode="ping" />;
Expand Down Expand Up @@ -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");
}
}

Expand Down Expand Up @@ -703,6 +718,7 @@ function BackendPlaceholder({ tool }: { tool: Tool }) {
durationMs: payload.durationMs,
requestId: payload.requestId
});
trackToolUse(tool.slug, "error", payload.durationMs);
return;
}

Expand All @@ -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");
}
}

Expand Down
Loading