@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.
- package/README.md +21 -0
- package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
- package/dist/_generated/correlation-attribute-keys.js +34 -0
- package/dist/_generated/diagnostic-codes.d.ts +4 -0
- package/dist/_generated/diagnostic-codes.js +48 -0
- package/dist/_generated/drop-reasons.d.ts +2 -0
- package/dist/_generated/drop-reasons.js +25 -0
- package/dist/_generated/{identity-pii-policy.d.ts → identity-pii-policy-text-contains.d.ts} +1 -1
- package/dist/_generated/{identity-pii-policy.js → identity-pii-policy-text-contains.js} +1 -1
- package/dist/_generated/identity-pii-policy-whole-value.d.ts +37 -0
- package/dist/_generated/identity-pii-policy-whole-value.js +106 -0
- package/dist/_generated/ingest-routes.d.ts +5 -0
- package/dist/_generated/ingest-routes.js +12 -0
- package/dist/_generated/otel-span-conversion.js +1 -9
- package/dist/context/browser-storage.js +1 -2
- package/dist/context/environment.js +5 -3
- package/dist/context/identity.js +5 -9
- package/dist/context/organization.d.ts +1 -1
- package/dist/context/organization.js +6 -8
- package/dist/context/session.js +6 -8
- package/dist/context/tab.js +8 -12
- package/dist/context/trace.js +4 -5
- package/dist/exporter/batch.d.ts +26 -27
- package/dist/exporter/batch.js +248 -294
- package/dist/exporter/browser-outbox-records.d.ts +8 -25
- package/dist/exporter/browser-outbox-records.js +21 -100
- package/dist/exporter/browser-outbox.d.ts +23 -25
- package/dist/exporter/browser-outbox.js +106 -181
- package/dist/exporter/client.d.ts +25 -35
- package/dist/exporter/client.js +107 -335
- package/dist/exporter/delivery-pipeline.d.ts +71 -0
- package/dist/exporter/delivery-pipeline.js +196 -0
- package/dist/exporter/delivery-response-contract.d.ts +1 -81
- package/dist/exporter/delivery-response-contract.js +0 -118
- package/dist/exporter/delivery-response.d.ts +9 -4
- package/dist/exporter/delivery-response.js +59 -122
- package/dist/exporter/diagnostics.d.ts +3 -2
- package/dist/exporter/diagnostics.js +5 -26
- package/dist/exporter/event-id.d.ts +1 -0
- package/dist/exporter/event-id.js +4 -3
- package/dist/exporter/frontend-resource.js +5 -4
- package/dist/exporter/observation-source-event-exporter.d.ts +2 -1
- package/dist/exporter/observation-source-event-exporter.js +7 -4
- package/dist/exporter/path-template.js +3 -1
- package/dist/exporter/raw-event-to-source-signal.js +4 -2
- package/dist/exporter/sanitize.d.ts +3 -2
- package/dist/exporter/sanitize.js +4 -3
- package/dist/exporter/sdk-core-delivery.d.ts +225 -0
- package/dist/exporter/sdk-core-delivery.js +97 -0
- package/dist/exporter/sdk-core-wasm.d.ts +11 -0
- package/dist/exporter/sdk-core-wasm.js +39 -0
- package/dist/exporter/source-event.d.ts +3 -0
- package/dist/exporter/source-event.js +17 -0
- package/dist/exporter/span-to-source-signal.d.ts +2 -2
- package/dist/exporter/span-to-source-signal.js +45 -125
- package/dist/index.d.ts +5 -4
- package/dist/index.js +18 -6
- package/dist/instrumentation/action.js +9 -8
- package/dist/instrumentation/dom.js +13 -21
- package/dist/instrumentation/drag.js +0 -3
- package/dist/instrumentation/error.js +3 -1
- package/dist/instrumentation/form.js +5 -6
- package/dist/instrumentation/input.js +4 -8
- package/dist/instrumentation/location.js +3 -1
- package/dist/instrumentation/network-request.js +1 -1
- package/dist/instrumentation/network-span-attributes.js +2 -2
- package/dist/instrumentation/network.d.ts +1 -1
- package/dist/instrumentation/network.js +24 -3
- package/dist/instrumentation/performance.js +1 -2
- package/dist/internal/internal-log.js +2 -1
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/config.js +16 -4
- package/dist/provider/constants.d.ts +1 -1
- package/dist/provider/constants.js +0 -2
- package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
- package/dist/provider/lifecycle-event-dedup.js +31 -0
- package/dist/provider/sdk-event-pipeline.d.ts +3 -4
- package/dist/provider/sdk-event-pipeline.js +5 -17
- package/dist/provider/sdk-input-normalization.js +2 -2
- package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
- package/dist/provider/sdk-otel-pipeline.js +7 -15
- package/dist/provider/sdk-runtime-surfaces.d.ts +17 -13
- package/dist/provider/sdk-runtime-surfaces.js +15 -13
- package/dist/provider/sdk.js +137 -48
- package/dist/provider/types.d.ts +18 -24
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +44 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +355 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +11 -0
- package/dist/wasm/pkg-web/package.json +15 -0
- package/package.json +62 -53
- package/dist/provider/usage-governor.d.ts +0 -7
- 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 {
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
18
|
-
return
|
|
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
|
|
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:
|
|
57
|
-
request_id:
|
|
58
|
-
request_span_id:
|
|
59
|
-
action_id:
|
|
60
|
-
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
context.
|
|
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
|
-
|
|
148
|
-
|
|
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 {
|
|
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 {
|
|
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 = [
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 {
|
|
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)
|
|
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 {
|
|
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(
|
|
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(
|
|
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
|
|
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 {
|
|
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(
|
|
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;
|