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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +21 -0
  2. package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
  3. package/dist/_generated/correlation-attribute-keys.js +34 -0
  4. package/dist/_generated/diagnostic-codes.d.ts +4 -0
  5. package/dist/_generated/diagnostic-codes.js +48 -0
  6. package/dist/_generated/drop-reasons.d.ts +2 -0
  7. package/dist/_generated/drop-reasons.js +25 -0
  8. package/dist/_generated/{identity-pii-policy.d.ts → identity-pii-policy-text-contains.d.ts} +1 -1
  9. package/dist/_generated/{identity-pii-policy.js → identity-pii-policy-text-contains.js} +1 -1
  10. package/dist/_generated/identity-pii-policy-whole-value.d.ts +37 -0
  11. package/dist/_generated/identity-pii-policy-whole-value.js +106 -0
  12. package/dist/_generated/ingest-routes.d.ts +5 -0
  13. package/dist/_generated/ingest-routes.js +12 -0
  14. package/dist/_generated/otel-span-conversion.js +1 -9
  15. package/dist/context/browser-storage.js +1 -2
  16. package/dist/context/environment.js +5 -3
  17. package/dist/context/identity.js +5 -9
  18. package/dist/context/organization.d.ts +1 -1
  19. package/dist/context/organization.js +6 -8
  20. package/dist/context/session.js +6 -8
  21. package/dist/context/tab.js +8 -12
  22. package/dist/context/trace.js +4 -5
  23. package/dist/exporter/batch.d.ts +26 -27
  24. package/dist/exporter/batch.js +248 -294
  25. package/dist/exporter/browser-outbox-records.d.ts +8 -25
  26. package/dist/exporter/browser-outbox-records.js +21 -100
  27. package/dist/exporter/browser-outbox.d.ts +23 -25
  28. package/dist/exporter/browser-outbox.js +106 -181
  29. package/dist/exporter/client.d.ts +25 -35
  30. package/dist/exporter/client.js +107 -335
  31. package/dist/exporter/delivery-pipeline.d.ts +71 -0
  32. package/dist/exporter/delivery-pipeline.js +196 -0
  33. package/dist/exporter/delivery-response-contract.d.ts +1 -81
  34. package/dist/exporter/delivery-response-contract.js +0 -118
  35. package/dist/exporter/delivery-response.d.ts +9 -4
  36. package/dist/exporter/delivery-response.js +59 -122
  37. package/dist/exporter/diagnostics.d.ts +3 -2
  38. package/dist/exporter/diagnostics.js +5 -26
  39. package/dist/exporter/event-id.d.ts +1 -0
  40. package/dist/exporter/event-id.js +4 -3
  41. package/dist/exporter/frontend-resource.js +5 -4
  42. package/dist/exporter/observation-source-event-exporter.d.ts +2 -1
  43. package/dist/exporter/observation-source-event-exporter.js +7 -4
  44. package/dist/exporter/path-template.js +3 -1
  45. package/dist/exporter/raw-event-to-source-signal.js +4 -2
  46. package/dist/exporter/sanitize.d.ts +3 -2
  47. package/dist/exporter/sanitize.js +4 -3
  48. package/dist/exporter/sdk-core-delivery.d.ts +225 -0
  49. package/dist/exporter/sdk-core-delivery.js +97 -0
  50. package/dist/exporter/sdk-core-wasm.d.ts +11 -0
  51. package/dist/exporter/sdk-core-wasm.js +39 -0
  52. package/dist/exporter/source-event.d.ts +3 -0
  53. package/dist/exporter/source-event.js +17 -0
  54. package/dist/exporter/span-to-source-signal.d.ts +2 -2
  55. package/dist/exporter/span-to-source-signal.js +45 -125
  56. package/dist/index.d.ts +5 -4
  57. package/dist/index.js +18 -6
  58. package/dist/instrumentation/action.js +9 -8
  59. package/dist/instrumentation/dom.js +13 -21
  60. package/dist/instrumentation/drag.js +0 -3
  61. package/dist/instrumentation/error.js +3 -1
  62. package/dist/instrumentation/form.js +5 -6
  63. package/dist/instrumentation/input.js +4 -8
  64. package/dist/instrumentation/location.js +3 -1
  65. package/dist/instrumentation/network-request.js +1 -1
  66. package/dist/instrumentation/network-span-attributes.js +2 -2
  67. package/dist/instrumentation/network.d.ts +1 -1
  68. package/dist/instrumentation/network.js +24 -3
  69. package/dist/instrumentation/performance.js +1 -2
  70. package/dist/internal/internal-log.js +2 -1
  71. package/dist/provider/_version.d.ts +1 -1
  72. package/dist/provider/_version.js +1 -1
  73. package/dist/provider/config.js +16 -4
  74. package/dist/provider/constants.d.ts +1 -1
  75. package/dist/provider/constants.js +0 -2
  76. package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
  77. package/dist/provider/lifecycle-event-dedup.js +31 -0
  78. package/dist/provider/sdk-event-pipeline.d.ts +3 -4
  79. package/dist/provider/sdk-event-pipeline.js +5 -17
  80. package/dist/provider/sdk-input-normalization.js +2 -2
  81. package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
  82. package/dist/provider/sdk-otel-pipeline.js +7 -15
  83. package/dist/provider/sdk-runtime-surfaces.d.ts +17 -13
  84. package/dist/provider/sdk-runtime-surfaces.js +15 -13
  85. package/dist/provider/sdk.js +137 -48
  86. package/dist/provider/types.d.ts +18 -24
  87. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +44 -0
  88. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +355 -0
  89. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
  90. package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +11 -0
  91. package/dist/wasm/pkg-web/package.json +15 -0
  92. package/package.json +62 -53
  93. package/dist/provider/usage-governor.d.ts +0 -7
  94. package/dist/provider/usage-governor.js +0 -114
