@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
package/README.md CHANGED
@@ -31,6 +31,27 @@ environment.
31
31
  appends `/api/v1/ingest/browser-tokens` and the canonical browser observation
32
32
  batch path internally.
33
33
 
34
+ ## Content Security Policy requirement
35
+
36
+ The SDK converts each OpenTelemetry span through a WebAssembly module, so a
37
+ host page that sends a `Content-Security-Policy` header must allow it:
38
+
39
+ - `script-src` must include `'wasm-unsafe-eval'`. Without it the browser blocks
40
+ `WebAssembly.instantiate` and the module never initializes. `'unsafe-eval'`
41
+ also works but is a far wider grant, so prefer `'wasm-unsafe-eval'`.
42
+ - `connect-src` must allow the origin that serves the bundled
43
+ `cluebase_sdk_core_wasm_bg.wasm` asset (usually `'self'`), because the module
44
+ is fetched at runtime relative to the SDK bundle.
45
+ - `connect-src` must also allow the Cluebase API origin (`endpoint`, by default
46
+ `https://api.cluebase.io`).
47
+
48
+ Bundlers must emit the `.wasm` asset. Webpack 5, Vite, and Next.js do this
49
+ automatically for the `new URL(..., import.meta.url)` form the SDK uses.
50
+
51
+ If the module fails to initialize, every span export is dropped and counted as
52
+ a dropped span; the SDK does not fall back to a JavaScript implementation.
53
+ Verify the policy in a staging environment before launch.
54
+
34
55
  ## Responsibility boundary
35
56
 
36
57
  - SDK captures browser signals and applies local minimization where the signal is obviously high-risk.
@@ -1,2 +1,5 @@
1
1
  /** 伏せる処理を通しても残す、相関のための属性名。 */
2
2
  export declare const CLUEBASE_CORRELATION_ATTRIBUTE_KEYS: ReadonlySet<string>;
3
+ export declare const CLUEBASE_KEYS_NO_CUSTOMER_VALUE_REACHES: ReadonlySet<string>;
4
+ export declare const CLUEBASE_KEYS_HASHED_WHEN_PERSONAL_INFORMATION_SHAPED: ReadonlySet<string>;
5
+ export declare const CLUEBASE_VALUE_FORMATS_THAT_KEEP_THE_EXEMPTION_TRUE: Readonly<Record<string, readonly string[]>>;
@@ -35,3 +35,37 @@ export const CLUEBASE_CORRELATION_ATTRIBUTE_KEYS = new Set([
35
35
  "mcp.client.version",
36
36
  "mcp.session.id",
37
37
  ]);
