@genn-inc/cluebase-frontend-sdk 0.0.6 → 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/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/ingest-routes.d.ts +5 -0
- package/dist/_generated/ingest-routes.js +12 -0
- package/dist/context/trace.js +2 -4
- package/dist/exporter/batch.d.ts +23 -26
- package/dist/exporter/batch.js +247 -289
- package/dist/exporter/browser-outbox-records.d.ts +7 -24
- package/dist/exporter/browser-outbox-records.js +6 -94
- package/dist/exporter/browser-outbox.d.ts +23 -28
- package/dist/exporter/browser-outbox.js +99 -211
- package/dist/exporter/client.d.ts +24 -34
- package/dist/exporter/client.js +105 -318
- 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 -168
- package/dist/exporter/delivery-response.d.ts +7 -4
- package/dist/exporter/delivery-response.js +59 -133
- package/dist/exporter/diagnostics.d.ts +2 -1
- package/dist/exporter/diagnostics.js +1 -25
- package/dist/exporter/event-id.d.ts +1 -0
- package/dist/exporter/event-id.js +3 -0
- package/dist/exporter/observation-source-event-exporter.d.ts +1 -1
- package/dist/exporter/observation-source-event-exporter.js +2 -2
- 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 +7 -1
- package/dist/exporter/sdk-core-wasm.js +24 -3
- package/dist/exporter/source-event.d.ts +2 -0
- package/dist/exporter/source-event.js +17 -0
- package/dist/exporter/span-to-source-signal.d.ts +1 -1
- package/dist/exporter/span-to-source-signal.js +20 -140
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -3
- package/dist/instrumentation/network.d.ts +1 -1
- package/dist/instrumentation/network.js +17 -0
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/config.js +16 -3
- package/dist/provider/constants.js +0 -1
- 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 +1 -3
- package/dist/provider/sdk-event-pipeline.js +0 -15
- package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
- package/dist/provider/sdk-otel-pipeline.js +2 -11
- package/dist/provider/sdk-runtime-surfaces.d.ts +3 -2
- package/dist/provider/sdk-runtime-surfaces.js +7 -6
- package/dist/provider/sdk.js +88 -54
- package/dist/provider/types.d.ts +12 -19
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +12 -9
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +117 -29
- 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 +8 -9
- package/package.json +1 -1
- package/dist/provider/usage-governor.d.ts +0 -7
- package/dist/provider/usage-governor.js +0 -114
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CanonicalJsonValue, CluebasePrivacySummary, CluebaseSubjectProfile, SdkCollectionMode } from "../provider/types.js";
|
|
2
2
|
export declare const SDK_SOURCE_SIGNAL_EVENT_NAME = "sdk_source_signal_observed";
|
|
3
3
|
export declare const SDK_SOURCE_SIGNAL_EVENT_TYPE = "sdk_source_signal";
|
|
4
|
+
export declare const SOURCE_SCHEMA_VERSION = 1;
|
|
4
5
|
export type BrowserSdkSignalContext = {
|
|
5
6
|
producer_id: string | null;
|
|
6
7
|
anonymous_id: string | null;
|
|
@@ -99,4 +100,5 @@ export type BrowserSourceSignalEvent = {
|
|
|
99
100
|
sdk_signal: BrowserSdkSignal;
|
|
100
101
|
};
|
|
101
102
|
export type BrowserSourceEvent = BrowserSourceSignalEvent;
|
|
103
|
+
export declare const withSourceEvidence: (event: BrowserSourceEvent) => BrowserSourceEvent;
|
|
102
104
|
export declare function isBrowserSourceSignalEvent(event: unknown): event is BrowserSourceSignalEvent;
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
+
import { DEFAULT_FRONTEND_SOURCE_IDENTIFIER } from "../provider/constants.js";
|
|
1
2
|
export const SDK_SOURCE_SIGNAL_EVENT_NAME = "sdk_source_signal_observed";
|
|
2
3
|
export const SDK_SOURCE_SIGNAL_EVENT_TYPE = "sdk_source_signal";
|
|
4
|
+
export const SOURCE_SCHEMA_VERSION = 1;
|
|
5
|
+
export const withSourceEvidence = (event) => ({
|
|
6
|
+
...event,
|
|
7
|
+
source_event_id: event.source_event_id ?? event.event_id,
|
|
8
|
+
source_schema_version: String(SOURCE_SCHEMA_VERSION),
|
|
9
|
+
source_event_type: SDK_SOURCE_SIGNAL_EVENT_TYPE,
|
|
10
|
+
source_event_kind: SDK_SOURCE_SIGNAL_EVENT_TYPE,
|
|
11
|
+
event_name: SDK_SOURCE_SIGNAL_EVENT_NAME,
|
|
12
|
+
event_category: "context",
|
|
13
|
+
producer_id: DEFAULT_FRONTEND_SOURCE_IDENTIFIER,
|
|
14
|
+
service_name: event.service_name ?? DEFAULT_FRONTEND_SOURCE_IDENTIFIER,
|
|
15
|
+
privacy_state: event.privacy_state ?? "masked",
|
|
16
|
+
privacy_level: event.privacy_level ?? "default_masked",
|
|
17
|
+
masking_state: event.masking_state ?? "masked",
|
|
18
|
+
allowed_value_keys: event.allowed_value_keys ?? [],
|
|
19
|
+
});
|
|
3
20
|
const isReadableObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
21
|
export function isBrowserSourceSignalEvent(event) {
|
|
5
22
|
if (!isReadableObject(event)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
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[];
|
|
@@ -1,55 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { generateEventId } from "./event-id.js";
|
|
5
|
-
import { isDeniedKey } from "./sanitize.js";
|
|
6
|
-
import { initSdkCoreWasm, isSdkCoreWasmReady, spanToSdkSignalJson, } from "./sdk-core-wasm.js";
|
|
7
|
-
import { SDK_SOURCE_SIGNAL_EVENT_NAME, SDK_SOURCE_SIGNAL_EVENT_TYPE, } from "./source-event.js";
|
|
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
|
-
};
|
|
21
|
-
}
|
|
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;
|
|
35
|
-
}
|
|
36
|
-
function preFilterDeniedAttributes(attributes, deniedKeys) {
|
|
37
|
-
const filtered = {};
|
|
38
|
-
for (const [key, value] of Object.entries(attributes ?? {})) {
|
|
39
|
-
if (!CLUEBASE_CORRELATION_ATTRIBUTE_KEYS.has(key) &&
|
|
40
|
-
isDeniedKey(key, deniedKeys)) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
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
|
-
}
|
|
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";
|
|
53
4
|
function resourceAttributesInput(resource) {
|
|
54
5
|
const input = resource;
|
|
55
6
|
try {
|
|
@@ -59,19 +10,13 @@ function resourceAttributesInput(resource) {
|
|
|
59
10
|
return {};
|
|
60
11
|
}
|
|
61
12
|
}
|
|
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();
|
|
67
|
-
}
|
|
68
13
|
function instrumentationScopeName(span) {
|
|
69
14
|
const instrumentation = span;
|
|
70
15
|
return (instrumentation.instrumentationScope?.name ??
|
|
71
16
|
instrumentation.instrumentationLibrary?.name ??
|
|
72
17
|
null);
|
|
73
18
|
}
|
|
74
|
-
function contextRecord(context
|
|
19
|
+
function contextRecord(context) {
|
|
75
20
|
return {
|
|
76
21
|
producer_id: context.producer_id,
|
|
77
22
|
anonymous_id: context.anonymous_id,
|
|
@@ -80,11 +25,11 @@ function contextRecord(context, overrides) {
|
|
|
80
25
|
user_profile: context.user_profile,
|
|
81
26
|
session_id: context.session_id,
|
|
82
27
|
tab_id: context.tab_id,
|
|
83
|
-
trace_id:
|
|
84
|
-
request_id:
|
|
85
|
-
request_span_id:
|
|
86
|
-
action_id:
|
|
87
|
-
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,
|
|
88
33
|
correlation_id: context.correlation_id,
|
|
89
34
|
sdk_collection_mode: context.sdk_collection_mode,
|
|
90
35
|
frontend_release: context.frontend_release,
|
|
@@ -119,100 +64,35 @@ export async function spanToSourceSignalEvent(span, options) {
|
|
|
119
64
|
await initSdkCoreWasm();
|
|
120
65
|
}
|
|
121
66
|
const context = options.context;
|
|
122
|
-
const deniedKeys = options.deniedKeys ?? [];
|
|
123
67
|
const spanContext = span.spanContext();
|
|
124
|
-
const spanId = spanContext.spanId || null;
|
|
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
68
|
const corePayload = {
|
|
135
69
|
name: span.name,
|
|
136
70
|
kind: typeof span.kind === "number" ? span.kind : undefined,
|
|
137
|
-
trace_id: traceId,
|
|
138
|
-
span_id: spanId
|
|
139
|
-
parent_span_id:
|
|
71
|
+
trace_id: spanContext.traceId,
|
|
72
|
+
span_id: spanContext.spanId,
|
|
73
|
+
parent_span_id: parentSpanId(span, span.attributes),
|
|
140
74
|
start_time: span.startTime,
|
|
141
75
|
end_time: span.endTime,
|
|
142
76
|
status: span.status
|
|
143
77
|
? { code: span.status.code ?? undefined, message: span.status.message }
|
|
144
78
|
: undefined,
|
|
145
|
-
attributes:
|
|
79
|
+
attributes: span.attributes,
|
|
146
80
|
events: (span.events ?? []).map((event) => ({
|
|
147
81
|
name: event.name,
|
|
148
|
-
attributes:
|
|
82
|
+
attributes: event.attributes ?? {},
|
|
149
83
|
})),
|
|
150
84
|
resource_attributes: resourceAttributesInput(span.resource),
|
|
151
|
-
instrumentation_scope_name: instrumentationScopeName(span)
|
|
85
|
+
instrumentation_scope_name: instrumentationScopeName(span),
|
|
152
86
|
};
|
|
153
87
|
const coreOptions = {
|
|
154
|
-
denied_keys:
|
|
88
|
+
denied_keys: options.deniedKeys ?? [],
|
|
155
89
|
surface: "frontend",
|
|
156
|
-
|
|
157
|
-
const core = JSON.parse(spanToSdkSignalJson(JSON.stringify(corePayload), JSON.stringify(coreOptions)));
|
|
158
|
-
const resolvedTraceId = core.trace_id ?? traceId ?? "";
|
|
159
|
-
const eventId = generateEventId();
|
|
160
|
-
return {
|
|
161
|
-
event_id: eventId,
|
|
162
|
-
source_event_id: eventId,
|
|
163
|
-
source_schema_version: SOURCE_SCHEMA_VERSION,
|
|
164
|
-
source_event_type: SDK_SOURCE_SIGNAL_EVENT_TYPE,
|
|
165
|
-
source_event_kind: SDK_SOURCE_SIGNAL_EVENT_TYPE,
|
|
90
|
+
sdk_collection_mode: context.sdk_collection_mode,
|
|
166
91
|
producer_id: context.producer_id,
|
|
167
92
|
service_name: EVENT_SOURCE,
|
|
168
|
-
event_name: SDK_SOURCE_SIGNAL_EVENT_NAME,
|
|
169
|
-
event_category: "context",
|
|
170
|
-
event_source: EVENT_SOURCE,
|
|
171
|
-
event_version: EVENT_VERSION,
|
|
172
|
-
occurred_at: nanosToIso(core.start_time_unix_nano),
|
|
173
|
-
ingested_at: new Date().toISOString(),
|
|
174
|
-
status: "observed",
|
|
175
|
-
sdk_collection_mode: context.sdk_collection_mode,
|
|
176
|
-
privacy_state: "masked",
|
|
177
|
-
privacy_level: "default_masked",
|
|
178
|
-
masking_state: "masked",
|
|
179
|
-
allowed_value_keys: [],
|
|
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,
|
|
188
93
|
frontend_release: context.frontend_release,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
metrics: {},
|
|
192
|
-
sdk_signal: {
|
|
193
|
-
surface: "frontend",
|
|
194
|
-
kind: "span",
|
|
195
|
-
name: span.name,
|
|
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,
|
|
209
|
-
context: contextRecord(context, {
|
|
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,
|
|
215
|
-
}),
|
|
216
|
-
},
|
|
94
|
+
context: contextRecord(context),
|
|
95
|
+
context_privacy_summaries: [context.privacy_summary],
|
|
217
96
|
};
|
|
97
|
+
return JSON.parse(spanToObservationSourceEventJson(JSON.stringify(corePayload), JSON.stringify(coreOptions)));
|
|
218
98
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { InternalMetricsSnapshot } from "./internal/metrics.js";
|
|
2
|
-
import type { 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";
|
|
3
3
|
declare const cluebase: {
|
|
4
4
|
init(options: CluebaseInitOptions): void;
|
|
5
5
|
identify(userId: string, traits?: CluebaseSubjectTraits): void;
|
|
6
6
|
group(groupType: CluebaseGroupType, groupKey: string, traits?: CluebaseGroupTraits): void;
|
|
7
|
+
link(input: CluebaseLinkInput): void;
|
|
7
8
|
linkExternalAccount(namespace: string, externalId: string): void;
|
|
8
|
-
logout(): void;
|
|
9
9
|
reset(): void;
|
|
10
10
|
track(eventName: string, properties?: CluebaseTrackProperties, metrics?: CluebaseTrackMetrics): void;
|
|
11
11
|
setConsent(consent: CluebaseConsentState): void;
|
|
@@ -18,5 +18,5 @@ declare const cluebase: {
|
|
|
18
18
|
export { EXPORT_RESULT_FAILED, EXPORT_RESULT_SUCCESS, ObservationSourceEventExporter, type ObservationSourceEventExporterOptions, } from "./exporter/observation-source-event-exporter.js";
|
|
19
19
|
export { attachNextRouterIntegration } from "./integrations/next-router.js";
|
|
20
20
|
export { attachReactRouterIntegration } from "./integrations/react-router.js";
|
|
21
|
-
export type { CluebaseConsentState, CluebaseGroupTraits, CluebaseGroupType, CluebaseInitOptions, CluebaseSubjectTraits, CluebaseTrackMetrics, CluebaseTrackProperties, InternalMetricsSnapshot, };
|
|
21
|
+
export type { CluebaseConsentState, CluebaseGroupTraits, CluebaseGroupType, CluebaseInitOptions, CluebaseLinkInput, CluebaseSubjectTraits, CluebaseTrackMetrics, CluebaseTrackProperties, InternalMetricsSnapshot, };
|
|
22
22
|
export default cluebase;
|
package/dist/index.js
CHANGED
|
@@ -97,14 +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
|
+
},
|
|
100
105
|
linkExternalAccount(namespace, externalId) {
|
|
101
106
|
safeCall("linkExternalAccount", () => {
|
|
102
107
|
internalSdk.linkExternalAccount(namespace, externalId);
|
|
103
108
|
});
|
|
104
109
|
},
|
|
105
|
-
logout() {
|
|
106
|
-
safeCall("logout", () => internalSdk.logout());
|
|
107
|
-
},
|
|
108
110
|
reset() {
|
|
109
111
|
safeCall("reset", () => internalSdk.reset());
|
|
110
112
|
},
|
|
@@ -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;
|
|
@@ -497,6 +497,23 @@ export const startNetworkCapture = (context, options = {}) => {
|
|
|
497
497
|
flush: async () => {
|
|
498
498
|
await waitForFetchSpanEnd();
|
|
499
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
|
+
},
|
|
500
517
|
stop: () => {
|
|
501
518
|
if (activeCapture !== capture)
|
|
502
519
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION: "0.0.
|
|
1
|
+
export declare const SDK_VERSION: "0.0.8";
|
package/dist/provider/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CANONICAL_BROWSER_INGEST_PATH, CLUEBASE_API_V1_PREFIX, } from "../_generated/ingest-routes.js";
|
|
1
2
|
import { ALWAYS_DENIED_KEYS, DEFAULT_API_BASE_URL, DEFAULT_CONFIG, DEFAULT_FRONTEND_SOURCE_IDENTIFIER, MAX_DIAGNOSTIC_TTL_MS, } from "./constants.js";
|
|
2
3
|
import { SDK_COLLECTION_MODE } from "./contracts.js";
|
|
3
4
|
const normalizeKeyList = (keys) => {
|
|
@@ -74,6 +75,12 @@ export const parseSendableUrl = (value) => {
|
|
|
74
75
|
return null;
|
|
75
76
|
}
|
|
76
77
|
};
|
|
78
|
+
const isExtendableIngestPath = (pathname) => {
|
|
79
|
+
const path = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
80
|
+
return (path === "" ||
|
|
81
|
+
path.endsWith(CLUEBASE_API_V1_PREFIX) ||
|
|
82
|
+
path.endsWith(CANONICAL_BROWSER_INGEST_PATH));
|
|
83
|
+
};
|
|
77
84
|
const normalizeConfiguredEndpoint = (value) => parseSendableUrl(value)?.toString() ?? "";
|
|
78
85
|
const normalizeTracePropagationOrigin = (value) => {
|
|
79
86
|
const trimmed = value.trim();
|
|
@@ -101,7 +108,6 @@ export const resolveConfig = (options) => {
|
|
|
101
108
|
return {
|
|
102
109
|
endpoint: originalEndpoint,
|
|
103
110
|
originalEndpoint,
|
|
104
|
-
localSetupCheckEndpoint: null,
|
|
105
111
|
projectKey: options.projectKey?.trim() ?? "",
|
|
106
112
|
producerId: DEFAULT_FRONTEND_SOURCE_IDENTIFIER,
|
|
107
113
|
autocapture: options.autocapture ?? DEFAULT_CONFIG.autocapture,
|
|
@@ -138,8 +144,15 @@ export const resolveConfig = (options) => {
|
|
|
138
144
|
};
|
|
139
145
|
export const validateConfig = (config) => {
|
|
140
146
|
const issues = [];
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
const endpoint = parseSendableUrl(config.endpoint);
|
|
148
|
+
if (!endpoint) {
|
|
149
|
+
issues.push("endpoint must be an absolute http(s) url with a host, such as https://api.cluebase.io");
|
|
150
|
+
}
|
|
151
|
+
else if (endpoint.search || endpoint.hash) {
|
|
152
|
+
issues.push("endpoint must not carry a query string or fragment; pass the api base url only");
|
|
153
|
+
}
|
|
154
|
+
else if (!isExtendableIngestPath(endpoint.pathname)) {
|
|
155
|
+
issues.push(`endpoint path cannot be extended to ${CANONICAL_BROWSER_INGEST_PATH}; pass the api base url, the ${CLUEBASE_API_V1_PREFIX} base, or the canonical ingest path`);
|
|
143
156
|
}
|
|
144
157
|
if (!config.projectKey) {
|
|
145
158
|
issues.push("projectKey is required");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CanonicalJsonValue } from "./types.js";
|
|
2
|
+
export type LifecycleEventIdentity = Readonly<Record<string, CanonicalJsonValue>>;
|
|
3
|
+
export declare class LifecycleEventDedup {
|
|
4
|
+
private readonly seenAtMs;
|
|
5
|
+
shouldEmit(identity: LifecycleEventIdentity): boolean;
|
|
6
|
+
clear(): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { canonicalize } from "json-canonicalize";
|
|
2
|
+
const LIFECYCLE_DEDUPE_TTL_MS = 300000;
|
|
3
|
+
const LIFECYCLE_DEDUPE_MAX_ENTRIES = 4096;
|
|
4
|
+
export class LifecycleEventDedup {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.seenAtMs = new Map();
|
|
7
|
+
}
|
|
8
|
+
shouldEmit(identity) {
|
|
9
|
+
const key = canonicalize(identity);
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
for (const [seenKey, seenAt] of this.seenAtMs) {
|
|
12
|
+
if (now - seenAt > LIFECYCLE_DEDUPE_TTL_MS) {
|
|
13
|
+
this.seenAtMs.delete(seenKey);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (this.seenAtMs.has(key)) {
|
|
17
|
+
this.seenAtMs.set(key, now);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (this.seenAtMs.size >= LIFECYCLE_DEDUPE_MAX_ENTRIES) {
|
|
21
|
+
const oldest = [...this.seenAtMs.entries()].sort((left, right) => left[1] - right[1])[0]?.[0];
|
|
22
|
+
if (oldest !== undefined)
|
|
23
|
+
this.seenAtMs.delete(oldest);
|
|
24
|
+
}
|
|
25
|
+
this.seenAtMs.set(key, now);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
clear() {
|
|
29
|
+
this.seenAtMs.clear();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -6,7 +6,7 @@ import type { TabManager } from "../context/tab.js";
|
|
|
6
6
|
import { TraceManager } from "../context/trace.js";
|
|
7
7
|
import type { BatchTransport } from "../exporter/batch.js";
|
|
8
8
|
import type { InternalMetricsStore } from "../internal/metrics.js";
|
|
9
|
-
import type { CluebaseConsentState, CluebaseContext, CluebaseInternalLogger,
|
|
9
|
+
import type { CluebaseConsentState, CluebaseContext, CluebaseInternalLogger, RawCapturedEvent, ResolvedCluebaseConfig } from "./types.js";
|
|
10
10
|
export declare const OFFICIAL_CUSTOM_EVENT_NAME: {
|
|
11
11
|
readonly identityIdentified: "identity_identified";
|
|
12
12
|
readonly identityReset: "identity_reset";
|
|
@@ -33,7 +33,6 @@ export declare const getSessionCaptureEligibility: (transport: BatchTransport) =
|
|
|
33
33
|
export declare class ProviderEventPipeline {
|
|
34
34
|
private runtime;
|
|
35
35
|
private currentViewId;
|
|
36
|
-
private usageGovernorFeedback;
|
|
37
36
|
private sessionCaptureEligible;
|
|
38
37
|
private unregisterSessionCaptureEligibility;
|
|
39
38
|
configure(runtime: ProviderEventPipelineRuntime): void;
|
|
@@ -48,7 +47,6 @@ export declare class ProviderEventPipeline {
|
|
|
48
47
|
tabId: string | null;
|
|
49
48
|
};
|
|
50
49
|
enqueueRawEvent(event: RawCapturedEvent, skipSessionTouch?: boolean): void;
|
|
51
|
-
applyUsageGovernorFeedback(feedback: IngestUsageGovernorFeedback | null | undefined): void;
|
|
52
50
|
refreshSessionCaptureEligibility(): void;
|
|
53
51
|
buildContext(event?: RawCapturedEvent): CluebaseContext;
|
|
54
52
|
private attachInteractionContext;
|
|
@@ -6,7 +6,6 @@ import { sanitizeObject, } from "../exporter/sanitize.js";
|
|
|
6
6
|
import { FRONTEND_EVENT_NAME } from "./contracts.js";
|
|
7
7
|
import { shouldSampleSession } from "./sampling.js";
|
|
8
8
|
import { stripQueryAndHashFromUrl, toStringOrNull, toSubjectProfile, } from "./sdk-input-normalization.js";
|
|
9
|
-
import { DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK, hasUsageGovernorFeedbackChanged, isCostCapabilityDisabled, normalizeIngestUsageGovernorFeedback, } from "./usage-governor.js";
|
|
10
9
|
const TRACE_ROOT_EVENTS = new Set([
|
|
11
10
|
"page_view",
|
|
12
11
|
"route_change",
|
|
@@ -76,7 +75,6 @@ export class ProviderEventPipeline {
|
|
|
76
75
|
constructor() {
|
|
77
76
|
this.runtime = null;
|
|
78
77
|
this.currentViewId = null;
|
|
79
|
-
this.usageGovernorFeedback = DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK;
|
|
80
78
|
this.sessionCaptureEligible = true;
|
|
81
79
|
this.unregisterSessionCaptureEligibility = null;
|
|
82
80
|
}
|
|
@@ -86,14 +84,12 @@ export class ProviderEventPipeline {
|
|
|
86
84
|
this.unregisterSessionCaptureEligibility =
|
|
87
85
|
registerSessionCaptureEligibility(runtime.transport, () => this.sessionCaptureEligible);
|
|
88
86
|
this.currentViewId = generateViewId();
|
|
89
|
-
this.usageGovernorFeedback = DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK;
|
|
90
87
|
this.refreshSessionCaptureEligibility();
|
|
91
88
|
}
|
|
92
89
|
clearRuntime() {
|
|
93
90
|
this.unregisterSessionCaptureEligibility?.();
|
|
94
91
|
this.unregisterSessionCaptureEligibility = null;
|
|
95
92
|
this.runtime = null;
|
|
96
|
-
this.usageGovernorFeedback = DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK;
|
|
97
93
|
this.sessionCaptureEligible = true;
|
|
98
94
|
}
|
|
99
95
|
resetViewId() {
|
|
@@ -150,10 +146,6 @@ export class ProviderEventPipeline {
|
|
|
150
146
|
runtime.internalMetrics.increment("sampling_dropped_count", droppedCount);
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
|
-
if (isCostCapabilityDisabled(this.usageGovernorFeedback, "event_collection")) {
|
|
154
|
-
runtime.internalMetrics.increment("dropped_count");
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
149
|
const sourceSignal = rawEventToSourceSignalEvent(eventWithLocation, {
|
|
158
150
|
context,
|
|
159
151
|
resource: createFrontendResourceAttributes(runtime.config),
|
|
@@ -188,13 +180,6 @@ export class ProviderEventPipeline {
|
|
|
188
180
|
});
|
|
189
181
|
}
|
|
190
182
|
}
|
|
191
|
-
applyUsageGovernorFeedback(feedback) {
|
|
192
|
-
const nextFeedback = normalizeIngestUsageGovernorFeedback(feedback);
|
|
193
|
-
if (!hasUsageGovernorFeedbackChanged(this.usageGovernorFeedback, nextFeedback)) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
this.usageGovernorFeedback = nextFeedback;
|
|
197
|
-
}
|
|
198
183
|
refreshSessionCaptureEligibility() {
|
|
199
184
|
const config = this.runtime?.config;
|
|
200
185
|
if (!config) {
|
|
@@ -6,8 +6,9 @@ type CreateCluebaseOtelPipelineOptions = {
|
|
|
6
6
|
resolved: ResolvedCluebaseConfig;
|
|
7
7
|
getTransport: () => BatchTransport | null;
|
|
8
8
|
getContext: () => CluebaseContext;
|
|
9
|
+
ignoreUrlPrefixes: readonly string[];
|
|
9
10
|
};
|
|
10
|
-
export declare const createCluebaseOtelPipeline: ({ resolved, getTransport, getContext, }: CreateCluebaseOtelPipelineOptions) => CluebaseOtelProvider | null;
|
|
11
|
+
export declare const createCluebaseOtelPipeline: ({ resolved, getTransport, getContext, ignoreUrlPrefixes, }: CreateCluebaseOtelPipelineOptions) => CluebaseOtelProvider | null;
|
|
11
12
|
export declare const shutdownCluebaseOtelProvider: (provider: CluebaseOtelProvider | null) => void;
|
|
12
13
|
export declare const shutdownCluebaseOtelProviderAsync: (provider: CluebaseOtelProvider | null) => Promise<void>;
|
|
13
14
|
export declare const forceFlushCluebaseOtelProvider: (provider: CluebaseOtelProvider | null, signal?: AbortSignal) => Promise<void>;
|
|
@@ -2,7 +2,6 @@ import { context, propagation, trace } from "@opentelemetry/api";
|
|
|
2
2
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
3
3
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
4
4
|
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
5
|
-
import { resolveBrowserTokenEndpoint, resolveCanonicalIngestEndpoint, } from "../exporter/client.js";
|
|
6
5
|
import { createFrontendResourceAttributes } from "../exporter/frontend-resource.js";
|
|
7
6
|
import { ObservationSourceEventExporter } from "../exporter/observation-source-event-exporter.js";
|
|
8
7
|
import { createCluebaseNetworkPropagator } from "../instrumentation/network.js";
|
|
@@ -16,7 +15,7 @@ const getTracerDelegate = (provider) => {
|
|
|
16
15
|
const candidate = provider;
|
|
17
16
|
return candidate.getDelegate?.();
|
|
18
17
|
};
|
|
19
|
-
export const createCluebaseOtelPipeline = ({ resolved, getTransport, getContext, }) => {
|
|
18
|
+
export const createCluebaseOtelPipeline = ({ resolved, getTransport, getContext, ignoreUrlPrefixes, }) => {
|
|
20
19
|
if (!getTransport()) {
|
|
21
20
|
return null;
|
|
22
21
|
}
|
|
@@ -58,15 +57,7 @@ export const createCluebaseOtelPipeline = ({ resolved, getTransport, getContext,
|
|
|
58
57
|
},
|
|
59
58
|
getContext,
|
|
60
59
|
deniedKeys: resolved.privacy?.deniedKeys ?? [],
|
|
61
|
-
ignoreUrlPrefixes
|
|
62
|
-
resolveCanonicalIngestEndpoint(resolved.endpoint),
|
|
63
|
-
resolveCanonicalIngestEndpoint(resolved.originalEndpoint),
|
|
64
|
-
resolveBrowserTokenEndpoint(resolved.endpoint),
|
|
65
|
-
resolveBrowserTokenEndpoint(resolved.originalEndpoint),
|
|
66
|
-
...(resolved.localSetupCheckEndpoint
|
|
67
|
-
? [resolved.localSetupCheckEndpoint]
|
|
68
|
-
: []),
|
|
69
|
-
].filter((url) => typeof url === "string" && url.length > 0),
|
|
60
|
+
ignoreUrlPrefixes,
|
|
70
61
|
});
|
|
71
62
|
const provider = new WebTracerProvider({
|
|
72
63
|
resource,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TraceManager } from "../context/trace.js";
|
|
2
2
|
import type { BatchTransport } from "../exporter/batch.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { IngestClient } from "../exporter/client.js";
|
|
4
4
|
import type { InternalMetricsStore } from "../internal/metrics.js";
|
|
5
5
|
import { type CluebaseOtelProvider } from "./sdk-otel-pipeline.js";
|
|
6
6
|
import type { CaptureModuleContext, CluebaseConsentState, CluebaseInternalLogger, RawCapturedEvent, ResolvedCluebaseConfig } from "./types.js";
|
|
@@ -41,7 +41,8 @@ export declare class ProviderRuntimeSurfaces {
|
|
|
41
41
|
constructor(options: ProviderRuntimeSurfacesOptions);
|
|
42
42
|
buildCaptureContext(): CaptureModuleContext;
|
|
43
43
|
warmupBrowserToken(client: IngestClient): void;
|
|
44
|
-
|
|
44
|
+
refreshIgnoreUrlPrefixes(): void;
|
|
45
|
+
startCaptureModules(config: ResolvedCluebaseConfig, ignoreUrlPrefixes: readonly string[]): void;
|
|
45
46
|
stopCaptureModules(): void;
|
|
46
47
|
abortBrowserTokenWarmup(): void;
|
|
47
48
|
flushCaptureSummaries(reason: string): Promise<void>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { TraceManager } from "../context/trace.js";
|
|
2
|
-
import { resolveBrowserTokenEndpoint, resolveCanonicalIngestEndpoint, } from "../exporter/client.js";
|
|
3
2
|
import { startActionCapture } from "../instrumentation/action.js";
|
|
4
3
|
import { startDragCapture } from "../instrumentation/drag.js";
|
|
5
4
|
import { startErrorCapture } from "../instrumentation/error.js";
|
|
@@ -88,7 +87,12 @@ export class ProviderRuntimeSurfaces {
|
|
|
88
87
|
}
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
|
-
|
|
90
|
+
refreshIgnoreUrlPrefixes() {
|
|
91
|
+
for (const handle of this.captureHandles) {
|
|
92
|
+
handle.refreshIgnoreUrlPrefixes?.();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
startCaptureModules(config, ignoreUrlPrefixes) {
|
|
92
96
|
const captureContext = this.buildCaptureContext();
|
|
93
97
|
const handles = [];
|
|
94
98
|
handles.push(startInputCapture(captureContext));
|
|
@@ -101,10 +105,7 @@ export class ProviderRuntimeSurfaces {
|
|
|
101
105
|
handles.push(startNetworkCapture(captureContext, {
|
|
102
106
|
tracerProvider: this.getTracerProvider() ?? undefined,
|
|
103
107
|
deniedKeys: config.privacy.deniedKeys,
|
|
104
|
-
ignoreUrlPrefixes
|
|
105
|
-
resolveCanonicalIngestEndpoint(config.endpoint),
|
|
106
|
-
resolveBrowserTokenEndpoint(config.endpoint),
|
|
107
|
-
],
|
|
108
|
+
ignoreUrlPrefixes,
|
|
108
109
|
tracePropagationOrigins: config.network.tracePropagationOrigins,
|
|
109
110
|
createRequestTraceContext: (input) => {
|
|
110
111
|
const traceManager = this.getTraceManager();
|