@clue-ai/browser-sdk 0.0.1

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 (102) hide show
  1. package/README.md +100 -0
  2. package/dist/authoring/overlay.d.ts +12 -0
  3. package/dist/authoring/overlay.js +468 -0
  4. package/dist/authoring/recording.d.ts +125 -0
  5. package/dist/authoring/recording.js +481 -0
  6. package/dist/authoring/service-logo.d.ts +1 -0
  7. package/dist/authoring/service-logo.generated.d.ts +1 -0
  8. package/dist/authoring/service-logo.generated.js +3 -0
  9. package/dist/authoring/service-logo.js +1 -0
  10. package/dist/authoring/session.d.ts +23 -0
  11. package/dist/authoring/session.js +127 -0
  12. package/dist/authoring/surface.d.ts +11 -0
  13. package/dist/authoring/surface.js +63 -0
  14. package/dist/authoring/toolbar-constants.d.ts +23 -0
  15. package/dist/authoring/toolbar-constants.js +42 -0
  16. package/dist/authoring/toolbar-drag.d.ts +29 -0
  17. package/dist/authoring/toolbar-drag.js +270 -0
  18. package/dist/authoring/toolbar-view.d.ts +21 -0
  19. package/dist/authoring/toolbar-view.js +2584 -0
  20. package/dist/capture/action.d.ts +2 -0
  21. package/dist/capture/action.js +62 -0
  22. package/dist/capture/dom.d.ts +23 -0
  23. package/dist/capture/dom.js +329 -0
  24. package/dist/capture/drag.d.ts +2 -0
  25. package/dist/capture/drag.js +75 -0
  26. package/dist/capture/error.d.ts +2 -0
  27. package/dist/capture/error.js +193 -0
  28. package/dist/capture/form.d.ts +2 -0
  29. package/dist/capture/form.js +137 -0
  30. package/dist/capture/frustration.d.ts +2 -0
  31. package/dist/capture/frustration.js +171 -0
  32. package/dist/capture/input.d.ts +2 -0
  33. package/dist/capture/input.js +109 -0
  34. package/dist/capture/location.d.ts +10 -0
  35. package/dist/capture/location.js +42 -0
  36. package/dist/capture/navigation.d.ts +2 -0
  37. package/dist/capture/navigation.js +100 -0
  38. package/dist/capture/network.d.ts +13 -0
  39. package/dist/capture/network.js +903 -0
  40. package/dist/capture/page.d.ts +2 -0
  41. package/dist/capture/page.js +78 -0
  42. package/dist/capture/performance.d.ts +2 -0
  43. package/dist/capture/performance.js +268 -0
  44. package/dist/context/account.d.ts +12 -0
  45. package/dist/context/account.js +129 -0
  46. package/dist/context/environment.d.ts +42 -0
  47. package/dist/context/environment.js +208 -0
  48. package/dist/context/identity.d.ts +14 -0
  49. package/dist/context/identity.js +123 -0
  50. package/dist/context/session.d.ts +28 -0
  51. package/dist/context/session.js +155 -0
  52. package/dist/context/tab.d.ts +22 -0
  53. package/dist/context/tab.js +142 -0
  54. package/dist/context/trace.d.ts +32 -0
  55. package/dist/context/trace.js +65 -0
  56. package/dist/core/config.d.ts +4 -0
  57. package/dist/core/config.js +199 -0
  58. package/dist/core/constants.d.ts +43 -0
  59. package/dist/core/constants.js +109 -0
  60. package/dist/core/contracts.d.ts +58 -0
  61. package/dist/core/contracts.js +53 -0
  62. package/dist/core/sdk.d.ts +2 -0
  63. package/dist/core/sdk.js +831 -0
  64. package/dist/core/types.d.ts +413 -0
  65. package/dist/core/types.js +1 -0
  66. package/dist/core/usage-governor.d.ts +7 -0
  67. package/dist/core/usage-governor.js +127 -0
  68. package/dist/index.d.ts +17 -0
  69. package/dist/index.js +36 -0
  70. package/dist/integrations/next-router.d.ts +16 -0
  71. package/dist/integrations/next-router.js +18 -0
  72. package/dist/integrations/react-router.d.ts +7 -0
  73. package/dist/integrations/react-router.js +37 -0
  74. package/dist/internal/metrics.d.ts +9 -0
  75. package/dist/internal/metrics.js +38 -0
  76. package/dist/normalize/builders.d.ts +15 -0
  77. package/dist/normalize/builders.js +786 -0
  78. package/dist/normalize/canonical.d.ts +13 -0
  79. package/dist/normalize/canonical.js +77 -0
  80. package/dist/normalize/event-id.d.ts +8 -0
  81. package/dist/normalize/event-id.js +39 -0
  82. package/dist/normalize/path-template.d.ts +1 -0
  83. package/dist/normalize/path-template.js +33 -0
  84. package/dist/privacy/local-minimization.d.ts +29 -0
  85. package/dist/privacy/local-minimization.js +88 -0
  86. package/dist/privacy/mask.d.ts +7 -0
  87. package/dist/privacy/mask.js +60 -0
  88. package/dist/privacy/parameter-snapshot.d.ts +14 -0
  89. package/dist/privacy/parameter-snapshot.js +206 -0
  90. package/dist/privacy/sanitize.d.ts +11 -0
  91. package/dist/privacy/sanitize.js +145 -0
  92. package/dist/privacy/schema-evidence.d.ts +20 -0
  93. package/dist/privacy/schema-evidence.js +238 -0
  94. package/dist/transport/batch.d.ts +37 -0
  95. package/dist/transport/batch.js +182 -0
  96. package/dist/transport/client.d.ts +61 -0
  97. package/dist/transport/client.js +267 -0
  98. package/dist/transport/queue.d.ts +22 -0
  99. package/dist/transport/queue.js +56 -0
  100. package/dist/transport/retry.d.ts +14 -0
  101. package/dist/transport/retry.js +46 -0
  102. package/package.json +38 -0
