@genn-inc/cluebase-frontend-sdk 0.0.3 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +21 -0
  2. package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
  3. package/dist/_generated/correlation-attribute-keys.js +34 -0
  4. package/dist/_generated/diagnostic-codes.d.ts +4 -0
  5. package/dist/_generated/diagnostic-codes.js +48 -0
  6. package/dist/_generated/drop-reasons.d.ts +2 -0
  7. package/dist/_generated/drop-reasons.js +25 -0
  8. package/dist/_generated/{identity-pii-policy.d.ts → identity-pii-policy-text-contains.d.ts} +1 -1
  9. package/dist/_generated/{identity-pii-policy.js → identity-pii-policy-text-contains.js} +1 -1
  10. package/dist/_generated/identity-pii-policy-whole-value.d.ts +37 -0
  11. package/dist/_generated/identity-pii-policy-whole-value.js +106 -0
  12. package/dist/_generated/ingest-routes.d.ts +5 -0
  13. package/dist/_generated/ingest-routes.js +12 -0
  14. package/dist/_generated/otel-span-conversion.js +1 -9
  15. package/dist/context/browser-storage.js +1 -2
  16. package/dist/context/environment.js +5 -3
  17. package/dist/context/identity.js +5 -9
  18. package/dist/context/organization.d.ts +1 -1
  19. package/dist/context/organization.js +6 -8
  20. package/dist/context/session.js +6 -8
  21. package/dist/context/tab.js +8 -12
  22. package/dist/context/trace.js +4 -5
  23. package/dist/exporter/batch.d.ts +26 -27
  24. package/dist/exporter/batch.js +248 -294
  25. package/dist/exporter/browser-outbox-records.d.ts +8 -25
  26. package/dist/exporter/browser-outbox-records.js +21 -100
  27. package/dist/exporter/browser-outbox.d.ts +23 -25
  28. package/dist/exporter/browser-outbox.js +106 -181
  29. package/dist/exporter/client.d.ts +25 -35
  30. package/dist/exporter/client.js +107 -335
  31. package/dist/exporter/delivery-pipeline.d.ts +71 -0
  32. package/dist/exporter/delivery-pipeline.js +196 -0
  33. package/dist/exporter/delivery-response-contract.d.ts +1 -81
  34. package/dist/exporter/delivery-response-contract.js +0 -118
  35. package/dist/exporter/delivery-response.d.ts +9 -4
  36. package/dist/exporter/delivery-response.js +59 -122
  37. package/dist/exporter/diagnostics.d.ts +3 -2
  38. package/dist/exporter/diagnostics.js +5 -26
  39. package/dist/exporter/event-id.d.ts +1 -0
  40. package/dist/exporter/event-id.js +4 -3
  41. package/dist/exporter/frontend-resource.js +5 -4
  42. package/dist/exporter/observation-source-event-exporter.d.ts +2 -1
  43. package/dist/exporter/observation-source-event-exporter.js +7 -4
  44. package/dist/exporter/path-template.js +3 -1
  45. package/dist/exporter/raw-event-to-source-signal.js +4 -2
  46. package/dist/exporter/sanitize.d.ts +3 -2
  47. package/dist/exporter/sanitize.js +4 -3
  48. package/dist/exporter/sdk-core-delivery.d.ts +225 -0
  49. package/dist/exporter/sdk-core-delivery.js +97 -0
  50. package/dist/exporter/sdk-core-wasm.d.ts +11 -0
  51. package/dist/exporter/sdk-core-wasm.js +39 -0
  52. package/dist/exporter/source-event.d.ts +3 -0
  53. package/dist/exporter/source-event.js +17 -0
  54. package/dist/exporter/span-to-source-signal.d.ts +2 -2
  55. package/dist/exporter/span-to-source-signal.js +45 -125
  56. package/dist/index.d.ts +5 -4
  57. package/dist/index.js +18 -6
  58. package/dist/instrumentation/action.js +9 -8
  59. package/dist/instrumentation/dom.js +13 -21
  60. package/dist/instrumentation/drag.js +0 -3
  61. package/dist/instrumentation/error.js +3 -1
  62. package/dist/instrumentation/form.js +5 -6
  63. package/dist/instrumentation/input.js +4 -8
  64. package/dist/instrumentation/location.js +3 -1
  65. package/dist/instrumentation/network-request.js +1 -1
  66. package/dist/instrumentation/network-span-attributes.js +2 -2
  67. package/dist/instrumentation/network.d.ts +1 -1
  68. package/dist/instrumentation/network.js +24 -3
  69. package/dist/instrumentation/performance.js +1 -2
  70. package/dist/internal/internal-log.js +2 -1
  71. package/dist/provider/_version.d.ts +1 -1
  72. package/dist/provider/_version.js +1 -1
  73. package/dist/provider/config.js +16 -4
  74. package/dist/provider/constants.d.ts +1 -1
  75. package/dist/provider/constants.js +0 -2
  76. package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
  77. package/dist/provider/lifecycle-event-dedup.js +31 -0
  78. package/dist/provider/sdk-event-pipeline.d.ts +3 -4
  79. package/dist/provider/sdk-event-pipeline.js +5 -17
  80. package/dist/provider/sdk-input-normalization.js +2 -2
  81. package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
  82. package/dist/provider/sdk-otel-pipeline.js +7 -15
  83. package/dist/provider/sdk-runtime-surfaces.d.ts +17 -13
  84. package/dist/provider/sdk-runtime-surfaces.js +15 -13
  85. package/dist/provider/sdk.js +137 -48
  86. package/dist/provider/types.d.ts +18 -24
  87. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +44 -0
  88. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +355 -0
  89. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
  90. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +11 -0
  91. package/dist/wasm/pkg-web/package.json +15 -0
  92. package/package.json +62 -53
  93. package/dist/provider/usage-governor.d.ts +0 -7
  94. package/dist/provider/usage-governor.js +0 -114
