@genn-inc/cluebase-frontend-sdk 0.0.3 → 0.0.6
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/{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/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 +2 -1
- package/dist/exporter/batch.d.ts +3 -1
- package/dist/exporter/batch.js +6 -10
- package/dist/exporter/browser-outbox-records.d.ts +1 -1
- package/dist/exporter/browser-outbox-records.js +15 -6
- package/dist/exporter/browser-outbox.d.ts +5 -2
- package/dist/exporter/browser-outbox.js +48 -11
- package/dist/exporter/client.d.ts +2 -2
- package/dist/exporter/client.js +15 -30
- package/dist/exporter/delivery-response-contract.js +67 -17
- package/dist/exporter/delivery-response.d.ts +3 -1
- package/dist/exporter/delivery-response.js +31 -20
- package/dist/exporter/diagnostics.d.ts +1 -1
- package/dist/exporter/diagnostics.js +4 -1
- package/dist/exporter/event-id.js +1 -3
- package/dist/exporter/frontend-resource.js +5 -4
- package/dist/exporter/observation-source-event-exporter.d.ts +1 -0
- package/dist/exporter/observation-source-event-exporter.js +5 -2
- 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-wasm.d.ts +5 -0
- package/dist/exporter/sdk-core-wasm.js +18 -0
- package/dist/exporter/source-event.d.ts +1 -0
- package/dist/exporter/span-to-source-signal.d.ts +1 -1
- package/dist/exporter/span-to-source-signal.js +122 -82
- package/dist/index.d.ts +5 -4
- package/dist/index.js +16 -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.js +7 -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 +0 -1
- package/dist/provider/constants.d.ts +1 -1
- package/dist/provider/constants.js +0 -1
- package/dist/provider/sdk-event-pipeline.d.ts +2 -1
- package/dist/provider/sdk-event-pipeline.js +6 -3
- package/dist/provider/sdk-input-normalization.js +2 -2
- package/dist/provider/sdk-otel-pipeline.js +5 -4
- package/dist/provider/sdk-runtime-surfaces.d.ts +15 -12
- package/dist/provider/sdk-runtime-surfaces.js +8 -7
- package/dist/provider/sdk.js +67 -12
- package/dist/provider/types.d.ts +6 -5
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +41 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +267 -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 +12 -0
- package/dist/wasm/pkg-web/package.json +15 -0
- package/package.json +62 -53
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import init, { spanToSdkSignalJson as wasmSpanToSdkSignalJson, } from "../wasm/pkg-web/cluebase_sdk_core_wasm.js";
|
|
2
|
+
let readyPromise = null;
|
|
3
|
+
let ready = false;
|
|
4
|
+
export const initSdkCoreWasm = (input) => {
|
|
5
|
+
if (!readyPromise) {
|
|
6
|
+
readyPromise = init(input).then(() => {
|
|
7
|
+
ready = true;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
return readyPromise;
|
|
11
|
+
};
|
|
12
|
+
export const isSdkCoreWasmReady = () => ready;
|
|
13
|
+
export const spanToSdkSignalJson = (spanJson, optionsJson) => {
|
|
14
|
+
if (!ready) {
|
|
15
|
+
throw new Error("cluebase sdk core wasm is not initialized; call initSdkCoreWasm() first");
|
|
16
|
+
}
|
|
17
|
+
return wasmSpanToSdkSignalJson(spanJson, optionsJson);
|
|
18
|
+
};
|
|
@@ -61,6 +61,7 @@ export type BrowserSdkSignal = {
|
|
|
61
61
|
attributes: Record<string, CanonicalJsonValue>;
|
|
62
62
|
}>;
|
|
63
63
|
resource: Record<string, CanonicalJsonValue>;
|
|
64
|
+
raw_resource: Record<string, CanonicalJsonValue>;
|
|
64
65
|
context: BrowserSdkSignalContext;
|
|
65
66
|
};
|
|
66
67
|
export type BrowserSourceSignalEvent = {
|
|
@@ -5,4 +5,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,69 @@
|
|
|
1
1
|
import { CLUEBASE_CORRELATION_ATTRIBUTE_KEYS } from "../_generated/correlation-attribute-keys.js";
|
|
2
|
-
import {
|
|
3
|
-
import { EVENT_SOURCE, EVENT_VERSION, SOURCE_SCHEMA_VERSION, } from "../provider/constants.js";
|
|
4
|
-
import { generateEventId
|
|
2
|
+
import { nanosToIso, parentSpanId } from "../_generated/otel-span-conversion.js";
|
|
3
|
+
import { ALWAYS_DENIED_KEYS, EVENT_SOURCE, EVENT_VERSION, SOURCE_SCHEMA_VERSION, } from "../provider/constants.js";
|
|
4
|
+
import { generateEventId } from "./event-id.js";
|
|
5
5
|
import { isDeniedKey } from "./sanitize.js";
|
|
6
|
-
import {
|
|
6
|
+
import { initSdkCoreWasm, isSdkCoreWasmReady, spanToSdkSignalJson, } from "./sdk-core-wasm.js";
|
|
7
7
|
import { SDK_SOURCE_SIGNAL_EVENT_NAME, SDK_SOURCE_SIGNAL_EVENT_TYPE, } from "./source-event.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
function withCoreMasking(summary, masking) {
|
|
9
|
+
const maskedValueCount = masking?.masked_value_count ?? 0;
|
|
10
|
+
const maskedFields = masking?.masked_fields ?? [];
|
|
11
|
+
if (!summary) {
|
|
12
|
+
if (maskedValueCount === 0 && maskedFields.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
return {
|
|
15
|
+
level: "masked",
|
|
16
|
+
masked_value_count: maskedValueCount,
|
|
17
|
+
dropped_field_count: 0,
|
|
18
|
+
masked_fields: [...maskedFields].sort(),
|
|
19
|
+
rule_set_version: null,
|
|
20
|
+
};
|
|
16
21
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
return {
|
|
23
|
+
...summary,
|
|
24
|
+
masked_value_count: summary.masked_value_count + maskedValueCount,
|
|
25
|
+
masked_fields: [
|
|
26
|
+
...new Set([...summary.masked_fields, ...maskedFields]),
|
|
27
|
+
].sort(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function rawAttrString(attributes, key) {
|
|
31
|
+
const value = attributes?.[key];
|
|
32
|
+
return typeof value === "string" && value.trim().length > 0
|
|
33
|
+
? value.trim()
|
|
34
|
+
: null;
|
|
29
35
|
}
|
|
30
|
-
function
|
|
31
|
-
const
|
|
32
|
-
for (const [key,
|
|
36
|
+
function preFilterDeniedAttributes(attributes, deniedKeys) {
|
|
37
|
+
const filtered = {};
|
|
38
|
+
for (const [key, value] of Object.entries(attributes ?? {})) {
|
|
33
39
|
if (!CLUEBASE_CORRELATION_ATTRIBUTE_KEYS.has(key) &&
|
|
34
40
|
isDeniedKey(key, deniedKeys)) {
|
|
35
41
|
continue;
|
|
36
42
|
}
|
|
37
|
-
|
|
43
|
+
filtered[key] = value;
|
|
44
|
+
}
|
|
45
|
+
return filtered;
|
|
46
|
+
}
|
|
47
|
+
function seedFallbackAttribute(attributesForCore, attributeKey, candidateKeys, fallback) {
|
|
48
|
+
const alreadyPresent = candidateKeys.some((candidateKey) => rawAttrString(attributesForCore, candidateKey) !== null);
|
|
49
|
+
if (alreadyPresent || !fallback)
|
|
50
|
+
return;
|
|
51
|
+
attributesForCore[attributeKey] = fallback;
|
|
52
|
+
}
|
|
53
|
+
function resourceAttributesInput(resource) {
|
|
54
|
+
const input = resource;
|
|
55
|
+
try {
|
|
56
|
+
return { ...(input?.attributes ?? {}) };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {};
|
|
38
60
|
}
|
|
39
|
-
|
|
61
|
+
}
|
|
62
|
+
const FIXED_CORE_DENIED_KEYS = Object.freeze([...new Set(ALWAYS_DENIED_KEYS)].sort());
|
|
63
|
+
function coreDeniedKeys(deniedKeys) {
|
|
64
|
+
if (deniedKeys.length === 0)
|
|
65
|
+
return FIXED_CORE_DENIED_KEYS;
|
|
66
|
+
return [...new Set([...FIXED_CORE_DENIED_KEYS, ...deniedKeys])].sort();
|
|
40
67
|
}
|
|
41
68
|
function instrumentationScopeName(span) {
|
|
42
69
|
const instrumentation = span;
|
|
@@ -87,34 +114,49 @@ function contextRecord(context, overrides) {
|
|
|
87
114
|
context_traits: context.context_traits,
|
|
88
115
|
};
|
|
89
116
|
}
|
|
90
|
-
export function spanToSourceSignalEvent(span, options) {
|
|
117
|
+
export async function spanToSourceSignalEvent(span, options) {
|
|
118
|
+
if (!isSdkCoreWasmReady()) {
|
|
119
|
+
await initSdkCoreWasm();
|
|
120
|
+
}
|
|
91
121
|
const context = options.context;
|
|
92
|
-
const
|
|
122
|
+
const deniedKeys = options.deniedKeys ?? [];
|
|
93
123
|
const spanContext = span.spanContext();
|
|
94
|
-
const traceId = attrString(attributes, "cluebase.trace_id") ??
|
|
95
|
-
spanContext.traceId ??
|
|
96
|
-
generateTraceId();
|
|
97
124
|
const spanId = spanContext.spanId || null;
|
|
98
|
-
const parentId = parentSpanId(span, attributes);
|
|
99
|
-
const
|
|
100
|
-
|
|
125
|
+
const parentId = parentSpanId(span, span.attributes);
|
|
126
|
+
const traceId = rawAttrString(span.attributes, "cluebase.trace_id") ??
|
|
127
|
+
spanContext.traceId ??
|
|
128
|
+
undefined;
|
|
129
|
+
const attributesForCore = preFilterDeniedAttributes(span.attributes, deniedKeys);
|
|
130
|
+
seedFallbackAttribute(attributesForCore, "cluebase.request_span_id", ["cluebase.request_span_id"], spanId ?? context.request_span_id);
|
|
131
|
+
seedFallbackAttribute(attributesForCore, "cluebase.request_id", ["cluebase.request.id", "cluebase.request_id"], context.request_id);
|
|
132
|
+
seedFallbackAttribute(attributesForCore, "cluebase.action_id", ["cluebase.action.id", "cluebase.action_id"], context.action_id ?? undefined);
|
|
133
|
+
seedFallbackAttribute(attributesForCore, "cluebase.interaction_id", ["cluebase.interaction.id", "cluebase.interaction_id"], context.interaction_id);
|
|
134
|
+
const corePayload = {
|
|
135
|
+
name: span.name,
|
|
136
|
+
kind: typeof span.kind === "number" ? span.kind : undefined,
|
|
137
|
+
trace_id: traceId,
|
|
138
|
+
span_id: spanId ?? undefined,
|
|
139
|
+
parent_span_id: parentId ?? undefined,
|
|
140
|
+
start_time: span.startTime,
|
|
141
|
+
end_time: span.endTime,
|
|
142
|
+
status: span.status
|
|
143
|
+
? { code: span.status.code ?? undefined, message: span.status.message }
|
|
144
|
+
: undefined,
|
|
145
|
+
attributes: attributesForCore,
|
|
146
|
+
events: (span.events ?? []).map((event) => ({
|
|
147
|
+
name: event.name,
|
|
148
|
+
attributes: preFilterDeniedAttributes(event.attributes, deniedKeys),
|
|
149
|
+
})),
|
|
150
|
+
resource_attributes: resourceAttributesInput(span.resource),
|
|
151
|
+
instrumentation_scope_name: instrumentationScopeName(span) ?? undefined,
|
|
152
|
+
};
|
|
153
|
+
const coreOptions = {
|
|
154
|
+
denied_keys: coreDeniedKeys(deniedKeys),
|
|
155
|
+
surface: "frontend",
|
|
156
|
+
};
|
|
157
|
+
const core = JSON.parse(spanToSdkSignalJson(JSON.stringify(corePayload), JSON.stringify(coreOptions)));
|
|
158
|
+
const resolvedTraceId = core.trace_id ?? traceId ?? "";
|
|
101
159
|
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
160
|
return {
|
|
119
161
|
event_id: eventId,
|
|
120
162
|
source_event_id: eventId,
|
|
@@ -127,7 +169,7 @@ export function spanToSourceSignalEvent(span, options) {
|
|
|
127
169
|
event_category: "context",
|
|
128
170
|
event_source: EVENT_SOURCE,
|
|
129
171
|
event_version: EVENT_VERSION,
|
|
130
|
-
occurred_at: nanosToIso(
|
|
172
|
+
occurred_at: nanosToIso(core.start_time_unix_nano),
|
|
131
173
|
ingested_at: new Date().toISOString(),
|
|
132
174
|
status: "observed",
|
|
133
175
|
sdk_collection_mode: context.sdk_collection_mode,
|
|
@@ -135,14 +177,14 @@ export function spanToSourceSignalEvent(span, options) {
|
|
|
135
177
|
privacy_level: "default_masked",
|
|
136
178
|
masking_state: "masked",
|
|
137
179
|
allowed_value_keys: [],
|
|
138
|
-
privacy_summary: context.privacy_summary,
|
|
139
|
-
trace_id:
|
|
140
|
-
span_id:
|
|
141
|
-
parent_span_id:
|
|
142
|
-
request_id:
|
|
143
|
-
request_span_id:
|
|
144
|
-
action_id:
|
|
145
|
-
interaction_id:
|
|
180
|
+
privacy_summary: withCoreMasking(context.privacy_summary, core.privacy_masking),
|
|
181
|
+
trace_id: resolvedTraceId,
|
|
182
|
+
span_id: core.span_id,
|
|
183
|
+
parent_span_id: core.parent_span_id,
|
|
184
|
+
request_id: core.request_id,
|
|
185
|
+
request_span_id: core.request_span_id,
|
|
186
|
+
action_id: core.action_id,
|
|
187
|
+
interaction_id: core.interaction_id,
|
|
146
188
|
frontend_release: context.frontend_release,
|
|
147
189
|
backend_release: null,
|
|
148
190
|
properties: {},
|
|
@@ -151,27 +193,25 @@ export function spanToSourceSignalEvent(span, options) {
|
|
|
151
193
|
surface: "frontend",
|
|
152
194
|
kind: "span",
|
|
153
195
|
name: span.name,
|
|
154
|
-
span_kind:
|
|
155
|
-
trace_id:
|
|
156
|
-
span_id:
|
|
157
|
-
parent_span_id:
|
|
158
|
-
start_time_unix_nano:
|
|
159
|
-
end_time_unix_nano:
|
|
160
|
-
status_code:
|
|
161
|
-
status_message:
|
|
162
|
-
instrumentation_scope_name:
|
|
163
|
-
attributes,
|
|
164
|
-
events:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
})),
|
|
168
|
-
resource: toFrontendSourceResource(span.resource, options.deniedKeys),
|
|
196
|
+
span_kind: core.span_kind,
|
|
197
|
+
trace_id: resolvedTraceId,
|
|
198
|
+
span_id: core.span_id,
|
|
199
|
+
parent_span_id: core.parent_span_id,
|
|
200
|
+
start_time_unix_nano: core.start_time_unix_nano,
|
|
201
|
+
end_time_unix_nano: core.end_time_unix_nano,
|
|
202
|
+
status_code: core.status_code ?? null,
|
|
203
|
+
status_message: core.status_message,
|
|
204
|
+
instrumentation_scope_name: core.instrumentation_scope_name,
|
|
205
|
+
attributes: core.attributes,
|
|
206
|
+
events: core.events,
|
|
207
|
+
resource: core.resource,
|
|
208
|
+
raw_resource: core.raw_resource,
|
|
169
209
|
context: contextRecord(context, {
|
|
170
|
-
trace_id:
|
|
171
|
-
request_id:
|
|
172
|
-
request_span_id:
|
|
173
|
-
action_id:
|
|
174
|
-
interaction_id:
|
|
210
|
+
trace_id: resolvedTraceId,
|
|
211
|
+
request_id: core.request_id,
|
|
212
|
+
request_span_id: core.request_span_id,
|
|
213
|
+
action_id: core.action_id,
|
|
214
|
+
interaction_id: core.interaction_id,
|
|
175
215
|
}),
|
|
176
216
|
},
|
|
177
217
|
};
|
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, 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
|
+
linkExternalAccount(namespace: string, externalId: string): void;
|
|
8
|
+
logout(): 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, 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,14 @@ const cluebase = {
|
|
|
97
97
|
internalSdk.setOrganization(groupKey, traits);
|
|
98
98
|
});
|
|
99
99
|
},
|
|
100
|
+
linkExternalAccount(namespace, externalId) {
|
|
101
|
+
safeCall("linkExternalAccount", () => {
|
|
102
|
+
internalSdk.linkExternalAccount(namespace, externalId);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
logout() {
|
|
106
|
+
safeCall("logout", () => internalSdk.logout());
|
|
107
|
+
},
|
|
100
108
|
reset() {
|
|
101
109
|
safeCall("reset", () => internalSdk.reset());
|
|
102
110
|
},
|
|
@@ -122,9 +130,11 @@ const cluebase = {
|
|
|
122
130
|
getDiagnostics() {
|
|
123
131
|
return internalSdk.getDiagnostics();
|
|
124
132
|
},
|
|
133
|
+
withActionContext(callback) {
|
|
134
|
+
return internalSdk.withActionContext(callback);
|
|
135
|
+
},
|
|
125
136
|
};
|
|
126
137
|
export { EXPORT_RESULT_FAILED, EXPORT_RESULT_SUCCESS, ObservationSourceEventExporter, } from "./exporter/observation-source-event-exporter.js";
|
|
127
138
|
export { attachNextRouterIntegration } from "./integrations/next-router.js";
|
|
128
139
|
export { attachReactRouterIntegration } from "./integrations/react-router.js";
|
|
129
|
-
export { createCluebaseBrowserSdk };
|
|
130
140
|
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) => {
|