@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.
- package/README.md +21 -0
- package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
- package/dist/_generated/correlation-attribute-keys.js +34 -0
- package/dist/_generated/diagnostic-codes.d.ts +4 -0
- package/dist/_generated/diagnostic-codes.js +48 -0
- package/dist/_generated/drop-reasons.d.ts +2 -0
- package/dist/_generated/drop-reasons.js +25 -0
- package/dist/_generated/{identity-pii-policy.d.ts → identity-pii-policy-text-contains.d.ts} +1 -1
- package/dist/_generated/{identity-pii-policy.js → identity-pii-policy-text-contains.js} +1 -1
- package/dist/_generated/identity-pii-policy-whole-value.d.ts +37 -0
- package/dist/_generated/identity-pii-policy-whole-value.js +106 -0
- package/dist/_generated/ingest-routes.d.ts +5 -0
- package/dist/_generated/ingest-routes.js +12 -0
- package/dist/_generated/otel-span-conversion.js +1 -9
- package/dist/context/browser-storage.js +1 -2
- package/dist/context/environment.js +5 -3
- package/dist/context/identity.js +5 -9
- package/dist/context/organization.d.ts +1 -1
- package/dist/context/organization.js +6 -8
- package/dist/context/session.js +6 -8
- package/dist/context/tab.js +8 -12
- package/dist/context/trace.js +4 -5
- package/dist/exporter/batch.d.ts +26 -27
- package/dist/exporter/batch.js +248 -294
- package/dist/exporter/browser-outbox-records.d.ts +8 -25
- package/dist/exporter/browser-outbox-records.js +21 -100
- package/dist/exporter/browser-outbox.d.ts +23 -25
- package/dist/exporter/browser-outbox.js +106 -181
- package/dist/exporter/client.d.ts +25 -35
- package/dist/exporter/client.js +107 -335
- package/dist/exporter/delivery-pipeline.d.ts +71 -0
- package/dist/exporter/delivery-pipeline.js +196 -0
- package/dist/exporter/delivery-response-contract.d.ts +1 -81
- package/dist/exporter/delivery-response-contract.js +0 -118
- package/dist/exporter/delivery-response.d.ts +9 -4
- package/dist/exporter/delivery-response.js +59 -122
- package/dist/exporter/diagnostics.d.ts +3 -2
- package/dist/exporter/diagnostics.js +5 -26
- package/dist/exporter/event-id.d.ts +1 -0
- package/dist/exporter/event-id.js +4 -3
- package/dist/exporter/frontend-resource.js +5 -4
- package/dist/exporter/observation-source-event-exporter.d.ts +2 -1
- package/dist/exporter/observation-source-event-exporter.js +7 -4
- package/dist/exporter/path-template.js +3 -1
- package/dist/exporter/raw-event-to-source-signal.js +4 -2
- package/dist/exporter/sanitize.d.ts +3 -2
- package/dist/exporter/sanitize.js +4 -3
- package/dist/exporter/sdk-core-delivery.d.ts +225 -0
- package/dist/exporter/sdk-core-delivery.js +97 -0
- package/dist/exporter/sdk-core-wasm.d.ts +11 -0
- package/dist/exporter/sdk-core-wasm.js +39 -0
- package/dist/exporter/source-event.d.ts +3 -0
- package/dist/exporter/source-event.js +17 -0
- package/dist/exporter/span-to-source-signal.d.ts +2 -2
- package/dist/exporter/span-to-source-signal.js +45 -125
- package/dist/index.d.ts +5 -4
- package/dist/index.js +18 -6
- package/dist/instrumentation/action.js +9 -8
- package/dist/instrumentation/dom.js +13 -21
- package/dist/instrumentation/drag.js +0 -3
- package/dist/instrumentation/error.js +3 -1
- package/dist/instrumentation/form.js +5 -6
- package/dist/instrumentation/input.js +4 -8
- package/dist/instrumentation/location.js +3 -1
- package/dist/instrumentation/network-request.js +1 -1
- package/dist/instrumentation/network-span-attributes.js +2 -2
- package/dist/instrumentation/network.d.ts +1 -1
- package/dist/instrumentation/network.js +24 -3
- package/dist/instrumentation/performance.js +1 -2
- package/dist/internal/internal-log.js +2 -1
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/config.js +16 -4
- package/dist/provider/constants.d.ts +1 -1
- package/dist/provider/constants.js +0 -2
- package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
- package/dist/provider/lifecycle-event-dedup.js +31 -0
- package/dist/provider/sdk-event-pipeline.d.ts +3 -4
- package/dist/provider/sdk-event-pipeline.js +5 -17
- package/dist/provider/sdk-input-normalization.js +2 -2
- package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
- package/dist/provider/sdk-otel-pipeline.js +7 -15
- package/dist/provider/sdk-runtime-surfaces.d.ts +17 -13
- package/dist/provider/sdk-runtime-surfaces.js +15 -13
- package/dist/provider/sdk.js +137 -48
- package/dist/provider/types.d.ts +18 -24
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +44 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +355 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +11 -0
- package/dist/wasm/pkg-web/package.json +15 -0
- package/package.json +62 -53
- package/dist/provider/usage-governor.d.ts +0 -7
- 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
|
|
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
|
|
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) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toPathTemplate } from "../exporter/path-template.js";
|
|
2
|
-
import {
|
|
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
|
|
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" &&
|
|
221
|
+
return typeof traits === "object" &&
|
|
222
|
+
traits !== null &&
|
|
223
|
+
!Array.isArray(traits)
|
|
222
224
|
? traits
|
|
223
225
|
: {};
|
|
224
226
|
}
|
package/dist/context/identity.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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: {},
|
package/dist/context/session.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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);
|
package/dist/context/tab.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
}
|
package/dist/context/trace.js
CHANGED
|
@@ -66,7 +66,8 @@ export class TraceManager {
|
|
|
66
66
|
endFrontendAction(interactionId) {
|
|
67
67
|
if (!this.activeInteraction)
|
|
68
68
|
return;
|
|
69
|
-
if (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
|
-
|
|
213
|
+
null,
|
|
215
214
|
originFrontendInteractionId: active?.interactionId ?? null,
|
|
216
215
|
};
|
|
217
216
|
}
|