@@ -1,31 +1,7 @@
1
1
  import { hmac } from "@noble/hashes/hmac";
2
2
  import { sha256 } from "@noble/hashes/sha256";
3
3
  import { bytesToHex, randomBytes, utf8ToBytes } from "@noble/hashes/utils";
4
- const CODES = [
5
- "batch_split",
6
- "consent_persistence_failed",
7
- "consent_purge",
8
- "consent_purge_failed",
9
- "consent_state_unavailable",
10
- "durable_outbox_unavailable",
11
- "duplicate_drop_details_unavailable",
12
- "event_oversize",
13
- "intentional_drop",
14
- "invalid_response",
15
- "legacy_pre_revocation_event_blocked",
16
- "legacy_bridge_capacity_reached",
17
- "legacy_writer_detected",
18
- "non_canonical_success_status",
19
- "outbox_key_collision",
20
- "outbox_record_corrupt",
21
- "protocol_redirect",
22
- "queue_overflow",
23
- "retry_exhausted",
24
- "retry_scheduled",
25
- "shutdown_failure",
26
- "terminal_rejection",
27
- "unknown_status",
28
- ];
4
+ import { DIAGNOSTIC_CODES as CODES, } from "../_generated/diagnostic-codes.js";
29
5
  const CODE_SET = new Set(CODES);