38
+ export const CLUEBASE_KEYS_NO_CUSTOMER_VALUE_REACHES = new Set([
39
+ "cluebase.span_id",
40
+ "gen_ai.agent.parent_step_id",
41
+ "gen_ai.agent.run_id",
42
+ ]);
43
+ export const CLUEBASE_KEYS_HASHED_WHEN_PERSONAL_INFORMATION_SHAPED = new Set([
44
+ "cluebase.actor.anonymous_id",
45
+ "cluebase.actor.user_id",
46
+ "cluebase.action.id",
47
+ "cluebase.action_id",
48
+ "cluebase.interaction.id",
49
+ "cluebase.interaction_id",
50
+ "cluebase.organization.id",
51
+ "cluebase.organization_id",
52
+ "cluebase.origin_frontend_interaction.id",
53
+ "cluebase.request.id",
54
+ "cluebase.request_id",
55
+ "cluebase.session.id",
56
+ "cluebase.session_id",
57
+ "cluebase.subject.organization_id",
58
+ "cluebase.tab.id",
59
+ "cluebase.tab_id",
60
+ "http.request.header.x-cluebase-action-id",
61
+ "http.request.header.x_cluebase_action_id",
62
+ "enduser.id",
63
+ "mcp.session.id",
64
+ ]);
65
+ export const CLUEBASE_VALUE_FORMATS_THAT_KEEP_THE_EXEMPTION_TRUE = {
66
+ "gen_ai.agent.run_id": [
67
+ "^[0-9a-f]{32}$",
68
+ "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
69
+ "^run_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
70
+ ]
71
+ };
@@ -0,0 +1,4 @@
1
+ export type DiagnosticCode = "batch_split" | "consent_persistence_failed" | "consent_purge" | "consent_purge_failed" | "consent_state_unavailable" | "delivered_directive_unreadable" | "duplicate_drop_details_unavailable" | "durable_outbox_unavailable" | "event_build_failed" | "event_lost" | "event_oversize" | "intentional_drop" | "invalid_response" | "legacy_bridge_capacity_reached" | "legacy_pre_revocation_event_blocked" | "legacy_writer_detected" | "non_canonical_success_status" | "outbox_key_collision" | "outbox_record_corrupt" | "protocol_redirect" | "queue_overflow" | "retry_exhausted" | "retry_scheduled" | "shutdown_failure" | "terminal_rejection" | "unknown_status";
2
+ export declare const DIAGNOSTIC_CODES: readonly DiagnosticCode[];
3
+ export type HostReportableDiagnosticCode = "consent_persistence_failed" | "consent_purge_failed" | "consent_state_unavailable" | "durable_outbox_unavailable" | "event_build_failed" | "legacy_bridge_capacity_reached" | "legacy_pre_revocation_event_blocked" | "legacy_writer_detected" | "outbox_key_collision" | "outbox_record_corrupt" | "shutdown_failure";
4
+ export declare const HOST_REPORTABLE_DIAGNOSTIC_CODES: readonly HostReportableDiagnosticCode[];
@@ -0,0 +1,48 @@
1
+ // 自動生成です。**手で編集しないでください。**
2
+ //
3
+ // 正本: packages/cluebase-backend-contract/src/delivery/sdk-diagnostic-codes.v1.json
4
+ // 出し直す: node scripts/generate-shared-contract-tables.mjs
5
+ //
6
+ // 正本を直したらこの file も一緒に commit してください。CI が
7
+ // `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
8
+ export const DIAGNOSTIC_CODES = [
9
+ "batch_split",
10
+ "consent_persistence_failed",
11
+ "consent_purge",
12
+ "consent_purge_failed",
13
+ "consent_state_unavailable",
14
+ "delivered_directive_unreadable",
15
+ "duplicate_drop_details_unavailable",
16
+ "durable_outbox_unavailable",
17
+ "event_build_failed",
18
+ "event_lost",
19
+ "event_oversize",
20
+ "intentional_drop",
21
+ "invalid_response",
22
+ "legacy_bridge_capacity_reached",
23
+ "legacy_pre_revocation_event_blocked",
24
+ "legacy_writer_detected",
25
+ "non_canonical_success_status",
26
+ "outbox_key_collision",
27
+ "outbox_record_corrupt",
28
+ "protocol_redirect",
29
+ "queue_overflow",
30
+ "retry_exhausted",
31
+ "retry_scheduled",
32
+ "shutdown_failure",
33
+ "terminal_rejection",
34
+ "unknown_status",
35
+ ];
36
+ export const HOST_REPORTABLE_DIAGNOSTIC_CODES = [
37
+ "consent_persistence_failed",
38
+ "consent_purge_failed",
39
+ "consent_state_unavailable",
40
+ "durable_outbox_unavailable",
41
+ "event_build_failed",
42
+ "legacy_bridge_capacity_reached",
43
+ "legacy_pre_revocation_event_blocked",
44
+ "legacy_writer_detected",
45
+ "outbox_key_collision",
46
+ "outbox_record_corrupt",
47
+ "shutdown_failure",
48
+ ];
@@ -0,0 +1,2 @@
1
+ export declare const DROP_REASONS: readonly ["event_schema_invalid", "event_too_large", "fact_admission_filtered", "ingest_control", "privacy_blocked", "privacy_gate_rejected_field", "privacy_policy_rejected", "source_signal_transform_failed", "unreadable_occurred_at", "unsupported_span"];
2
+ export declare const DROP_REASONS_EXCLUDED_BY_DESIGN: ReadonlySet<string>;
@@ -0,0 +1,25 @@
1
+ // 自動生成です。**手で編集しないでください。**
2
+ //
3
+ // 正本: packages/cluebase-backend-contract/src/delivery/delivery-response-vectors.v1.json
4
+ // 出し直す: node scripts/generate-shared-contract-tables.mjs
5
+ //
6
+ // 正本を直したらこの file も一緒に commit してください。CI が
7
+ // `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
8
+ export const DROP_REASONS = [
9
+ "event_schema_invalid",
10
+ "event_too_large",
11
+ "fact_admission_filtered",
12
+ "ingest_control",
13
+ "privacy_blocked",
14
+ "privacy_gate_rejected_field",
15
+ "privacy_policy_rejected",
16
+ "source_signal_transform_failed",
17
+ "unreadable_occurred_at",
18
+ "unsupported_span",
19
+ ];
20
+ export const DROP_REASONS_EXCLUDED_BY_DESIGN = new Set([
21
+ "fact_admission_filtered",
22
+ "privacy_blocked",
23
+ "privacy_gate_rejected_field",
24
+ "privacy_policy_rejected",
25
+ ]);
@@ -35,4 +35,4 @@ export declare const passesLuhn: (digits: string) => boolean;
35
35
  * 保管されます。値**そのもの**が識別子かを問いたい面は、もう一方の面
