// Telemetry registry. // // This package deliberately knows nothing about how telemetry is collected, // batched, authenticated or shipped. It only knows that an interaction // happened and what it was called. The consuming app registers a sink at // startup — the same arrangement as `configureApi` for HTTP — so one app can // post to its own server, another to a collector, and a third to nothing at // all. // // Nothing is registered by default, and every entry point is a no-op until // something is. An app that never calls `registerTelemetry` behaves exactly as // it did before this existed, which is what makes it safe to ship in a package // that self-hosted deployments consume. /** What a host app must provide to receive interaction events. */ export interface TelemetrySink { /** * An interactive element was activated. * * `element` is the author-assigned, stable id — not label text, which * changes with copy and can carry user data. */ trackClick(element: string, route?: string): void; } class TelemetryStore { private sink: TelemetrySink | null = null; register(sink: TelemetrySink | null): void { this.sink = sink; } trackClick(element: string, route?: string): void { // Never allowed to throw into a press handler. A telemetry sink that // fails must not stop the button from doing its job. try { this.sink?.trackClick(element, route); } catch { // Intentionally swallowed. } } reset(): void { this.sink = null; } } const store = new TelemetryStore(); /** * Install a telemetry sink. Call once at app startup; pass `null` to remove. */ export function registerTelemetry(sink: TelemetrySink | null): void { store.register(sink); } /** * Report an element activation. Safe to call unconditionally — it is a no-op * when no sink is registered. */ export function trackClick(element: string, route?: string): void { store.trackClick(element, route); } /** * Wrap a press handler so activating the element is reported first. * * Used by the primitives to implement `telemetryId`, and exported so app code * can tag anything the primitives do not cover. */ export function withClickTracking unknown>( element: string | undefined, handler: F ): F; export function withClickTracking unknown>( element: string | undefined, handler: F | null | undefined ): F | undefined; /** * Wrap a press handler so activating the element is reported first. * * Used by the primitives to implement `telemetryId`, and exported so app code * can tag anything the primitives do not cover. * * Overloaded so the wrapper is exactly as optional as the handler it wraps: a * required `onPress` stays required, and React Native's nullable `onPress` is * accepted without every call site having to coalesce it. */ export function withClickTracking( element: string | undefined, handler: ((...args: never[]) => unknown) | null | undefined ): ((...args: never[]) => unknown) | undefined { // Untagged controls keep their original handler rather than gaining a // closure per render for nothing. if (element === undefined) { return handler ?? undefined; } return (...args: never[]) => { trackClick(element); return handler?.(...args); }; } /** Test seam: drop the registered sink so cases cannot leak into each other. */ export function __resetTelemetryForTests(): void { store.reset(); }