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

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 (77) hide show
  1. package/README.md +11 -9
  2. package/dist/_generated/correlation-attribute-keys.js +4 -0
  3. package/dist/_generated/otel-span-conversion.d.ts +0 -34
  4. package/dist/_generated/otel-span-conversion.js +0 -32
  5. package/dist/context/action-context.d.ts +39 -0
  6. package/dist/context/action-context.js +318 -0
  7. package/dist/context/browser-storage.d.ts +0 -12
  8. package/dist/context/browser-storage.js +0 -13
  9. package/dist/context/identity.js +0 -4
  10. package/dist/context/organization.d.ts +0 -4
  11. package/dist/context/organization.js +0 -7
  12. package/dist/context/session.js +0 -3
  13. package/dist/context/tab.js +0 -5
  14. package/dist/context/trace.d.ts +13 -1
  15. package/dist/context/trace.js +119 -21
  16. package/dist/exporter/batch.d.ts +0 -12
  17. package/dist/exporter/batch.js +0 -27
  18. package/dist/exporter/browser-outbox.js +0 -17
  19. package/dist/exporter/client.d.ts +0 -15
  20. package/dist/exporter/client.js +0 -43
  21. package/dist/exporter/delivery-response-contract.js +0 -6
  22. package/dist/exporter/delivery-response.d.ts +0 -16
  23. package/dist/exporter/delivery-response.js +0 -19
  24. package/dist/exporter/frontend-resource.d.ts +0 -9
  25. package/dist/exporter/frontend-resource.js +0 -9
  26. package/dist/exporter/observation-source-event-exporter.d.ts +0 -28
  27. package/dist/exporter/observation-source-event-exporter.js +0 -18
  28. package/dist/exporter/parameter-snapshot.js +1 -24
  29. package/dist/exporter/raw-event-to-source-signal.js +18 -11
  30. package/dist/exporter/sanitize.d.ts +1 -12
  31. package/dist/exporter/sanitize.js +6 -39
  32. package/dist/exporter/source-event.d.ts +2 -0
  33. package/dist/exporter/span-to-source-signal.js +7 -3
  34. package/dist/exporter/text-fingerprint.d.ts +0 -8
  35. package/dist/exporter/text-fingerprint.js +0 -8
  36. package/dist/index.d.ts +0 -4
  37. package/dist/index.js +16 -44
  38. package/dist/instrumentation/action.js +3 -2
  39. package/dist/instrumentation/cluebase-authored-attributes.d.ts +0 -2
  40. package/dist/instrumentation/cluebase-authored-attributes.js +0 -23
  41. package/dist/instrumentation/common.d.ts +0 -63
  42. package/dist/instrumentation/common.js +0 -48
  43. package/dist/instrumentation/dom.d.ts +0 -25
  44. package/dist/instrumentation/dom.js +5 -41
  45. package/dist/instrumentation/drag.js +1 -2
  46. package/dist/instrumentation/form.js +1 -2
  47. package/dist/instrumentation/input.js +10 -8
  48. package/dist/instrumentation/naming-evidence.d.ts +0 -12
  49. package/dist/instrumentation/navigation.js +0 -2
  50. package/dist/instrumentation/network-request.d.ts +2 -2
  51. package/dist/instrumentation/network-request.js +1 -2
  52. package/dist/instrumentation/network-span-attributes.d.ts +0 -7
  53. package/dist/instrumentation/network-span-attributes.js +1 -3
  54. package/dist/instrumentation/network.js +6 -1
  55. package/dist/instrumentation/page.js +0 -2
  56. package/dist/instrumentation/performance.js +0 -7
  57. package/dist/integrations/react-router.js +0 -3
  58. package/dist/internal/internal-log.d.ts +0 -7
  59. package/dist/internal/internal-log.js +0 -11
  60. package/dist/provider/_version.d.ts +1 -1
  61. package/dist/provider/_version.js +1 -1
  62. package/dist/provider/config.d.ts +0 -7
  63. package/dist/provider/config.js +3 -16
  64. package/dist/provider/constants.d.ts +1 -17
  65. package/dist/provider/constants.js +1 -31
  66. package/dist/provider/privacy-policy-store.d.ts +0 -12
  67. package/dist/provider/privacy-policy-store.js +0 -12
  68. package/dist/provider/sdk-error.d.ts +7 -0
  69. package/dist/provider/sdk-error.js +48 -0
  70. package/dist/provider/sdk-event-pipeline.d.ts +1 -1
  71. package/dist/provider/sdk-event-pipeline.js +41 -54
  72. package/dist/provider/sdk-otel-pipeline.js +50 -4
  73. package/dist/provider/sdk-runtime-surfaces.js +3 -1
  74. package/dist/provider/sdk.js +18 -24
  75. package/dist/provider/types.d.ts +3 -39
  76. package/dist/provider/usage-governor.js +0 -2
  77. package/package.json +53 -58
@@ -1,11 +1,4 @@
1
1
  import { type Span } from "@opentelemetry/api";
2
- /**
3
- * 属性を書き込む側が、span に実際に求めるもの。
4
- *
5
- * **`Span` 全体(11 member)を求めていました。** ここで使うのは 2 つだけなので、
6
- * 試験の作り物は残りを持てず `as unknown as Span` で嘘をつくしかない状態でした
7
- * (`tests/README.md` §10)。3 つ目を使い始めたら、ここが変わって作り物が落ちます。
8
- */
9
2
  export type AttributeWritableSpan = {
10
3
  [K in "setAttribute" | "setStatus"]: (...args: Parameters<Span[K]>) => unknown;
11
4
  };