36
36
  * (whole_value_is_pii)を使ってください。
37
37
  */
38
- export declare const detectIdentityPII: (value: string) => string | null;
38
+ export declare const detectTextContainsPII: (value: string) => string | null;
@@ -80,7 +80,7 @@ const DIGIT_RUN_MATCHERS = {
80
80
  * 保管されます。値**そのもの**が識別子かを問いたい面は、もう一方の面
81
81
  * (whole_value_is_pii)を使ってください。
82
82
  */
83
- export const detectIdentityPII = (value) => {
83
+ export const detectTextContainsPII = (value) => {
84
84
  if (typeof value !== "string" || value.length === 0)
85
85
  return null;
86
86
  const trimmed = value.trim();
@@ -0,0 +1,37 @@
1
+ /** 見る順序。先に当たった種類を返します。 */
2
+ export declare const PII_CATEGORY_ORDER: readonly ["email", "ssn", "phone", "credit_card"];
3
+ /** 電子メールの形。 */
4
+ export declare const PII_EMAIL_PATTERN = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
5
+ /** 社会保障番号の形。 */
6
+ export declare const PII_SSN_PATTERN = "^\\d{3}-\\d{2}-\\d{4}$";
7
+ /** 電話とみなす桁数の範囲と、求める区切り。 */
8
+ export declare const PII_PHONE_MIN_DIGITS = 7;
9
+ export declare const PII_PHONE_MAX_DIGITS = 15;
10
+ export declare const PII_PHONE_SEPARATOR_PATTERN = "[+().\\-\\s]";
11
+ /** 支払い用の番号とみなす桁数の範囲。Luhn の検算を必ず通します。 */
12
+ export declare const PII_CREDIT_CARD_MIN_DIGITS = 13;
13
+ export declare const PII_CREDIT_CARD_MAX_DIGITS = 19;
14
+ /** 電話の見た目として許す文字だけでできているか。 */
15
+ export declare const PII_PHONE_SHAPE_PATTERN = "^[+()0-9.\\-\\s]+$";
16
+ /** 支払い用の番号の見た目として許す文字だけでできているか。 */
17
+ export declare const PII_CREDIT_CARD_SHAPE_PATTERN = "^[0-9\\s\\-]+$";
18
+ /** 伏せた値の形。同じ入力なら配る部品 3 つとも同じ値になります。 */
19
+ export declare const PII_HASH_PREFIX = "cluebase_pii_hashed:";
20
+ export declare const PII_HASH_HEX_LENGTH = 32;
21
+ /** どの面の規則か(読み手が取り違えないため)。 */
22
+ export declare const PII_SURFACE: "whole_value_is_pii";
23
+ /**
24
+ * Luhn の検算。支払い用の番号らしさを、無作為な数字の並びと分けます。
25
+ *
26
+ * **手順もここから出します。** 桁数と綴りだけを正本化して手順を手書きで残すと、
27
+ * 同じ算法が 4 か所(受付側と配る部品 3 つ)に残ります。実際に残っていました。
28
+ */
29
+ export declare const passesLuhn: (digits: string) => boolean;
30
+ /**
31
+ * この値**そのもの**が個人情報か。両端まで含めて照合します。
32
+ *
33
+ * 途中に埋もれた形は拾いません(拾うと利用者の不透明な識別子を巻き込み、
34
+ * 紐付けが黙って壊れます)。文字列が個人情報を**含む**かを問いたい面は、
35
+ * もう一方の面(text_contains_pii)を使ってください。
36
+ */
37
+ export declare const detectWholeValuePII: (value: string) => string | null;
@@ -0,0 +1,106 @@
1
+ // 自動生成です。**手で編集しないでください。**
2
+ //
3
+ // 正本: packages/cluebase-shared/src/ingest/identity-pii-policy.v1.json
4
+ // 出し直す: node scripts/generate-shared-contract-tables.mjs
5
+ //
6
+ // 正本を直したらこの file も一緒に commit してください。CI が
7
+ // `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
8
+ /** 見る順序。先に当たった種類を返します。 */
9
+ export const PII_CATEGORY_ORDER = [
10
+ "email",
11
+ "ssn",
12
+ "phone",
13
+ "credit_card",
14
+ ];
15
+ /** 電子メールの形。 */
16
+ export const PII_EMAIL_PATTERN = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
17
+ /** 社会保障番号の形。 */
18
+ export const PII_SSN_PATTERN = "^\\d{3}-\\d{2}-\\d{4}$";
19
+ /** 電話とみなす桁数の範囲と、求める区切り。 */
20
+ export const PII_PHONE_MIN_DIGITS = 7;
21
+ export const PII_PHONE_MAX_DIGITS = 15;
22
+ export const PII_PHONE_SEPARATOR_PATTERN = "[+().\\-\\s]";
23
+ /** 支払い用の番号とみなす桁数の範囲。Luhn の検算を必ず通します。 */
24
+ export const PII_CREDIT_CARD_MIN_DIGITS = 13;
25
+ export const PII_CREDIT_CARD_MAX_DIGITS = 19;
26
+ /** 電話の見た目として許す文字だけでできているか。 */
27
+ export const PII_PHONE_SHAPE_PATTERN = "^[+()0-9.\\-\\s]+$";
28
+ /** 支払い用の番号の見た目として許す文字だけでできているか。 */
29
+ export const PII_CREDIT_CARD_SHAPE_PATTERN = "^[0-9\\s\\-]+$";
30
+ /** 伏せた値の形。同じ入力なら配る部品 3 つとも同じ値になります。 */
31
+ export const PII_HASH_PREFIX = "cluebase_pii_hashed:";
32
+ export const PII_HASH_HEX_LENGTH = 32;
33
+ /** どの面の規則か(読み手が取り違えないため)。 */
34
+ export const PII_SURFACE = "whole_value_is_pii";
35
+ /**
36
+ * Luhn の検算。支払い用の番号らしさを、無作為な数字の並びと分けます。
37
+ *
38
+ * **手順もここから出します。** 桁数と綴りだけを正本化して手順を手書きで残すと、
39
+ * 同じ算法が 4 か所(受付側と配る部品 3 つ)に残ります。実際に残っていました。
40
+ */
41
+ export const passesLuhn = (digits) => {
42
+ let sum = 0;
43
+ let doubleDigit = false;
44
+ for (let index = digits.length - 1; index >= 0; index -= 1) {
45
+ let digit = Number(digits[index]);
46
+ if (doubleDigit) {
47
+ digit *= 2;
48
+ if (digit > 9)
49
+ digit -= 9;
50
+ }
51
+ sum += digit;
52
+ doubleDigit = !doubleDigit;
53
+ }
54
+ return sum % 10 === 0;
55
+ };
56
+ const EMAIL_RE = new RegExp(PII_EMAIL_PATTERN);
57
+ const SSN_RE = new RegExp(PII_SSN_PATTERN);
58
+ const PHONE_SHAPE_RE = new RegExp(PII_PHONE_SHAPE_PATTERN);
59
+ const PHONE_SEPARATOR_RE = new RegExp(PII_PHONE_SEPARATOR_PATTERN);
60
+ const CREDIT_CARD_SHAPE_RE = new RegExp(PII_CREDIT_CARD_SHAPE_PATTERN);
61
+ const digitsOf = (value) => value.replace(/[^0-9]/g, "");
62
+ const MATCHERS = {
63
+ email: (value) => EMAIL_RE.test(value),
64
+ ssn: (value) => SSN_RE.test(value),
65
+ phone: (value) => {
66
+ const digits = digitsOf(value);
67
+ if (digits.length < PII_PHONE_MIN_DIGITS)
68
+ return false;
69
+ if (digits.length > PII_PHONE_MAX_DIGITS)
70
+ return false;
71
+ // 電話の見た目でない文字(英字など)が混ざっていたら電話ではありません。
72
+ if (!PHONE_SHAPE_RE.test(value))
73
+ return false;
74
+ // 区切りを 1 つ以上求めます。求めないと "10000000" のような整数の主キーを
75
+ // 電話とみなし、利用者の識別子が黙って伏せられて後段の突合が壊れます。
76
+ return PHONE_SEPARATOR_RE.test(value);
77
+ },
78
+ credit_card: (value) => {
79
+ const digits = digitsOf(value);
80
+ if (digits.length < PII_CREDIT_CARD_MIN_DIGITS)
81
+ return false;
82
+ if (digits.length > PII_CREDIT_CARD_MAX_DIGITS)
83
+ return false;
84
+ if (!CREDIT_CARD_SHAPE_RE.test(value))
85
+ return false;
86
+ return passesLuhn(digits);
87
+ },
88
+ };
89
+ /**
90
+ * この値**そのもの**が個人情報か。両端まで含めて照合します。
91
+ *
92
+ * 途中に埋もれた形は拾いません(拾うと利用者の不透明な識別子を巻き込み、
93
+ * 紐付けが黙って壊れます)。文字列が個人情報を**含む**かを問いたい面は、
94
+ * もう一方の面(text_contains_pii)を使ってください。
95
+ */
96
+ export const detectWholeValuePII = (value) => {
97
+ if (typeof value !== "string" || value.length === 0)
98
+ return null;
99
+ const trimmed = value.trim();
100
+ // 見る順序は正本が決めます。ここには書きません。
101
+ for (const category of PII_CATEGORY_ORDER) {
102
+ if (MATCHERS[category]?.(trimmed))
103
+ return category;
104
+ }
105
+ return null;
106
+ };
@@ -0,0 +1,5 @@
1
+ /** 取り込みの受け口の綴り。送信先の設定を伸ばせる形かどうかはこれで決まります。 */
2
+ export declare const CLUEBASE_API_V1_PREFIX: "/api/v1";
3
+ export declare const CANONICAL_BACKEND_INGEST_PATH: "/api/v1/ingest/backend";
4
+ export declare const CANONICAL_BROWSER_INGEST_PATH: "/api/v1/ingest/browser";
5
+ export declare const CANONICAL_BROWSER_TOKEN_PATH: "/api/v1/ingest/browser-tokens";
@@ -0,0 +1,12 @@
1
+ // 自動生成です。**手で編集しないでください。**
2
+ //
3
+ // 正本: packages/cluebase-shared/src/ingest/routes.ts
4
+ // 出し直す: node scripts/generate-shared-contract-tables.mjs
5
+ //
6
+ // 正本を直したらこの file も一緒に commit してください。CI が
7
+ // `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
8
+ /** 取り込みの受け口の綴り。送信先の設定を伸ばせる形かどうかはこれで決まります。 */
9
+ export const CLUEBASE_API_V1_PREFIX = "/api/v1";
10
+ export const CANONICAL_BACKEND_INGEST_PATH = "/api/v1/ingest/backend";
11
+ export const CANONICAL_BROWSER_INGEST_PATH = "/api/v1/ingest/browser";
12
+ export const CANONICAL_BROWSER_TOKEN_PATH = "/api/v1/ingest/browser-tokens";
@@ -1,10 +1,3 @@
1
- // 自動生成です。**手で編集しないでください。**
2
- //
3
- // 正本: packages/cluebase-backend-contract/src/delivery/otel-span-conversion.source.ts
4
- // 出し直す: node scripts/generate-shared-contract-tables.mjs
5
- //
6
- // 正本を直したらこの file も一緒に commit してください。CI が
7
- // `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
8
1
  export function hrTimeToUnixNano(time) {
9
2
  if (!time)
10
3
  return `${Date.now()}000000`;
@@ -20,8 +13,7 @@ export function nanosToIso(nanos) {
20
13
  if (Number.isFinite(ms) && ms > 0)
21
14
  return new Date(ms).toISOString();
22
15
  }
23
- catch {
24
- }
16
+ catch { }
25
17
  return new Date().toISOString();
26
18
  }
27
19
  export function attrString(attributes, key) {
@@ -19,7 +19,6 @@ export const resolveWebStorage = (kind, probeKey) => {
19
19
  return storage;
20
20
  }
21
21
  }
22
- catch {
23
- }
22
+ catch { }
24
23
  return createMemoryStorage();
25
24
  };
@@ -1,5 +1,5 @@
1
1
  import { toPathTemplate } from "../exporter/path-template.js";
2
- import { detectIdentityPII } from "../exporter/sanitize.js";
2
+ import { detectTextContainsPII } from "../exporter/sanitize.js";
3
3
  import { stripQueryAndHashFromUrl } from "../provider/sdk-input-normalization.js";
4
4
  const firstMatch = (userAgent, pattern) => {
5
5
  const match = pattern.exec(userAgent);
@@ -136,7 +136,7 @@ const sanitizeReferrer = (value) => {
136
136
  .split("/")
137
137
  .map((segment) => {
138
138
  try {
139
- return detectIdentityPII(decodeURIComponent(segment)) === null
139
+ return detectTextContainsPII(decodeURIComponent(segment)) === null
140
140
  ? segment
141
141
  : ":id";
142
142
  }
@@ -218,7 +218,9 @@ export class EnvironmentManager {
218
218
  safeContextTraits() {
219
219
  try {
220
220
  const traits = this.options.contextTraitsProvider?.() ?? {};
221
- return typeof traits === "object" && traits !== null && !Array.isArray(traits)
221
+ return typeof traits === "object" &&
222
+ traits !== null &&
223
+ !Array.isArray(traits)
222
224
  ? traits
223
225
  : {};
224
226
  }
@@ -1,6 +1,6 @@
1
1
  import { generateAnonymousId } from "../exporter/event-id.js";
2
2
  import { STORAGE_KEYS } from "../provider/constants.js";
3
- import { resolveWebStorage, } from "./browser-storage.js";
3
+ import { resolveWebStorage } from "./browser-storage.js";
4
4
  const CONTEXT_ID_TRAIT_KEYS = new Set([
5
5
  "accountId",
6
6
  "account_id",
@@ -89,8 +89,7 @@ export class IdentityManager {
89
89
  try {
90
90
  this.storage.setItem(STORAGE_KEYS.userId, userId);
91
91
  }
92
- catch {
93
- }
92
+ catch { }
94
93
  }
95
94
  clearUser() {
96
95
  this.state.userId = null;
@@ -98,8 +97,7 @@ export class IdentityManager {
98
97
  try {
99
98
  this.storage.removeItem(STORAGE_KEYS.userId);
100
99
  }
101
- catch {
102
- }
100
+ catch { }
103
101
  }
104
102
  rotateAnonymousId() {
105
103
  const anonymousId = generateAnonymousId();
@@ -107,8 +105,7 @@ export class IdentityManager {
107
105
  try {
108
106
  this.storage.setItem(STORAGE_KEYS.anonymousId, anonymousId);
109
107
  }
110
- catch {
111
- }
108
+ catch { }
112
109
  return anonymousId;
113
110
  }
114
111
  reset() {
@@ -122,8 +119,7 @@ export class IdentityManager {
122
119
  try {
123
120
  this.storage.setItem(STORAGE_KEYS.anonymousId, anonymousId);
124
121
  }
125
- catch {
126
- }
122
+ catch { }
127
123
  }
128
124
  const userId = this.storage.getItem(STORAGE_KEYS.userId);
129
125
  return {
@@ -1,6 +1,6 @@
1
1
  import type { OrganizationContext } from "../provider/types.js";
2
- import { type ContextTraitInput } from "./identity.js";
3
2
  import { type StorageLike } from "./browser-storage.js";
3
+ import { type ContextTraitInput } from "./identity.js";
4
4
  export declare class OrganizationManager {
5
5
  private readonly storage;
6
6
  private state;
@@ -1,9 +1,10 @@
1
1
  import { STORAGE_KEYS } from "../provider/constants.js";
2
+ import { resolveWebStorage } from "./browser-storage.js";
2
3
  import { sanitizeContextTraits, } from "./identity.js";
3
- import { resolveWebStorage, } from "./browser-storage.js";
4
4
  export class OrganizationManager {
5
5
  constructor(storage) {
6
- this.storage = storage ?? resolveWebStorage("local", "cluebase:organization_probe");
6
+ this.storage =
7
+ storage ?? resolveWebStorage("local", "cluebase:organization_probe");
7
8
  this.state = this.loadState();
8
9
  }
9
10
  getContext() {
@@ -22,8 +23,7 @@ export class OrganizationManager {
22
23
  try {
23
24
  this.storage.setItem(STORAGE_KEYS.organizationId, organizationId);
24
25
  }
25
- catch {
26
- }
26
+ catch { }
27
27
  }
28
28
  reset() {
29
29
  this.state = {
@@ -33,8 +33,7 @@ export class OrganizationManager {
33
33
  try {
34
34
  this.storage.removeItem(STORAGE_KEYS.organizationId);
35
35
  }
36
- catch {
37
- }
36
+ catch { }
38
37
  }
39
38
  loadState() {
40
39
  const organizationId = this.storage.getItem(STORAGE_KEYS.organizationId);
@@ -43,8 +42,7 @@ export class OrganizationManager {
43
42
  this.storage.setItem(STORAGE_KEYS.organizationId, organizationId);
44
43
  }
45
44
  }
46
- catch {
47
- }
45
+ catch { }
48
46
  return {
49
47
  organizationId,
50
48
  traits: {},
@@ -1,6 +1,6 @@
1
1
  import { generateSessionId } from "../exporter/event-id.js";
2
2
  import { DEFAULT_SESSION_TIMEOUT_MS, STORAGE_KEYS, } from "../provider/constants.js";
3
- import { resolveWebStorage, } from "./browser-storage.js";
3
+ import { resolveWebStorage } from "./browser-storage.js";
4
4
  const parseMs = (value) => {
5
5
  if (!value) {
6
6
  return null;
@@ -14,7 +14,8 @@ const parseMs = (value) => {
14
14
  export class SessionManager {
15
15
  constructor(timeoutMs = DEFAULT_SESSION_TIMEOUT_MS, storage) {
16
16
  this.timeoutMs = timeoutMs;
17
- this.storage = storage ?? resolveWebStorage("session", "cluebase:session_probe");
17
+ this.storage =
18
+ storage ?? resolveWebStorage("session", "cluebase:session_probe");
18
19
  this.context = this.loadState();
19
20
  }
20
21
  getContext() {
@@ -58,8 +59,7 @@ export class SessionManager {
58
59
  try {
59
60
  this.storage.setItem(STORAGE_KEYS.sessionLastActivityAt, String(nowMs));
60
61
  }
61
- catch {
62
- }
62
+ catch { }
63
63
  }
64
64
  endSession(nowMs = Date.now()) {
65
65
  if (!this.context.sessionId || !this.context.startedAtMs) {
@@ -89,8 +89,7 @@ export class SessionManager {
89
89
  this.storage.setItem(STORAGE_KEYS.sessionStartedAt, String(nowMs));
90
90
  this.storage.setItem(STORAGE_KEYS.sessionLastActivityAt, String(nowMs));
91
91
  }
92
- catch {
93
- }
92
+ catch { }
94
93
  return sessionId;
95
94
  }
96
95
  clear() {
@@ -104,8 +103,7 @@ export class SessionManager {
104
103
  this.storage.removeItem(STORAGE_KEYS.sessionStartedAt);
105
104
  this.storage.removeItem(STORAGE_KEYS.sessionLastActivityAt);
106
105
  }
107
- catch {
108
- }
106
+ catch { }
109
107
  }
110
108
  loadState() {
111
109
  const sessionId = this.storage.getItem(STORAGE_KEYS.sessionId);
@@ -1,6 +1,6 @@
1
1
  import { generateEventId, generateTabId } from "../exporter/event-id.js";
2
2
  import { STORAGE_KEYS } from "../provider/constants.js";
3
- import { resolveWebStorage, } from "./browser-storage.js";
3
+ import { resolveWebStorage } from "./browser-storage.js";
4
4
  const TAB_CLAIM_KEY_PREFIX = "cluebase:tab_claim:";
5
5
  const TAB_CLAIM_TTL_MS = 5 * 60 * 1000;
6
6
  const DOCUMENT_CLAIM_ID = `doc_${generateEventId()}`;
@@ -13,8 +13,7 @@ const resolveClaimStorage = () => {
13
13
  return globalThis.localStorage;
14
14
  }
15
15
  }
16
- catch {
17
- }
16
+ catch { }
18
17
  return null;
19
18
  };
20
19
  const parseTabClaim = (value) => {
@@ -31,13 +30,13 @@ const parseTabClaim = (value) => {
31
30
  };
32
31
  }
33
32
  }
34
- catch {
35
- }
33
+ catch { }
36
34
  return null;
37
35
  };
38
36
  export class TabManager {
39
37
  constructor(storage, options) {
40
- this.storage = storage ?? resolveWebStorage("session", "cluebase:tab_probe");
38
+ this.storage =
39
+ storage ?? resolveWebStorage("session", "cluebase:tab_probe");
41
40
  this.claimStorage =
42
41
  options && "claimStorage" in options
43
42
  ? (options.claimStorage ?? null)
@@ -61,8 +60,7 @@ export class TabManager {
61
60
  try {
62
61
  this.storage.setItem(STORAGE_KEYS.tabId, tabId);
63
62
  }
64
- catch {
65
- }
63
+ catch { }
66
64
  return tabId;
67
65
  }
68
66
  isClaimedByAnotherDocument(tabId) {
@@ -85,8 +83,7 @@ export class TabManager {
85
83
  expiresAt: this.now() + TAB_CLAIM_TTL_MS,
86
84
  }));
87
85
  }
88
- catch {
89
- }
86
+ catch { }
90
87
  }
91
88
  installLifecycleCleanup() {
92
89
  if (!this.bindLifecycleEvents || !this.claimStorage) {
@@ -106,7 +103,6 @@ export class TabManager {
106
103
  globalThis.addEventListener("pagehide", cleanup, { once: true });
107
104
  globalThis.addEventListener("beforeunload", cleanup, { once: true });
108
105
  }
109
- catch {
110
- }
106
+ catch { }
111
107
  }
112
108
  }
@@ -66,7 +66,8 @@ export class TraceManager {
66
66
  endFrontendAction(interactionId) {
67
67
  if (!this.activeInteraction)
68
68
  return;
69
- if (interactionId && this.activeInteraction.interactionId !== interactionId) {
69
+ if (interactionId &&
70
+ this.activeInteraction.interactionId !== interactionId) {
70
71
  return;
71
72
  }
72
73
  this.recentInteraction = {
@@ -206,12 +207,10 @@ export class TraceManager {
206
207
  requestSpanId: generateRequestSpanId(),
207
208
  actionId: active?.actionId ?? null,
208
209
  interactionId: generateInteractionId(),
209
- interactionInitiator: trigger?.interactionInitiator ??
210
- active?.interactionInitiator ??
211
- "background",
210
+ interactionInitiator: trigger?.interactionInitiator ?? active?.interactionInitiator ?? null,
212
211
  interactionStartEventKind: trigger?.interactionStartEventKind ??
213
212
  active?.interactionStartEventKind ??
214
- "background_request",
213
+ null,
215
214
  originFrontendInteractionId: active?.interactionId ?? null,
216
215
  };
217
216
  }