From 89967eec67da13951837f19b7671647fb96b2c8c Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:22:33 -0800 Subject: [PATCH] feat: Add browser-telemetry API types. (#669) Add API surface for browser telemetry. Review after: https://github.com/launchdarkly/js-core/pull/659 --- .../browser-telemetry/src/api/Breadcrumb.ts | 163 ++++++++++++++++++ .../src/api/BrowserTelemetry.ts | 28 +++ .../browser-telemetry/src/api/Collector.ts | 25 +++ .../browser-telemetry/src/api/ErrorData.ts | 32 ++++ .../browser-telemetry/src/api/Options.ts | 146 ++++++++++++++++ .../browser-telemetry/src/api/Recorder.ts | 28 +++ .../src/api/stack/StackFrame.ts | 45 +++++ .../src/api/stack/StackTrace.ts | 12 ++ 8 files changed, 479 insertions(+) create mode 100644 packages/telemetry/browser-telemetry/src/api/Breadcrumb.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/BrowserTelemetry.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/Collector.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/ErrorData.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/Options.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/Recorder.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/stack/StackFrame.ts create mode 100644 packages/telemetry/browser-telemetry/src/api/stack/StackTrace.ts diff --git a/packages/telemetry/browser-telemetry/src/api/Breadcrumb.ts b/packages/telemetry/browser-telemetry/src/api/Breadcrumb.ts new file mode 100644 index 000000000..dfeb0ec19 --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/Breadcrumb.ts @@ -0,0 +1,163 @@ +/** + * Defines the 'class' of the breadcrumb. + */ +export type BreadcrumbClass = + | 'custom' + | 'log' + | 'navigation' + | 'feature-management' + | 'ui' + | 'http'; + +/** + * Indicates the severity of the breadcrumb. + */ +export type BreadcrumbLevel = 'error' | 'warning' | 'info' | 'debug'; + +/** + * Types of data support with breadcrumbs. + */ +export type BreadcrumbDataValue = boolean | number | string; + +/** + * Defines arbitrary data that may be associated with a breadcrumb. + */ +export type BreadcrumbData = Record; + +/** + * Interface which defines a breadcrumb. + */ +export interface Breadcrumb { + /** + * The class of the breadcrumb. This is the top level categorization of breadcrumbs. + */ + class: BreadcrumbClass; + + /** + * When the event associated with the breadcrumb happened. The timestamp is in milliseconds since January 1, 1970 + * Universal Coordinated Time (UTC) + * + * For most breadcrumbs this will not be different than the time of breadcrumb creation, but if there is a delay + * between the event and breadcrumb capture, then the time of the event should be used instead. + */ + timestamp: number; + + /** + * The level of severity of the breadcrumb. The default choice of level should be `info` if there isn't a clear + * reason to use a different level. + */ + level: BreadcrumbLevel; + + /** + * The type of the breadcrumb. Each class may be split into multiple types with the type more specifically + * categorizing the type of event. + */ + type?: string; + + /** + * A message associated with the breadcrumb. + */ + message?: string; + + /** + * Any data associated with the breadcrumb. + */ + data?: BreadcrumbData; +} + +/** + * Utility type which allows for easy extension of base breadcrumb type. + */ +type ImplementsCrumb = U; + +/** + * Type for custom breadcrumbs. + */ +export type CustomBreadcrumb = ImplementsCrumb<{ + class: 'custom'; + timestamp: number; + level: BreadcrumbLevel; + type?: string; + message?: string; + data?: BreadcrumbData; +}>; + +/** + * Type for log breadcrumbs. + */ +export type LogBreadcrumb = ImplementsCrumb<{ + class: 'log'; + timestamp: number; + level: BreadcrumbLevel; + message: string; + data?: BreadcrumbData; +}>; + +/** + * Type for navigation breadcrumbs. + */ +export type NavigationBreadcrumb = ImplementsCrumb<{ + class: 'navigation'; + timestamp: number; + level: 'info'; + type?: string; + data?: { + /** + * The location being navigated from. In a web application this would typically be a URL. + */ + from?: string; + /** + * The location being navigated to. In a web application this would typically be a URL. + */ + to?: string; + }; +}>; + +/** + * Type for feature management breadcrumbs. + */ +export type FeatureManagementBreadcrumb = ImplementsCrumb<{ + class: 'feature-management'; + timestamp: number; + level: 'info'; + type: 'flag-evaluated' | 'flag-detail-changed'; + data?: { + /** + * The flag key. + */ + key?: string; + // Not supporting JSON flags in breadcrumbs. As noted in design we may want to eventually support none of the + // values in the breadcrumb. + /** + * The evaluated value for simple types. + */ + value?: boolean | string | number; + }; +}>; + +/** + * Type for UI breadcrumbs. + */ +export type UiBreadcrumb = ImplementsCrumb<{ + class: 'ui'; + timestamp: number; + level: 'info'; + type: 'click' | 'input'; + message: string; +}>; + +/** + * Type for HTTP breadcrumbs. + */ +export type HttpBreadcrumb = ImplementsCrumb<{ + class: 'http'; + timestamp: number; + level: 'error' | 'info'; // Error if an error status code? + type: 'xhr' | 'fetch'; + data?: { + url?: string; + method?: string; + statusCode: number; + statusText: string; + }; +}>; diff --git a/packages/telemetry/browser-telemetry/src/api/BrowserTelemetry.ts b/packages/telemetry/browser-telemetry/src/api/BrowserTelemetry.ts new file mode 100644 index 000000000..38c730f88 --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/BrowserTelemetry.ts @@ -0,0 +1,28 @@ +import { LDClient, LDInspection } from 'launchdarkly-js-client-sdk'; + +import { Recorder } from './Recorder'; + +/** + * Interface LaunchDarkly browser telemetry. + */ +export interface BrowserTelemetry extends Recorder { + /** + * Get inspectors to use with the LaunchDarkly client. + */ + inspectors(): LDInspection[]; + + // TODO: Consider hooks as well. Hooks will allow registration to happen in a + // single step. + + /** + * Register the telemetry instance with the LaunchDarkly client. + * + * @param client The LaunchDarkly client. + */ + register(client: LDClient): void; + + /** + * Close the telemetry client. + */ + close(): void; +} diff --git a/packages/telemetry/browser-telemetry/src/api/Collector.ts b/packages/telemetry/browser-telemetry/src/api/Collector.ts new file mode 100644 index 000000000..cb250aac4 --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/Collector.ts @@ -0,0 +1,25 @@ +import { Recorder } from './Recorder'; + +/** + * Interface to be implemented by collectors. + * + * Collectors collect data and inform the client of events. + * + * For instance a collector may notify the telemetry instance of HTTP navigation + * or of UI events. A collector can be created independently of a {@link Recorder} + * and can begin collecting immediately. It may queue information until it can + * be registered with a recorder. + */ +export interface Collector { + /** + * Register the collector with a recorder. + * @param recorder Recorder to report events or breadcrumbs to. + * @param sessionId The current session ID. + */ + register(recorder: Recorder, sessionId: string): void; + + /** + * Unregister the collector. It will stop sending events to the recorder. + */ + unregister(): void; +} diff --git a/packages/telemetry/browser-telemetry/src/api/ErrorData.ts b/packages/telemetry/browser-telemetry/src/api/ErrorData.ts new file mode 100644 index 000000000..0ed03945a --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/ErrorData.ts @@ -0,0 +1,32 @@ +import { Breadcrumb } from './Breadcrumb'; +import StackTrace from './stack/StackTrace'; + +/** + * Interface representing error data. + */ +export interface ErrorData { + /** + * The type of the error. + */ + + type: string; + /** + * A message associated with the error. + */ + + message: string; + /** + * The stack trace for the error. + */ + + stack: StackTrace; + /** + * Breadcrumbs leading up to the error. + */ + + breadcrumbs: Breadcrumb[]; + /** + * The ID of the session during which the error occurred. + */ + sessionId: string; +} diff --git a/packages/telemetry/browser-telemetry/src/api/Options.ts b/packages/telemetry/browser-telemetry/src/api/Options.ts new file mode 100644 index 000000000..4238af19b --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/Options.ts @@ -0,0 +1,146 @@ +import { Collector } from './Collector'; + +/** + * Interface for URL filters. + * + * Given a URL the filter may return a different string to represent that URL. + * This string will be included in the telemetry events instead of the original. + * + * The URL will be filtered by SDK internal filters before this function is called. + * + * To redact a URL entirely return an empty string. + * + * Example: + * customUrlFilter: (url) => { + * if (url.includes('secret')) { + * return '' + * } + * return url; + * } + */ +export interface UrlFilter { + (url: string): string; +} + +export interface HttpBreadCrumbOptions { + /** + * If fetch should be instrumented and breadcrumbs included for fetch requests. + * + * Defaults to true. + */ + instrumentFetch?: boolean; + + /** + * If XMLHttpRequests should be instrumented and breadcrumbs included for XMLHttpRequests. + * + * Defaults to true. + */ + instrumentXhr?: boolean; + + /** + * Customize URL filtering. This will be applied in addition to some baseline filtering included + * which redacts components of LaunchDarkly URLs. + */ + customUrlFilter?: UrlFilter; +} + +export interface StackOptions { + /** + * Configuration that controls how source is captured. + */ + source?: { + /** + * The number of lines captured before the originating line. + * + * Defaults to 3. + */ + beforeLines?: number; + /** + * The number of lines captured after the originating line. + * + * Defaults to 3. + */ + afterLines?: number; + + /** + * The maximum length of source line to include. Lines longer than this will be + * trimmed. + * + * Defaults to 280. + */ + maxLineLength?: number; + }; +} + +/** + * Options for configuring browser telemetry. + */ +export interface Options { + /** + * The maximum number of pending events. Events may be captured before the LaunchDarkly + * SDK is initialized and these are stored until they can be sent. This only affects the + * events captured during initialization. + */ + maxPendingEvents?: number; + /** + * Properties related to automatic breadcrumb collection. + */ + breadcrumbs?: { + /** + * Set the maximum number of breadcrumbs. Defaults to 50. + */ + maxBreadcrumbs?: number; + + /** + * True to enable automatic evaluation breadcrumbs. Defaults to true. + */ + evaluations?: boolean; + + /** + * True to enable flag change breadcrumbs. Defaults to true. + */ + flagChange?: boolean; + + /** + * True to enable click breadcrumbs. Defaults to true. + */ + click?: boolean; + + /** + * True to enable input breadcrumbs for keypresses. Defaults to true. + * + * Input breadcrumbs do not include entered text, just that text was entered. + */ + keyboardInput?: boolean; + + /** + * Controls instrumentation and breadcrumbs for HTTP requests. + * The default is to instrument XMLHttpRequests and fetch requests. + * + * `false` to disable all HTTP breadcrumbs and instrumentation. + * + * Example: + * ``` + * // This would instrument only XmlHttpRequests + * http: { + * instrumentFetch: false + * instrumentXhr: true + * } + * + * // Disable all HTTP instrumentation: + * http: false + * ``` + */ + http?: HttpBreadCrumbOptions | false; + }; + + /** + * Additional, or custom, collectors. + */ + collectors?: Collector[]; + + /** + * Configuration that controls the capture of the stack trace. + */ + stack?: StackOptions; +} diff --git a/packages/telemetry/browser-telemetry/src/api/Recorder.ts b/packages/telemetry/browser-telemetry/src/api/Recorder.ts new file mode 100644 index 000000000..dbbf21012 --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/Recorder.ts @@ -0,0 +1,28 @@ +import { Breadcrumb } from './Breadcrumb'; + +/** + * Interface for capturing telemetry data. + */ +export interface Recorder { + /** + * Capture an error. + * + * @param exception The exception to capture. + */ + captureError(exception: Error): void; + + /** + * Capture an error event. + * + * @param errorEvent The error event to capture. + */ + captureErrorEvent(errorEvent: ErrorEvent): void; + + /** + * Add a breadcrumb. When a capture is performed breadcrumb data can be + * included with it. + * + * @param breadcrumb The breadcrumb to add. + */ + addBreadcrumb(breadcrumb: Breadcrumb): void; +} diff --git a/packages/telemetry/browser-telemetry/src/api/stack/StackFrame.ts b/packages/telemetry/browser-telemetry/src/api/stack/StackFrame.ts new file mode 100644 index 000000000..9d12f3b02 --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/stack/StackFrame.ts @@ -0,0 +1,45 @@ +/** + * Represents a frame in a stack. + */ +export default interface StackFrame { + /** + * The fileName, relative to the project root, of the stack frame. + */ + fileName?: string; + + /** + * The name of the function the frame occurs in. + */ + function?: string; + + /** + * The line number in the file where the frame originates. + */ + line?: number; + + /** + * The column in the file where the frame originates. + */ + col?: number; + + /** + * A number of source code lines before the line the frame originates from. + * + * The number of lines is configurable. + */ + srcBefore?: string[]; + + /** + * The line of source code the frame originates from. + * + * This line may be partial if the line is too large. + */ + srcLine?: string; + + /** + * A number of source code lines after the line the frame originates from. + * + * The number of lines is configurable. + */ + srcAfter?: string[]; +} diff --git a/packages/telemetry/browser-telemetry/src/api/stack/StackTrace.ts b/packages/telemetry/browser-telemetry/src/api/stack/StackTrace.ts new file mode 100644 index 000000000..783a90040 --- /dev/null +++ b/packages/telemetry/browser-telemetry/src/api/stack/StackTrace.ts @@ -0,0 +1,12 @@ +import StackFrame from './StackFrame'; + +/** + * Represents a stack trace. + */ +export default interface StackTrace { + /** + * Frames associated with the stack. If no frames can be collected, then this + * will be an empty array. + */ + frames: StackFrame[]; +}