@@ -1,8 +1,8 @@
1
1
  import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
2
2
  import type { CluebaseContext } from "../provider/types.js";
3
- import { type BrowserSourceSignalEvent } from "./source-event.js";
3
+ import type { BrowserSourceSignalEvent } from "./source-event.js";
4
4
  export interface SpanToSourceSignalOptions {
5
5
  context: CluebaseContext;
6
6
  deniedKeys?: readonly string[];
7
7
  }
8
- export declare function spanToSourceSignalEvent(span: ReadableSpan, options: SpanToSourceSignalOptions): BrowserSourceSignalEvent;
8
+ export declare function spanToSourceSignalEvent(span: ReadableSpan, options: SpanToSourceSignalOptions): Promise<BrowserSourceSignalEvent>;
@@ -1,42 +1,14 @@
1
- import { CLUEBASE_CORRELATION_ATTRIBUTE_KEYS } from "../_generated/correlation-attribute-keys.js";
2
- import { attrString, hrTimeToUnixNano, nanosToIso, parentSpanId, } from "../_generated/otel-span-conversion.js";
3
- import { EVENT_SOURCE, EVENT_VERSION, SOURCE_SCHEMA_VERSION, } from "../provider/constants.js";
4
- import { generateEventId, generateInteractionId, generateRequestId, generateRequestSpanId, generateTraceId, } from "./event-id.js";
5
- import { isDeniedKey } from "./sanitize.js";
6
- import { toFrontendSourceResource } from "./frontend-resource.js";
7
- import { SDK_SOURCE_SIGNAL_EVENT_NAME, SDK_SOURCE_SIGNAL_EVENT_TYPE, } from "./source-event.js";
8
- const isDynamicJsonObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9
- function toJsonValue(value) {
10
- if (value === null)
11
- return null;
12
- if (typeof value === "string" ||
13
- typeof value === "number" ||
14
- typeof value === "boolean") {
15
- return typeof value === "number" && !Number.isFinite(value) ? null : value;
1
+ import { parentSpanId } from "../_generated/otel-span-conversion.js";
2
+ import { EVENT_SOURCE } from "../provider/constants.js";
3
+ import { initSdkCoreWasm, isSdkCoreWasmReady, spanToObservationSourceEventJson, } from "./sdk-core-wasm.js";
4
+ function resourceAttributesInput(resource) {
5
+ const input = resource;
6
+ try {
7
+ return { ...(input?.attributes ?? {}) };
16
8
  }
17
- if (Array.isArray(value))
18
- return value.map((entry) => toJsonValue(entry));
19
- if (isDynamicJsonObject(value)) {
20
- const record = {};
21
- for (const [key, entry] of Object.entries(value)) {
22
- record[key] = toJsonValue(entry);
23
- }
24
- return record;
9
+ catch {
10
+ return {};
25
11
  }
26
- if (typeof value === "bigint")
27
- return value.toString();
28
- return null;
29
- }
30
- function toJsonRecord(value, deniedKeys = []) {
31
- const record = {};
32
- for (const [key, entry] of Object.entries(value ?? {})) {
33
- if (!CLUEBASE_CORRELATION_ATTRIBUTE_KEYS.has(key) &&
34
- isDeniedKey(key, deniedKeys)) {
35
- continue;
36
- }
37
- record[key] = toJsonValue(entry);
38
- }
39
- return record;
40
12
  }
41
13
  function instrumentationScopeName(span) {
42
14
  const instrumentation = span;
@@ -44,7 +16,7 @@ function instrumentationScopeName(span) {
44
16
  instrumentation.instrumentationLibrary?.name ??
45
17
  null);
46
18
  }
47
- function contextRecord(context, overrides) {
19
+ function contextRecord(context) {
48
20
  return {
49
21
  producer_id: context.producer_id,
50
22
  anonymous_id: context.anonymous_id,
@@ -53,11 +25,11 @@ function contextRecord(context, overrides) {
53
25
  user_profile: context.user_profile,
54
26
  session_id: context.session_id,
55
27
  tab_id: context.tab_id,
56
- trace_id: overrides.trace_id,
57
- request_id: overrides.request_id,
58
- request_span_id: overrides.request_span_id,
59
- action_id: overrides.action_id,
60
- interaction_id: overrides.interaction_id,
28
+ trace_id: context.trace_id,
29
+ request_id: context.request_id,
30
+ request_span_id: context.request_span_id,
31
+ action_id: context.action_id,
32
+ interaction_id: context.interaction_id,
61
33
  correlation_id: context.correlation_id,
62
34
  sdk_collection_mode: context.sdk_collection_mode,
63
35
  frontend_release: context.frontend_release,
@@ -87,92 +59,40 @@ function contextRecord(context, overrides) {
87
59
  context_traits: context.context_traits,
88
60
  };
89
61
  }
90
- export function spanToSourceSignalEvent(span, options) {
62
+ export async function spanToSourceSignalEvent(span, options) {
63
+ if (!isSdkCoreWasmReady()) {
64
+ await initSdkCoreWasm();
65
+ }
91
66
  const context = options.context;
92
- const attributes = toJsonRecord(span.attributes, options.deniedKeys);
93
67
  const spanContext = span.spanContext();
94
- const traceId = attrString(attributes, "cluebase.trace_id") ??
95
- spanContext.traceId ??
96
- generateTraceId();
97
- const spanId = spanContext.spanId || null;
98
- const parentId = parentSpanId(span, attributes);
99
- const startTimeUnixNano = hrTimeToUnixNano(span.startTime);
100
- const endTimeUnixNano = hrTimeToUnixNano(span.endTime);
101
- const eventId = generateEventId();
102
- const requestSpanId = attrString(attributes, "cluebase.request_span_id") ??
103
- spanId ??
104
- context.request_span_id ??
105
- generateRequestSpanId();
106
- const requestId = attrString(attributes, "cluebase.request.id") ??
107
- attrString(attributes, "cluebase.request_id") ??
108
- context.request_id ??
109
- generateRequestId();
110
- const actionId = attrString(attributes, "cluebase.action.id") ??
111
- attrString(attributes, "cluebase.action_id") ??
112
- context.action_id ??
113
- null;
114
- const interactionId = attrString(attributes, "cluebase.interaction.id") ??
115
- attrString(attributes, "cluebase.interaction_id") ??
116
- context.interaction_id ??
117
- generateInteractionId();
118
- return {
119
- event_id: eventId,
120
- source_event_id: eventId,
121
- source_schema_version: SOURCE_SCHEMA_VERSION,
122
- source_event_type: SDK_SOURCE_SIGNAL_EVENT_TYPE,
123
- source_event_kind: SDK_SOURCE_SIGNAL_EVENT_TYPE,
68
+ const corePayload = {
69
+ name: span.name,
70
+ kind: typeof span.kind === "number" ? span.kind : undefined,
71
+ trace_id: spanContext.traceId,
72
+ span_id: spanContext.spanId,
73
+ parent_span_id: parentSpanId(span, span.attributes),
74
+ start_time: span.startTime,
75
+ end_time: span.endTime,
76
+ status: span.status
77
+ ? { code: span.status.code ?? undefined, message: span.status.message }
78
+ : undefined,
79
+ attributes: span.attributes,
80
+ events: (span.events ?? []).map((event) => ({
81
+ name: event.name,
82
+ attributes: event.attributes ?? {},
83
+ })),
84
+ resource_attributes: resourceAttributesInput(span.resource),
85
+ instrumentation_scope_name: instrumentationScopeName(span),
86
+ };
87
+ const coreOptions = {
88
+ denied_keys: options.deniedKeys ?? [],
89
+ surface: "frontend",
90
+ sdk_collection_mode: context.sdk_collection_mode,
124
91
  producer_id: context.producer_id,
125
92
  service_name: EVENT_SOURCE,
126
- event_name: SDK_SOURCE_SIGNAL_EVENT_NAME,
127
- event_category: "context",
128
- event_source: EVENT_SOURCE,
129
- event_version: EVENT_VERSION,
130
- occurred_at: nanosToIso(startTimeUnixNano),
131
- ingested_at: new Date().toISOString(),
132
- status: "observed",
133
- sdk_collection_mode: context.sdk_collection_mode,
134
- privacy_state: "masked",
135
- privacy_level: "default_masked",
136
- masking_state: "masked",
137
- allowed_value_keys: [],
138
- privacy_summary: context.privacy_summary,
139
- trace_id: traceId,
140
- span_id: spanId,
141
- parent_span_id: parentId,
142
- request_id: requestId,
143
- request_span_id: requestSpanId,
144
- action_id: actionId,
145
- interaction_id: interactionId,
146
93
  frontend_release: context.frontend_release,
147
- backend_release: null,
148
- properties: {},
149
- metrics: {},
150
- sdk_signal: {
151
- surface: "frontend",
152
- kind: "span",
153
- name: span.name,
154
- span_kind: span.kind ?? null,
155
- trace_id: traceId,
156
- span_id: spanId,
157
- parent_span_id: parentId,
158
- start_time_unix_nano: startTimeUnixNano,
159
- end_time_unix_nano: endTimeUnixNano,
160
- status_code: span.status?.code ?? null,
161
- status_message: span.status?.message ?? null,
162
- instrumentation_scope_name: instrumentationScopeName(span),
163
- attributes,
164
- events: (span.events ?? []).map((event) => ({
165
- name: event.name,
166
- attributes: toJsonRecord(event.attributes, options.deniedKeys),
167
- })),
168
- resource: toFrontendSourceResource(span.resource, options.deniedKeys),
169
- context: contextRecord(context, {
170
- trace_id: traceId,
171
- request_id: requestId,
172
- request_span_id: requestSpanId,
173
- action_id: actionId,
174
- interaction_id: interactionId,
175
- }),
176
- },
94
+ context: contextRecord(context),
95
+ context_privacy_summaries: [context.privacy_summary],
177
96
  };
97
+ return JSON.parse(spanToObservationSourceEventJson(JSON.stringify(corePayload), JSON.stringify(coreOptions)));
178
98
  }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { InternalMetricsSnapshot } from "./internal/metrics.js";
2
- import { createCluebaseBrowserSdk } from "./provider/sdk.js";
3
- import type { CluebaseBrowserSdk, CluebaseConsentState, CluebaseGroupTraits, CluebaseGroupType, CluebaseInitOptions, CluebaseSubjectTraits, CluebaseTrackMetrics, CluebaseTrackProperties } from "./provider/types.js";
2
+ import type { CluebaseConsentState, CluebaseGroupTraits, CluebaseGroupType, CluebaseInitOptions, CluebaseLinkInput, CluebaseSubjectTraits, CluebaseTrackMetrics, CluebaseTrackProperties } from "./provider/types.js";
4
3
  declare const cluebase: {
5
4
  init(options: CluebaseInitOptions): void;
6
5
  identify(userId: string, traits?: CluebaseSubjectTraits): void;
7
6
  group(groupType: CluebaseGroupType, groupKey: string, traits?: CluebaseGroupTraits): void;
7
+ link(input: CluebaseLinkInput): void;
8
+ linkExternalAccount(namespace: string, externalId: string): void;
8
9
  reset(): void;
9
10
  track(eventName: string, properties?: CluebaseTrackProperties, metrics?: CluebaseTrackMetrics): void;
10
11
  setConsent(consent: CluebaseConsentState): void;
@@ -12,10 +13,10 @@ declare const cluebase: {
12
13
  close(): Promise<void>;
13
14
  getInternalMetrics(): InternalMetricsSnapshot;
14
15
  getDiagnostics(): import("./exporter/diagnostics.js").DiagnosticsSnapshot;
16
+ withActionContext<T>(callback: () => T): T;
15
17
  };
16
18
  export { EXPORT_RESULT_FAILED, EXPORT_RESULT_SUCCESS, ObservationSourceEventExporter, type ObservationSourceEventExporterOptions, } from "./exporter/observation-source-event-exporter.js";
17
19
  export { attachNextRouterIntegration } from "./integrations/next-router.js";
18
20
  export { attachReactRouterIntegration } from "./integrations/react-router.js";
19
- export type { CluebaseBrowserSdk, CluebaseConsentState, CluebaseGroupTraits, CluebaseGroupType, CluebaseInitOptions, CluebaseSubjectTraits, CluebaseTrackMetrics, CluebaseTrackProperties, InternalMetricsSnapshot, };
20
- export { createCluebaseBrowserSdk };
21
+ export type { CluebaseConsentState, CluebaseGroupTraits, CluebaseGroupType, CluebaseInitOptions, CluebaseLinkInput, CluebaseSubjectTraits, CluebaseTrackMetrics, CluebaseTrackProperties, InternalMetricsSnapshot, };
21
22
  export default cluebase;
package/dist/index.js CHANGED
@@ -17,8 +17,7 @@ const reportPublicSdkError = (operation, error) => {
17
17
  });
18
18
  }
19
19
  }
20
- catch {
21
- }
20
+ catch { }
22
21
  };
23
22
  const safeCall = (operation, callback) => {
24
23
  try {
@@ -44,7 +43,9 @@ const REMOVED_PRIVACY_OPTIONS = {
44
43
  "and select those fields in the Cluebase privacy screen instead. Until then " +
45
44
  "every value stays masked.",
46
45
  };
47
- const REQUIRED_INIT_OPTIONS = ["projectKey"];
46
+ const REQUIRED_INIT_OPTIONS = [
47
+ "projectKey",
48
+ ];
48
49
  const validateInitOptions = (options) => {
49
50
  const privacy = options.privacy ?? {};
50
51
  for (const [name, message] of Object.entries(REMOVED_PRIVACY_OPTIONS)) {
@@ -64,8 +65,7 @@ const validateInitOptions = (options) => {
64
65
  "projectKey=NEXT_PUBLIC_CLUEBASE_PROJECT_KEY. The endpoint is optional " +
65
66
  "and defaults to the hosted Cluebase API.");
66
67
  }
67
- if (options.endpoint !== undefined &&
68
- typeof options.endpoint !== "string") {
68
+ if (options.endpoint !== undefined && typeof options.endpoint !== "string") {
69
69
  throw new Error("cluebase.init: 'endpoint' must be a string when provided. Omit it to " +
70
70
  "use the hosted Cluebase endpoint.");
71
71
  }
@@ -97,6 +97,16 @@ const cluebase = {
97
97
  internalSdk.setOrganization(groupKey, traits);
98
98
  });
99
99
  },
100
+ link(input) {
101
+ safeCall("link", () => {
102
+ internalSdk.link(input);
103
+ });
104
+ },
105
+ linkExternalAccount(namespace, externalId) {
106
+ safeCall("linkExternalAccount", () => {
107
+ internalSdk.linkExternalAccount(namespace, externalId);
108
+ });
109
+ },
100
110
  reset() {
101
111
  safeCall("reset", () => internalSdk.reset());
102
112
  },
@@ -122,9 +132,11 @@ const cluebase = {
122
132
  getDiagnostics() {
123
133
  return internalSdk.getDiagnostics();
124
134
  },
135
+ withActionContext(callback) {
136
+ return internalSdk.withActionContext(callback);
137
+ },
125
138
  };
126
139
  export { EXPORT_RESULT_FAILED, EXPORT_RESULT_SUCCESS, ObservationSourceEventExporter, } from "./exporter/observation-source-event-exporter.js";
127
140
  export { attachNextRouterIntegration } from "./integrations/next-router.js";
128
141
  export { attachReactRouterIntegration } from "./integrations/react-router.js";
129
- export { createCluebaseBrowserSdk };
130
142
  export default cluebase;
@@ -1,6 +1,6 @@
1
1
  import { buildLocalTextDescriptor, buildLocalValueDescriptor, } from "../exporter/local-minimization.js";
2
- import { extractFieldName, extractFieldType, getElementAriaLabel, getElementClassList, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, isEditableElement, resolveClickDisplayTarget, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, shouldCaptureClickTarget, } from "./dom.js";
3
2
  import { buildPrimaryActionKey } from "./common.js";
3
+ import { extractFieldName, extractFieldType, getElementAriaLabel, getElementClassList, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, isEditableElement, resolveClickDisplayTarget, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, shouldCaptureClickTarget, } from "./dom.js";
4
4
  import { getCurrentPageSnapshot } from "./location.js";
5
5
  import { buildScreenActionNamingEvidence } from "./naming-evidence.js";
6
6
  const ariaToggleRoles = new Set([
@@ -37,14 +37,12 @@ const ensureCssEscape = () => {
37
37
  };
38
38
  const emitElementClicked = (context, target, occurredAtMs, event) => {
39
39
  const displayTarget = resolveClickDisplayTarget(target);
40
- if (context.shouldIgnoreElement?.(displayTarget)) {
41
- return;
42
- }
43
40
  if (!shouldCaptureClickTarget(displayTarget)) {
44
41
  return;
45
42
  }
46
43
  const stableKey = resolveStableKeyDescriptor(displayTarget);
47
44
  const interactionScope = resolveInteractionScopeDescriptor(displayTarget);
45
+ const pageSnapshot = getCurrentPageSnapshot();
48
46
  const interactionId = context.startInteraction?.({
49
47
  nowMs: occurredAtMs,
50
48
  interactionInitiator: "click",
@@ -59,6 +57,7 @@ const emitElementClicked = (context, target, occurredAtMs, event) => {
59
57
  occurredAtMs,
60
58
  payload: {
61
59
  viewId: context.getViewId(),
60
+ screenKey: pageSnapshot.screenKey,
62
61
  ...(interactionId
63
62
  ? {
64
63
  interactionId,
@@ -68,7 +67,7 @@ const emitElementClicked = (context, target, occurredAtMs, event) => {
68
67
  : {}),
69
68
  elementTag: displayTarget.tagName.toLowerCase(),
70
69
  elementRole: getElementRole(displayTarget),
71
- primaryActionKey: buildPrimaryActionKey(getCurrentPageSnapshot().screenKey, stableKey.stableKey),
70
+ primaryActionKey: buildPrimaryActionKey(pageSnapshot.screenKey, stableKey.stableKey),
72
71
  stableKey: stableKey.stableKey,
73
72
  stableKeyQuality: stableKey.stableKeyQuality,
74
73
  formKey: stableKey.formKey,
@@ -93,9 +92,10 @@ const emitElementClicked = (context, target, occurredAtMs, event) => {
93
92
  },
94
93
  });
95
94
  context.bindEventContext?.(event);
96
- maybeEmitAriaToggleChanged(context, displayTarget);
95
+ context.requestFlush?.();
96
+ maybeEmitAriaToggleChanged(context, displayTarget, pageSnapshot.screenKey);
97
97
  };
98
- const maybeEmitAriaToggleChanged = (context, element) => {
98
+ const maybeEmitAriaToggleChanged = (context, element, screenKey) => {
99
99
  if (isNativeToggleInput(element)) {
100
100
  return;
101
101
  }
@@ -105,7 +105,7 @@ const maybeEmitAriaToggleChanged = (context, element) => {
105
105
  }
106
106
  window.setTimeout(() => {
107
107
  try {
108
- if (!element.isConnected || context.shouldIgnoreElement?.(element)) {
108
+ if (!element.isConnected) {
109
109
  return;
110
110
  }
111
111
  const stableKey = resolveStableKeyDescriptor(element);
@@ -116,6 +116,7 @@ const maybeEmitAriaToggleChanged = (context, element) => {
116
116
  occurredAtMs: Date.now(),
117
117
  payload: {
118
118
  viewId: context.getViewId(),
119
+ screenKey,
119
120
  elementTag: element.tagName.toLowerCase(),
120
121
  elementRole: role,
121
122
  stableKey: stableKey.stableKey,
@@ -158,11 +158,11 @@ const getAssociatedLabel = (element) => {
158
158
  if (label) {
159
159
  return label.replace(/\s+/g, " ").slice(0, 120);
160
160
  }
161
- }
162
- const closestLabel = element.closest("label");
163
- const closestLabelText = closestLabel?.textContent?.trim();
164
- if (closestLabelText) {
165
- return closestLabelText.replace(/\s+/g, " ").slice(0, 120);
161
+ const closestLabel = element.closest("label");
162
+ const closestLabelText = closestLabel?.textContent?.trim();
163
+ if (closestLabelText) {
164
+ return closestLabelText.replace(/\s+/g, " ").slice(0, 120);
165
+ }
166
166
  }
167
167
  return null;
168
168
  };
@@ -173,6 +173,10 @@ const readElementContentLabel = (element) => {
173
173
  (element instanceof HTMLElement && element.isContentEditable)) {
174
174
  return null;
175
175
  }
176
+ const role = getElementRole(element);
177
+ if (!role || !CLICK_ACTION_ROLES.has(role)) {
178
+ return null;
179
+ }
176
180
  const ownText = Array.from(element.childNodes)
177
181
  .filter((node) => node.nodeType === Node.TEXT_NODE)
178
182
  .map((node) => node.textContent?.trim() ?? "")
@@ -182,26 +186,11 @@ const readElementContentLabel = (element) => {
182
186
  if (ownText) {
183
187
  return ownText.slice(0, 120);
184
188
  }
185
- const isInteractiveDisplayTarget = element instanceof HTMLButtonElement ||
186
- element instanceof HTMLAnchorElement ||
187
- element.getAttribute("role") === "button" ||
188
- element.getAttribute("role") === "link";
189
189
  const descendantCandidates = Array.from(element.querySelectorAll("label, p, span, strong, em, small, button, a, [role='button'], [role='link']"))
190
190
  .map((candidate) => candidate.textContent?.trim() ?? "")
191
191
  .map((value) => value.replace(/\s+/g, " ").trim())
192
192
  .filter((value) => value.length > 0 && value.length <= 80);
193
- if (isInteractiveDisplayTarget && descendantCandidates.length > 0) {
194
- return descendantCandidates[0] ?? null;
195
- }
196
193
  if (descendantCandidates.length > 0) {
197
- descendantCandidates.sort((left, right) => {
198
- const leftWordCount = left.split(/\s+/).length;
199
- const rightWordCount = right.split(/\s+/).length;
200
- if (leftWordCount !== rightWordCount) {
201
- return leftWordCount - rightWordCount;
202
- }
203
- return left.length - right.length;
204
- });
205
194
  return descendantCandidates[0] ?? null;
206
195
  }
207
196
  return getTextPreview(element);
@@ -246,7 +235,10 @@ export const getAccessibleName = (element) => {
246
235
  }
247
236
  const title = getElementTitle(element);
248
237
  if (title?.trim()) {
249
- return { name: title.trim().replace(/\s+/g, " ").slice(0, 120), source: "title" };
238
+ return {
239
+ name: title.trim().replace(/\s+/g, " ").slice(0, 120),
240
+ source: "title",
241
+ };
250
242
  }
251
243
  return null;
252
244
  };
@@ -31,9 +31,6 @@ export const startDragCapture = (context) => {
31
31
  if (!(target instanceof Element)) {
32
32
  return;
33
33
  }
34
- if (context.shouldIgnoreElement?.(target)) {
35
- return;
36
- }
37
34
  try {
38
35
  const stableKey = resolveStableKeyDescriptor(target);
39
36
  const interactionScope = resolveInteractionScopeDescriptor(target);
@@ -54,7 +54,9 @@ const readStringAttribute = (logRecord, key) => {
54
54
  };
55
55
  const readNumberAttribute = (logRecord, key) => {
56
56
  const value = logRecord.attributes?.[key];
57
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
57
+ return typeof value === "number" && Number.isFinite(value)
58
+ ? value
59
+ : undefined;
58
60
  };
59
61
  export const startErrorCapture = (context) => {
60
62
  if (typeof window === "undefined") {
@@ -1,9 +1,9 @@
1
1
  import { buildLocalTextDescriptor } from "../exporter/local-minimization.js";
2
- import { extractFieldName, extractFieldType, getElementAriaLabel, getElementClassList, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, isEditableElement, resolveClickDisplayTarget, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, } from "./dom.js";
2
+ import { fingerprintText } from "../exporter/text-fingerprint.js";
3
3
  import { buildPrimaryActionKey } from "./common.js";
4
+ import { extractFieldName, extractFieldType, getElementAriaLabel, getElementClassList, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, isEditableElement, resolveClickDisplayTarget, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, } from "./dom.js";
4
5
  import { getCurrentPageSnapshot } from "./location.js";
5
6
  import { buildScreenActionNamingEvidence } from "./naming-evidence.js";
6
- import { fingerprintText } from "../exporter/text-fingerprint.js";
7
7
  const VALIDATION_REASON_KEYS = [
8
8
  "valueMissing",
9
9
  "typeMismatch",
@@ -57,9 +57,6 @@ export const startFormCapture = (context) => {
57
57
  if (!(target instanceof HTMLFormElement)) {
58
58
  return;
59
59
  }
60
- if (context.shouldIgnoreElement?.(target)) {
61
- return;
62
- }
63
60
  try {
64
61
  const formKey = target.id || target.getAttribute("name") || null;
65
62
  const interactionScope = resolveInteractionScopeDescriptor(target);
@@ -75,14 +72,16 @@ export const startFormCapture = (context) => {
75
72
  ? "validation_blocked"
76
73
  : "unknown";
77
74
  const resultSource = validationEvidence.invalidFieldCount > 0 ? "browser_validation" : null;
75
+ const pageSnapshot = getCurrentPageSnapshot();
78
76
  context.onEvent({
79
77
  type: "form_submitted",
80
78
  occurredAtMs: Date.now(),
81
79
  payload: {
82
80
  viewId: context.getViewId(),
81
+ screenKey: pageSnapshot.screenKey,
83
82
  formId: target.id || null,
84
83
  formKey,
85
- primaryActionKey: buildPrimaryActionKey(getCurrentPageSnapshot().screenKey, formKey ?? submitterStableKey?.stableKey ?? null),
84
+ primaryActionKey: buildPrimaryActionKey(pageSnapshot.screenKey, formKey ?? submitterStableKey?.stableKey ?? null),
86
85
  formName: target.getAttribute("name"),
87
86
  interactionScopeKind: interactionScope?.interactionScopeKind ?? null,
88
87
  interactionScopeKey: interactionScope?.interactionScopeKey ?? null,
@@ -1,6 +1,6 @@
1
1
  import { buildLocalTextDescriptor, buildLocalValueDescriptor, } from "../exporter/local-minimization.js";
2
- import { extractFieldName, extractFieldType, getElementAriaLabel, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, getElementValue, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, } from "./dom.js";
3
2
  import { buildPrimaryActionKey } from "./common.js";
3
+ import { extractFieldName, extractFieldType, getElementAriaLabel, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, getElementValue, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, } from "./dom.js";
4
4
  import { getCurrentPageSnapshot } from "./location.js";
5
5
  import { buildScreenActionNamingEvidence } from "./naming-evidence.js";
6
6
  const buildFileDescriptors = (input) => {
@@ -28,23 +28,22 @@ export const startInputCapture = (context) => {
28
28
  if (context.getConsent() === "denied") {
29
29
  return;
30
30
  }
31
- if (context.shouldIgnoreElement?.(target)) {
32
- return;
33
- }
34
31
  if (target instanceof HTMLInputElement && target.type === "password") {
35
32
  return;
36
33
  }
37
34
  try {
38
35
  const stableKey = resolveStableKeyDescriptor(target);
39
36
  const interactionScope = resolveInteractionScopeDescriptor(target);
37
+ const pageSnapshot = getCurrentPageSnapshot();
40
38
  const commonPayload = {
41
39
  viewId: context.getViewId(),
40
+ screenKey: pageSnapshot.screenKey,
42
41
  formId: stableKey.formKey,
43
42
  formKey: stableKey.formKey,
44
43
  fieldType: extractFieldType(target),
45
44
  elementTag: target.tagName.toLowerCase(),
46
45
  elementRole: getElementRole(target),
47
- primaryActionKey: buildPrimaryActionKey(getCurrentPageSnapshot().screenKey, stableKey.stableKey),
46
+ primaryActionKey: buildPrimaryActionKey(pageSnapshot.screenKey, stableKey.stableKey),
48
47
  stableKey: stableKey.stableKey,
49
48
  stableKeyQuality: stableKey.stableKeyQuality,
50
49
  interactionScopeKind: interactionScope?.interactionScopeKind ?? null,
@@ -138,9 +137,6 @@ export const startInputCapture = (context) => {
138
137
  if (context.getConsent() === "denied") {
139
138
  return;
140
139
  }
141
- if (context.shouldIgnoreElement?.(target)) {
142
- return;
143
- }
144
140
  dirtyControls.add(target);
145
141
  };
146
142
  const changeHandler = (event) => {
@@ -53,7 +53,9 @@ export const getPageSnapshotFromUrl = (href) => {
53
53
  }
54
54
  let path = null;
55
55
  try {
56
- path = href ? new URL(href, globalThis.location?.origin).pathname || "/" : null;
56
+ path = href
57
+ ? new URL(href, globalThis.location?.origin).pathname || "/"
58
+ : null;
57
59
  }
58
60
  catch {
59
61
  path = null;
@@ -1,9 +1,9 @@
1
1
  import { buildLocalQuerySummary, buildLocalStructuredSummary, } from "../exporter/local-minimization.js";
2
2
  import { buildParameterSnapshotBundle, } from "../exporter/parameter-snapshot.js";
3
- import { allowedValuePathsFor } from "../provider/privacy-policy-store.js";
4
3
  import { toPathTemplate } from "../exporter/path-template.js";
5
4
  import { maybeParseJson, sanitizeHeaders } from "../exporter/sanitize.js";
6
5
  import { buildSchemaEvidence, deriveOutcomeClass, extractErrorCodeCandidate, extractGraphqlEvidence, } from "../exporter/schema-evidence.js";
6
+ import { allowedValuePathsFor } from "../provider/privacy-policy-store.js";
7
7
  import { currentPageUrl, resolveUrlAgainstCurrentLocation, stripQueryFromUrl, } from "./common.js";
8
8
  const MAX_CAPTURED_RESPONSE_BODY_BYTES = 16 * 1024;
9
9
  export const headersToRecord = (headers) => {
@@ -1,10 +1,10 @@
1
1
  import { SpanStatusCode } from "@opentelemetry/api";
2
2
  import { buildLocalStructuredSummary } from "../exporter/local-minimization.js";
3
- import { cluebaseAuthoredMetricAttribute, cluebaseAuthoredPropertyAttribute, } from "./cluebase-authored-attributes.js";
3
+ import { buildParameterSnapshotBundle } from "../exporter/parameter-snapshot.js";
4
4
  import { sanitizeHeaders } from "../exporter/sanitize.js";
5
5
  import { FRONTEND_EVENT_CATEGORY, FRONTEND_EVENT_NAME, FRONTEND_EVENT_STATUS, } from "../provider/contracts.js";
6
- import { buildParameterSnapshotBundle } from "../exporter/parameter-snapshot.js";
7
6
  import { allowedValuePathsFor } from "../provider/privacy-policy-store.js";
7
+ import { cluebaseAuthoredMetricAttribute, cluebaseAuthoredPropertyAttribute, } from "./cluebase-authored-attributes.js";
8
8
  import { buildResponseShapeEvidence, } from "./network-request.js";
9
9
  const PRIVACY_POLICY_VERSION = 1;
10
10
  const setOptionalString = (span, key, value) => {
@@ -9,7 +9,7 @@ export declare const createCluebaseNetworkPropagator: () => TextMapPropagator;
9
9
  export declare const startNetworkCapture: (context: CaptureModuleContext, options?: {
10
10
  tracerProvider?: TracerProvider;
11
11
  deniedKeys?: readonly string[];
12
- ignoreUrlPrefixes?: string[];
12
+ ignoreUrlPrefixes?: readonly string[];
13
13
  tracePropagationOrigins?: string[];
14
14
  createRequestTraceContext?: (input?: RequestTraceContextInput) => RequestTraceContext;
15
15
  }) => CaptureStopHandle;
@@ -324,9 +324,12 @@ const failOpenRequestLifecycles = (failureType) => {
324
324
  }
325
325
  pendingRequestLifecycles.clear();
326
326
  };
327
- const trackFetchSpanEndWait = () => {
327
+ const trackFetchSpanEndWait = (capture) => {
328
328
  const wait = new Promise((resolve) => {
329
- setTimeout(resolve, FETCH_SPAN_END_SETTLE_MS);
329
+ setTimeout(() => {
330
+ capture.context.requestFlush?.();
331
+ resolve();
332
+ }, FETCH_SPAN_END_SETTLE_MS);
330
333
  });
331
334
  pendingFetchSpanEndWaits.add(wait);
332
335
  void wait.finally(() => pendingFetchSpanEndWaits.delete(wait));
@@ -400,7 +403,7 @@ const configureFetchInstrumentation = (capture) => {
400
403
  observationContext: capture.context.getObservationContext?.() ?? null,
401
404
  deniedKeys: capture.deniedKeys,
402
405
  });
403
- trackFetchSpanEndWait();
406
+ trackFetchSpanEndWait(capture);
404
407
  markRequestLifecycleTerminal(meta);
405
408
  capture.context.requestFlush?.();
406
409
  },
@@ -448,6 +451,7 @@ const configureXhrInstrumentation = (capture) => {
448
451
  observationContext: capture.context.getObservationContext?.() ?? null,
449
452
  deniedKeys: capture.deniedKeys,
450
453
  });
454
+ trackFetchSpanEndWait(capture);
451
455
  markRequestLifecycleTerminal(meta);
452
456
  capture.context.requestFlush?.();
453
457
  },
@@ -493,6 +497,23 @@ export const startNetworkCapture = (context, options = {}) => {
493
497
  flush: async () => {
494
498
  await waitForFetchSpanEnd();
495
499
  },
500
+ refreshIgnoreUrlPrefixes: () => {
501
+ if (activeCapture !== capture)
502
+ return;
503
+ const ignoreUrls = urlPrefixPatterns(capture.ignoreUrlPrefixes);
504
+ if (fetchInstrumentation) {
505
+ fetchInstrumentation.setConfig({
506
+ ...fetchInstrumentation.getConfig(),
507
+ ignoreUrls,
508
+ });
509
+ }
510
+ if (xhrInstrumentation) {
511
+ xhrInstrumentation.setConfig({
512
+ ...xhrInstrumentation.getConfig(),
513
+ ignoreUrls,
514
+ });
515
+ }
516
+ },
496
517
  stop: () => {
497
518
  if (activeCapture !== capture)
498
519
  return;