-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtelemetry.ts
More file actions
96 lines (86 loc) · 3.19 KB
/
Copy pathtelemetry.ts
File metadata and controls
96 lines (86 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { getOrCreateAnonId, readConfig, writeConfig } from "./config.js"
/**
* Opt-in CLI telemetry.
*
* First interactive run: we ask once (see commands/login.ts) whether to
* share anonymous usage data. Consent stored in ~/.helpbase/config.json.
*
* What we send: command name, duration, exit code, flag NAMES (not values),
* CLI version, Node version, platform, arch, plus a random install-id
* (anon_id). That's it.
*
* What we NEVER send: content, URLs, emails, slugs, file paths, arg values,
* error messages, tokens.
*
* Disable at any time:
* • `helpbase config set telemetry off` (writes to config.json)
* • `HELPBASE_TELEMETRY=off` (env, per-command override)
* • CI=true or any non-TTY environment — we never prompt or send there.
*/
const ENDPOINT = process.env.HELPBASE_TELEMETRY_ENDPOINT
?? "https://helpbase.dev/api/telemetry"
export interface TelemetryEvent {
command: string
durationMs: number
exitCode: number
/** Flag names actually used (e.g. "--slug", "--yes"). Never values. */
flags: string[]
/**
* If the user invoked a deprecated alias (e.g. `helpbase context` for the
* `ingest` command), `command` is the canonical name and `alias` is what
* they actually typed. Preserves metric continuity across renames while
* still surfacing the alias-adoption rate. Optional so non-alias calls
* stay the same shape. Server-side consumers that don't know this field
* can ignore it safely.
*/
alias?: string
}
export function isTelemetryEnabled(): boolean {
if (process.env.HELPBASE_TELEMETRY === "off") return false
if (process.env.NO_TELEMETRY === "1") return false
const cfg = readConfig()
return cfg.telemetry === "on"
}
export function setTelemetryConsent(choice: "on" | "off"): void {
const cfg = readConfig()
writeConfig({ ...cfg, telemetry: choice })
}
export function hasAskedForConsent(): boolean {
const cfg = readConfig()
return cfg.telemetry !== undefined
}
/**
* Fire-and-forget. Never blocks the CLI, never throws, never logs failures
* to the user. If the endpoint is down or the network is slow, we just
* move on — telemetry isn't important enough to annoy anyone with.
*/
export function sendEvent(event: TelemetryEvent, cliVersion: string): void {
if (!isTelemetryEnabled()) return
const payload = {
anonId: getOrCreateAnonId(),
command: event.command,
durationMs: event.durationMs,
exitCode: event.exitCode,
flags: event.flags,
cliVersion,
nodeVersion: process.version,
platform: process.platform,
arch: process.arch,
// Only present when the user invoked via a deprecated alias. Lets
// dashboards track rename-migration progress without double-counting.
...(event.alias ? { alias: event.alias } : {}),
}
// 2 second timeout — telemetry must never slow users down.
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 2000)
fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: controller.signal,
})
.catch(() => {
// swallow every error — offline, dns, endpoint down, etc.
})
.finally(() => clearTimeout(timer))
}