@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.
Files changed (62) hide show
  1. package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
  2. package/dist/_generated/correlation-attribute-keys.js +34 -0
  3. package/dist/_generated/diagnostic-codes.d.ts +4 -0
  4. package/dist/_generated/diagnostic-codes.js +48 -0
  5. package/dist/_generated/drop-reasons.d.ts +2 -0
  6. package/dist/_generated/drop-reasons.js +25 -0
  7. package/dist/_generated/ingest-routes.d.ts +5 -0
  8. package/dist/_generated/ingest-routes.js +12 -0
  9. package/dist/context/trace.js +2 -4
  10. package/dist/exporter/batch.d.ts +23 -26
  11. package/dist/exporter/batch.js +247 -289
  12. package/dist/exporter/browser-outbox-records.d.ts +7 -24
  13. package/dist/exporter/browser-outbox-records.js +6 -94
  14. package/dist/exporter/browser-outbox.d.ts +23 -28
  15. package/dist/exporter/browser-outbox.js +99 -211
  16. package/dist/exporter/client.d.ts +24 -34
  17. package/dist/exporter/client.js +105 -318
  18. package/dist/exporter/delivery-pipeline.d.ts +71 -0
  19. package/dist/exporter/delivery-pipeline.js +196 -0
  20. package/dist/exporter/delivery-response-contract.d.ts +1 -81
  21. package/dist/exporter/delivery-response-contract.js +0 -168
  22. package/dist/exporter/delivery-response.d.ts +7 -4
  23. package/dist/exporter/delivery-response.js +59 -133
  24. package/dist/exporter/diagnostics.d.ts +2 -1
  25. package/dist/exporter/diagnostics.js +1 -25
  26. package/dist/exporter/event-id.d.ts +1 -0
  27. package/dist/exporter/event-id.js +3 -0
  28. package/dist/exporter/observation-source-event-exporter.d.ts +1 -1
  29. package/dist/exporter/observation-source-event-exporter.js +2 -2
  30. package/dist/exporter/sdk-core-delivery.d.ts +225 -0
  31. package/dist/exporter/sdk-core-delivery.js +97 -0
  32. package/dist/exporter/sdk-core-wasm.d.ts +7 -1
  33. package/dist/exporter/sdk-core-wasm.js +24 -3
  34. package/dist/exporter/source-event.d.ts +2 -0
  35. package/dist/exporter/source-event.js +17 -0
  36. package/dist/exporter/span-to-source-signal.d.ts +1 -1
  37. package/dist/exporter/span-to-source-signal.js +20 -140
  38. package/dist/index.d.ts +3 -3
  39. package/dist/index.js +5 -3
  40. package/dist/instrumentation/network.d.ts +1 -1
  41. package/dist/instrumentation/network.js +17 -0
  42. package/dist/provider/_version.d.ts +1 -1
  43. package/dist/provider/_version.js +1 -1
  44. package/dist/provider/config.js +16 -3
  45. package/dist/provider/constants.js +0 -1
  46. package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
  47. package/dist/provider/lifecycle-event-dedup.js +31 -0
  48. package/dist/provider/sdk-event-pipeline.d.ts +1 -3
  49. package/dist/provider/sdk-event-pipeline.js +0 -15
  50. package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
  51. package/dist/provider/sdk-otel-pipeline.js +2 -11
  52. package/dist/provider/sdk-runtime-surfaces.d.ts +3 -2
  53. package/dist/provider/sdk-runtime-surfaces.js +7 -6
  54. package/dist/provider/sdk.js +88 -54
  55. package/dist/provider/types.d.ts +12 -19
  56. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +12 -9
  57. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +117 -29
  58. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
  59. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +8 -9
  60. package/package.json +1 -1
  61. package/dist/provider/usage-governor.d.ts +0 -7
  62. 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 { type BrowserSourceSignalEvent } from "./source-event.js";
3
+ import type { BrowserSourceSignalEvent } from "./source-event.js";
4
4
  export interface SpanToSourceSignalOptions {
5
5
  context: CluebaseContext;
6
6
  deniedKeys?: readonly string[];
@@ -1,55 +1,6 @@
1
- import { CLUEBASE_CORRELATION_ATTRIBUTE_KEYS } from "../_generated/correlation-attribute-keys.js";
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
- 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, overrides) {
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: overrides.trace_id,
84
- request_id: overrides.request_id,
85
- request_span_id: overrides.request_span_id,
86
- action_id: overrides.action_id,
87
- interaction_id: overrides.interaction_id,
28
+ trace_id: context.trace_id,
29
+ request_id: context.request_id,
30
+ request_span_id: context.request_span_id,
31
+ action_id: context.action_id,
32
+ interaction_id: context.interaction_id,
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 ?? undefined,
139
- parent_span_id: parentId ?? undefined,
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: attributesForCore,
79
+ attributes: span.attributes,
146
80
  events: (span.events ?? []).map((event) => ({
147
81
  name: event.name,
148
- attributes: preFilterDeniedAttributes(event.attributes, deniedKeys),
82
+ attributes: event.attributes ?? {},
149
83
  })),
150
84
  resource_attributes: resourceAttributesInput(span.resource),
151
- instrumentation_scope_name: instrumentationScopeName(span) ?? undefined,
85
+ instrumentation_scope_name: instrumentationScopeName(span),
152
86
  };
153
87
  const coreOptions = {
154
- denied_keys: coreDeniedKeys(deniedKeys),
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
- backend_release: null,
190
- properties: {},
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.6";
1
+ export declare const SDK_VERSION: "0.0.8";
@@ -1,3 +1,3 @@
1
1
  // This file is generated by scripts/generate-version-module.mjs.
2
2
  // Do NOT edit by hand. The source of truth is `package.json` `version`.
3
- export const SDK_VERSION = "0.0.6";
3
+ export const SDK_VERSION = "0.0.8";
@@ -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
- if (!config.endpoint) {
142
- issues.push("endpoint is required");
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");
@@ -41,7 +41,6 @@ export const MAX_SANITIZE_DEPTH = 20;
41
41
  export const DEFAULT_CONFIG = {
42
42
  endpoint: "",
43
43
  originalEndpoint: "",
44
- localSetupCheckEndpoint: null,
45
44
  projectKey: "",
46
45
  producerId: DEFAULT_FRONTEND_SOURCE_IDENTIFIER,
47
46
  autocapture: true,
@@ -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, IngestUsageGovernorFeedback, RawCapturedEvent, ResolvedCluebaseConfig } from "./types.js";
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 { type IngestClient } from "../exporter/client.js";
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
- startCaptureModules(config: ResolvedCluebaseConfig): void;
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
- startCaptureModules(config) {
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();