@@ -56,6 +56,7 @@ const setRequestSpanAttributes = (span, meta, eventName, status, observationCont
56
56
  span.setAttribute("cluebase.path_template", meta.pathTemplate);
57
57
  setOptionalString(span, "cluebase.target_service", meta.targetService);
58
58
  setOptionalString(span, "cluebase.parent_span_id", meta.parentSpanId);
59
+ setOptionalString(span, "cluebase.action.id", meta.actionId);
59
60
  setOptionalString(span, "cluebase.interaction.id", meta.interactionId);
60
61
  setOptionalString(span, "cluebase.interaction.initiator", meta.interactionInitiator);
61
62
  setOptionalString(span, "cluebase.interaction.start_event_kind", meta.interactionStartEventKind);
@@ -79,8 +80,6 @@ const setRequestSpanAttributes = (span, meta, eventName, status, observationCont
79
80
  setOptionalStringArray(span, cluebaseAuthoredPropertyAttribute("graphql_top_level_fields"), requestEvidence.graphqlTopLevelFields);
80
81
  setOptionalNumber(span, cluebaseAuthoredMetricAttribute("request_size_bytes"), meta.requestSizeBytes);
81
82
  span.setAttribute(cluebaseAuthoredPropertyAttribute("request_field_paths"), meta.queryKeys);
82
- // 顧客の値が入る面。**値は入れ物の中で伏せられている。**顧客が画面で名指しした
83
- // 項目だけが値を持ち、その項目名は下の控えに並ぶ。
84
83
  setJsonRecordAttribute(span, "cluebase.request_parameters_snapshot_json", meta.requestParametersSnapshot.snapshot);
85
84
  setJsonRecordAttribute(span, "cluebase.request_metadata_json", {
86
85
  ...meta.requestMetadataSummary,
@@ -142,7 +141,6 @@ export const setResponseSpanAttributes = (span, meta, input) => {
142
141
  deniedKeys: input.deniedKeys ?? [],
143
142
  });
144
143
  setJsonRecordAttribute(span, "cluebase.response_parameters_snapshot_json", responseParametersSnapshot.snapshot);
145
- // 平文で残した項目の控え。面をまたいで 1 本にする。空なら属性を出さない。
146
144
  setOptionalStringArray(span, "cluebase.privacy.allowed_keys", [
147
145
  ...new Set([
148
146
  ...meta.requestParametersSnapshot.allowedValueKeys,
@@ -18,7 +18,6 @@ let fetchInstrumentation = null;
18
18
  let xhrInstrumentation = null;
19
19
  let cluebaseNetworkPropagatorRegistered = false;
20
20
  const REQUEST_LIFECYCLE_ORPHAN_TIMEOUT_MS = 30000;
21
- // OTel fetch waits 300ms for browser resource timing before ending spans.
22
21
  const FETCH_SPAN_END_SETTLE_MS = 350;
23
22
  const pendingFetchSpanEndWaits = new Set();
24
23
  class ManagedFetchInstrumentation extends FetchInstrumentation {
@@ -70,6 +69,9 @@ const toTraceHeaders = (capture, traceContext) => {
70
69
  if (traceContext.parentSpanId) {
71
70
  headers["x-cluebase-parent-span-id"] = traceContext.parentSpanId;
72
71
  }
72
+ if (traceContext.actionId) {
73
+ headers["x-cluebase-action-id"] = traceContext.actionId;
74
+ }
73
75
  if (traceContext.interactionId) {
74
76
  headers["x-cluebase-interaction-id"] = traceContext.interactionId;
75
77
  }
@@ -99,6 +101,7 @@ const traceContextFromMeta = (meta) => ({
99
101
  parentSpanId: meta.parentSpanId,
100
102
  requestId: meta.requestId,
101
103
  requestSpanId: meta.requestSpanId,
104
+ actionId: meta.actionId,
102
105
  interactionId: meta.interactionId,
103
106
  interactionInitiator: meta.interactionInitiator,
104
107
  interactionStartEventKind: meta.interactionStartEventKind,
@@ -108,6 +111,7 @@ const CLUEBASE_NETWORK_PROPAGATION_FIELDS = [
108
111
  "x-cluebase-trace-id",
109
112
  "x-cluebase-request-id",
110
113
  "x-cluebase-request-span-id",
114
+ "x-cluebase-action-id",
111
115
  "x-cluebase-parent-span-id",
112
116
  "x-cluebase-interaction-id",
113
117
  "x-cluebase-origin-frontend-interaction-id",
@@ -471,6 +475,7 @@ export const startNetworkCapture = (context, options = {}) => {
471
475
  parentSpanId: null,
472
476
  requestId: generateRequestId(),
473
477
  requestSpanId: generateRequestSpanId(),
478
+ actionId: null,
474
479
  interactionId: generateInteractionId(),
475
480
  interactionInitiator: null,
476
481
  interactionStartEventKind: null,
@@ -4,8 +4,6 @@ export const startPageCapture = (context) => {
4
4
  return { stop: () => undefined };
5
5
  }
6
6
  let pageEnteredAtMs = Date.now();
7
- // Page-view events are emitted from OTel navigation/document-load signals.
8
- // This module only keeps Cluebase-specific time-on-page evidence.
9
7
  const handlePageLeave = () => {
10
8
  if (context.getConsent() === "denied") {
11
9
  return;
@@ -92,7 +92,6 @@ const observeEntryType = (observers, entryType, callback) => {
92
92
  observers.push(observer);
93
93
  }
94
94
  catch {
95
- // Some browsers expose an entry type but still reject buffered observation.
96
95
  }
97
96
  };
98
97
  const readPerformanceNavigation = () => {
@@ -233,9 +232,6 @@ export const startPerformanceCapture = (context) => {
233
232
  occurredAtMs: Date.now(),
234
233
  payload: {
235
234
  viewId,
236
- // must strip query string to avoid leaking OAuth
237
- // callback / password-reset / magic-link tokens that may
238
- // live in `window.location.href`. See `instrumentation/common.ts`.
239
235
  pageUrl: currentPageUrl() ?? "",
240
236
  collectorName: "performance_rum",
241
237
  aggregationKind: "per_view_window",
@@ -305,9 +301,6 @@ export const startPerformanceCapture = (context) => {
305
301
  window.addEventListener("pointerdown", interactionHandler, true);
306
302
  window.addEventListener("pagehide", pageHideHandler, true);
307
303
  document.addEventListener("visibilitychange", visibilityHandler, true);
308
- // Long tasks and resource load failures are retained as Cluebase summary-only
309
- // UX evidence because the accepted browser OTel modules above cover
310
- // document load and Web Vitals, not this per-view aggregate.
311
304
  observeEntryType(observers, "longtask", (list) => {
312
305
  for (const entry of list.getEntries()) {
313
306
  const nearInteraction = state.lastInteractionAtMs !== null &&
@@ -18,9 +18,6 @@ export const attachReactRouterIntegration = (options) => {
18
18
  options.onRouteChange(lastPath, nextPath);
19
19
  lastPath = nextPath;
20
20
  };
21
- // Idempotent guard for Cluebase-owned History API wrappers. OTel navigation
22
- // instrumentation may patch history separately and remains the primary
23
- // navigation source.
24
21
  const sentinelPushState = history.pushState;
25
22
  const sentinelReplaceState = history.replaceState;
26
23
  const alreadyWrapped = isHistoryAlreadyCluebaseWrapped(history);
@@ -1,10 +1,3 @@
1
- /**
2
- * SDK-internal diagnostic logger for the Frontend SDK.
3
- *
4
- * Mirror of Node SDK ``internal-log.ts`` and Python SDK ``_internal_log.py``.
5
- * Exposes a single configurable {@link InternalLogger}. Default is a no-op
6
- * so the SDK does not write to the host page's console.
7
- */
8
1
  export type InternalLogContext = {
9
2
  [key: string]: string | number | boolean | null;
10
3
  };
@@ -1,10 +1,3 @@
1
- /**
2
- * SDK-internal diagnostic logger for the Frontend SDK.
3
- *
4
- * Mirror of Node SDK ``internal-log.ts`` and Python SDK ``_internal_log.py``.
5
- * Exposes a single configurable {@link InternalLogger}. Default is a no-op
6
- * so the SDK does not write to the host page's console.
7
- */
8
1
  const SAFE_CONTEXT_KEYS = new Set([
9
2
  "attempt",
10
3
  "code",
@@ -39,9 +32,5 @@ export function logSwallowed(operation, _cause, context = {}) {
39
32
  details[key] = value;
40
33
  }
41
34
  }
42
- // Exception text and stacks may contain customer payloads, credentials, or
43
- // source paths. The internal logger is an optional diagnostic sink, so it
44
- // keeps only bounded caller-provided classification fields, matching the
45
- // backend SDKs and the API/worker log contract.
46
35
  currentLogger.debug(`cluebase.sdk swallowed ${operation}`, details);
47
36
  }
@@ -1 +1 @@
1
- export declare const SDK_VERSION: "0.0.1";
1
+ export declare const SDK_VERSION: "0.0.3";
@@ -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.1";
3
+ export const SDK_VERSION = "0.0.3";
@@ -1,11 +1,4 @@
1
1
  import type { CluebaseInitOptions, ResolvedCluebaseConfig } from "./types.js";
2
- /**
3
- * Read an address Cluebase is able to send to, or `null` when it cannot.
4
- *
5
- * `cluebase.init` refuses such an endpoint loudly and `resolveConfig` drops it to
6
- * `""`; the two reactions differ but the question does not, so neither restates
7
- * the rule.
8
- */
9
2
  export declare const parseSendableUrl: (value: string) => URL | null;
10
3
  export declare const resolveConfig: (options: CluebaseInitOptions) => ResolvedCluebaseConfig;
11
4
  export declare const validateConfig: (config: ResolvedCluebaseConfig) => string[];
@@ -1,4 +1,4 @@
1
- import { ALWAYS_DENIED_KEYS, DEFAULT_CONFIG, DEFAULT_FRONTEND_SOURCE_IDENTIFIER, MAX_DIAGNOSTIC_TTL_MS, } from "./constants.js";
1
+ import { ALWAYS_DENIED_KEYS, DEFAULT_API_BASE_URL, DEFAULT_CONFIG, DEFAULT_FRONTEND_SOURCE_IDENTIFIER, MAX_DIAGNOSTIC_TTL_MS, } from "./constants.js";
2
2
  import { SDK_COLLECTION_MODE } from "./contracts.js";
3
3
  const normalizeKeyList = (keys) => {
4
4
  if (!keys || keys.length === 0) {
@@ -64,17 +64,7 @@ const normalizeDiagnosticConfig = (value) => {
64
64
  enabledBy: value?.enabledBy?.trim() || null,
65
65
  };
66
66
  };
67
- // The protocols Cluebase can actually deliver over. Every place that rejects an
68
- // address reads this one set, so a browser-only scheme cannot be refused here
69
- // and accepted there.
70
67
  const SENDABLE_PROTOCOLS = new Set(["http:", "https:"]);
71
- /**
72
- * Read an address Cluebase is able to send to, or `null` when it cannot.
73
- *
74
- * `cluebase.init` refuses such an endpoint loudly and `resolveConfig` drops it to
75
- * `""`; the two reactions differ but the question does not, so neither restates
76
- * the rule.
77
- */
78
68
  export const parseSendableUrl = (value) => {
79
69
  try {
80
70
  const parsed = new URL(value.trim());
@@ -90,8 +80,6 @@ const normalizeTracePropagationOrigin = (value) => {
90
80
  if (!trimmed) {
91
81
  return null;
92
82
  }
93
- // Unlike an endpoint, an origin that does not parse is kept as written: the
94
- // caller matches these against request URLs itself.
95
83
  try {
96
84
  const parsed = new URL(trimmed);
97
85
  return SENDABLE_PROTOCOLS.has(parsed.protocol) ? parsed.origin : null;
@@ -100,10 +88,9 @@ const normalizeTracePropagationOrigin = (value) => {
100
88
  return trimmed;
101
89
  }
102
90
  };
103
- // The SDK does not emit a ProjectService environment. The server resolves
104
- // dev/prod from the `projectKey` prefix and ProjectService.
105
91
  export const resolveConfig = (options) => {
106
- const originalEndpoint = normalizeConfiguredEndpoint(options.endpoint);
92
+ const configuredEndpoint = options.endpoint?.trim() || DEFAULT_API_BASE_URL;
93
+ const originalEndpoint = normalizeConfiguredEndpoint(configuredEndpoint);
107
94
  const customDenied = normalizeKeyList(options.privacy?.deniedKeys);
108
95
  const deniedKeys = [...new Set([...ALWAYS_DENIED_KEYS, ...customDenied])];
109
96
  const tracePropagationOrigins = [
@@ -7,6 +7,7 @@ export declare const EVENT_VERSION = 1;
7
7
  export declare const SOURCE_SCHEMA_VERSION = "1";
8
8
  export declare const SURFACE_TYPE: "web";
9
9
  export declare const DEFAULT_FRONTEND_SOURCE_IDENTIFIER = "frontend";
10
+ export declare const DEFAULT_API_BASE_URL = "https://api.cluebase.io";
10
11
  export declare const MASKED_VALUE = "__cluebase_masked__";
11
12
  export declare const FORBIDDEN_PARAMETER_VALUE = "__forbidden__";
12
13
  export declare const TRUNCATED_PARAMETER_VALUE = "__truncated__";
@@ -32,23 +33,6 @@ export declare const STORAGE_KEYS: {
32
33
  readonly sessionStartedAt: "cluebase:session_started_at";
33
34
  readonly sessionLastActivityAt: "cluebase:session_last_activity_at";
34
35
  };
35
- /**
36
- * SDK-side denied keys. Stripped from event properties / metrics /
37
- * traits / headers / body fragments before the value is set as an OTel
38
- * span attribute or attached to a source-signal envelope.
39
- *
40
- * This list is a strict superset of the server-side hard-deny list
41
- * (`INGEST_HARD_DENY_KEYS`) **by construction**: both are emitted from the
42
- * same canonical data file,
43
- * `packages/cluebase-shared/src/ingest/ingest-privacy-policy.v1.json`.
44
- * It is not a hand-kept copy asserted by a parity test any more.
45
- *
46
- * `@cluebase/shared` is `private: true` and the published SDK is built with
47
- * `tsc` alone (no bundling), so the SDK cannot import it at customer
48
- * runtime. The shared-contract generator therefore writes the table into
49
- * this package instead; the generated file's banner names the command to
50
- * re-run. Do not re-declare the list, or the command, here.
51
- */
52
36
  export declare const ALWAYS_DENIED_KEYS: readonly string[];
53
37
  export declare const MAX_SCHEMA_DEPTH = 6;
54
38
  export declare const MAX_SCHEMA_KEYS = 512;
@@ -1,9 +1,6 @@
1
1
  import { SDK_ALWAYS_DENIED_KEYS } from "../_generated/ingest-privacy-tables.js";
2
2
  import { SDK_RETRY_POLICY } from "../_generated/sdk-runtime-policy.js";
3
3
  import { SDK_VERSION } from "./_version.js";
4
- // SDK_VERSION SoT is `package.json` `version`, generated into
5
- // `_version.ts` by `scripts/generate-version-module.mjs` at build time.
6
- // Re-export it here so call sites keep the existing import path.
7
4
  export { SDK_VERSION };
8
5
  export const SDK_NAME = "@genn-inc/cluebase-frontend-sdk";
9
6
  export const EVENT_SOURCE = "frontend_sdk";
@@ -11,18 +8,11 @@ export const EVENT_VERSION = 1;
11
8
  export const SOURCE_SCHEMA_VERSION = "1";
12
9
  export const SURFACE_TYPE = "web";
13
10
  export const DEFAULT_FRONTEND_SOURCE_IDENTIFIER = "frontend";
14
- // Canonical mask sentinel — matches Node SDK / Python SDK / `@cluebase/shared`
15
- // (`__cluebase_masked__`). Aligning across the 3 SDKs lets downstream analytics
16
- // queries and redaction UIs detect masked values by a single string literal.
11
+ export const DEFAULT_API_BASE_URL = "https://api.cluebase.io";
17
12
  export const MASKED_VALUE = "__cluebase_masked__";
18
13
  export const FORBIDDEN_PARAMETER_VALUE = "__forbidden__";
19
14
  export const TRUNCATED_PARAMETER_VALUE = "__truncated__";
20
15
  export const DEFAULT_CONSENT = "unknown";
21
- // 送信中の取りやめが「同意の取り消し」だったのかを、中断の理由で見分けます。
22
- // 立てる側 (`batch.ts`) と読む側 (`client.ts`) が別の module なので、字面を
23
- // 両方へ書くと片方だけずれても誰も気づきません。**ずれると取り消し後の束が
24
- // 破棄ではなく再送**になり、利用者が拒否した後の観測が顧客のサイトから出ます。
25
- // 正本を 1 つにして、両端がここを読みます (AGENTS §0.2)。
26
16
  export const CONSENT_DENIED_ABORT_REASON = "consent_denied";
27
17
  export const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000;
28
18
  export const DEFAULT_LIFECYCLE_INTERACTION_GRACE_WINDOW_MS = 30000;
@@ -30,9 +20,6 @@ export const DEFAULT_MAX_EVENT_PAYLOAD_BYTES = 48 * 1024;
30
20
  export const DEFAULT_BATCH_FLUSH_THRESHOLD_BYTES = 48 * 1024;
31
21
  export const DEFAULT_MAX_BATCH_PAYLOAD_BYTES = 60 * 1024;
32
22
  export const DEFAULT_MAX_BATCH_QUEUE_BYTES = 300 * 1024;
33
- // EventQueue の保持件数上限。 maxBytes (300KB) と二重にガードして OOM を防ぐ。
34
- // queueMaxBytes は payload size 起点で削るが、件数ベースの暴走 (極小 event の連射等)
35
- // を捕捉するために件数上限も併設する。
36
23
  export const DEFAULT_MAX_BATCH_QUEUE_ITEMS = 5000;
37
24
  export const DEFAULT_DIAGNOSTIC_TTL_MS = 600000;
38
25
  export const MAX_DIAGNOSTIC_TTL_MS = 1800000;
@@ -47,23 +34,6 @@ export const STORAGE_KEYS = {
47
34
  sessionStartedAt: "cluebase:session_started_at",
48
35
  sessionLastActivityAt: "cluebase:session_last_activity_at",
49
36
  };
50
- /**
51
- * SDK-side denied keys. Stripped from event properties / metrics /
52
- * traits / headers / body fragments before the value is set as an OTel
53
- * span attribute or attached to a source-signal envelope.
54
- *
55
- * This list is a strict superset of the server-side hard-deny list
56
- * (`INGEST_HARD_DENY_KEYS`) **by construction**: both are emitted from the
57
- * same canonical data file,
58
- * `packages/cluebase-shared/src/ingest/ingest-privacy-policy.v1.json`.
59
- * It is not a hand-kept copy asserted by a parity test any more.
60
- *
61
- * `@cluebase/shared` is `private: true` and the published SDK is built with
62
- * `tsc` alone (no bundling), so the SDK cannot import it at customer
63
- * runtime. The shared-contract generator therefore writes the table into
64
- * this package instead; the generated file's banner names the command to
65
- * re-run. Do not re-declare the list, or the command, here.
66
- */
67
37
  export const ALWAYS_DENIED_KEYS = SDK_ALWAYS_DENIED_KEYS;
68
38
  export const MAX_SCHEMA_DEPTH = 6;
69
39
  export const MAX_SCHEMA_KEYS = 512;
@@ -1,18 +1,6 @@
1
- /**
2
- * 届いた方針を適用する。**契約に合わない形は捨てて、伏せた側へ倒れます。**
3
- * 途中まで読めた分を活かすと、顧客が決めていない項目を平文にし得ます。
4
- */
5
1
  export declare const applyDeliveredPrivacyPolicy: (policy: unknown) => void;
6
- /** まだ何も適用していない状態へ戻す。= 全部伏せる。 */
7
2
  export declare const resetPrivacyPolicy: () => void;
8
- /** 塊の要求に載せる合印。持っていなければ載せない。 */
9
3
  export declare const appliedPrivacyPolicyToken: () => string | undefined;
10
- /**
11
- * この入口・この向きで、顧客が平文にした項目名。
12
- *
13
- * **入口が決まらない時は空を返します。**曖昧に当てると、顧客が 1 つの入口について
14
- * 決めたことが、関係のない通信まで平文にします。
15
- */
16
4
  export declare const allowedValuePathsFor: (input: {
17
5
  method: string | null | undefined;
18
6
  pathTemplate: string | null | undefined;
@@ -2,10 +2,6 @@ import { ingestPrivacyPolicySchema, } from "../exporter/delivery-response-contra
2
2
  const policyKey = (input) => `${input.direction} ${input.method.toUpperCase()} ${input.pathTemplate}`;
3
3
  let appliedToken = null;
4
4
  let allowedPathsByEntrance = new Map();
5
- /**
6
- * 届いた方針を適用する。**契約に合わない形は捨てて、伏せた側へ倒れます。**
7
- * 途中まで読めた分を活かすと、顧客が決めていない項目を平文にし得ます。
8
- */
9
5
  export const applyDeliveredPrivacyPolicy = (policy) => {
10
6
  const parsed = ingestPrivacyPolicySchema.safeParse(policy);
11
7
  if (!parsed.success) {
@@ -32,19 +28,11 @@ const setPrivacyPolicy = (policy) => {
32
28
  allowedPathsByEntrance = next;
33
29
  appliedToken = policy.token;
34
30
  };
35
- /** まだ何も適用していない状態へ戻す。= 全部伏せる。 */
36
31
  export const resetPrivacyPolicy = () => {
37
32
  allowedPathsByEntrance = new Map();
38
33
  appliedToken = null;
39
34
  };
40
- /** 塊の要求に載せる合印。持っていなければ載せない。 */
41
35
  export const appliedPrivacyPolicyToken = () => appliedToken ?? undefined;
42
- /**
43
- * この入口・この向きで、顧客が平文にした項目名。
44
- *
45
- * **入口が決まらない時は空を返します。**曖昧に当てると、顧客が 1 つの入口について
46
- * 決めたことが、関係のない通信まで平文にします。
47
- */
48
36
  export const allowedValuePathsFor = (input) => {
49
37
  if (!input.method || !input.pathTemplate)
50
38
  return [];
@@ -0,0 +1,7 @@
1
+ export type SdkErrorDetails = {
2
+ error_name: string;
3
+ error_message: string;
4
+ error_stack?: string;
5
+ };
6
+ export declare const getSdkErrorDetails: (error: unknown) => SdkErrorDetails;
7
+ export declare const reportSdkError: (operation: string, error: unknown) => SdkErrorDetails;
@@ -0,0 +1,48 @@
1
+ const redactSensitiveText = (value) => value
2
+ .replace(/\bpk_[A-Za-z0-9_-]+\b/g, "[redacted-project-key]")
3
+ .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/-]+=*/gi, "$1 [redacted]")
4
+ .replace(/(https?:\/\/[^\s?]+)\?[^\s]*/gi, "$1?[redacted]")
5
+ .slice(0, 500);
6
+ const readErrorField = (error, field) => {
7
+ try {
8
+ const value = error?.[field];
9
+ return typeof value === "string" ? value : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ };
15
+ export const getSdkErrorDetails = (error) => {
16
+ let fallbackMessage;
17
+ try {
18
+ fallbackMessage = String(error);
19
+ }
20
+ catch {
21
+ fallbackMessage = "Unknown thrown value";
22
+ }
23
+ const stack = readErrorField(error, "stack");
24
+ return {
25
+ error_name: readErrorField(error, "name") || "UnknownError",
26
+ error_message: redactSensitiveText(readErrorField(error, "message") || fallbackMessage),
27
+ ...(stack
28
+ ? {
29
+ error_stack: stack
30
+ .split(/\r?\n/)
31
+ .slice(1, 9)
32
+ .map(redactSensitiveText)
33
+ .join("\n"),
34
+ }
35
+ : {}),
36
+ };
37
+ };
38
+ export const reportSdkError = (operation, error) => {
39
+ const details = getSdkErrorDetails(error);
40
+ try {
41
+ if (typeof console !== "undefined" && typeof console.error === "function") {
42
+ const stack = details.error_stack ? `\n${details.error_stack}` : "";
43
+ console.error(`[cluebase] ${operation} failed (${details.error_name}): ${details.error_message}${stack}`);
44
+ }
45
+ }
46
+ catch { }
47
+ return details;
48
+ };
@@ -49,7 +49,7 @@ export declare class ProviderEventPipeline {
49
49
  enqueueRawEvent(event: RawCapturedEvent, skipSessionTouch?: boolean): void;
50
50
  applyUsageGovernorFeedback(feedback: IngestUsageGovernorFeedback | null | undefined): void;
51
51
  refreshSessionCaptureEligibility(): void;
52
- buildContext(): CluebaseContext;
52
+ buildContext(event?: RawCapturedEvent): CluebaseContext;
53
53
  private attachInteractionContext;
54
54
  private attachCurrentLocation;
55
55
  private handleSessionLifecycle;
@@ -4,7 +4,6 @@ import { rawEventToSourceSignalEvent } from "../exporter/raw-event-to-source-sig
4
4
  import { createFrontendResourceAttributes } from "../exporter/frontend-resource.js";
5
5
  import { sanitizeObject, } from "../exporter/sanitize.js";
6
6
  import { DEFAULT_INGEST_USAGE_GOVERNOR_FEEDBACK, hasUsageGovernorFeedbackChanged, isCostCapabilityDisabled, normalizeIngestUsageGovernorFeedback, } from "./usage-governor.js";
7
- import { DEFAULT_LIFECYCLE_INTERACTION_GRACE_WINDOW_MS, } from "./constants.js";
8
7
  import { FRONTEND_EVENT_NAME } from "./contracts.js";
9
8
  import { shouldSampleSession } from "./sampling.js";
10
9
  import { stripQueryAndHashFromUrl, toStringOrNull, toSubjectProfile, } from "./sdk-input-normalization.js";
@@ -20,13 +19,6 @@ const TRACE_ROOT_EVENTS = new Set([
20
19
  "drag_drop_completed",
21
20
  "custom_emitted",
22
21
  ]);
23
- const SUBJECT_PROFILE_TRAIT_KEYS = new Set([
24
- "name",
25
- "displayName",
26
- "email",
27
- "emailAddress",
28
- "avatarUrl",
29
- ]);
30
22
  export const OFFICIAL_CUSTOM_EVENT_NAME = {
31
23
  identityIdentified: "identity_identified",
32
24
  identityReset: "identity_reset",
@@ -146,7 +138,7 @@ export class ProviderEventPipeline {
146
138
  }
147
139
  }
148
140
  try {
149
- const context = this.buildContext();
141
+ const context = this.buildContext(eventWithLocation);
150
142
  if (SUMMARY_EVENT_NAMES.has(eventWithLocation.type)) {
151
143
  const summaryCount = eventWithLocation.payload.summaryCount;
152
144
  const droppedCount = eventWithLocation.payload.droppedCount;
@@ -211,7 +203,7 @@ export class ProviderEventPipeline {
211
203
  const rate = config.sampling.sessionSampleRate;
212
204
  this.sessionCaptureEligible = shouldSampleSession(rate);
213
205
  }
214
- buildContext() {
206
+ buildContext(event) {
215
207
  const runtime = this.runtime;
216
208
  if (!runtime) {
217
209
  throw new Error("SDK is not initialized");
@@ -221,6 +213,9 @@ export class ProviderEventPipeline {
221
213
  const organization = runtime.organizationManager.getContext();
222
214
  const environment = runtime.environmentManager.getContext();
223
215
  const trace = runtime.traceManager.getSnapshot();
216
+ const actionContext = runtime.traceManager.getBoundActionContext();
217
+ const eventActionId = toStringOrNull(event?.payload.actionId);
218
+ const eventInteractionId = toStringOrNull(event?.payload.interactionId);
224
219
  const requestContext = runtime.traceManager.createRequestContext() ??
225
220
  new TraceManager().createRequestContext();
226
221
  const privacySummary = {
@@ -228,9 +223,9 @@ export class ProviderEventPipeline {
228
223
  droppedFieldCount: 0,
229
224
  maskedFields: new Set(),
230
225
  };
231
- const subjectTraits = sanitizeContextTraits(identity.traits, runtime.config.privacy.deniedKeys, SUBJECT_PROFILE_TRAIT_KEYS, privacySummary, "subject.trait");
232
- const organizationTraits = sanitizeContextTraits(organization.traits, runtime.config.privacy.deniedKeys, new Set(), privacySummary, "organization.trait");
233
- const contextTraits = sanitizeContextTraits(environment.contextTraits, runtime.config.privacy.deniedKeys, new Set(), privacySummary, "context.trait");
226
+ const subjectTraits = sanitizeContextTraits(identity.traits, runtime.config.privacy.deniedKeys, new Set(), privacySummary, "subject.trait", false, true);
227
+ const organizationTraits = sanitizeContextTraits(organization.traits, runtime.config.privacy.deniedKeys, new Set(), privacySummary, "organization.trait", false, true);
228
+ const contextTraits = sanitizeContextTraits(environment.contextTraits, runtime.config.privacy.deniedKeys, new Set(), privacySummary, "context.trait", false, true);
234
229
  return {
235
230
  producer_id: runtime.config.producerId,
236
231
  anonymous_id: identity.anonymousId,
@@ -239,10 +234,12 @@ export class ProviderEventPipeline {
239
234
  user_profile: toSubjectProfile(identity.traits),
240
235
  session_id: session.sessionId,
241
236
  tab_id: runtime.tabManager.getTabId(),
242
- trace_id: trace.traceId ?? requestContext.traceId,
237
+ trace_id: actionContext?.traceId ?? trace.traceId ?? requestContext.traceId,
243
238
  request_id: requestContext.requestId,
244
239
  request_span_id: requestContext.requestSpanId,
245
- interaction_id: runtime.traceManager.getActiveInteractionId() ??
240
+ action_id: eventActionId ?? actionContext?.actionId ?? requestContext.actionId,
241
+ interaction_id: eventInteractionId ??
242
+ actionContext?.interactionId ??
246
243
  requestContext.interactionId,
247
244
  correlation_id: null,
248
245
  sdk_collection_mode: runtime.config.collection.mode,
@@ -283,49 +280,42 @@ export class ProviderEventPipeline {
283
280
  attachInteractionContext(event) {
284
281
  const traceManager = this.runtime?.traceManager;
285
282
  const nowMs = event.occurredAtMs ?? Date.now();
286
- const payloadInteractionId = toStringOrNull(event.payload.interactionId);
283
+ const actionContext = traceManager?.getBoundActionContext();
284
+ const payloadInteractionId = toStringOrNull(event.payload.interactionId) ??
285
+ actionContext?.interactionId ??
286
+ null;
287
+ const payloadActionId = toStringOrNull(event.payload.actionId) ?? actionContext?.actionId ?? null;
287
288
  const occurrenceKind = "frontend_action";
288
289
  if (event.type === "page_view") {
289
- const interactionId = traceManager?.startFrontendAction({
290
- interactionId: payloadInteractionId,
291
- interactionInitiator: toStringOrNull(event.payload.interactionInitiator) ?? "page_visit",
292
- interactionStartEventKind: toStringOrNull(event.payload.interactionStartEventKind) ??
293
- "page_view",
294
- viewId: toStringOrNull(event.payload.viewId),
295
- nowMs,
296
- }) ?? payloadInteractionId;
297
- if (!interactionId)
290
+ if (!payloadActionId && !payloadInteractionId)
298
291
  return event;
299
292
  return {
300
293
  ...event,
301
294
  payload: {
302
295
  ...event.payload,
303
- interactionId,
304
- interactionInitiator: toStringOrNull(event.payload.interactionInitiator) ?? "page_visit",
305
- interactionStartEventKind: toStringOrNull(event.payload.interactionStartEventKind) ??
306
- "page_view",
307
- occurrenceKind,
296
+ ...(payloadActionId ? { actionId: payloadActionId } : {}),
297
+ ...(payloadInteractionId
298
+ ? { interactionId: payloadInteractionId }
299
+ : {}),
308
300
  },
309
301
  };
310
302
  }
311
303
  if (event.type === "page_leave") {
312
- const interactionId = payloadInteractionId ??
313
- traceManager?.getActiveInteractionId() ??
314
- null;
315
- if (!interactionId)
304
+ if (!payloadActionId && !payloadInteractionId)
316
305
  return event;
317
- traceManager?.endFrontendAction(interactionId);
318
306
  return {
319
307
  ...event,
320
308
  payload: {
321
309
  ...event.payload,
322
- interactionId,
323
- occurrenceKind,
310
+ ...(payloadActionId ? { actionId: payloadActionId } : {}),
311
+ ...(payloadInteractionId
312
+ ? { interactionId: payloadInteractionId }
313
+ : {}),
324
314
  },
325
315
  };
326
316
  }
327
317
  if (isOfficialCustomLifecycleEvent(event)) {
328
- const lifecycleInteraction = traceManager?.getRecentInteractionSnapshot(nowMs, DEFAULT_LIFECYCLE_INTERACTION_GRACE_WINDOW_MS) ?? null;
318
+ const lifecycleInteraction = traceManager?.getBoundActionContext() ?? null;
329
319
  const interactionId = payloadInteractionId ?? lifecycleInteraction?.interactionId ?? null;
330
320
  if (!interactionId)
331
321
  return event;
@@ -333,6 +323,11 @@ export class ProviderEventPipeline {
333
323
  ...event,
334
324
  payload: {
335
325
  ...event.payload,
326
+ ...(lifecycleInteraction?.actionId
327
+ ? { actionId: lifecycleInteraction.actionId }
328
+ : payloadActionId
329
+ ? { actionId: payloadActionId }
330
+ : {}),
336
331
  interactionId,
337
332
  occurrenceKind,
338
333
  interactionInitiator: toStringOrNull(event.payload.interactionInitiator) ??
@@ -364,6 +359,11 @@ export class ProviderEventPipeline {
364
359
  ...event,
365
360
  payload: {
366
361
  ...event.payload,
362
+ ...(traceManager?.getActiveActionId()
363
+ ? { actionId: traceManager.getActiveActionId() }
364
+ : payloadActionId
365
+ ? { actionId: payloadActionId }
366
+ : {}),
367
367
  interactionId,
368
368
  occurrenceKind,
369
369
  interactionInitiator: toStringOrNull(event.payload.interactionInitiator) ??
@@ -373,20 +373,7 @@ export class ProviderEventPipeline {
373
373
  },
374
374
  };
375
375
  }
376
- const activeInteractionId = payloadInteractionId ??
377
- traceManager?.getActiveInteractionId() ??
378
- null;
379
- if (!activeInteractionId || activeInteractionId === payloadInteractionId) {
380
- return event;
381
- }
382
- return {
383
- ...event,
384
- payload: {
385
- ...event.payload,
386
- interactionId: activeInteractionId,
387
- occurrenceKind,
388
- },
389
- };
376
+ return event;
390
377
  }
391
378
  attachCurrentLocation(event) {
392
379
  const payload = event.payload;
@@ -469,8 +456,8 @@ export class ProviderEventPipeline {
469
456
  traceManager.setActiveSpan(eventId);
470
457
  }
471
458
  }
472
- function sanitizeContextTraits(traits, deniedKeys, excludedKeys = new Set(), summary, pathPrefix = "") {
473
- const sanitized = sanitizeObject(traits, deniedKeys, summary, pathPrefix);
459
+ function sanitizeContextTraits(traits, deniedKeys, excludedKeys = new Set(), summary, pathPrefix = "", maskIdentityPii = true, preserveExplicitTraits = false) {
460
+ const sanitized = sanitizeObject(traits, deniedKeys, summary, pathPrefix, maskIdentityPii, preserveExplicitTraits);
474
461
  if (typeof sanitized !== "object" || sanitized === null || Array.isArray(sanitized)) {
475
462
  return {};
476
463
  }