30
6
  const STRATEGIES = new Set([
31
7
  "split_batch",
@@ -51,6 +27,7 @@ const SAFE_REASONS = new Set([
51
27
  "raw_ingest_ack_unknown",
52
28
  "raw_ingest_produce_failed",
53
29
  "raw_ingest_receipt_metadata_failed",
30
+ "raw_ingest_receipt_unprocessable",
54
31
  "route_throttled",
55
32
  "stale_browser_token",
56
33
  "unresolved_project_or_environment",
@@ -131,7 +108,9 @@ const normalize = (input) => {
131
108
  if (input.reason && SAFE_REASONS.has(input.reason))
132
109
  entry.reason = input.reason;
133
110
  const attempt = input.attempt;
134
- if (typeof attempt === "number" && Number.isSafeInteger(attempt) && attempt > 0) {
111
+ if (typeof attempt === "number" &&
112
+ Number.isSafeInteger(attempt) &&
113
+ attempt > 0) {
135
114
  entry.attempt = attempt;
136
115
  }
137
116
  const count = input.count;
@@ -1,6 +1,7 @@
1
1
  export declare const generateMonotonicId: (prefix?: string) => string;
2
2
  export declare const generateTraceId: () => string;
3
3
  export declare const generateEventId: () => string;
4
+ export declare const generateBatchId: () => string;
4
5
  export declare const generateAnonymousId: () => string;
5
6
  export declare const generateSessionId: () => string;
6
7
  export declare const generateTabId: () => string;
@@ -20,9 +20,7 @@ const nextMonotonicParts = () => {
20
20
  typeof globalThis.performance.now === "function"
21
21
  ? Math.floor(globalThis.performance.now() * 1000)
22
22
  : Date.now();
23
- const monotonicTime = performanceNow > lastMonotonicTime
24
- ? performanceNow
25
- : lastMonotonicTime + 1;
23
+ const monotonicTime = performanceNow > lastMonotonicTime ? performanceNow : lastMonotonicTime + 1;
26
24
  lastMonotonicTime = monotonicTime;
27
25
  monotonicCounter = (monotonicCounter + 1) % Number.MAX_SAFE_INTEGER;
28
26
  return { monotonicTime, counter: monotonicCounter };
@@ -51,6 +49,9 @@ export const generateTraceId = () => {
51
49
  export const generateEventId = () => {
52
50
  return generateMonotonicId();
53
51
  };
52
+ export const generateBatchId = () => {
53
+ return toHex(randomBytes(16));
54
+ };
54
55
  export const generateAnonymousId = () => {
55
56
  return generateMonotonicId("anon");
56
57
  };
@@ -1,7 +1,7 @@
1
- import { isDeniedKey } from "./sanitize.js";
1
+ import { CLUEBASE_OTEL_ALLOWED_RESOURCE_KEYS, CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS, } from "../_generated/otel-resource-keys.js";
2
2
  import { SDK_VERSION } from "../provider/constants.js";
3
- import { CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS, CLUEBASE_OTEL_SDK_SOURCE_RESOURCE_KEYS, } from "../_generated/otel-resource-keys.js";
4
- const FRONTEND_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_SDK_SOURCE_RESOURCE_KEYS);
3
+ import { isDeniedKey } from "./sanitize.js";
4
+ const FRONTEND_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_ALLOWED_RESOURCE_KEYS);
5
5
  const FRONTEND_REQUIRED_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS);
6
6
  const MAX_RESOURCE_VALUE_BYTES = 512;
7
7
  export const createFrontendResourceAttributes = (resolved) => ({
@@ -27,7 +27,8 @@ export const toFrontendSourceResource = (resource, deniedKeys = []) => {
27
27
  }
28
28
  for (const [key, value] of entries) {
29
29
  if (!FRONTEND_RESOURCE_KEYS.has(key) ||
30
- (isDeniedKey(key, deniedKeys) && !FRONTEND_REQUIRED_RESOURCE_KEYS.has(key))) {
30
+ (isDeniedKey(key, deniedKeys) &&
31
+ !FRONTEND_REQUIRED_RESOURCE_KEYS.has(key))) {
31
32
  continue;
32
33
  }
33
34
  if (key === "cluebase.service_type" && value !== "frontend") {
@@ -1,7 +1,7 @@
1
1
  import { type ExportResult, ExportResultCode } from "@opentelemetry/core";
2
2
  import type { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
3
3
  import type { CluebaseContext } from "../provider/types.js";
4
- import type { CollectionPermit } from "./browser-outbox.js";
4
+ import type { CollectionPermit } from "./sdk-core-delivery.js";
5
5
  import type { BrowserSourceEvent, BrowserSourceSignalEvent } from "./source-event.js";
6
6
  export declare const EXPORT_RESULT_SUCCESS: ExportResultCode;
7
7
  export declare const EXPORT_RESULT_FAILED: ExportResultCode;
@@ -30,6 +30,7 @@ export declare class ObservationSourceEventExporter implements SpanExporter {
30
30
  private ignoreSpanReason;
31
31
  enqueue(event: BrowserSourceSignalEvent): void;
32
32
  export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
33
+ private exportAsync;
33
34
  forceFlush(): Promise<void>;
34
35
  shutdown(): Promise<void>;
35
36
  internalMetrics(): {
@@ -15,7 +15,7 @@ export class ObservationSourceEventExporter {
15
15
  this.shutdownImpl = options.shutdownImpl;
16
16
  this.getContext = options.getContext;
17
17
  this.deniedKeys = options.deniedKeys ?? [];
18
- this.ignoreUrlPrefixes = (options.ignoreUrlPrefixes ?? []).filter((p) => typeof p === "string" && p.length > 0);
18
+ this.ignoreUrlPrefixes = options.ignoreUrlPrefixes ?? [];
19
19
  }
20
20
  ignoreSpanReason(span) {
21
21
  const spanFields = span;
@@ -30,7 +30,7 @@ export class ObservationSourceEventExporter {
30
30
  ? fullUrl
31
31
  : undefined;
32
32
  if (url &&
33
- this.ignoreUrlPrefixes.some((prefix) => url.startsWith(prefix))) {
33
+ this.ignoreUrlPrefixes.some((prefix) => prefix.length > 0 && url.startsWith(prefix))) {
34
34
  return "loop";
35
35
  }
36
36
  if (!Object.keys(attrs).some((key) => key.startsWith("cluebase."))) {
@@ -55,6 +55,9 @@ export class ObservationSourceEventExporter {
55
55
  });
56
56
  return;
57
57
  }
58
+ void this.exportAsync(spans, resultCallback);
59
+ }
60
+ async exportAsync(spans, resultCallback) {
58
61
  try {
59
62
  const context = this.getContext();
60
63
  for (const span of spans) {
@@ -71,7 +74,7 @@ export class ObservationSourceEventExporter {
71
74
  this.droppedSpanCount += 1;
72
75
  continue;
73
76
  }
74
- const sourceSignal = spanToSourceSignalEvent(span, {
77
+ const sourceSignal = await spanToSourceSignalEvent(span, {
75
78
  context,
76
79
  deniedKeys: this.deniedKeys,
77
80
  });
@@ -85,7 +88,7 @@ export class ObservationSourceEventExporter {
85
88
  });
86
89
  }
87
90
  }
88
- void this.flushImpl()
91
+ await this.flushImpl()
89
92
  .then(() => resultCallback({ code: ExportResultCode.SUCCESS }))
90
93
  .catch((cause) => resultCallback({
91
94
  code: ExportResultCode.FAILED,
@@ -56,7 +56,9 @@ export const toPathTemplate = (pathname) => {
56
56
  }
57
57
  const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
58
58
  const [pathOnly] = normalizedPath.split("?");
59
- const rawSegments = pathOnly.split("/").filter((segment) => segment.length > 0);
59
+ const rawSegments = pathOnly
60
+ .split("/")
61
+ .filter((segment) => segment.length > 0);
60
62
  const segments = rawSegments.map((segment, index) => shouldTemplateSegment(segment, rawSegments[index - 1] ?? null)
61
63
  ? ":id"
62
64
  : segment);
@@ -2,8 +2,8 @@ import { CLUEBASE_CORRELATION_ATTRIBUTE_KEYS } from "../_generated/correlation-a
2
2
  import { cluebaseAuthoredPropertyAttribute } from "../instrumentation/cluebase-authored-attributes.js";
3
3
  import { EVENT_SOURCE, EVENT_VERSION, SOURCE_SCHEMA_VERSION, } from "../provider/constants.js";
4
4
  import { generateEventId, generateInteractionId, generateRequestId, generateRequestSpanId, generateTraceId, } from "./event-id.js";
5
- import { isDeniedKey } from "./sanitize.js";
6
5
  import { toFrontendSourceResource, } from "./frontend-resource.js";
6
+ import { isDeniedKey } from "./sanitize.js";
7
7
  import { SDK_SOURCE_SIGNAL_EVENT_NAME, SDK_SOURCE_SIGNAL_EVENT_TYPE, } from "./source-event.js";
8
8
  export const RAW_FRONTEND_SOURCE_SIGNAL_KIND = "frontend_raw_event";
9
9
  const PUBLIC_CUSTOM_EVENT_NAME_PATTERN = /^[A-Za-z0-9_.:-]{1,128}$/;
@@ -236,7 +236,8 @@ export function rawEventToSourceSignalEvent(raw, options) {
236
236
  if (viewId)
237
237
  attributes["cluebase.view.id"] = viewId;
238
238
  if (failureType) {
239
- attributes[cluebaseAuthoredPropertyAttribute("failure_type")] = failureType;
239
+ attributes[cluebaseAuthoredPropertyAttribute("failure_type")] =
240
+ failureType;
240
241
  }
241
242
  if (outcomeClass)
242
243
  attributes["cluebase.outcome_class"] = outcomeClass;
@@ -291,6 +292,7 @@ export function rawEventToSourceSignalEvent(raw, options) {
291
292
  attributes,
292
293
  events: [],
293
294
  resource: toFrontendSourceResource(options.resource, options.deniedKeys),
295
+ raw_resource: toFrontendSourceResource(options.resource, options.deniedKeys),
294
296
  context: contextRecord(context, {
295
297
  session_id: sessionId,
296
298
  trace_id: traceId,
@@ -1,4 +1,5 @@
1
- import { detectIdentityPII } from "../_generated/identity-pii-policy.js";
1
+ import { detectTextContainsPII } from "../_generated/identity-pii-policy-text-contains.js";
2
+ import { detectWholeValuePII } from "../_generated/identity-pii-policy-whole-value.js";
2
3
  export type SanitizationAccumulator = {
3
4
  maskedValueCount: number;
4
5
  droppedFieldCount: number;
@@ -15,5 +16,5 @@ export declare const parseUrlForNetwork: (rawUrl: string) => {
15
16
  urlMaskedQuery: string;
16
17
  };
17
18
  export declare const maybeParseJson: (value: unknown) => unknown;
18
- export { detectIdentityPII };
19
+ export { detectTextContainsPII, detectWholeValuePII };
19
20
  export declare const hashIdentityValue: (value: string) => string;
@@ -1,6 +1,7 @@
1
1
  import { sha256 } from "@noble/hashes/sha256";
2
2
  import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
3
- import { detectIdentityPII, PII_HASH_HEX_LENGTH, PII_HASH_PREFIX, } from "../_generated/identity-pii-policy.js";
3
+ import { detectTextContainsPII, PII_HASH_HEX_LENGTH, PII_HASH_PREFIX, } from "../_generated/identity-pii-policy-text-contains.js";
4
+ import { detectWholeValuePII } from "../_generated/identity-pii-policy-whole-value.js";
4
5
  import { isDeniedLookupKey, toDeniedLookupKeySet, } from "../_generated/ingest-privacy-tables.js";
5
6
  import { toPathTemplate } from "../exporter/path-template.js";
6
7
  import { ALWAYS_DENIED_KEYS, MASKED_VALUE, MAX_SANITIZE_DEPTH, } from "../provider/constants.js";
@@ -67,7 +68,7 @@ export const sanitizeObject = (value, deniedKeys = [], summary, pathPrefix = "",
67
68
  if (maskIdentityPii &&
68
69
  !preserveExplicitTraits &&
69
70
  typeof current === "string" &&
70
- detectIdentityPII(current) !== null) {
71
+ detectTextContainsPII(current) !== null) {
71
72
  if (current === MASKED_VALUE)
72
73
  return current;
73
74
  if (summary) {
@@ -140,5 +141,5 @@ export const maybeParseJson = (value) => {
140
141
  return null;
141
142
  }
142
143
  };
143
- export { detectIdentityPII };
144
+ export { detectTextContainsPII, detectWholeValuePII };
144
145
  export const hashIdentityValue = (value) => `${PII_HASH_PREFIX}${bytesToHex(sha256(utf8ToBytes(value))).slice(0, PII_HASH_HEX_LENGTH)}`;
@@ -0,0 +1,225 @@
1
+ import type { DiagnosticCode } from "./diagnostics.js";
2
+ export type ConsentState = "unknown" | "granted" | "denied";
3
+ export type CollectionPermit = {
4
+ readonly generation: string;
5
+ readonly gate_id: string;
6
+ readonly permitted_at_start: boolean;
7
+ readonly capture_open_at_start: boolean;
8
+ };
9
+ export type TransportConfigInput = {
10
+ readonly surface: "frontend";
11
+ readonly endpoint: string;
12
+ readonly project_key: string;
13
+ readonly sdk_request?: string;
14
+ readonly sdk_version?: string;
15
+ readonly source_schema_version?: string;
16
+ };
17
+ export type QueueLimitsInput = {
18
+ readonly max_payload_bytes: number;
19
+ readonly max_buffer_bytes: number;
20
+ readonly max_buffer_events?: number;
21
+ };
22
+ export type DriverOptions = {
23
+ readonly transport: TransportConfigInput;
24
+ readonly queue_limits: QueueLimitsInput;
25
+ readonly request_timeout_ms?: number;
26
+ };
27
+ export type QueuedEvent = {
28
+ readonly event_key: string;
29
+ readonly byte_length: number;
30
+ readonly permit: CollectionPermit;
31
+ readonly diagnostic_canonical_bytes?: string;
32
+ };
33
+ export type RestoredEvent = {
34
+ readonly event_key: string;
35
+ readonly byte_length: number;
36
+ readonly diagnostic_canonical_bytes?: string;
37
+ };
38
+ export type DiagnosticEntry = {
39
+ readonly code: DiagnosticCode;
40
+ readonly count?: number;
41
+ readonly event_fingerprint?: string;
42
+ readonly batch_fingerprint?: string;
43
+ readonly http_status?: number;
44
+ readonly attempt?: number;
45
+ };
46
+ export type HostDiagnostic = {
47
+ readonly code: DiagnosticCode;
48
+ readonly count?: number;
49
+ readonly event_canonical_bytes?: string;
50
+ readonly batch_canonical_bytes?: string;
51
+ };
52
+ export type EnqueueReport = {
53
+ readonly retained: boolean;
54
+ readonly overflowed_event_keys: readonly string[];
55
+ readonly diagnostics: readonly DiagnosticEntry[];
56
+ };
57
+ export type FenceReport = {
58
+ readonly target_ordinal: number;
59
+ readonly oversized_event_keys: readonly string[];
60
+ readonly frame_admits_no_event: boolean;
61
+ readonly diagnostics: readonly DiagnosticEntry[];
62
+ };
63
+ export type RestoreReport = {
64
+ readonly adopted: boolean;
65
+ readonly event_keys: readonly string[];
66
+ readonly batch_id?: number;
67
+ };
68
+ export type ReleaseReport = {
69
+ readonly events: readonly QueuedEvent[];
70
+ };
71
+ export type QueueDepthReport = {
72
+ readonly draft_count: number;
73
+ readonly draft_body_bytes: number;
74
+ readonly pending_bytes: number;
75
+ };
76
+ export type ConsentReport = {
77
+ readonly consent: ConsentState;
78
+ readonly purged_event_keys: readonly string[];
79
+ readonly diagnostics: readonly DiagnosticEntry[];
80
+ };
81
+ export type TransportHeader = {
82
+ readonly name: string;
83
+ readonly value: string;
84
+ };
85
+ export type TransportRequest = {
86
+ readonly attempt_id: number;
87
+ readonly target: "ingest_batch" | "browser_token";
88
+ readonly method: string;
89
+ readonly url: string;
90
+ readonly headers: readonly TransportHeader[];
91
+ readonly body: string;
92
+ readonly timeout_ms: number;
93
+ readonly max_response_body_bytes: number;
94
+ readonly follow_redirects: boolean;
95
+ readonly keepalive: boolean;
96
+ readonly omit_credentials: boolean;
97
+ readonly suppress_instrumentation: boolean;
98
+ };
99
+ export type TransportAbortCause = "consent_revoked" | "deadline" | "shutdown";
100
+ export type TransportOutcome = {
101
+ readonly kind: "response";
102
+ readonly status: number;
103
+ readonly body: string;
104
+ readonly body_over_limit?: boolean;
105
+ } | {
106
+ readonly kind: "timeout";
107
+ } | {
108
+ readonly kind: "network_error";
109
+ } | {
110
+ readonly kind: "aborted";
111
+ readonly cause: TransportAbortCause;
112
+ };
113
+ export type DeliveryStep = {
114
+ readonly kind: "idle";
115
+ } | {
116
+ readonly kind: "send";
117
+ readonly attempt_id: number;
118
+ readonly batch_id: number;
119
+ readonly attempt: number;
120
+ readonly event_keys: readonly string[];
121
+ readonly timeout_ms: number;
122
+ } | {
123
+ readonly kind: "wait_retry";
124
+ readonly resume_at_ms: number;
125
+ readonly delay_ms: number;
126
+ } | {
127
+ readonly kind: "blocked";
128
+ readonly reason: "awaiting_result" | "consent_revoked" | "deadline_exceeded" | "retries_exhausted";
129
+ };
130
+ export type DeliveryProgress = {
131
+ readonly kind: "ignored";
132
+ } | {
133
+ readonly kind: "settled";
134
+ readonly batch_id: number;
135
+ readonly settled_event_keys: readonly string[];
136
+ readonly dropped_event_keys: readonly string[];
137
+ } | {
138
+ readonly kind: "split_scheduled";
139
+ readonly batch_id: number;
140
+ } | {
141
+ readonly kind: "dropped_oversized";
142
+ readonly batch_id: number;
143
+ readonly event_key: string;
144
+ } | {
145
+ readonly kind: "retry_scheduled";
146
+ readonly batch_id: number;
147
+ readonly delay_ms: number;
148
+ readonly resume_at_ms: number;
149
+ } | {
150
+ readonly kind: "retries_exhausted";
151
+ readonly batch_id: number;
152
+ } | {
153
+ readonly kind: "consent_revoked";
154
+ };
155
+ export type DroppedEventDetail = {
156
+ readonly event_index: number;
157
+ readonly reason: string;
158
+ readonly source_event_payload_fingerprint: string | null;
159
+ };
160
+ export type IngestVerdict = {
161
+ readonly accepted: boolean;
162
+ readonly observation_ingest_batch_id?: string;
163
+ readonly status?: string;
164
+ readonly reason?: string;
165
+ readonly retry_strategy?: string;
166
+ readonly submitted_event_count?: number;
167
+ readonly accepted_event_count?: number;
168
+ readonly dropped_event_count?: number;
169
+ readonly dropped_events?: readonly DroppedEventDetail[];
170
+ readonly duplicate?: boolean;
171
+ readonly error?: string;
172
+ readonly limits?: Readonly<Record<string, number>>;
173
+ };
174
+ export type DeliveryDecision = {
175
+ readonly outcome: string;
176
+ readonly action: "none" | "retry_same_batch" | "split_batch";
177
+ readonly result: IngestVerdict;
178
+ readonly response_contract: "valid" | "invalid" | "not_applicable";
179
+ readonly diagnostic_codes: readonly DiagnosticCode[];
180
+ readonly http_status?: number | null;
181
+ readonly used_status_fallback?: boolean;
182
+ readonly dropped_indexes?: readonly number[];
183
+ readonly privacy_policy?: unknown;
184
+ readonly directive_unreadable?: boolean;
185
+ };
186
+ export type DeliveryReport = {
187
+ readonly progress: DeliveryProgress;
188
+ readonly decision?: DeliveryDecision;
189
+ readonly diagnostics: readonly DiagnosticEntry[];
190
+ };
191
+ export type ResponseEvaluation = {
192
+ readonly kind: "decision";
193
+ readonly decision: DeliveryDecision;
194
+ } | {
195
+ readonly kind: "consent_revoked";
196
+ };
197
+ export declare const openConsent: (consent: ConsentState) => number;
198
+ export declare const readConsentState: (consentHandle: number) => ConsentState;
199
+ export declare const setConsent: (consentHandle: number, consent: ConsentState) => ConsentReport;
200
+ export declare const openDriver: (options: DriverOptions, consentHandle: number) => number;
201
+ export declare const closeDriver: (handle: number) => boolean;
202
+ export declare const capturePermit: (handle: number) => CollectionPermit;
203
+ export declare const permitsCapture: (handle: number, permit: CollectionPermit) => boolean;
204
+ export declare const rebindPermit: (handle: number, permit: CollectionPermit) => CollectionPermit;
205
+ export declare const enqueueEvent: (handle: number, event: QueuedEvent) => EnqueueReport;
206
+ export declare const adoptRestoredBatch: (handle: number, ordinal: number, events: readonly RestoredEvent[]) => RestoreReport;
207
+ export declare const releaseDrafts: (handle: number) => ReleaseReport;
208
+ export declare const queueDepth: (handle: number, envelopeOverheadBytes: number) => QueueDepthReport;
209
+ export declare const fenceQueue: (handle: number, envelopeOverheadBytes: number) => FenceReport;
210
+ export declare const planNextStep: (handle: number, nowMs: number, deadlineMs?: number) => DeliveryStep;
211
+ export declare const attemptRequest: (handle: number, input: {
212
+ readonly attemptId: number;
213
+ readonly body: string;
214
+ readonly timeoutMs: number;
215
+ readonly batchCanonicalBytes?: string;
216
+ }) => TransportRequest;
217
+ export declare const applyTransportResult: (handle: number, attemptId: number, outcome: TransportOutcome, nowMs: number) => DeliveryReport;
218
+ export declare const evaluateResponse: (outcome: TransportOutcome, localSubmittedEventCount: number) => ResponseEvaluation;
219
+ export declare const setBrowserToken: (handle: number, token: string | null) => boolean;
220
+ export declare const beginClose: (handle: number) => void;
221
+ export declare const pendingEventCount: (handle: number) => number;
222
+ export declare const diagnosticsSnapshot: (handle: number) => Readonly<Record<string, unknown>>;
223
+ export declare const recordHostDiagnostic: (handle: number, diagnostic: HostDiagnostic) => void;
224
+ export declare const eventFingerprint: (handle: number, canonicalBytes: string) => string;
225
+ export declare const batchFingerprint: (handle: number, canonicalBytes: string) => string;
@@ -0,0 +1,97 @@
1
+ import { deliveryCommandJson } from "./sdk-core-wasm.js";
2
+ const call = (request) => JSON.parse(deliveryCommandJson(JSON.stringify(request)));
3
+ export const openConsent = (consent) => call({ command: "open_consent", consent })
4
+ .consent;
5
+ export const readConsentState = (consentHandle) => call({
6
+ handle: consentHandle,
7
+ command: "consent_state",
8
+ }).consent;
9
+ export const setConsent = (consentHandle, consent) => call({
10
+ handle: consentHandle,
11
+ command: "set_consent",
12
+ consent,
13
+ });
14
+ export const openDriver = (options, consentHandle) => call({
15
+ command: "open",
16
+ options,
17
+ consent: consentHandle,
18
+ }).handle;
19
+ export const closeDriver = (handle) => call({ handle, command: "close" }).closed;
20
+ export const capturePermit = (handle) => call({ handle, command: "capture_permit" });
21
+ export const permitsCapture = (handle, permit) => call({
22
+ handle,
23
+ command: "permits_capture",
24
+ permit,
25
+ }).permitted;
26
+ export const rebindPermit = (handle, permit) => call({ handle, command: "rebind_permit", permit });
27
+ export const enqueueEvent = (handle, event) => call({ handle, command: "enqueue", event });
28
+ export const adoptRestoredBatch = (handle, ordinal, events) => call({
29
+ handle,
30
+ command: "adopt_restored_batch",
31
+ ordinal,
32
+ events,
33
+ });
34
+ export const releaseDrafts = (handle) => call({ handle, command: "release_drafts" });
35
+ export const queueDepth = (handle, envelopeOverheadBytes) => call({
36
+ handle,
37
+ command: "queue_depth",
38
+ envelope_overhead_bytes: envelopeOverheadBytes,
39
+ });
40
+ export const fenceQueue = (handle, envelopeOverheadBytes) => call({
41
+ handle,
42
+ command: "fence",
43
+ envelope_overhead_bytes: envelopeOverheadBytes,
44
+ });
45
+ export const planNextStep = (handle, nowMs, deadlineMs) => call({
46
+ handle,
47
+ command: "plan_next_step",
48
+ now_ms: nowMs,
49
+ ...(deadlineMs === undefined ? {} : { deadline_ms: deadlineMs }),
50
+ });
51
+ export const attemptRequest = (handle, input) => call({
52
+ handle,
53
+ command: "attempt_request",
54
+ attempt_id: input.attemptId,
55
+ body: input.body,
56
+ timeout_ms: input.timeoutMs,
57
+ ...(input.batchCanonicalBytes === undefined
58
+ ? {}
59
+ : { batch_canonical_bytes: input.batchCanonicalBytes }),
60
+ });
61
+ export const applyTransportResult = (handle, attemptId, outcome, nowMs) => call({
62
+ handle,
63
+ command: "apply_result",
64
+ result: { attempt_id: attemptId, outcome },
65
+ now_ms: nowMs,
66
+ });
67
+ export const evaluateResponse = (outcome, localSubmittedEventCount) => call({
68
+ command: "evaluate_response",
69
+ outcome,
70
+ local_submitted_event_count: localSubmittedEventCount,
71
+ surface: "frontend",
72
+ });
73
+ export const setBrowserToken = (handle, token) => call({ handle, command: "set_browser_token", token });
74
+ export const beginClose = (handle) => {
75
+ call({ handle, command: "begin_close" });
76
+ };
77
+ export const pendingEventCount = (handle) => call({
78
+ handle,
79
+ command: "pending_event_count",
80
+ }).pending_event_count;
81
+ export const diagnosticsSnapshot = (handle) => call({
82
+ handle,
83
+ command: "diagnostics_snapshot",
84
+ });
85
+ export const recordHostDiagnostic = (handle, diagnostic) => {
86
+ call({ handle, command: "record_diagnostic", diagnostic });
87
+ };
88
+ export const eventFingerprint = (handle, canonicalBytes) => call({
89
+ handle,
90
+ command: "event_fingerprint",
91
+ canonical_bytes: canonicalBytes,
92
+ }).fingerprint;
93
+ export const batchFingerprint = (handle, canonicalBytes) => call({
94
+ handle,
95
+ command: "batch_fingerprint",
96
+ canonical_bytes: canonicalBytes,
97
+ }).fingerprint;
@@ -0,0 +1,11 @@
1
+ import init from "../wasm/pkg-web/cluebase_sdk_core_wasm.js";
2
+ export type SdkCoreWasmInitInput = Parameters<typeof init>[0];
3
+ export declare const initSdkCoreWasm: (input?: SdkCoreWasmInitInput) => Promise<void>;
4
+ export declare const isSdkCoreWasmReady: () => boolean;
5
+ export declare const spanToObservationSourceEventJson: (spanJson: string, optionsJson: string) => string;
6
+ export declare const deliveryCommandJson: (requestJson: string) => string;
7
+ export type IngestEndpointDescription = {
8
+ readonly ingestBatchUrl: string;
9
+ readonly browserTokenUrl: string;
10
+ };
11
+ export declare const describeIngestEndpoint: (endpoint: string) => Promise<IngestEndpointDescription>;
@@ -0,0 +1,39 @@
1
+ import init, { deliveryCommandJson as wasmDeliveryCommandJson, spanToObservationSourceEventJson as wasmSpanToObservationSourceEventJson, } 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
+ const requireReady = () => {
14
+ if (!ready) {
15
+ throw new Error("cluebase sdk core wasm is not initialized; call initSdkCoreWasm() first");
16
+ }
17
+ };
18
+ export const spanToObservationSourceEventJson = (spanJson, optionsJson) => {
19
+ requireReady();
20
+ return wasmSpanToObservationSourceEventJson(spanJson, optionsJson);
21
+ };
22
+ export const deliveryCommandJson = (requestJson) => {
23
+ requireReady();
24
+ return wasmDeliveryCommandJson(requestJson);
25
+ };
26
+ export const describeIngestEndpoint = async (endpoint) => {
27
+ if (!isSdkCoreWasmReady()) {
28
+ await initSdkCoreWasm();
29
+ }
30
+ const described = JSON.parse(deliveryCommandJson(JSON.stringify({
31
+ command: "describe_endpoint",
32
+ endpoint,
33
+ surface: "frontend",
34
+ })));
35
+ return {
36
+ ingestBatchUrl: described.ingest_batch_url,
37
+ browserTokenUrl: described.browser_token_url,
38
+ };
39
+ };
@@ -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;
@@ -61,6 +62,7 @@ export type BrowserSdkSignal = {
61
62
  attributes: Record<string, CanonicalJsonValue>;
62
63
  }>;
63
64
  resource: Record<string, CanonicalJsonValue>;
65
+ raw_resource: Record<string, CanonicalJsonValue>;
64
66
  context: BrowserSdkSignalContext;
65
67
  };
66
68
  export type BrowserSourceSignalEvent = {
@@ -98,4 +100,5 @@ export type BrowserSourceSignalEvent = {
98
100
  sdk_signal: BrowserSdkSignal;
99
101
  };
100
102
  export type BrowserSourceEvent = BrowserSourceSignalEvent;
103
+ export declare const withSourceEvidence: (event: BrowserSourceEvent) => BrowserSourceEvent;
101
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)) {