-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtoggle.ts
More file actions
142 lines (137 loc) · 5.74 KB
/
Copy pathtoggle.ts
File metadata and controls
142 lines (137 loc) · 5.74 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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { getLogger } from "@intx/log";
import { LOG_NAMESPACE_ROOT } from "../branding.js";
import {
ensureTelemetrySettings,
loadSettings,
saveGlobalSettings,
type Settings,
} from "../config/settings.js";
import {
createTelemetry,
TELEMETRY_ENV,
telemetryDisabledByEnv,
type CreateTelemetryOptions,
type Telemetry,
} from "./index.js";
import { getTelemetry, setTelemetry } from "./singleton.js";
const logger = getLogger([LOG_NAMESPACE_ROOT, "telemetry", "toggle"]);
export type TelemetryToggleDeps = {
getTelemetry: () => Telemetry;
setTelemetry: (telemetry: Telemetry) => void;
loadSettings: (path: string) => Promise<Settings | null>;
telemetryDisabledByEnv: () => boolean;
ensureTelemetrySettings: (path: string) => Promise<Settings>;
saveGlobalSettings: (path: string, settings: Settings) => Promise<void>;
createTelemetry: (options: CreateTelemetryOptions) => Telemetry;
};
const defaultDeps: TelemetryToggleDeps = {
getTelemetry,
setTelemetry,
loadSettings,
telemetryDisabledByEnv,
ensureTelemetrySettings,
saveGlobalSettings,
createTelemetry,
};
// Builds the /settings > Telemetry toggle handler, bound to the true global
// settings path (never a --config override — see index.ts / runner.ts for
// why). Returns whether the requested value was accepted so the UI can refuse
// a flip that would be a silent no-op (env kill switch).
export function createTelemetryToggleHandler(
globalSettingsPath: string,
deps: TelemetryToggleDeps = defaultDeps,
): (enabled: boolean) => boolean {
return (enabled: boolean): boolean => {
if (enabled && deps.telemetryDisabledByEnv()) {
// Env kills own the "disabled means no settings writes" constraint
// (see index.ts); honoring the enable here would generate and persist
// an installationId while the env override keeps telemetry off — a
// silent no-op that also breaks that invariant. Refuse instead.
logger.warn(
`Telemetry re-enable ignored: disabled by environment (DO_NOT_TRACK or ${TELEMETRY_ENV})`,
);
return false;
}
if (!enabled) {
// Opt-out must be immediate and absolute: discard whatever the outgoing
// instance has queued (dropping the singleton alone would leave its
// batch timer armed to send it anyway), then swap the in-memory
// singleton synchronously, before any await, so no capture in flight
// during the persistence step below can land on a still-enabled
// instance, and so an unhandled rejection from disk I/O can never
// leave telemetry on.
//
// Preserve installationId so intentional /feedback can still ship while
// ambient product events are off. Env kill switches remain the hard stop.
const previous = deps.getTelemetry();
previous.discard();
deps.setTelemetry(
deps.createTelemetry({
settings: {
providers: {},
telemetry: {
enabled: false,
...(previous.installationId.length > 0
? { installationId: previous.installationId }
: {}),
},
},
}),
);
}
void (async () => {
// Re-enable goes through ensureTelemetrySettings so an installationId
// always exists — writing { enabled: true } without one would leave the
// re-created instance resolving disabled and the toggle a silent no-op.
// Opt-out keeps plain loadSettings: disabling must never generate an id.
const current = enabled
? await deps.ensureTelemetrySettings(globalSettingsPath).catch((err: unknown) => {
logger.warn("Failed to ensure telemetry settings for re-enable: {error}", { error: err });
return undefined;
})
: await deps.loadSettings(globalSettingsPath).catch((err: unknown) => {
logger.warn("Failed to load global settings for telemetry toggle: {error}", { error: err });
return undefined;
});
if (current === undefined) {
// Load failed (corrupt file, I/O error): never persist over it — a
// bare `{ providers: {} }` write would wipe unrelated settings. The
// synchronous in-memory disable above (if this was an opt-out) still
// holds; a re-enable during an outage just won't persist until the
// file is readable again.
return;
}
const previous = deps.getTelemetry();
const base: Settings = current ?? { providers: {} };
// Keep installation identity across ambient opt-out so /feedback still
// works. Prefer the on-disk id; fall back to the live instance when the
// disk row never got one (first-run race).
const installationId =
(typeof base.telemetry?.installationId === "string" &&
base.telemetry.installationId.length > 0
? base.telemetry.installationId
: undefined) ??
(previous.installationId.length > 0 ? previous.installationId : undefined);
const next: Settings = {
...base,
telemetry: {
...base.telemetry,
enabled,
...(installationId !== undefined ? { installationId } : {}),
},
};
try {
await deps.saveGlobalSettings(globalSettingsPath, next);
} catch (err) {
// Persistence failure only threatens durability across restarts, not
// the in-memory state already set above — swallow, per telemetry's
// fail-silent contract.
logger.warn("Failed to persist telemetry setting: {error}", { error: err });
}
// Re-create so this session's remaining captures honor the change (and,
// on the load-succeeded path, carry forward the real installationId).
deps.setTelemetry(deps.createTelemetry({ settings: next }));
})();
return true;
};
}