@@ -0,0 +1,13 @@
1
+ import type { CanonicalBaseEvent, ClueContext, FrontendEventCategory, FrontendEventName, FrontendEventStatus } from "../core/types";
2
+ export type CanonicalBaseInput = {
3
+ eventName: FrontendEventName;
4
+ eventCategory: FrontendEventCategory;
5
+ status: FrontendEventStatus;
6
+ context: ClueContext;
7
+ occurredAtMs?: number;
8
+ traceId?: string | null;
9
+ requestId?: string | null;
10
+ requestSpanId?: string | null;
11
+ interactionId?: string | null;
12
+ };
13
+ export declare const createCanonicalBaseEvent: (input: CanonicalBaseInput) => CanonicalBaseEvent;
@@ -0,0 +1,77 @@
1
+ import { EVENT_SOURCE, EVENT_VERSION, SOURCE_SCHEMA_VERSION, SURFACE_TYPE, } from "../core/constants";
2
+ import { generateEventId } from "./event-id";
3
+ export const createCanonicalBaseEvent = (input) => {
4
+ const eventId = generateEventId();
5
+ const occurredAt = new Date(input.occurredAtMs ?? Date.now()).toISOString();
6
+ const ingestedAt = new Date().toISOString();
7
+ return {
8
+ event_id: eventId,
9
+ source_event_id: eventId,
10
+ source_schema_version: SOURCE_SCHEMA_VERSION,
11
+ source_event_type: input.eventName,
12
+ source_event_kind: input.eventCategory,
13
+ producer_id: input.context.producer_id,
14
+ event_name: input.eventName,
15
+ event_category: input.eventCategory,
16
+ event_source: EVENT_SOURCE,
17
+ event_version: EVENT_VERSION,
18
+ occurred_at: occurredAt,
19
+ ingested_at: ingestedAt,
20
+ status: input.status,
21
+ sdk_collection_mode: input.context.sdk_collection_mode,
22
+ // project / tenant / privacy authority is resolved server-side at ingest.
23
+ anonymous_id: input.context.anonymous_id,
24
+ user_id: input.context.user_id,
25
+ account_id: input.context.account_id,
26
+ organization_id: input.context.organization_id ?? null,
27
+ user_profile: input.context.user_profile ?? undefined,
28
+ account_profile: input.context.account_profile ?? undefined,
29
+ workspace_id: input.context.workspace_id,
30
+ session_id: input.context.session_id,
31
+ tab_id: input.context.tab_id,
32
+ trace_id: input.traceId ?? input.context.trace_id,
33
+ request_id: input.requestId ?? input.context.request_id,
34
+ request_span_id: input.requestSpanId ?? input.context.request_span_id,
35
+ interaction_id: input.interactionId ?? input.context.interaction_id,
36
+ correlation_id: input.context.correlation_id,
37
+ surface_type: SURFACE_TYPE,
38
+ environment: input.context.environment,
39
+ frontend_release: input.context.frontend_release,
40
+ backend_release: null,
41
+ feature_flags: input.context.feature_flags,
42
+ experiment_variant: input.context.experiment_variant,
43
+ device_type: input.context.device_type,
44
+ browser: input.context.browser,
45
+ os: input.context.os,
46
+ locale: input.context.locale,
47
+ country: input.context.country,
48
+ referrer: input.context.referrer,
49
+ browser_family: input.context.browser_family,
50
+ browser_major_version: input.context.browser_major_version,
51
+ browser_engine: input.context.browser_engine,
52
+ os_family: input.context.os_family,
53
+ os_major_version: input.context.os_major_version,
54
+ timezone: input.context.timezone,
55
+ pointer_type_candidate: input.context.pointer_type_candidate,
56
+ touch_capable: input.context.touch_capable,
57
+ cookies_enabled: input.context.cookies_enabled,
58
+ local_storage_available: input.context.local_storage_available,
59
+ session_storage_available: input.context.session_storage_available,
60
+ service_worker_supported: input.context.service_worker_supported,
61
+ webgl_supported: input.context.webgl_supported,
62
+ reduced_motion: input.context.reduced_motion,
63
+ color_scheme: input.context.color_scheme,
64
+ account_status: input.context.account_status,
65
+ plan_tier: input.context.plan_tier,
66
+ billing_status: input.context.billing_status,
67
+ customer_lifecycle_stage: input.context.customer_lifecycle_stage,
68
+ organization_size_bucket: input.context.organization_size_bucket,
69
+ user_role: input.context.user_role,
70
+ permission_group: input.context.permission_group,
71
+ onboarding_state: input.context.onboarding_state,
72
+ trial_days_remaining_bucket: input.context.trial_days_remaining_bucket,
73
+ customer_health_bucket: input.context.customer_health_bucket,
74
+ properties: {},
75
+ metrics: {},
76
+ };
77
+ };
@@ -0,0 +1,8 @@
1
+ export declare const generateEventId: () => string;
2
+ export declare const generateAnonymousId: () => string;
3
+ export declare const generateSessionId: () => string;
4
+ export declare const generateTabId: () => string;
5
+ export declare const generateRequestId: () => string;
6
+ export declare const generateRequestSpanId: () => string;
7
+ export declare const generateInteractionId: () => string;
8
+ export declare const generateViewId: () => string;
@@ -0,0 +1,39 @@
1
+ const fallbackRandom = () => {
2
+ return `${Math.random().toString(16).slice(2)}${Date.now().toString(16)}`;
3
+ };
4
+ const randomUuidLike = () => {
5
+ try {
6
+ if (typeof globalThis.crypto?.randomUUID === "function") {
7
+ return globalThis.crypto.randomUUID();
8
+ }
9
+ }
10
+ catch {
11
+ // Ignore and fallback.
12
+ }
13
+ const seed = fallbackRandom().padEnd(32, "0").slice(0, 32);
14
+ return `${seed.slice(0, 8)}-${seed.slice(8, 12)}-${seed.slice(12, 16)}-${seed.slice(16, 20)}-${seed.slice(20, 32)}`;
15
+ };
16
+ export const generateEventId = () => {
17
+ return randomUuidLike();
18
+ };
19
+ export const generateAnonymousId = () => {
20
+ return `anon_${randomUuidLike()}`;
21
+ };
22
+ export const generateSessionId = () => {
23
+ return `sess_${randomUuidLike()}`;
24
+ };
25
+ export const generateTabId = () => {
26
+ return `tab_${randomUuidLike()}`;
27
+ };
28
+ export const generateRequestId = () => {
29
+ return `req_${randomUuidLike()}`;
30
+ };
31
+ export const generateRequestSpanId = () => {
32
+ return `rsp_${randomUuidLike()}`;
33
+ };
34
+ export const generateInteractionId = () => {
35
+ return `int_${randomUuidLike()}`;
36
+ };
37
+ export const generateViewId = () => {
38
+ return `view_${randomUuidLike()}`;
39
+ };
@@ -0,0 +1 @@
1
+ export declare const toPathTemplate: (pathname: string) => string;
@@ -0,0 +1,33 @@
1
+ const UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2
+ const HEX_SEGMENT = /^[0-9a-f]{16,}$/i;
3
+ const NUMBER_SEGMENT = /^\d+$/;
4
+ const TOKEN_SEGMENT = /^[A-Za-z0-9_-]{24,}$/;
5
+ export const toPathTemplate = (pathname) => {
6
+ if (!pathname || pathname === "/") {
7
+ return "/";
8
+ }
9
+ const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
10
+ const [pathOnly] = normalizedPath.split("?");
11
+ const segments = pathOnly
12
+ .split("/")
13
+ .filter((segment) => segment.length > 0)
14
+ .map((segment) => {
15
+ if (NUMBER_SEGMENT.test(segment)) {
16
+ return ":id";
17
+ }
18
+ if (UUID_SEGMENT.test(segment)) {
19
+ return ":uuid";
20
+ }
21
+ if (HEX_SEGMENT.test(segment)) {
22
+ return ":hex";
23
+ }
24
+ if (TOKEN_SEGMENT.test(segment)) {
25
+ return ":token";
26
+ }
27
+ return segment;
28
+ });
29
+ if (segments.length === 0) {
30
+ return "/";
31
+ }
32
+ return `/${segments.join("/")}`;
33
+ };
@@ -0,0 +1,29 @@
1
+ type LocalValueType = "string" | "number" | "boolean" | "null" | "object" | "array";
2
+ export type LocalTextDescriptor = {
3
+ present: true;
4
+ length: number;
5
+ };
6
+ export type LocalValueDescriptor = {
7
+ present: true;
8
+ valueType: LocalValueType;
9
+ length?: number;
10
+ itemCount?: number;
11
+ keyCount?: number;
12
+ fileCount?: number;
13
+ };
14
+ export type LocalStructuredSummary = LocalValueDescriptor | {
15
+ present: true;
16
+ valueType: "object";
17
+ keyCount: number;
18
+ fields: Record<string, LocalStructuredSummary | null>;
19
+ } | {
20
+ present: true;
21
+ valueType: "array";
22
+ itemCount: number;
23
+ items: Array<LocalStructuredSummary | null>;
24
+ };
25
+ export declare const buildLocalTextDescriptor: (value: string | null | undefined) => LocalTextDescriptor | null;
26
+ export declare const buildLocalValueDescriptor: (value: unknown) => LocalValueDescriptor | null;
27
+ export declare const buildLocalStructuredSummary: (value: unknown, depth?: number) => LocalStructuredSummary | null;
28
+ export declare const buildLocalQuerySummary: (query: Record<string, string | string[]>) => Record<string, LocalStructuredSummary | null>;
29
+ export {};
@@ -0,0 +1,88 @@
1
+ const MAX_SUMMARY_DEPTH = 4;
2
+ const MAX_SUMMARY_KEYS = 32;
3
+ const MAX_SUMMARY_ITEMS = 12;
4
+ export const buildLocalTextDescriptor = (value) => {
5
+ if (value == null) {
6
+ return null;
7
+ }
8
+ const trimmed = value.trim();
9
+ if (!trimmed) {
10
+ return null;
11
+ }
12
+ return {
13
+ present: true,
14
+ length: trimmed.length,
15
+ };
16
+ };
17
+ export const buildLocalValueDescriptor = (value) => {
18
+ if (value == null) {
19
+ return null;
20
+ }
21
+ if (typeof value === "string") {
22
+ const trimmed = value.trim();
23
+ return trimmed
24
+ ? { present: true, valueType: "string", length: trimmed.length }
25
+ : null;
26
+ }
27
+ if (typeof value === "number") {
28
+ return { present: true, valueType: "number" };
29
+ }
30
+ if (typeof value === "boolean") {
31
+ return { present: true, valueType: "boolean" };
32
+ }
33
+ if (Array.isArray(value)) {
34
+ return {
35
+ present: true,
36
+ valueType: "array",
37
+ itemCount: value.length,
38
+ };
39
+ }
40
+ if (typeof value === "object") {
41
+ const record = value;
42
+ const fileCount = typeof record.fileCount === "number" && Number.isFinite(record.fileCount)
43
+ ? Math.max(0, Math.floor(record.fileCount))
44
+ : undefined;
45
+ return {
46
+ present: true,
47
+ valueType: "object",
48
+ keyCount: Object.keys(record).length,
49
+ ...(typeof fileCount === "number" ? { fileCount } : {}),
50
+ };
51
+ }
52
+ return null;
53
+ };
54
+ export const buildLocalStructuredSummary = (value, depth = 0) => {
55
+ if (depth >= MAX_SUMMARY_DEPTH) {
56
+ return buildLocalValueDescriptor(value);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return {
60
+ present: true,
61
+ valueType: "array",
62
+ itemCount: value.length,
63
+ items: value
64
+ .slice(0, MAX_SUMMARY_ITEMS)
65
+ .map((entry) => buildLocalStructuredSummary(entry, depth + 1)),
66
+ };
67
+ }
68
+ if (value && typeof value === "object") {
69
+ const record = value;
70
+ const limitedEntries = Object.entries(record).slice(0, MAX_SUMMARY_KEYS);
71
+ return {
72
+ present: true,
73
+ valueType: "object",
74
+ keyCount: Object.keys(record).length,
75
+ fields: Object.fromEntries(limitedEntries.map(([key, child]) => [
76
+ key,
77
+ buildLocalStructuredSummary(child, depth + 1),
78
+ ])),
79
+ };
80
+ }
81
+ return buildLocalValueDescriptor(value);
82
+ };
83
+ export const buildLocalQuerySummary = (query) => {
84
+ return Object.fromEntries(Object.entries(query).map(([key, value]) => [
85
+ key,
86
+ buildLocalStructuredSummary(value),
87
+ ]));
88
+ };
@@ -0,0 +1,7 @@
1
+ export declare const maskString: (value: string | null | undefined) => string | null;
2
+ export declare const maskStringArray: (value: string[] | null | undefined) => string[] | null;
3
+ export declare const maskUnknown: (value: unknown) => unknown;
4
+ export declare const maskDomPath: (domPath: string | null | undefined) => string | null;
5
+ export declare const maskMessage: (message: string | null | undefined) => string | null;
6
+ export declare const maskUrl: (urlValue: string | null | undefined) => string | null;
7
+ export declare const maskInputValue: () => string;
@@ -0,0 +1,60 @@
1
+ import { MASKED_VALUE } from "../core/constants";
2
+ export const maskString = (value) => {
3
+ if (value == null) {
4
+ return null;
5
+ }
6
+ if (!value.trim()) {
7
+ return null;
8
+ }
9
+ return MASKED_VALUE;
10
+ };
11
+ export const maskStringArray = (value) => {
12
+ if (!value || value.length === 0) {
13
+ return null;
14
+ }
15
+ return value.map(() => MASKED_VALUE);
16
+ };
17
+ export const maskUnknown = (value) => {
18
+ if (value == null) {
19
+ return null;
20
+ }
21
+ if (typeof value === "string" ||
22
+ typeof value === "number" ||
23
+ typeof value === "boolean" ||
24
+ typeof value === "bigint") {
25
+ return MASKED_VALUE;
26
+ }
27
+ if (Array.isArray(value)) {
28
+ return value.map((entry) => maskUnknown(entry));
29
+ }
30
+ if (typeof value === "object") {
31
+ const out = {};
32
+ for (const [key, child] of Object.entries(value)) {
33
+ out[key] = maskUnknown(child);
34
+ }
35
+ return out;
36
+ }
37
+ return MASKED_VALUE;
38
+ };
39
+ export const maskDomPath = (domPath) => {
40
+ return maskString(domPath);
41
+ };
42
+ export const maskMessage = (message) => {
43
+ return maskString(message);
44
+ };
45
+ export const maskUrl = (urlValue) => {
46
+ if (!urlValue) {
47
+ return null;
48
+ }
49
+ try {
50
+ const url = new URL(urlValue, globalThis.location?.origin);
51
+ const query = url.search ? `?${MASKED_VALUE}` : "";
52
+ return `${url.origin}${url.pathname}${query}`;
53
+ }
54
+ catch {
55
+ return MASKED_VALUE;
56
+ }
57
+ };
58
+ export const maskInputValue = () => {
59
+ return MASKED_VALUE;
60
+ };
@@ -0,0 +1,14 @@
1
+ import type { ParameterSnapshotValue } from "../core/types";
2
+ type ParameterSnapshotBundle = {
3
+ snapshot: ParameterSnapshotValue;
4
+ allowedValueKeys: string[];
5
+ };
6
+ type ParameterSnapshotOptions = {
7
+ allowedPaths: string[];
8
+ deniedKeys: string[];
9
+ pathPrefix?: string;
10
+ };
11
+ export declare const buildUnavailableParameterSnapshot: () => ParameterSnapshotValue;
12
+ export declare const truncateParameterSnapshotValue: (value: ParameterSnapshotValue) => ParameterSnapshotValue;
13
+ export declare const buildParameterSnapshotBundle: (value: unknown, options: ParameterSnapshotOptions) => ParameterSnapshotBundle;
14
+ export {};
@@ -0,0 +1,206 @@
1
+ import { FORBIDDEN_PARAMETER_VALUE, MASKED_VALUE, MAX_SANITIZE_DEPTH, TRUNCATED_PARAMETER_VALUE, } from "../core/constants";
2
+ import { PARAMETER_CAPTURE_MODE } from "../core/contracts";
3
+ import { isDeniedKey } from "./sanitize";
4
+ const SNAPSHOT_UNAVAILABLE_KEY = "_clue_snapshot_unavailable";
5
+ const normalizeParameterLookupPath = (path) => {
6
+ return path.replace(/\[\d+\]/g, "[]");
7
+ };
8
+ const inferLeafType = (value) => {
9
+ if (value === null) {
10
+ return "null";
11
+ }
12
+ if (Array.isArray(value)) {
13
+ return "array";
14
+ }
15
+ if (typeof value === "object") {
16
+ return "object";
17
+ }
18
+ switch (typeof value) {
19
+ case "string":
20
+ return "string";
21
+ case "number":
22
+ return "number";
23
+ case "boolean":
24
+ return "boolean";
25
+ default:
26
+ return "string";
27
+ }
28
+ };
29
+ const toPlaintextLeafValue = (value) => {
30
+ if (value === null) {
31
+ return null;
32
+ }
33
+ if (typeof value === "string" ||
34
+ typeof value === "number" ||
35
+ typeof value === "boolean") {
36
+ return value;
37
+ }
38
+ return String(value);
39
+ };
40
+ const measureSerializedBytes = (value) => {
41
+ try {
42
+ return new TextEncoder().encode(JSON.stringify(value)).length;
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ };
48
+ const buildLeaf = (value, captureMode) => {
49
+ const leafType = inferLeafType(value);
50
+ const originalSizeBytes = captureMode === PARAMETER_CAPTURE_MODE.forbidden
51
+ ? undefined
52
+ : measureSerializedBytes(value);
53
+ return {
54
+ type: leafType,
55
+ value: captureMode === PARAMETER_CAPTURE_MODE.allowedPlaintext
56
+ ? toPlaintextLeafValue(value)
57
+ : captureMode === PARAMETER_CAPTURE_MODE.forbidden
58
+ ? FORBIDDEN_PARAMETER_VALUE
59
+ : MASKED_VALUE,
60
+ capture_mode: captureMode,
61
+ ...(typeof originalSizeBytes === "number"
62
+ ? { original_size_bytes: originalSizeBytes }
63
+ : {}),
64
+ };
65
+ };
66
+ export const buildUnavailableParameterSnapshot = () => ({
67
+ [SNAPSHOT_UNAVAILABLE_KEY]: buildLeaf("snapshot_unavailable", PARAMETER_CAPTURE_MODE.maskedFingerprint),
68
+ });
69
+ const isParameterSnapshotLeaf = (value) => {
70
+ return (!!value &&
71
+ typeof value === "object" &&
72
+ !Array.isArray(value) &&
73
+ "type" in value &&
74
+ "value" in value &&
75
+ "capture_mode" in value);
76
+ };
77
+ export const truncateParameterSnapshotValue = (value) => {
78
+ if (Array.isArray(value)) {
79
+ return value.map((entry) => truncateParameterSnapshotValue(entry));
80
+ }
81
+ if (isParameterSnapshotLeaf(value)) {
82
+ const originalSizeBytes = typeof value.original_size_bytes === "number"
83
+ ? value.original_size_bytes
84
+ : value.capture_mode === PARAMETER_CAPTURE_MODE.allowedPlaintext
85
+ ? measureSerializedBytes(value.value)
86
+ : undefined;
87
+ return {
88
+ type: value.type,
89
+ value: TRUNCATED_PARAMETER_VALUE,
90
+ capture_mode: PARAMETER_CAPTURE_MODE.truncated,
91
+ truncated_reason: "max_event_bytes",
92
+ ...(typeof originalSizeBytes === "number"
93
+ ? { original_size_bytes: originalSizeBytes }
94
+ : {}),
95
+ };
96
+ }
97
+ const truncated = {};
98
+ for (const [key, child] of Object.entries(value)) {
99
+ truncated[key] = truncateParameterSnapshotValue(child);
100
+ }
101
+ return truncated;
102
+ };
103
+ const buildAllowedPathLookup = (allowedPaths) => {
104
+ const lookup = new Map();
105
+ for (const entry of allowedPaths) {
106
+ const trimmed = entry.trim();
107
+ if (!trimmed) {
108
+ continue;
109
+ }
110
+ const normalized = normalizeParameterLookupPath(trimmed);
111
+ if (!lookup.has(normalized)) {
112
+ lookup.set(normalized, trimmed);
113
+ }
114
+ }
115
+ return lookup;
116
+ };
117
+ export const buildParameterSnapshotBundle = (value, options) => {
118
+ const allowedPathLookup = buildAllowedPathLookup(options.allowedPaths);
119
+ const seen = new WeakSet();
120
+ const rootPathPrefix = options.pathPrefix?.trim() ?? "";
121
+ const walk = (current, path, depth) => {
122
+ if (depth > MAX_SANITIZE_DEPTH) {
123
+ return {
124
+ snapshot: buildLeaf(null, PARAMETER_CAPTURE_MODE.maskedFingerprint),
125
+ allowedValueKeys: [],
126
+ };
127
+ }
128
+ if (current === undefined ||
129
+ typeof current === "function" ||
130
+ typeof current === "symbol") {
131
+ return {
132
+ snapshot: undefined,
133
+ allowedValueKeys: [],
134
+ };
135
+ }
136
+ if (Array.isArray(current)) {
137
+ if (current.length === 0) {
138
+ return {
139
+ snapshot: [],
140
+ allowedValueKeys: [],
141
+ };
142
+ }
143
+ const representative = walk(current[0], `${path}[]`, depth + 1);
144
+ if (representative.snapshot === undefined) {
145
+ return {
146
+ snapshot: [],
147
+ allowedValueKeys: representative.allowedValueKeys,
148
+ };
149
+ }
150
+ return {
151
+ snapshot: [representative.snapshot],
152
+ allowedValueKeys: representative.allowedValueKeys,
153
+ };
154
+ }
155
+ if (current && typeof current === "object") {
156
+ const objectValue = current;
157
+ if (seen.has(objectValue)) {
158
+ return {
159
+ snapshot: buildLeaf(null, PARAMETER_CAPTURE_MODE.maskedFingerprint),
160
+ allowedValueKeys: [],
161
+ };
162
+ }
163
+ seen.add(objectValue);
164
+ const snapshot = {};
165
+ const allowedValueKeys = [];
166
+ for (const [key, child] of Object.entries(objectValue)) {
167
+ const childPath = path ? `${path}.${key}` : key;
168
+ if (isDeniedKey(key, options.deniedKeys)) {
169
+ snapshot[key] = buildLeaf(child, PARAMETER_CAPTURE_MODE.forbidden);
170
+ continue;
171
+ }
172
+ const childBundle = walk(child, childPath, depth + 1);
173
+ if (childBundle.snapshot === undefined) {
174
+ continue;
175
+ }
176
+ snapshot[key] = childBundle.snapshot;
177
+ allowedValueKeys.push(...childBundle.allowedValueKeys);
178
+ }
179
+ seen.delete(objectValue);
180
+ return {
181
+ snapshot,
182
+ allowedValueKeys,
183
+ };
184
+ }
185
+ const matchedAllowedPath = path
186
+ ? allowedPathLookup.get(normalizeParameterLookupPath(path)) ?? null
187
+ : null;
188
+ const captureMode = matchedAllowedPath
189
+ ? PARAMETER_CAPTURE_MODE.allowedPlaintext
190
+ : PARAMETER_CAPTURE_MODE.maskedFingerprint;
191
+ return {
192
+ snapshot: buildLeaf(current, captureMode),
193
+ allowedValueKeys: matchedAllowedPath ? [matchedAllowedPath] : [],
194
+ };
195
+ };
196
+ const rootBundle = walk(value, rootPathPrefix, 0);
197
+ const snapshot = rootBundle.snapshot &&
198
+ typeof rootBundle.snapshot === "object" &&
199
+ !Array.isArray(rootBundle.snapshot)
200
+ ? rootBundle.snapshot
201
+ : {};
202
+ return {
203
+ snapshot,
204
+ allowedValueKeys: [...new Set(rootBundle.allowedValueKeys)].sort(),
205
+ };
206
+ };
@@ -0,0 +1,11 @@
1
+ export declare const isDeniedKey: (key: string, deniedKeys?: string[]) => boolean;
2
+ export declare const sanitizeHeaders: (headers: Record<string, string>, deniedKeys?: string[]) => Record<string, string>;
3
+ export declare const sanitizeObject: (value: unknown, deniedKeys?: string[]) => unknown;
4
+ export declare const parseUrlForNetwork: (rawUrl: string) => {
5
+ host: string;
6
+ pathTemplate: string;
7
+ path: string;
8
+ query: Record<string, string | string[]>;
9
+ urlMaskedQuery: string;
10
+ };
11
+ export declare const maybeParseJson: (value: unknown) => unknown;