@honeycombio/opentelemetry-web
Advanced tools
| 'use strict'; | ||
| const VERSION = '1.0.0'; | ||
| exports.VERSION = VERSION; |
| const VERSION = '1.0.0'; | ||
| export { VERSION as V }; |
| 'use strict'; | ||
| var userInteractionInstrumentation = require('../user-interaction-instrumentation-hRrl_5VU.js'); | ||
| require('@opentelemetry/api'); | ||
| require('@opentelemetry/instrumentation'); | ||
| require('@opentelemetry/sdk-trace-web'); | ||
| var api = require('@opentelemetry/api'); | ||
| var instrumentation = require('@opentelemetry/instrumentation'); | ||
| var sdkTraceWeb = require('@opentelemetry/sdk-trace-web'); | ||
| var version = require('../version-DL_wPs07.js'); | ||
| const INSTRUMENTATION_NAME = '@honeycombio/user-instrumentation'; | ||
| const DEFAULT_EVENT_NAMES = ['click']; | ||
| class UserInteractionInstrumentation extends instrumentation.InstrumentationBase { | ||
| constructor(config = {}) { | ||
| var _a, _b; | ||
| super(INSTRUMENTATION_NAME, version.VERSION, config); | ||
| this._config = config; | ||
| this._isEnabled = (_a = this._config.enabled) !== null && _a !== void 0 ? _a : false; | ||
| // enable() gets called by our superclass constructor | ||
| // @ts-expect-error this may get set in enable() | ||
| this._listeners = (_b = this._listeners) !== null && _b !== void 0 ? _b : []; | ||
| } | ||
| init() {} | ||
| static handleEndSpan(ev) { | ||
| var _a; | ||
| (_a = UserInteractionInstrumentation._eventMap.get(ev)) === null || _a === void 0 ? void 0 : _a.end(); | ||
| } | ||
| static createGlobalEventListener(eventName, rootNodeId, isInstrumentationEnabled) { | ||
| return event => { | ||
| const element = event.target; | ||
| if (isInstrumentationEnabled() === false) return; | ||
| if (UserInteractionInstrumentation._eventMap.has(event)) return; | ||
| if (!shouldCreateSpan(event, element, eventName, rootNodeId)) return; | ||
| const xpath = sdkTraceWeb.getElementXPath(element); | ||
| const tracer = api.trace.getTracer(INSTRUMENTATION_NAME); | ||
| api.context.with(api.context.active(), () => { | ||
| tracer.startActiveSpan(eventName, { | ||
| attributes: { | ||
| event_type: eventName, | ||
| target_element: element.tagName, | ||
| target_xpath: xpath, | ||
| 'http.url': window.location.href | ||
| } | ||
| }, span => { | ||
| // if user space code calls stopPropagation, we'll never see it again | ||
| // so let's monkey patch those funcs to end the span if they do kill it | ||
| wrapEventPropagationCb(event, 'stopPropagation', span); | ||
| wrapEventPropagationCb(event, 'stopImmediatePropagation', span); | ||
| UserInteractionInstrumentation._eventMap.set(event, span); | ||
| }); | ||
| }); | ||
| }; | ||
| } | ||
| enable() { | ||
| var _a; | ||
| if (this._isEnabled) { | ||
| return; | ||
| } | ||
| const rootNode = this.getRootNode(); | ||
| // enable() gets called by our superclass constructor | ||
| // meaning our private fields aren't initialized yet!! | ||
| this._listeners = []; | ||
| // | ||
| const eventNames = (_a = this._config.eventNames) !== null && _a !== void 0 ? _a : DEFAULT_EVENT_NAMES; | ||
| eventNames.forEach(eventName => { | ||
| // we need a stable reference to this handler so that we can remove it later | ||
| const handler = UserInteractionInstrumentation.createGlobalEventListener(eventName, this._config.rootNodeId, () => this._isEnabled); | ||
| this._listeners.push({ | ||
| eventName, | ||
| handler | ||
| }); | ||
| // capture phase listener to kick in before any other listeners | ||
| rootNode.addEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| // bubble phase listener gets called at the end, if user space doesn't call e.stopPropagation() | ||
| rootNode.addEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._isEnabled = true; | ||
| } | ||
| getRootNode() { | ||
| if (this._config.rootNodeId) { | ||
| const rootNode = document.getElementById(this._config.rootNodeId); | ||
| if (rootNode === null) { | ||
| this._diag.warn(`Root Node id: ${this._config.rootNodeId} not found!`); | ||
| return document; | ||
| } | ||
| return rootNode; | ||
| } | ||
| return document; | ||
| } | ||
| disable() { | ||
| this._isEnabled = false; | ||
| this._listeners.forEach(({ | ||
| eventName, | ||
| handler | ||
| }) => { | ||
| document.removeEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| document.removeEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._listeners = []; | ||
| } | ||
| } | ||
| UserInteractionInstrumentation._eventMap = new WeakMap(); | ||
| const shouldCreateSpan = (event, element, eventName, rootNodeId) => { | ||
| if (!(element instanceof HTMLElement)) { | ||
| return false; | ||
| } | ||
| const handlerName = `on${eventName}`; | ||
| if (!elementHasEventHandler(element, handlerName, rootNodeId)) { | ||
| return false; | ||
| } | ||
| if (!element.getAttribute) { | ||
| return false; | ||
| } | ||
| if (element.hasAttribute('disabled')) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }; | ||
| /** | ||
| * Detects if this event on this element is useful | ||
| * by checking if this element or any of its parents have handlers | ||
| * for this event. | ||
| * | ||
| * Accounts for the fact that frameworks like React will put dummy/noop | ||
| * handlers at their root, and ignores those. | ||
| */ | ||
| const elementHasEventHandler = (element, eventName, rootNodeId) => { | ||
| if (!element || !!rootNodeId && element.id === rootNodeId) { | ||
| return false; | ||
| } | ||
| if (element[eventName]) { | ||
| return true; | ||
| } | ||
| return elementHasEventHandler(element.parentElement, eventName, rootNodeId); | ||
| }; | ||
| const wrapEventPropagationCb = (event, key, span) => { | ||
| const oldCb = event[key].bind(event); | ||
| event[key] = () => { | ||
| span.end(); | ||
| oldCb(); | ||
| }; | ||
| }; | ||
| exports.UserInteractionInstrumentation = userInteractionInstrumentation.UserInteractionInstrumentation; | ||
| exports.UserInteractionInstrumentation = UserInteractionInstrumentation; |
@@ -1,4 +0,143 @@ | ||
| export { U as UserInteractionInstrumentation } from '../user-interaction-instrumentation-C8QJwPpu.js'; | ||
| import '@opentelemetry/api'; | ||
| import '@opentelemetry/instrumentation'; | ||
| import '@opentelemetry/sdk-trace-web'; | ||
| import { trace, context } from '@opentelemetry/api'; | ||
| import { InstrumentationBase } from '@opentelemetry/instrumentation'; | ||
| import { getElementXPath } from '@opentelemetry/sdk-trace-web'; | ||
| import { V as VERSION } from '../version-0FZhXgGR.js'; | ||
| const INSTRUMENTATION_NAME = '@honeycombio/user-instrumentation'; | ||
| const DEFAULT_EVENT_NAMES = ['click']; | ||
| class UserInteractionInstrumentation extends InstrumentationBase { | ||
| constructor(config = {}) { | ||
| var _a, _b; | ||
| super(INSTRUMENTATION_NAME, VERSION, config); | ||
| this._config = config; | ||
| this._isEnabled = (_a = this._config.enabled) !== null && _a !== void 0 ? _a : false; | ||
| // enable() gets called by our superclass constructor | ||
| // @ts-expect-error this may get set in enable() | ||
| this._listeners = (_b = this._listeners) !== null && _b !== void 0 ? _b : []; | ||
| } | ||
| init() {} | ||
| static handleEndSpan(ev) { | ||
| var _a; | ||
| (_a = UserInteractionInstrumentation._eventMap.get(ev)) === null || _a === void 0 ? void 0 : _a.end(); | ||
| } | ||
| static createGlobalEventListener(eventName, rootNodeId, isInstrumentationEnabled) { | ||
| return event => { | ||
| const element = event.target; | ||
| if (isInstrumentationEnabled() === false) return; | ||
| if (UserInteractionInstrumentation._eventMap.has(event)) return; | ||
| if (!shouldCreateSpan(event, element, eventName, rootNodeId)) return; | ||
| const xpath = getElementXPath(element); | ||
| const tracer = trace.getTracer(INSTRUMENTATION_NAME); | ||
| context.with(context.active(), () => { | ||
| tracer.startActiveSpan(eventName, { | ||
| attributes: { | ||
| event_type: eventName, | ||
| target_element: element.tagName, | ||
| target_xpath: xpath, | ||
| 'http.url': window.location.href | ||
| } | ||
| }, span => { | ||
| // if user space code calls stopPropagation, we'll never see it again | ||
| // so let's monkey patch those funcs to end the span if they do kill it | ||
| wrapEventPropagationCb(event, 'stopPropagation', span); | ||
| wrapEventPropagationCb(event, 'stopImmediatePropagation', span); | ||
| UserInteractionInstrumentation._eventMap.set(event, span); | ||
| }); | ||
| }); | ||
| }; | ||
| } | ||
| enable() { | ||
| var _a; | ||
| if (this._isEnabled) { | ||
| return; | ||
| } | ||
| const rootNode = this.getRootNode(); | ||
| // enable() gets called by our superclass constructor | ||
| // meaning our private fields aren't initialized yet!! | ||
| this._listeners = []; | ||
| // | ||
| const eventNames = (_a = this._config.eventNames) !== null && _a !== void 0 ? _a : DEFAULT_EVENT_NAMES; | ||
| eventNames.forEach(eventName => { | ||
| // we need a stable reference to this handler so that we can remove it later | ||
| const handler = UserInteractionInstrumentation.createGlobalEventListener(eventName, this._config.rootNodeId, () => this._isEnabled); | ||
| this._listeners.push({ | ||
| eventName, | ||
| handler | ||
| }); | ||
| // capture phase listener to kick in before any other listeners | ||
| rootNode.addEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| // bubble phase listener gets called at the end, if user space doesn't call e.stopPropagation() | ||
| rootNode.addEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._isEnabled = true; | ||
| } | ||
| getRootNode() { | ||
| if (this._config.rootNodeId) { | ||
| const rootNode = document.getElementById(this._config.rootNodeId); | ||
| if (rootNode === null) { | ||
| this._diag.warn(`Root Node id: ${this._config.rootNodeId} not found!`); | ||
| return document; | ||
| } | ||
| return rootNode; | ||
| } | ||
| return document; | ||
| } | ||
| disable() { | ||
| this._isEnabled = false; | ||
| this._listeners.forEach(({ | ||
| eventName, | ||
| handler | ||
| }) => { | ||
| document.removeEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| document.removeEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._listeners = []; | ||
| } | ||
| } | ||
| UserInteractionInstrumentation._eventMap = new WeakMap(); | ||
| const shouldCreateSpan = (event, element, eventName, rootNodeId) => { | ||
| if (!(element instanceof HTMLElement)) { | ||
| return false; | ||
| } | ||
| const handlerName = `on${eventName}`; | ||
| if (!elementHasEventHandler(element, handlerName, rootNodeId)) { | ||
| return false; | ||
| } | ||
| if (!element.getAttribute) { | ||
| return false; | ||
| } | ||
| if (element.hasAttribute('disabled')) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }; | ||
| /** | ||
| * Detects if this event on this element is useful | ||
| * by checking if this element or any of its parents have handlers | ||
| * for this event. | ||
| * | ||
| * Accounts for the fact that frameworks like React will put dummy/noop | ||
| * handlers at their root, and ignores those. | ||
| */ | ||
| const elementHasEventHandler = (element, eventName, rootNodeId) => { | ||
| if (!element || !!rootNodeId && element.id === rootNodeId) { | ||
| return false; | ||
| } | ||
| if (element[eventName]) { | ||
| return true; | ||
| } | ||
| return elementHasEventHandler(element.parentElement, eventName, rootNodeId); | ||
| }; | ||
| const wrapEventPropagationCb = (event, key, span) => { | ||
| const oldCb = event[key].bind(event); | ||
| event[key] = () => { | ||
| span.end(); | ||
| oldCb(); | ||
| }; | ||
| }; | ||
| export { UserInteractionInstrumentation }; |
+534
-25
@@ -10,3 +10,2 @@ import * as _opentelemetry_api from '@opentelemetry/api'; | ||
| import { Metric, ReportOpts, CLSMetricWithAttribution, LCPMetricWithAttribution, INPMetricWithAttribution, FCPMetricWithAttribution, TTFBMetricWithAttribution } from 'web-vitals/attribution'; | ||
| export { UserInteractionInstrumentation } from './experimental/index.js'; | ||
@@ -120,3 +119,2 @@ type ApplyCustomAttributesFn = (vital: Metric, span: Span) => void; | ||
| private getAttrPrefix; | ||
| private getSharedAttributes; | ||
| private getAttributesForPerformanceLongAnimationFrameTiming; | ||
@@ -140,23 +138,2 @@ private getAttributesForPerformanceScriptTiming; | ||
| /** | ||
| * Extracts and structures the stack trace from an error object. | ||
| * | ||
| * This function breaks down the stack trace into arrays of strings and numbers | ||
| * to comply with OTLP (OpenTelemetry Protocol) requirements, which do not accept | ||
| * arrays of objects. | ||
| * | ||
| * @param {Error | undefined} error - The error object from which to extract the stack trace. | ||
| * @returns {Object} An object containing structured stack trace information with arrays of columns, lines, functions, and URLs. | ||
| */ | ||
| declare function getStructuredStackTrace(error: Error | undefined): { | ||
| 'exception.structured_stacktrace.columns'?: undefined; | ||
| 'exception.structured_stacktrace.lines'?: undefined; | ||
| 'exception.structured_stacktrace.functions'?: undefined; | ||
| 'exception.structured_stacktrace.urls'?: undefined; | ||
| } | { | ||
| 'exception.structured_stacktrace.columns': number[]; | ||
| 'exception.structured_stacktrace.lines': number[]; | ||
| 'exception.structured_stacktrace.functions': string[]; | ||
| 'exception.structured_stacktrace.urls': string[]; | ||
| }; | ||
| /** | ||
| * Records an exception as a span in the OpenTelemetry tracer. | ||
@@ -206,2 +183,6 @@ * | ||
| spanProcessors?: SpanProcessor[]; | ||
| timeout?: number; | ||
| tracesTimeout?: number; | ||
| metricsTimeout?: number; | ||
| logsTimeout?: number; | ||
| traceExporter: SpanExporter; | ||
@@ -271,2 +252,10 @@ spanLimits: SpanLimits; | ||
| spanProcessors?: SpanProcessor[]; | ||
| /** Timeout used by exporters when sending data. Defaults to 10000ms (10 seconds). */ | ||
| timeout?: number; | ||
| /** Timeout used by the traces exporter when sending data. Overrides timeout for trace data. */ | ||
| tracesTimeout?: number; | ||
| /** Timeout used by the metrics exporter when sending data. Overrides timeout for metric data. */ | ||
| metricsTimeout?: number; | ||
| /** Timeout used by the logs exporter when sending data. Overrides timeout for log data. */ | ||
| logsTimeout?: number; | ||
| /** Provide an array of exporters | ||
@@ -416,3 +405,523 @@ * Use this to configure custom tracing services in addition | ||
| export { BaggageSpanProcessor, GlobalErrorsInstrumentation, HoneycombWebSDK, InstrumentationAbstract, WebSDK, WebVitalsInstrumentation, getStructuredStackTrace, recordException }; | ||
| export type { EntryPageConfig, GlobalErrorsInstrumentationConfig, HoneycombOptions, WebSDKConfiguration, WebVitalsInstrumentationConfig }; | ||
| /** | ||
| * Custom semantic attribute constants for the Honeycomb OpenTelemetry Web SDK. | ||
| * These attributes extend the standard OpenTelemetry semantic conventions with | ||
| * Honeycomb-specific and browser-specific attributes. | ||
| * | ||
| * https://github.com/open-telemetry/semantic-conventions/tree/main/model/browser | ||
| */ | ||
| /** | ||
| * The name of the browser. | ||
| * @example "Chrome", "Firefox", "Safari" | ||
| */ | ||
| declare const ATTR_BROWSER_NAME = "browser.name"; | ||
| /** | ||
| * The version of the browser. | ||
| * @example "95.0.4638.54" | ||
| */ | ||
| declare const ATTR_BROWSER_VERSION = "browser.version"; | ||
| /** | ||
| * Whether the browser has touch screen capabilities. | ||
| * @example true, false | ||
| */ | ||
| declare const ATTR_BROWSER_TOUCH_SCREEN_ENABLED = "browser.touch_screen_enabled"; | ||
| /** | ||
| * The current width of the browser viewport in pixels. | ||
| * @example 1024 | ||
| */ | ||
| declare const ATTR_BROWSER_WIDTH = "browser.width"; | ||
| /** | ||
| * The current height of the browser viewport in pixels. | ||
| * @example 768 | ||
| */ | ||
| declare const ATTR_BROWSER_HEIGHT = "browser.height"; | ||
| /** | ||
| * The type of device. | ||
| * @example "desktop", "mobile", "tablet" | ||
| */ | ||
| declare const ATTR_DEVICE_TYPE = "device.type"; | ||
| /** | ||
| * The effective network connection type. | ||
| * @example "4g", "3g", "2g", "slow-2g", "unknown" | ||
| */ | ||
| declare const ATTR_NETWORK_EFFECTIVE_TYPE = "network.effectiveType"; | ||
| /** | ||
| * The width of the screen in pixels. | ||
| * @example 1920 | ||
| */ | ||
| declare const ATTR_SCREEN_WIDTH = "screen.width"; | ||
| /** | ||
| * The height of the screen in pixels. | ||
| * @example 1080 | ||
| */ | ||
| declare const ATTR_SCREEN_HEIGHT = "screen.height"; | ||
| /** | ||
| * The computed screen size category based on width. | ||
| * @example "small", "medium", "large", "unknown" | ||
| */ | ||
| declare const ATTR_SCREEN_SIZE = "screen.size"; | ||
| /** | ||
| * The current page URL hash fragment. | ||
| * @example "#section1" | ||
| */ | ||
| declare const ATTR_PAGE_HASH = "page.hash"; | ||
| /** | ||
| * The current page full URL. | ||
| * @example "https://example.com/path?query=value#hash" | ||
| */ | ||
| declare const ATTR_PAGE_URL = "page.url"; | ||
| /** | ||
| * The current page route/pathname. | ||
| * @example "/products/123" | ||
| */ | ||
| declare const ATTR_PAGE_ROUTE = "page.route"; | ||
| /** | ||
| * The current page hostname. | ||
| * @example "example.com" | ||
| */ | ||
| declare const ATTR_PAGE_HOSTNAME = "page.hostname"; | ||
| /** | ||
| * The current page search parameters. | ||
| * @example "?query=value&sort=asc" | ||
| */ | ||
| declare const ATTR_PAGE_SEARCH = "page.search"; | ||
| /** | ||
| * The current URL path. | ||
| * @example "/products/123" | ||
| */ | ||
| declare const ATTR_URL_PATH = "url.path"; | ||
| /** | ||
| * The URL of the entry page (page where the session started). | ||
| * @example "https://example.com/landing?utm_source=google" | ||
| */ | ||
| declare const ATTR_ENTRY_PAGE_URL = "entry_page.url"; | ||
| /** | ||
| * The path of the entry page (page where the session started). | ||
| * @example "/landing" | ||
| */ | ||
| declare const ATTR_ENTRY_PAGE_PATH = "entry_page.path"; | ||
| /** | ||
| * The search parameters of the entry page. | ||
| * @example "?utm_source=google&utm_medium=cpc" | ||
| */ | ||
| declare const ATTR_ENTRY_PAGE_SEARCH = "entry_page.search"; | ||
| /** | ||
| * The hash fragment of the entry page. | ||
| * @example "#welcome" | ||
| */ | ||
| declare const ATTR_ENTRY_PAGE_HASH = "entry_page.hash"; | ||
| /** | ||
| * The hostname of the entry page. | ||
| * @example "example.com" | ||
| */ | ||
| declare const ATTR_ENTRY_PAGE_HOSTNAME = "entry_page.hostname"; | ||
| /** | ||
| * The referrer URL that led to the entry page. | ||
| * @example "https://google.com/search?q=example" | ||
| */ | ||
| declare const ATTR_ENTRY_PAGE_REFERRER = "entry_page.referrer"; | ||
| /** | ||
| * The version of the Honeycomb distribution. | ||
| * @example "1.2.3" | ||
| */ | ||
| declare const ATTR_HONEYCOMB_DISTRO_VERSION = "honeycomb.distro.version"; | ||
| /** | ||
| * The runtime version of the Honeycomb distribution. | ||
| * @example "browser" | ||
| */ | ||
| declare const ATTR_HONEYCOMB_DISTRO_RUNTIME_VERSION = "honeycomb.distro.runtime_version"; | ||
| /** | ||
| * CLS metric ID. | ||
| * @example "v1-123456789" | ||
| */ | ||
| declare const ATTR_CLS_ID = "cls.id"; | ||
| /** | ||
| * CLS metric value. | ||
| * @example 0.123 | ||
| */ | ||
| declare const ATTR_CLS_VALUE = "cls.value"; | ||
| /** | ||
| * CLS metric delta. | ||
| * @example 0.045 | ||
| */ | ||
| declare const ATTR_CLS_DELTA = "cls.delta"; | ||
| /** | ||
| * CLS metric rating. | ||
| * @example "good", "needs-improvement", "poor" | ||
| */ | ||
| declare const ATTR_CLS_RATING = "cls.rating"; | ||
| /** | ||
| * CLS navigation type. | ||
| * @example "navigate", "reload", "back-forward" | ||
| */ | ||
| declare const ATTR_CLS_NAVIGATION_TYPE = "cls.navigation_type"; | ||
| /** | ||
| * LCP metric ID. | ||
| * @example "v1-123456789" | ||
| */ | ||
| declare const ATTR_LCP_ID = "lcp.id"; | ||
| /** | ||
| * LCP metric value. | ||
| * @example 1234.56 | ||
| */ | ||
| declare const ATTR_LCP_VALUE = "lcp.value"; | ||
| /** | ||
| * LCP metric delta. | ||
| * @example 123.45 | ||
| */ | ||
| declare const ATTR_LCP_DELTA = "lcp.delta"; | ||
| /** | ||
| * LCP metric rating. | ||
| * @example "good", "needs-improvement", "poor" | ||
| */ | ||
| declare const ATTR_LCP_RATING = "lcp.rating"; | ||
| /** | ||
| * LCP navigation type. | ||
| * @example "navigate", "reload", "back-forward" | ||
| */ | ||
| declare const ATTR_LCP_NAVIGATION_TYPE = "lcp.navigation_type"; | ||
| /** | ||
| * INP metric ID. | ||
| * @example "v1-123456789" | ||
| */ | ||
| declare const ATTR_INP_ID = "inp.id"; | ||
| /** | ||
| * INP metric value. | ||
| * @example 89.12 | ||
| */ | ||
| declare const ATTR_INP_VALUE = "inp.value"; | ||
| /** | ||
| * INP metric delta. | ||
| * @example 23.45 | ||
| */ | ||
| declare const ATTR_INP_DELTA = "inp.delta"; | ||
| /** | ||
| * INP metric rating. | ||
| * @example "good", "needs-improvement", "poor" | ||
| */ | ||
| declare const ATTR_INP_RATING = "inp.rating"; | ||
| /** | ||
| * INP navigation type. | ||
| * @example "navigate", "reload", "back-forward" | ||
| */ | ||
| declare const ATTR_INP_NAVIGATION_TYPE = "inp.navigation_type"; | ||
| /** | ||
| * FCP metric ID. | ||
| * @example "v1-123456789" | ||
| */ | ||
| declare const ATTR_FCP_ID = "fcp.id"; | ||
| /** | ||
| * FCP metric value. | ||
| * @example 678.90 | ||
| */ | ||
| declare const ATTR_FCP_VALUE = "fcp.value"; | ||
| /** | ||
| * FCP metric delta. | ||
| * @example 67.89 | ||
| */ | ||
| declare const ATTR_FCP_DELTA = "fcp.delta"; | ||
| /** | ||
| * FCP metric rating. | ||
| * @example "good", "needs-improvement", "poor" | ||
| */ | ||
| declare const ATTR_FCP_RATING = "fcp.rating"; | ||
| /** | ||
| * FCP navigation type. | ||
| * @example "navigate", "reload", "back-forward" | ||
| */ | ||
| declare const ATTR_FCP_NAVIGATION_TYPE = "fcp.navigation_type"; | ||
| /** | ||
| * TTFB metric ID. | ||
| * @example "v1-123456789" | ||
| */ | ||
| declare const ATTR_TTFB_ID = "ttfb.id"; | ||
| /** | ||
| * TTFB metric value. | ||
| * @example 234.56 | ||
| */ | ||
| declare const ATTR_TTFB_VALUE = "ttfb.value"; | ||
| /** | ||
| * TTFB metric delta. | ||
| * @example 34.56 | ||
| */ | ||
| declare const ATTR_TTFB_DELTA = "ttfb.delta"; | ||
| /** | ||
| * TTFB metric rating. | ||
| * @example "good", "needs-improvement", "poor" | ||
| */ | ||
| declare const ATTR_TTFB_RATING = "ttfb.rating"; | ||
| /** | ||
| * TTFB navigation type. | ||
| * @example "navigate", "reload", "back-forward" | ||
| */ | ||
| declare const ATTR_TTFB_NAVIGATION_TYPE = "ttfb.navigation_type"; | ||
| /** | ||
| * The largest shift target element for CLS. | ||
| * @example "div.main-content" | ||
| */ | ||
| declare const ATTR_CLS_LARGEST_SHIFT_TARGET = "cls.largest_shift_target"; | ||
| /** | ||
| * The element that caused the largest shift for CLS. | ||
| * @example "div.main-content" | ||
| */ | ||
| declare const ATTR_CLS_ELEMENT = "cls.element"; | ||
| /** | ||
| * The time when the largest shift occurred for CLS. | ||
| * @example 1234.56 | ||
| */ | ||
| declare const ATTR_CLS_LARGEST_SHIFT_TIME = "cls.largest_shift_time"; | ||
| /** | ||
| * The value of the largest shift for CLS. | ||
| * @example 0.123 | ||
| */ | ||
| declare const ATTR_CLS_LARGEST_SHIFT_VALUE = "cls.largest_shift_value"; | ||
| /** | ||
| * The load state when CLS occurred. | ||
| * @example "complete", "loading" | ||
| */ | ||
| declare const ATTR_CLS_LOAD_STATE = "cls.load_state"; | ||
| /** | ||
| * Whether there was recent input before the CLS. | ||
| * @example true, false | ||
| */ | ||
| declare const ATTR_CLS_HAD_RECENT_INPUT = "cls.had_recent_input"; | ||
| /** | ||
| * The element that was the largest contentful paint. | ||
| * @example "img.hero-image" | ||
| */ | ||
| declare const ATTR_LCP_ELEMENT = "lcp.element"; | ||
| /** | ||
| * The URL of the resource for LCP. | ||
| * @example "https://example.com/hero.jpg" | ||
| */ | ||
| declare const ATTR_LCP_URL = "lcp.url"; | ||
| /** | ||
| * Time to first byte for LCP. | ||
| * @example 123.45 | ||
| */ | ||
| declare const ATTR_LCP_TIME_TO_FIRST_BYTE = "lcp.time_to_first_byte"; | ||
| /** | ||
| * Resource load delay for LCP. | ||
| * @example 45.67 | ||
| */ | ||
| declare const ATTR_LCP_RESOURCE_LOAD_DELAY = "lcp.resource_load_delay"; | ||
| /** | ||
| * Resource load duration for LCP. | ||
| * @example 89.12 | ||
| */ | ||
| declare const ATTR_LCP_RESOURCE_LOAD_DURATION = "lcp.resource_load_duration"; | ||
| /** | ||
| * Element render delay for LCP. | ||
| * @example 12.34 | ||
| */ | ||
| declare const ATTR_LCP_ELEMENT_RENDER_DELAY = "lcp.element_render_delay"; | ||
| /** | ||
| * Resource load time for LCP (deprecated, use resource_load_duration). | ||
| * @example 89.12 | ||
| * @deprecated Use ATTR_LCP_RESOURCE_LOAD_DURATION instead | ||
| */ | ||
| declare const ATTR_LCP_RESOURCE_LOAD_TIME = "lcp.resource_load_time"; | ||
| /** | ||
| * Input delay for INP. | ||
| * @example 12.34 | ||
| */ | ||
| declare const ATTR_INP_INPUT_DELAY = "inp.input_delay"; | ||
| /** | ||
| * Interaction target for INP. | ||
| * @example "button.submit" | ||
| */ | ||
| declare const ATTR_INP_INTERACTION_TARGET = "inp.interaction_target"; | ||
| /** | ||
| * Interaction time for INP. | ||
| * @example 1234567890123 | ||
| */ | ||
| declare const ATTR_INP_INTERACTION_TIME = "inp.interaction_time"; | ||
| /** | ||
| * Interaction type for INP. | ||
| * @example "click", "keydown" | ||
| */ | ||
| declare const ATTR_INP_INTERACTION_TYPE = "inp.interaction_type"; | ||
| /** | ||
| * Load state when INP occurred. | ||
| * @example "complete", "loading" | ||
| */ | ||
| declare const ATTR_INP_LOAD_STATE = "inp.load_state"; | ||
| /** | ||
| * Next paint time for INP. | ||
| * @example 1234567890234 | ||
| */ | ||
| declare const ATTR_INP_NEXT_PAINT_TIME = "inp.next_paint_time"; | ||
| /** | ||
| * Presentation delay for INP. | ||
| * @example 23.45 | ||
| */ | ||
| declare const ATTR_INP_PRESENTATION_DELAY = "inp.presentation_delay"; | ||
| /** | ||
| * Processing duration for INP. | ||
| * @example 34.56 | ||
| */ | ||
| declare const ATTR_INP_PROCESSING_DURATION = "inp.processing_duration"; | ||
| /** | ||
| * Total duration for INP. | ||
| * @example 70.35 | ||
| */ | ||
| declare const ATTR_INP_DURATION = "inp.duration"; | ||
| /** | ||
| * Element for INP (deprecated, use interaction_target). | ||
| * @example "button.submit" | ||
| * @deprecated Use ATTR_INP_INTERACTION_TARGET instead | ||
| */ | ||
| declare const ATTR_INP_ELEMENT = "inp.element"; | ||
| /** | ||
| * Event type for INP (deprecated, use interaction_type). | ||
| * @example "click" | ||
| * @deprecated Use ATTR_INP_INTERACTION_TYPE instead | ||
| */ | ||
| declare const ATTR_INP_EVENT_TYPE = "inp.event_type"; | ||
| /** | ||
| * Script entry type for INP timing. | ||
| * @example "script" | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_ENTRY_TYPE = "inp.timing.script.entry_type"; | ||
| /** | ||
| * Script start time for INP timing. | ||
| * @example 1234567890123 | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_START_TIME = "inp.timing.script.start_time"; | ||
| /** | ||
| * Script execution start for INP timing. | ||
| * @example 1234567890125 | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_EXECUTION_START = "inp.timing.script.execution_start"; | ||
| /** | ||
| * Script duration for INP timing. | ||
| * @example 45.67 | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_DURATION = "inp.timing.script.duration"; | ||
| /** | ||
| * Script forced style and layout duration for INP timing. | ||
| * @example 12.34 | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_FORCED_STYLE_AND_LAYOUT_DURATION = "inp.timing.script.forced_style_and_layout_duration"; | ||
| /** | ||
| * Script invoker for INP timing. | ||
| * @example "event-listener" | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_INVOKER = "inp.timing.script.invoker"; | ||
| /** | ||
| * Script pause duration for INP timing. | ||
| * @example 5.67 | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_PAUSE_DURATION = "inp.timing.script.pause_duration"; | ||
| /** | ||
| * Script source URL for INP timing. | ||
| * @example "https://example.com/script.js" | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_SOURCE_URL = "inp.timing.script.source_url"; | ||
| /** | ||
| * Script source function name for INP timing. | ||
| * @example "handleClick" | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_SOURCE_FUNCTION_NAME = "inp.timing.script.source_function_name"; | ||
| /** | ||
| * Script source character position for INP timing. | ||
| * @example 123 | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_SOURCE_CHAR_POSITION = "inp.timing.script.source_char_position"; | ||
| /** | ||
| * Script window attribution for INP timing. | ||
| * @example "self" | ||
| */ | ||
| declare const ATTR_INP_SCRIPT_WINDOW_ATTRIBUTION = "inp.timing.script.window_attribution"; | ||
| /** | ||
| * Long animation frame duration for INP timing. | ||
| * @example 123.45 | ||
| */ | ||
| declare const ATTR_INP_TIMING_DURATION = "inp.timing.duration"; | ||
| /** | ||
| * Long animation frame entry type for INP timing. | ||
| * @example "long-animation-frame" | ||
| */ | ||
| declare const ATTR_INP_TIMING_ENTRY_TYPE = "inp.timing.entryType"; | ||
| /** | ||
| * Long animation frame name for INP timing. | ||
| * @example "same-origin-descendant" | ||
| */ | ||
| declare const ATTR_INP_TIMING_NAME = "inp.timing.name"; | ||
| /** | ||
| * Long animation frame render start for INP timing. | ||
| * @example 1234567890234 | ||
| */ | ||
| declare const ATTR_INP_TIMING_RENDER_START = "inp.timing.renderStart"; | ||
| /** | ||
| * Long animation frame start time for INP timing. | ||
| * @example 1234567890123 | ||
| */ | ||
| declare const ATTR_INP_TIMING_START_TIME = "inp.timing.startTime"; | ||
| /** | ||
| * Time to first byte for FCP. | ||
| * @example 123.45 | ||
| */ | ||
| declare const ATTR_FCP_TIME_TO_FIRST_BYTE = "fcp.time_to_first_byte"; | ||
| /** | ||
| * Time since first byte for FCP. | ||
| * @example 67.89 | ||
| */ | ||
| declare const ATTR_FCP_TIME_SINCE_FIRST_BYTE = "fcp.time_since_first_byte"; | ||
| /** | ||
| * Load state when FCP occurred. | ||
| * @example "complete", "loading" | ||
| */ | ||
| declare const ATTR_FCP_LOAD_STATE = "fcp.load_state"; | ||
| /** | ||
| * Waiting duration for TTFB. | ||
| * @example 45.67 | ||
| */ | ||
| declare const ATTR_TTFB_WAITING_DURATION = "ttfb.waiting_duration"; | ||
| /** | ||
| * DNS duration for TTFB. | ||
| * @example 12.34 | ||
| */ | ||
| declare const ATTR_TTFB_DNS_DURATION = "ttfb.dns_duration"; | ||
| /** | ||
| * Connection duration for TTFB. | ||
| * @example 23.45 | ||
| */ | ||
| declare const ATTR_TTFB_CONNECTION_DURATION = "ttfb.connection_duration"; | ||
| /** | ||
| * Request duration for TTFB. | ||
| * @example 34.56 | ||
| */ | ||
| declare const ATTR_TTFB_REQUEST_DURATION = "ttfb.request_duration"; | ||
| /** | ||
| * Cache duration for TTFB. | ||
| * @example 5.67 | ||
| */ | ||
| declare const ATTR_TTFB_CACHE_DURATION = "ttfb.cache_duration"; | ||
| /** | ||
| * Waiting time for TTFB (deprecated, use waiting_duration). | ||
| * @example 45.67 | ||
| * @deprecated Use ATTR_TTFB_WAITING_DURATION instead | ||
| */ | ||
| declare const ATTR_TTFB_WAITING_TIME = "ttfb.waiting_time"; | ||
| /** | ||
| * DNS time for TTFB (deprecated, use dns_duration). | ||
| * @example 12.34 | ||
| * @deprecated Use ATTR_TTFB_DNS_DURATION instead | ||
| */ | ||
| declare const ATTR_TTFB_DNS_TIME = "ttfb.dns_time"; | ||
| /** | ||
| * Connection time for TTFB (deprecated, use connection_duration). | ||
| * @example 23.45 | ||
| * @deprecated Use ATTR_TTFB_CONNECTION_DURATION instead | ||
| */ | ||
| declare const ATTR_TTFB_CONNECTION_TIME = "ttfb.connection_time"; | ||
| /** | ||
| * Request time for TTFB (deprecated, use request_duration). | ||
| * @example 34.56 | ||
| * @deprecated Use ATTR_TTFB_REQUEST_DURATION instead | ||
| */ | ||
| declare const ATTR_TTFB_REQUEST_TIME = "ttfb.request_time"; | ||
| export { ATTR_BROWSER_HEIGHT, ATTR_BROWSER_NAME, ATTR_BROWSER_TOUCH_SCREEN_ENABLED, ATTR_BROWSER_VERSION, ATTR_BROWSER_WIDTH, ATTR_CLS_DELTA, ATTR_CLS_ELEMENT, ATTR_CLS_HAD_RECENT_INPUT, ATTR_CLS_ID, ATTR_CLS_LARGEST_SHIFT_TARGET, ATTR_CLS_LARGEST_SHIFT_TIME, ATTR_CLS_LARGEST_SHIFT_VALUE, ATTR_CLS_LOAD_STATE, ATTR_CLS_NAVIGATION_TYPE, ATTR_CLS_RATING, ATTR_CLS_VALUE, ATTR_DEVICE_TYPE, ATTR_ENTRY_PAGE_HASH, ATTR_ENTRY_PAGE_HOSTNAME, ATTR_ENTRY_PAGE_PATH, ATTR_ENTRY_PAGE_REFERRER, ATTR_ENTRY_PAGE_SEARCH, ATTR_ENTRY_PAGE_URL, ATTR_FCP_DELTA, ATTR_FCP_ID, ATTR_FCP_LOAD_STATE, ATTR_FCP_NAVIGATION_TYPE, ATTR_FCP_RATING, ATTR_FCP_TIME_SINCE_FIRST_BYTE, ATTR_FCP_TIME_TO_FIRST_BYTE, ATTR_FCP_VALUE, ATTR_HONEYCOMB_DISTRO_RUNTIME_VERSION, ATTR_HONEYCOMB_DISTRO_VERSION, ATTR_INP_DELTA, ATTR_INP_DURATION, ATTR_INP_ELEMENT, ATTR_INP_EVENT_TYPE, ATTR_INP_ID, ATTR_INP_INPUT_DELAY, ATTR_INP_INTERACTION_TARGET, ATTR_INP_INTERACTION_TIME, ATTR_INP_INTERACTION_TYPE, ATTR_INP_LOAD_STATE, ATTR_INP_NAVIGATION_TYPE, ATTR_INP_NEXT_PAINT_TIME, ATTR_INP_PRESENTATION_DELAY, ATTR_INP_PROCESSING_DURATION, ATTR_INP_RATING, ATTR_INP_SCRIPT_DURATION, ATTR_INP_SCRIPT_ENTRY_TYPE, ATTR_INP_SCRIPT_EXECUTION_START, ATTR_INP_SCRIPT_FORCED_STYLE_AND_LAYOUT_DURATION, ATTR_INP_SCRIPT_INVOKER, ATTR_INP_SCRIPT_PAUSE_DURATION, ATTR_INP_SCRIPT_SOURCE_CHAR_POSITION, ATTR_INP_SCRIPT_SOURCE_FUNCTION_NAME, ATTR_INP_SCRIPT_SOURCE_URL, ATTR_INP_SCRIPT_START_TIME, ATTR_INP_SCRIPT_WINDOW_ATTRIBUTION, ATTR_INP_TIMING_DURATION, ATTR_INP_TIMING_ENTRY_TYPE, ATTR_INP_TIMING_NAME, ATTR_INP_TIMING_RENDER_START, ATTR_INP_TIMING_START_TIME, ATTR_INP_VALUE, ATTR_LCP_DELTA, ATTR_LCP_ELEMENT, ATTR_LCP_ELEMENT_RENDER_DELAY, ATTR_LCP_ID, ATTR_LCP_NAVIGATION_TYPE, ATTR_LCP_RATING, ATTR_LCP_RESOURCE_LOAD_DELAY, ATTR_LCP_RESOURCE_LOAD_DURATION, ATTR_LCP_RESOURCE_LOAD_TIME, ATTR_LCP_TIME_TO_FIRST_BYTE, ATTR_LCP_URL, ATTR_LCP_VALUE, ATTR_NETWORK_EFFECTIVE_TYPE, ATTR_PAGE_HASH, ATTR_PAGE_HOSTNAME, ATTR_PAGE_ROUTE, ATTR_PAGE_SEARCH, ATTR_PAGE_URL, ATTR_SCREEN_HEIGHT, ATTR_SCREEN_SIZE, ATTR_SCREEN_WIDTH, ATTR_TTFB_CACHE_DURATION, ATTR_TTFB_CONNECTION_DURATION, ATTR_TTFB_CONNECTION_TIME, ATTR_TTFB_DELTA, ATTR_TTFB_DNS_DURATION, ATTR_TTFB_DNS_TIME, ATTR_TTFB_ID, ATTR_TTFB_NAVIGATION_TYPE, ATTR_TTFB_RATING, ATTR_TTFB_REQUEST_DURATION, ATTR_TTFB_REQUEST_TIME, ATTR_TTFB_VALUE, ATTR_TTFB_WAITING_DURATION, ATTR_TTFB_WAITING_TIME, ATTR_URL_PATH, BaggageSpanProcessor, GlobalErrorsInstrumentation, HoneycombWebSDK, WebSDK, WebVitalsInstrumentation, recordException }; | ||
| export type { EntryPageConfig, HoneycombOptions, WebSDKConfiguration, WebVitalsInstrumentationConfig }; |
+9
-9
| { | ||
| "name": "@honeycombio/opentelemetry-web", | ||
| "version": "0.21.0", | ||
| "version": "1.0.0", | ||
| "description": "Honeycomb OpenTelemetry Wrapper for Browser Applications", | ||
@@ -109,9 +109,9 @@ "repository": { | ||
| "@opentelemetry/api": "~1.9.0", | ||
| "@opentelemetry/auto-instrumentations-web": "^0.48.0", | ||
| "@opentelemetry/auto-instrumentations-web": "^0.49.0", | ||
| "@opentelemetry/core": "^2.0.0", | ||
| "@opentelemetry/exporter-logs-otlp-http": "^0.202.0", | ||
| "@opentelemetry/exporter-metrics-otlp-http": "^0.202.0", | ||
| "@opentelemetry/exporter-trace-otlp-http": "~0.202.0", | ||
| "@opentelemetry/instrumentation": "~0.202.0", | ||
| "@opentelemetry/opentelemetry-browser-detector": "~0.202.0", | ||
| "@opentelemetry/exporter-logs-otlp-http": "^0.203.0", | ||
| "@opentelemetry/exporter-metrics-otlp-http": "^0.203.0", | ||
| "@opentelemetry/exporter-trace-otlp-http": "~0.203.0", | ||
| "@opentelemetry/instrumentation": "~0.203.0", | ||
| "@opentelemetry/opentelemetry-browser-detector": "~0.203.0", | ||
| "@opentelemetry/resources": "^2.0.0", | ||
@@ -121,3 +121,3 @@ "@opentelemetry/sdk-trace-base": "^2.0.0", | ||
| "@opentelemetry/semantic-conventions": "^1.30.0", | ||
| "@opentelemetry/web-common": "~0.202.0", | ||
| "@opentelemetry/web-common": "~0.203.0", | ||
| "shimmer": "^1.2.1", | ||
@@ -129,3 +129,3 @@ "tracekit": "^0.4.7", | ||
| "peerDependencies": { | ||
| "@opentelemetry/auto-instrumentations-web": "^0.48.0", | ||
| "@opentelemetry/auto-instrumentations-web": "^0.49.0", | ||
| "@opentelemetry/context-zone": "^2.0.0" | ||
@@ -132,0 +132,0 @@ }, |
+6
-10
@@ -9,10 +9,6 @@ # Honeycomb OpenTelemetry Web | ||
| <!-- TODO: happy badges of the OTel versions we are using --> | ||
| <!-- TODO: evergreen question of whether to use fields or attributes --> | ||
| **STATUS: this library is in BETA.** Data shapes are stable and safe for production. We are actively seeking feedback to ensure usability. | ||
| Latest release: | ||
| * built with OpenTelemetry JS [Stable v2.0.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/v2.0.0), [Experimental v0.202.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/experimental%2Fv0.202.0), [API v1.9.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/api%2Fv1.9.0), [Semantic Conventions v1.34.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/semconv%2Fv1.34.0) | ||
| * compatible with OpenTelemetry Auto-Instrumentations for Web [~0.48.0](https://github.com/open-telemetry/opentelemetry-js-contrib/releases/tag/auto-instrumentations-web-v0.48.0) | ||
| * built with OpenTelemetry JS [Stable v2.0.1](https://github.com/open-telemetry/opentelemetry-js/releases/tag/v2.0.1), [Experimental v0.203.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/experimental%2Fv0.203.0), [API v1.9.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/api%2Fv1.9.0), [Semantic Conventions v1.36.0](https://github.com/open-telemetry/opentelemetry-js/releases/tag/semconv%2Fv1.34.0) | ||
| * compatible with OpenTelemetry Auto-Instrumentations for Web [~0.49.0](https://github.com/open-telemetry/opentelemetry-js-contrib/releases/tag/auto-instrumentations-web-v0.49.0) | ||
@@ -31,6 +27,2 @@ This package sets up OpenTelemetry for tracing, using our recommended practices, including: | ||
| <!-- TODO: determine whether we must call this a distro instead of a wrapper. --> | ||
| <!-- Things to come: smoke tests in multiple browsers, smoke tests for popular frameworks, CDN distribution --> | ||
| ## Why use this? | ||
@@ -94,2 +86,6 @@ | ||
| | traceExporters | optional | SpanExporter[] | | Array of [span exporters](https://opentelemetry.io/docs/languages/js/exporters) | | ||
| | timeout | optional | number | 10000 | Timeout used by exporters when sending data. Defaults to 10000ms. | | ||
| | tracesTimeout | optional | number | 10000 | Timeout used by the traces exporter when sending data. Overrides timeout for trace data. | | ||
| | metricsTimeout | optional | number | 10000 | Timeout used by the metrics exporter when sending data. Overrides timeout for metric data. | | ||
| | logsTimeout | optional | number | 10000 | Timeout used by the logs exporter when sending data. Overrides timeout for log data. | | ||
| | disableDefaultTraceExporter | optional | boolean | false | Disable default honeycomb trace exporter. You can provide additional exporters via `traceExporters` config option. | | ||
@@ -96,0 +92,0 @@ | webVitalsInstrumentationConfig | optional | WebVitalsInstrumentationConfig | `{ enabled: true }` | See [WebVitalsInstrumentationConfig](####WebVitalsInstrumentationConfig). | |
| 'use strict'; | ||
| var api = require('@opentelemetry/api'); | ||
| var instrumentation = require('@opentelemetry/instrumentation'); | ||
| var sdkTraceWeb = require('@opentelemetry/sdk-trace-web'); | ||
| const VERSION = '0.21.0'; | ||
| const INSTRUMENTATION_NAME = '@honeycombio/user-instrumentation'; | ||
| const DEFAULT_EVENT_NAMES = ['click']; | ||
| class UserInteractionInstrumentation extends instrumentation.InstrumentationBase { | ||
| constructor(config = {}) { | ||
| var _a, _b; | ||
| super(INSTRUMENTATION_NAME, VERSION, config); | ||
| this._config = config; | ||
| this._isEnabled = (_a = this._config.enabled) !== null && _a !== void 0 ? _a : false; | ||
| // enable() gets called by our superclass constructor | ||
| // @ts-expect-error this may get set in enable() | ||
| this._listeners = (_b = this._listeners) !== null && _b !== void 0 ? _b : []; | ||
| } | ||
| init() {} | ||
| static handleEndSpan(ev) { | ||
| var _a; | ||
| (_a = UserInteractionInstrumentation._eventMap.get(ev)) === null || _a === void 0 ? void 0 : _a.end(); | ||
| } | ||
| static createGlobalEventListener(eventName, rootNodeId, isInstrumentationEnabled) { | ||
| return event => { | ||
| const element = event.target; | ||
| if (isInstrumentationEnabled() === false) return; | ||
| if (UserInteractionInstrumentation._eventMap.has(event)) return; | ||
| if (!shouldCreateSpan(event, element, eventName, rootNodeId)) return; | ||
| const xpath = sdkTraceWeb.getElementXPath(element); | ||
| const tracer = api.trace.getTracer(INSTRUMENTATION_NAME); | ||
| api.context.with(api.context.active(), () => { | ||
| tracer.startActiveSpan(eventName, { | ||
| attributes: { | ||
| event_type: eventName, | ||
| target_element: element.tagName, | ||
| target_xpath: xpath, | ||
| 'http.url': window.location.href | ||
| } | ||
| }, span => { | ||
| // if user space code calls stopPropagation, we'll never see it again | ||
| // so let's monkey patch those funcs to end the span if they do kill it | ||
| wrapEventPropagationCb(event, 'stopPropagation', span); | ||
| wrapEventPropagationCb(event, 'stopImmediatePropagation', span); | ||
| UserInteractionInstrumentation._eventMap.set(event, span); | ||
| }); | ||
| }); | ||
| }; | ||
| } | ||
| enable() { | ||
| var _a; | ||
| if (this._isEnabled) { | ||
| return; | ||
| } | ||
| const rootNode = this.getRootNode(); | ||
| // enable() gets called by our superclass constructor | ||
| // meaning our private fields aren't initialized yet!! | ||
| this._listeners = []; | ||
| // | ||
| const eventNames = (_a = this._config.eventNames) !== null && _a !== void 0 ? _a : DEFAULT_EVENT_NAMES; | ||
| eventNames.forEach(eventName => { | ||
| // we need a stable reference to this handler so that we can remove it later | ||
| const handler = UserInteractionInstrumentation.createGlobalEventListener(eventName, this._config.rootNodeId, () => this._isEnabled); | ||
| this._listeners.push({ | ||
| eventName, | ||
| handler | ||
| }); | ||
| // capture phase listener to kick in before any other listeners | ||
| rootNode.addEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| // bubble phase listener gets called at the end, if user space doesn't call e.stopPropagation() | ||
| rootNode.addEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._isEnabled = true; | ||
| } | ||
| getRootNode() { | ||
| if (this._config.rootNodeId) { | ||
| const rootNode = document.getElementById(this._config.rootNodeId); | ||
| if (rootNode === null) { | ||
| this._diag.warn(`Root Node id: ${this._config.rootNodeId} not found!`); | ||
| return document; | ||
| } | ||
| return rootNode; | ||
| } | ||
| return document; | ||
| } | ||
| disable() { | ||
| this._isEnabled = false; | ||
| this._listeners.forEach(({ | ||
| eventName, | ||
| handler | ||
| }) => { | ||
| document.removeEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| document.removeEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._listeners = []; | ||
| } | ||
| } | ||
| UserInteractionInstrumentation._eventMap = new WeakMap(); | ||
| const shouldCreateSpan = (event, element, eventName, rootNodeId) => { | ||
| if (!(element instanceof HTMLElement)) { | ||
| return false; | ||
| } | ||
| const handlerName = `on${eventName}`; | ||
| if (!elementHasEventHandler(element, handlerName, rootNodeId)) { | ||
| return false; | ||
| } | ||
| if (!element.getAttribute) { | ||
| return false; | ||
| } | ||
| if (element.hasAttribute('disabled')) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }; | ||
| /** | ||
| * Detects if this event on this element is useful | ||
| * by checking if this element or any of its parents have handlers | ||
| * for this event. | ||
| * | ||
| * Accounts for the fact that frameworks like React will put dummy/noop | ||
| * handlers at their root, and ignores those. | ||
| */ | ||
| const elementHasEventHandler = (element, eventName, rootNodeId) => { | ||
| if (!element || !!rootNodeId && element.id === rootNodeId) { | ||
| return false; | ||
| } | ||
| if (element[eventName]) { | ||
| return true; | ||
| } | ||
| return elementHasEventHandler(element.parentElement, eventName, rootNodeId); | ||
| }; | ||
| const wrapEventPropagationCb = (event, key, span) => { | ||
| const oldCb = event[key].bind(event); | ||
| event[key] = () => { | ||
| span.end(); | ||
| oldCb(); | ||
| }; | ||
| }; | ||
| exports.UserInteractionInstrumentation = UserInteractionInstrumentation; | ||
| exports.VERSION = VERSION; |
| import { trace, context } from '@opentelemetry/api'; | ||
| import { InstrumentationBase } from '@opentelemetry/instrumentation'; | ||
| import { getElementXPath } from '@opentelemetry/sdk-trace-web'; | ||
| const VERSION = '0.21.0'; | ||
| const INSTRUMENTATION_NAME = '@honeycombio/user-instrumentation'; | ||
| const DEFAULT_EVENT_NAMES = ['click']; | ||
| class UserInteractionInstrumentation extends InstrumentationBase { | ||
| constructor(config = {}) { | ||
| var _a, _b; | ||
| super(INSTRUMENTATION_NAME, VERSION, config); | ||
| this._config = config; | ||
| this._isEnabled = (_a = this._config.enabled) !== null && _a !== void 0 ? _a : false; | ||
| // enable() gets called by our superclass constructor | ||
| // @ts-expect-error this may get set in enable() | ||
| this._listeners = (_b = this._listeners) !== null && _b !== void 0 ? _b : []; | ||
| } | ||
| init() {} | ||
| static handleEndSpan(ev) { | ||
| var _a; | ||
| (_a = UserInteractionInstrumentation._eventMap.get(ev)) === null || _a === void 0 ? void 0 : _a.end(); | ||
| } | ||
| static createGlobalEventListener(eventName, rootNodeId, isInstrumentationEnabled) { | ||
| return event => { | ||
| const element = event.target; | ||
| if (isInstrumentationEnabled() === false) return; | ||
| if (UserInteractionInstrumentation._eventMap.has(event)) return; | ||
| if (!shouldCreateSpan(event, element, eventName, rootNodeId)) return; | ||
| const xpath = getElementXPath(element); | ||
| const tracer = trace.getTracer(INSTRUMENTATION_NAME); | ||
| context.with(context.active(), () => { | ||
| tracer.startActiveSpan(eventName, { | ||
| attributes: { | ||
| event_type: eventName, | ||
| target_element: element.tagName, | ||
| target_xpath: xpath, | ||
| 'http.url': window.location.href | ||
| } | ||
| }, span => { | ||
| // if user space code calls stopPropagation, we'll never see it again | ||
| // so let's monkey patch those funcs to end the span if they do kill it | ||
| wrapEventPropagationCb(event, 'stopPropagation', span); | ||
| wrapEventPropagationCb(event, 'stopImmediatePropagation', span); | ||
| UserInteractionInstrumentation._eventMap.set(event, span); | ||
| }); | ||
| }); | ||
| }; | ||
| } | ||
| enable() { | ||
| var _a; | ||
| if (this._isEnabled) { | ||
| return; | ||
| } | ||
| const rootNode = this.getRootNode(); | ||
| // enable() gets called by our superclass constructor | ||
| // meaning our private fields aren't initialized yet!! | ||
| this._listeners = []; | ||
| // | ||
| const eventNames = (_a = this._config.eventNames) !== null && _a !== void 0 ? _a : DEFAULT_EVENT_NAMES; | ||
| eventNames.forEach(eventName => { | ||
| // we need a stable reference to this handler so that we can remove it later | ||
| const handler = UserInteractionInstrumentation.createGlobalEventListener(eventName, this._config.rootNodeId, () => this._isEnabled); | ||
| this._listeners.push({ | ||
| eventName, | ||
| handler | ||
| }); | ||
| // capture phase listener to kick in before any other listeners | ||
| rootNode.addEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| // bubble phase listener gets called at the end, if user space doesn't call e.stopPropagation() | ||
| rootNode.addEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._isEnabled = true; | ||
| } | ||
| getRootNode() { | ||
| if (this._config.rootNodeId) { | ||
| const rootNode = document.getElementById(this._config.rootNodeId); | ||
| if (rootNode === null) { | ||
| this._diag.warn(`Root Node id: ${this._config.rootNodeId} not found!`); | ||
| return document; | ||
| } | ||
| return rootNode; | ||
| } | ||
| return document; | ||
| } | ||
| disable() { | ||
| this._isEnabled = false; | ||
| this._listeners.forEach(({ | ||
| eventName, | ||
| handler | ||
| }) => { | ||
| document.removeEventListener(eventName, handler, { | ||
| capture: true | ||
| }); | ||
| document.removeEventListener(eventName, UserInteractionInstrumentation.handleEndSpan); | ||
| }); | ||
| this._listeners = []; | ||
| } | ||
| } | ||
| UserInteractionInstrumentation._eventMap = new WeakMap(); | ||
| const shouldCreateSpan = (event, element, eventName, rootNodeId) => { | ||
| if (!(element instanceof HTMLElement)) { | ||
| return false; | ||
| } | ||
| const handlerName = `on${eventName}`; | ||
| if (!elementHasEventHandler(element, handlerName, rootNodeId)) { | ||
| return false; | ||
| } | ||
| if (!element.getAttribute) { | ||
| return false; | ||
| } | ||
| if (element.hasAttribute('disabled')) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }; | ||
| /** | ||
| * Detects if this event on this element is useful | ||
| * by checking if this element or any of its parents have handlers | ||
| * for this event. | ||
| * | ||
| * Accounts for the fact that frameworks like React will put dummy/noop | ||
| * handlers at their root, and ignores those. | ||
| */ | ||
| const elementHasEventHandler = (element, eventName, rootNodeId) => { | ||
| if (!element || !!rootNodeId && element.id === rootNodeId) { | ||
| return false; | ||
| } | ||
| if (element[eventName]) { | ||
| return true; | ||
| } | ||
| return elementHasEventHandler(element.parentElement, eventName, rootNodeId); | ||
| }; | ||
| const wrapEventPropagationCb = (event, key, span) => { | ||
| const oldCb = event[key].bind(event); | ||
| event[key] = () => { | ||
| span.end(); | ||
| oldCb(); | ||
| }; | ||
| }; | ||
| export { UserInteractionInstrumentation as U, VERSION as V }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1450270
5.35%36209
6.85%1
-50%295
-1.34%193
3.21%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated