@genn-inc/cluebase-frontend-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 (140) hide show
  1. package/README.md +222 -0
  2. package/dist/_generated/correlation-attribute-keys.d.ts +2 -0
  3. package/dist/_generated/correlation-attribute-keys.js +33 -0
  4. package/dist/_generated/identity-pii-policy.d.ts +38 -0
  5. package/dist/_generated/identity-pii-policy.js +103 -0
  6. package/dist/_generated/ingest-privacy-tables.d.ts +120 -0
  7. package/dist/_generated/ingest-privacy-tables.js +890 -0
  8. package/dist/_generated/otel-resource-keys.d.ts +21 -0
  9. package/dist/_generated/otel-resource-keys.js +72 -0
  10. package/dist/_generated/otel-span-conversion.d.ts +48 -0
  11. package/dist/_generated/otel-span-conversion.js +69 -0
  12. package/dist/_generated/parameter-snapshot-leaf.d.ts +11 -0
  13. package/dist/_generated/parameter-snapshot-leaf.js +37 -0
  14. package/dist/_generated/sdk-runtime-policy.d.ts +7 -0
  15. package/dist/_generated/sdk-runtime-policy.js +14 -0
  16. package/dist/_generated/usage-governor-tables.d.ts +15 -0
  17. package/dist/_generated/usage-governor-tables.js +44 -0
  18. package/dist/context/browser-storage.d.ts +15 -0
  19. package/dist/context/browser-storage.js +38 -0
  20. package/dist/context/environment.d.ts +37 -0
  21. package/dist/context/environment.js +229 -0
  22. package/dist/context/identity.d.ts +16 -0
  23. package/dist/context/identity.js +139 -0
  24. package/dist/context/organization.d.ts +16 -0
  25. package/dist/context/organization.js +60 -0
  26. package/dist/context/session.d.ts +28 -0
  27. package/dist/context/session.js +130 -0
  28. package/dist/context/tab.d.ts +22 -0
  29. package/dist/context/tab.js +117 -0
  30. package/dist/context/trace.d.ts +76 -0
  31. package/dist/context/trace.js +163 -0
  32. package/dist/exporter/batch.d.ts +94 -0
  33. package/dist/exporter/batch.js +485 -0
  34. package/dist/exporter/browser-outbox-records.d.ts +159 -0
  35. package/dist/exporter/browser-outbox-records.js +341 -0
  36. package/dist/exporter/browser-outbox.d.ts +112 -0
  37. package/dist/exporter/browser-outbox.js +876 -0
  38. package/dist/exporter/client.d.ts +92 -0
  39. package/dist/exporter/client.js +541 -0
  40. package/dist/exporter/delivery-response-contract.d.ts +103 -0
  41. package/dist/exporter/delivery-response-contract.js +147 -0
  42. package/dist/exporter/delivery-response.d.ts +37 -0
  43. package/dist/exporter/delivery-response.js +151 -0
  44. package/dist/exporter/diagnostics.d.ts +42 -0
  45. package/dist/exporter/diagnostics.js +148 -0
  46. package/dist/exporter/event-id.d.ts +10 -0
  47. package/dist/exporter/event-id.js +75 -0
  48. package/dist/exporter/frontend-resource.d.ts +20 -0
  49. package/dist/exporter/frontend-resource.js +52 -0
  50. package/dist/exporter/local-minimization.d.ts +29 -0
  51. package/dist/exporter/local-minimization.js +87 -0
  52. package/dist/exporter/observation-source-event-exporter.d.ts +69 -0
  53. package/dist/exporter/observation-source-event-exporter.js +154 -0
  54. package/dist/exporter/parameter-snapshot.d.ts +14 -0
  55. package/dist/exporter/parameter-snapshot.js +215 -0
  56. package/dist/exporter/path-template.d.ts +1 -0
  57. package/dist/exporter/path-template.js +67 -0
  58. package/dist/exporter/raw-event-to-source-signal.d.ts +12 -0
  59. package/dist/exporter/raw-event-to-source-signal.js +297 -0
  60. package/dist/exporter/retry.d.ts +8 -0
  61. package/dist/exporter/retry.js +8 -0
  62. package/dist/exporter/sanitize.d.ts +30 -0
  63. package/dist/exporter/sanitize.js +177 -0
  64. package/dist/exporter/schema-evidence.d.ts +20 -0
  65. package/dist/exporter/schema-evidence.js +232 -0
  66. package/dist/exporter/source-event.d.ts +99 -0
  67. package/dist/exporter/source-event.js +13 -0
  68. package/dist/exporter/span-to-source-signal.d.ts +8 -0
  69. package/dist/exporter/span-to-source-signal.js +174 -0
  70. package/dist/exporter/text-fingerprint.d.ts +9 -0
  71. package/dist/exporter/text-fingerprint.js +16 -0
  72. package/dist/index.d.ts +25 -0
  73. package/dist/index.js +158 -0
  74. package/dist/instrumentation/action.d.ts +2 -0
  75. package/dist/instrumentation/action.js +171 -0
  76. package/dist/instrumentation/cluebase-authored-attributes.d.ts +4 -0
  77. package/dist/instrumentation/cluebase-authored-attributes.js +32 -0
  78. package/dist/instrumentation/common.d.ts +73 -0
  79. package/dist/instrumentation/common.js +90 -0
  80. package/dist/instrumentation/dom.d.ts +63 -0
  81. package/dist/instrumentation/dom.js +640 -0
  82. package/dist/instrumentation/drag.d.ts +2 -0
  83. package/dist/instrumentation/drag.js +82 -0
  84. package/dist/instrumentation/error.d.ts +2 -0
  85. package/dist/instrumentation/error.js +318 -0
  86. package/dist/instrumentation/form.d.ts +2 -0
  87. package/dist/instrumentation/form.js +132 -0
  88. package/dist/instrumentation/input.d.ts +2 -0
  89. package/dist/instrumentation/input.js +180 -0
  90. package/dist/instrumentation/location.d.ts +11 -0
  91. package/dist/instrumentation/location.js +69 -0
  92. package/dist/instrumentation/naming-evidence.d.ts +20 -0
  93. package/dist/instrumentation/naming-evidence.js +11 -0
  94. package/dist/instrumentation/navigation.d.ts +2 -0
  95. package/dist/instrumentation/navigation.js +144 -0
  96. package/dist/instrumentation/network-request.d.ts +103 -0
  97. package/dist/instrumentation/network-request.js +285 -0
  98. package/dist/instrumentation/network-span-attributes.d.ts +25 -0
  99. package/dist/instrumentation/network-span-attributes.js +153 -0
  100. package/dist/instrumentation/network.d.ts +16 -0
  101. package/dist/instrumentation/network.js +502 -0
  102. package/dist/instrumentation/page.d.ts +2 -0
  103. package/dist/instrumentation/page.js +56 -0
  104. package/dist/instrumentation/performance.d.ts +2 -0
  105. package/dist/instrumentation/performance.js +365 -0
  106. package/dist/integrations/next-router.d.ts +16 -0
  107. package/dist/integrations/next-router.js +18 -0
  108. package/dist/integrations/react-router.d.ts +7 -0
  109. package/dist/integrations/react-router.js +61 -0
  110. package/dist/internal/internal-log.d.ts +23 -0
  111. package/dist/internal/internal-log.js +47 -0
  112. package/dist/internal/metrics.d.ts +10 -0
  113. package/dist/internal/metrics.js +50 -0
  114. package/dist/provider/_version.d.ts +1 -0
  115. package/dist/provider/_version.js +3 -0
  116. package/dist/provider/config.d.ts +12 -0
  117. package/dist/provider/config.js +177 -0
  118. package/dist/provider/constants.d.ts +62 -0
  119. package/dist/provider/constants.js +118 -0
  120. package/dist/provider/contracts.d.ts +51 -0
  121. package/dist/provider/contracts.js +46 -0
  122. package/dist/provider/privacy-policy-store.d.ts +20 -0
  123. package/dist/provider/privacy-policy-store.js +56 -0
  124. package/dist/provider/sampling.d.ts +2 -0
  125. package/dist/provider/sampling.js +17 -0
  126. package/dist/provider/sdk-event-pipeline.d.ts +60 -0
  127. package/dist/provider/sdk-event-pipeline.js +478 -0
  128. package/dist/provider/sdk-input-normalization.d.ts +7 -0
  129. package/dist/provider/sdk-input-normalization.js +113 -0
  130. package/dist/provider/sdk-otel-pipeline.d.ts +14 -0
  131. package/dist/provider/sdk-otel-pipeline.js +148 -0
  132. package/dist/provider/sdk-runtime-surfaces.d.ts +49 -0
  133. package/dist/provider/sdk-runtime-surfaces.js +216 -0
  134. package/dist/provider/sdk.d.ts +2 -0
  135. package/dist/provider/sdk.js +614 -0
  136. package/dist/provider/types.d.ts +323 -0
  137. package/dist/provider/types.js +1 -0
  138. package/dist/provider/usage-governor.d.ts +7 -0
  139. package/dist/provider/usage-governor.js +116 -0
  140. package/package.json +59 -0
@@ -0,0 +1,180 @@
1
+ import { buildLocalTextDescriptor, buildLocalValueDescriptor, } from "../exporter/local-minimization.js";
2
+ import { extractFieldName, extractFieldType, getElementAriaLabel, getElementLabel, getElementPlaceholder, getElementRole, getElementSectionHeading, getElementValue, resolveInteractionScopeDescriptor, resolveStableKeyDescriptor, } from "./dom.js";
3
+ import { buildPrimaryActionKey } from "./common.js";
4
+ import { getCurrentPageSnapshot } from "./location.js";
5
+ import { buildScreenActionNamingEvidence } from "./naming-evidence.js";
6
+ const buildFileDescriptors = (input) => {
7
+ const files = input.files ? Array.from(input.files) : [];
8
+ return files.map((file) => ({
9
+ mimeType: file.type || null,
10
+ size: file.size,
11
+ }));
12
+ };
13
+ const isInputCaptureElement = (target) => target instanceof HTMLInputElement ||
14
+ target instanceof HTMLTextAreaElement ||
15
+ target instanceof HTMLSelectElement;
16
+ const isDeferredCommitElement = (target) => (target instanceof HTMLInputElement &&
17
+ target.type !== "checkbox" &&
18
+ target.type !== "radio" &&
19
+ target.type !== "file" &&
20
+ target.type !== "password") ||
21
+ target instanceof HTMLTextAreaElement;
22
+ export const startInputCapture = (context) => {
23
+ if (typeof document === "undefined") {
24
+ return { stop: () => undefined };
25
+ }
26
+ const dirtyControls = new Set();
27
+ // OTel user-action covers discrete clicks; committed field values,
28
+ // selections, toggles, and file counts are Cluebase-only UX evidence.
29
+ const emitCommit = (target) => {
30
+ if (context.getConsent() === "denied") {
31
+ return;
32
+ }
33
+ if (context.shouldIgnoreElement?.(target)) {
34
+ return;
35
+ }
36
+ if (target instanceof HTMLInputElement && target.type === "password") {
37
+ return;
38
+ }
39
+ try {
40
+ const stableKey = resolveStableKeyDescriptor(target);
41
+ const interactionScope = resolveInteractionScopeDescriptor(target);
42
+ const commonPayload = {
43
+ viewId: context.getViewId(),
44
+ formId: stableKey.formKey,
45
+ formKey: stableKey.formKey,
46
+ fieldType: extractFieldType(target),
47
+ elementTag: target.tagName.toLowerCase(),
48
+ elementRole: getElementRole(target),
49
+ primaryActionKey: buildPrimaryActionKey(getCurrentPageSnapshot().screenKey, stableKey.stableKey),
50
+ stableKey: stableKey.stableKey,
51
+ stableKeyQuality: stableKey.stableKeyQuality,
52
+ interactionScopeKind: interactionScope?.interactionScopeKind ?? null,
53
+ interactionScopeKey: interactionScope?.interactionScopeKey ?? null,
54
+ fieldNameMeta: buildLocalTextDescriptor(extractFieldName(target)),
55
+ elementLabelMeta: buildLocalTextDescriptor(getElementLabel(target)),
56
+ ariaLabelMeta: buildLocalTextDescriptor(getElementAriaLabel(target)),
57
+ placeholderMeta: buildLocalTextDescriptor(getElementPlaceholder(target)),
58
+ fieldName: extractFieldName(target),
59
+ fieldDisplayLabel: getElementLabel(target),
60
+ fieldAriaLabel: getElementAriaLabel(target),
61
+ fieldPlaceholder: getElementPlaceholder(target),
62
+ sectionHeading: getElementSectionHeading(target),
63
+ ...buildScreenActionNamingEvidence(target),
64
+ };
65
+ if (target instanceof HTMLInputElement &&
66
+ (target.type === "checkbox" || target.type === "radio")) {
67
+ context.onEvent({
68
+ type: "toggle_changed",
69
+ occurredAtMs: Date.now(),
70
+ payload: {
71
+ ...commonPayload,
72
+ checked: target.checked,
73
+ inputValueMeta: buildLocalValueDescriptor(target.value),
74
+ },
75
+ });
76
+ return;
77
+ }
78
+ if (target instanceof HTMLSelectElement) {
79
+ const selectedOptions = Array.from(target.selectedOptions);
80
+ const selectedValues = selectedOptions.map((option) => option.value);
81
+ const selectedOptionLabels = selectedOptions
82
+ .map((option) => (option.label || option.textContent || "").replace(/\s+/g, " ").trim())
83
+ .filter((value) => value.length > 0);
84
+ context.onEvent({
85
+ type: "selection_committed",
86
+ occurredAtMs: Date.now(),
87
+ payload: {
88
+ ...commonPayload,
89
+ selectedCount: selectedValues.length,
90
+ selectedOptionLabels,
91
+ selectedValueMeta: buildLocalValueDescriptor(selectedValues),
92
+ },
93
+ });
94
+ return;
95
+ }
96
+ if (target instanceof HTMLInputElement && target.type === "file") {
97
+ const fileDescriptors = buildFileDescriptors(target);
98
+ context.onEvent({
99
+ type: "file_selected",
100
+ occurredAtMs: Date.now(),
101
+ payload: {
102
+ ...commonPayload,
103
+ fileCount: fileDescriptors.length,
104
+ fileSelectionMeta: buildLocalValueDescriptor({
105
+ fileCount: fileDescriptors.length,
106
+ }),
107
+ fileDescriptors,
108
+ },
109
+ });
110
+ return;
111
+ }
112
+ context.onEvent({
113
+ type: "input_committed",
114
+ occurredAtMs: Date.now(),
115
+ payload: {
116
+ ...commonPayload,
117
+ inputValueMeta: buildLocalValueDescriptor(getElementValue(target)),
118
+ },
119
+ });
120
+ if (isDeferredCommitElement(target)) {
121
+ dirtyControls.delete(target);
122
+ }
123
+ }
124
+ catch (error) {
125
+ context.onInternalError("capture/input:change", error);
126
+ }
127
+ };
128
+ const inputHandler = (event) => {
129
+ const target = event.target;
130
+ if (!isInputCaptureElement(target)) {
131
+ return;
132
+ }
133
+ if (!isDeferredCommitElement(target)) {
134
+ return;
135
+ }
136
+ if (context.getConsent() === "denied") {
137
+ return;
138
+ }
139
+ if (context.shouldIgnoreElement?.(target)) {
140
+ return;
141
+ }
142
+ dirtyControls.add(target);
143
+ };
144
+ const changeHandler = (event) => {
145
+ const target = event.target;
146
+ if (!isInputCaptureElement(target)) {
147
+ return;
148
+ }
149
+ emitCommit(target);
150
+ };
151
+ const flushDirtyControls = () => {
152
+ for (const target of Array.from(dirtyControls)) {
153
+ if (!target.isConnected) {
154
+ dirtyControls.delete(target);
155
+ continue;
156
+ }
157
+ emitCommit(target);
158
+ }
159
+ };
160
+ const visibilityHandler = () => {
161
+ if (document.visibilityState === "hidden") {
162
+ flushDirtyControls();
163
+ }
164
+ };
165
+ document.addEventListener("change", changeHandler, true);
166
+ document.addEventListener("input", inputHandler, true);
167
+ document.addEventListener("visibilitychange", visibilityHandler, true);
168
+ window.addEventListener("pagehide", flushDirtyControls, true);
169
+ window.addEventListener("beforeunload", flushDirtyControls, true);
170
+ return {
171
+ stop: () => {
172
+ document.removeEventListener("change", changeHandler, true);
173
+ document.removeEventListener("input", inputHandler, true);
174
+ document.removeEventListener("visibilitychange", visibilityHandler, true);
175
+ window.removeEventListener("pagehide", flushDirtyControls, true);
176
+ window.removeEventListener("beforeunload", flushDirtyControls, true);
177
+ dirtyControls.clear();
178
+ },
179
+ };
180
+ };
@@ -0,0 +1,11 @@
1
+ type PageSnapshot = {
2
+ url: string | null;
3
+ path: string | null;
4
+ screenKey: string | null;
5
+ urlCanonical: string | null;
6
+ pageTitleCandidate: string | null;
7
+ primaryHeadingCandidate: string | null;
8
+ };
9
+ export declare const getCurrentPageSnapshot: () => PageSnapshot;
10
+ export declare const getPageSnapshotFromUrl: (href: string | null) => PageSnapshot;
11
+ export {};
@@ -0,0 +1,69 @@
1
+ const buildUrlCanonical = (href) => {
2
+ if (!href) {
3
+ return null;
4
+ }
5
+ try {
6
+ const url = new URL(href, globalThis.location?.origin);
7
+ return `${url.origin}${url.pathname}`;
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ };
13
+ const readPrimaryHeadingCandidate = (documentRef) => {
14
+ const heading = documentRef.querySelector("h1, main h1, [role='heading'][aria-level='1']");
15
+ if (!(heading instanceof Element)) {
16
+ return null;
17
+ }
18
+ const text = heading.textContent?.replace(/\s+/g, " ").trim() ?? "";
19
+ return text.length > 0 ? text.slice(0, 120) : null;
20
+ };
21
+ export const getCurrentPageSnapshot = () => {
22
+ if (typeof window === "undefined" || typeof document === "undefined") {
23
+ return {
24
+ url: null,
25
+ path: null,
26
+ screenKey: null,
27
+ urlCanonical: null,
28
+ pageTitleCandidate: null,
29
+ primaryHeadingCandidate: null,
30
+ };
31
+ }
32
+ const url = window.location.href;
33
+ const path = window.location.pathname || "/";
34
+ return {
35
+ url,
36
+ path,
37
+ screenKey: path,
38
+ urlCanonical: buildUrlCanonical(url),
39
+ pageTitleCandidate: document.title || null,
40
+ primaryHeadingCandidate: readPrimaryHeadingCandidate(document),
41
+ };
42
+ };
43
+ export const getPageSnapshotFromUrl = (href) => {
44
+ if (typeof document === "undefined") {
45
+ return {
46
+ url: href,
47
+ path: null,
48
+ screenKey: null,
49
+ urlCanonical: buildUrlCanonical(href),
50
+ pageTitleCandidate: null,
51
+ primaryHeadingCandidate: null,
52
+ };
53
+ }
54
+ let path = null;
55
+ try {
56
+ path = href ? new URL(href, globalThis.location?.origin).pathname || "/" : null;
57
+ }
58
+ catch {
59
+ path = null;
60
+ }
61
+ return {
62
+ url: href,
63
+ path,
64
+ screenKey: path,
65
+ urlCanonical: buildUrlCanonical(href),
66
+ pageTitleCandidate: document.title || null,
67
+ primaryHeadingCandidate: readPrimaryHeadingCandidate(document),
68
+ };
69
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 画面上の操作が**何なのか**を後段が言い当てるための材料。
3
+ *
4
+ * 名付ける相手は、押された要素そのものを見られません。ここで採らなかったものは
5
+ * 「無かった」のと同じになります。実測では、要素の鍵だけを渡した押下が
6
+ * `execute_primary_action` / 確信度 0.18 になりました。
7
+ *
8
+ * 文言 (名前・題・行き先) は顧客が書いたもので、個人に結び付く値が混ざり得ます。
9
+ * 伏せるかどうかは受付側が顧客の設定 1 本で決めるので、ここでは伏せません。
10
+ * **文言が伏せられても、役割・祖先の並び・安定した識別子だけで慎重な一般名が
11
+ * 作れる**ように、機械が決めた値を必ず一緒に載せます。
12
+ */
13
+ export type ScreenActionNamingEvidence = {
14
+ accessibleName: string | null;
15
+ accessibleNameSource: string | null;
16
+ ancestorChain: string | null;
17
+ documentTitle: string | null;
18
+ navigationHref: string | null;
19
+ };
20
+ export declare const buildScreenActionNamingEvidence: (element: Element) => ScreenActionNamingEvidence;
@@ -0,0 +1,11 @@
1
+ import { getAccessibleName, getAncestorChain, getDocumentTitle, getNavigationHref, } from "./dom.js";
2
+ export const buildScreenActionNamingEvidence = (element) => {
3
+ const accessibleName = getAccessibleName(element);
4
+ return {
5
+ accessibleName: accessibleName?.name ?? null,
6
+ accessibleNameSource: accessibleName?.source ?? null,
7
+ ancestorChain: getAncestorChain(element),
8
+ documentTitle: getDocumentTitle(element),
9
+ navigationHref: getNavigationHref(element),
10
+ };
11
+ };
@@ -0,0 +1,2 @@
1
+ import type { CaptureModuleContext, CaptureStopHandle } from "../provider/types.js";
2
+ export declare const startNavigationCapture: (context: CaptureModuleContext) => CaptureStopHandle;
@@ -0,0 +1,144 @@
1
+ import { NavigationInstrumentation } from "@opentelemetry/browser-instrumentation/experimental/navigation";
2
+ import { generateViewId } from "../exporter/event-id.js";
3
+ import { getCurrentPageSnapshot, getPageSnapshotFromUrl } from "./location.js";
4
+ const readStringAttribute = (attributes, key) => {
5
+ const value = attributes?.[key];
6
+ return typeof value === "string" && value.length > 0 ? value : null;
7
+ };
8
+ const readBooleanAttribute = (attributes, key) => {
9
+ const value = attributes?.[key];
10
+ return typeof value === "boolean" ? value : null;
11
+ };
12
+ const sanitizeNavigationUrl = (value) => {
13
+ try {
14
+ const url = new URL(value, globalThis.location?.origin);
15
+ url.search = "";
16
+ url.hash = "";
17
+ return url.toString();
18
+ }
19
+ catch {
20
+ return value.split(/[?#]/, 1)[0] ?? value;
21
+ }
22
+ };
23
+ export const startNavigationCapture = (context) => {
24
+ if (typeof window === "undefined") {
25
+ return { stop: () => undefined };
26
+ }
27
+ let currentSnapshot = getCurrentPageSnapshot();
28
+ let currentViewId = context.getViewId();
29
+ let viewEnteredAtMs = Date.now();
30
+ let initialPageViewEmitted = false;
31
+ const emitInitialPageView = (destinationUrl) => {
32
+ if (initialPageViewEmitted || context.getConsent() === "denied") {
33
+ return;
34
+ }
35
+ const snapshot = getPageSnapshotFromUrl(destinationUrl);
36
+ const viewId = context.getViewId();
37
+ const now = Date.now();
38
+ context.onEvent({
39
+ type: "page_view",
40
+ occurredAtMs: now,
41
+ payload: {
42
+ viewId,
43
+ screenKey: snapshot.screenKey,
44
+ path: snapshot.path,
45
+ urlCanonical: snapshot.urlCanonical,
46
+ pageTitleCandidate: snapshot.pageTitleCandidate,
47
+ routeNameCandidate: null,
48
+ primaryHeadingCandidate: snapshot.primaryHeadingCandidate,
49
+ },
50
+ });
51
+ initialPageViewEmitted = true;
52
+ currentSnapshot = snapshot;
53
+ currentViewId = viewId;
54
+ viewEnteredAtMs = now;
55
+ };
56
+ const emitRouteChange = (destinationUrl) => {
57
+ if (context.getConsent() === "denied") {
58
+ return;
59
+ }
60
+ const nextSnapshot = getPageSnapshotFromUrl(destinationUrl);
61
+ if (!nextSnapshot.path || nextSnapshot.path === currentSnapshot.path) {
62
+ return;
63
+ }
64
+ const now = Date.now();
65
+ const previousViewId = currentViewId;
66
+ const nextViewId = generateViewId();
67
+ context.onEvent({
68
+ type: "page_leave",
69
+ occurredAtMs: now,
70
+ payload: {
71
+ viewId: previousViewId,
72
+ screenKey: currentSnapshot.screenKey,
73
+ path: currentSnapshot.path,
74
+ // 離脱イベントは遷移後に送られるため、周囲の文脈から URL を取ると
75
+ // 次の画面を指す。離れた画面自身の URL を payload で名乗る。
76
+ urlCanonical: currentSnapshot.urlCanonical,
77
+ timeOnPageMs: Math.max(0, now - viewEnteredAtMs),
78
+ },
79
+ });
80
+ context.onEvent({
81
+ type: "route_change",
82
+ occurredAtMs: now,
83
+ payload: {
84
+ fromViewId: previousViewId,
85
+ toViewId: nextViewId,
86
+ fromScreenKey: currentSnapshot.screenKey,
87
+ toScreenKey: nextSnapshot.screenKey,
88
+ fromPath: currentSnapshot.path,
89
+ toPath: nextSnapshot.path,
90
+ },
91
+ });
92
+ context.onEvent({
93
+ type: "page_view",
94
+ occurredAtMs: now,
95
+ payload: {
96
+ viewId: nextViewId,
97
+ screenKey: nextSnapshot.screenKey,
98
+ path: nextSnapshot.path,
99
+ urlCanonical: nextSnapshot.urlCanonical,
100
+ pageTitleCandidate: nextSnapshot.pageTitleCandidate,
101
+ routeNameCandidate: null,
102
+ primaryHeadingCandidate: nextSnapshot.primaryHeadingCandidate,
103
+ },
104
+ });
105
+ currentViewId = nextViewId;
106
+ currentSnapshot = nextSnapshot;
107
+ viewEnteredAtMs = now;
108
+ context.requestFlush?.();
109
+ };
110
+ const instrumentation = new NavigationInstrumentation({
111
+ useNavigationApiIfAvailable: true,
112
+ sanitizeUrl: sanitizeNavigationUrl,
113
+ });
114
+ const logger = {
115
+ enabled: () => true,
116
+ emit: (logRecord) => {
117
+ if (logRecord.eventName !== "browser.navigation") {
118
+ return;
119
+ }
120
+ try {
121
+ const destinationUrl = readStringAttribute(logRecord.attributes, "url.full") ??
122
+ window.location.href;
123
+ if (readBooleanAttribute(logRecord.attributes, "browser.navigation.same_document") === false) {
124
+ emitInitialPageView(destinationUrl);
125
+ return;
126
+ }
127
+ emitRouteChange(destinationUrl);
128
+ }
129
+ catch (error) {
130
+ context.onInternalError("capture/navigation:otel", error);
131
+ }
132
+ },
133
+ };
134
+ const loggerProvider = {
135
+ getLogger: () => logger,
136
+ };
137
+ instrumentation.setLoggerProvider(loggerProvider);
138
+ instrumentation.enable();
139
+ return {
140
+ stop: () => {
141
+ instrumentation.disable();
142
+ },
143
+ };
144
+ };
@@ -0,0 +1,103 @@
1
+ import { type LocalStructuredSummary } from "../exporter/local-minimization.js";
2
+ import { type ParameterSnapshotBundle } from "../exporter/parameter-snapshot.js";
3
+ import type { CaptureModuleContext } from "../provider/types.js";
4
+ export type HeaderRecord = {
5
+ [key: string]: string;
6
+ };
7
+ type QueryValues = {
8
+ [key: string]: string | string[];
9
+ };
10
+ type EmptyStructuredSummary = {
11
+ readonly [key: string]: never;
12
+ };
13
+ export type RequestParameterSource = {
14
+ headers?: HeaderRecord;
15
+ query?: QueryValues;
16
+ body?: unknown;
17
+ };
18
+ export type RequestMetadataSummary = LocalStructuredSummary | EmptyStructuredSummary;
19
+ export type RequestTraceContext = {
20
+ traceId: string;
21
+ parentSpanId: string | null;
22
+ requestId: string;
23
+ requestSpanId: string;
24
+ interactionId: string;
25
+ interactionInitiator?: string | null;
26
+ interactionStartEventKind?: string | null;
27
+ originFrontendInteractionId?: string | null;
28
+ };
29
+ export type RequestShapeEvidence = {
30
+ requestKind: "fetch" | "xhr" | string;
31
+ requestSchemaHash: string | null;
32
+ requestFieldPaths: string[];
33
+ requestShapeSize: number;
34
+ graphqlOperationName: string | null;
35
+ graphqlOperationType: string | null;
36
+ graphqlTopLevelFields: string[];
37
+ };
38
+ export type ResponseShapeEvidence = {
39
+ outcomeClass: string | null;
40
+ errorCodeCandidate: string | null;
41
+ responseSchemaHash: string | null;
42
+ responseFieldPaths: string[];
43
+ responseShapeSize: number;
44
+ };
45
+ export type NetworkRequestMeta = {
46
+ method: string;
47
+ rawUrl: string;
48
+ url: string;
49
+ path: string;
50
+ pathTemplate: string;
51
+ targetService: string | null;
52
+ startedAtMs: number;
53
+ traceId: string;
54
+ parentSpanId: string | null;
55
+ requestId: string;
56
+ requestSpanId: string;
57
+ interactionId: string;
58
+ interactionInitiator: string | null;
59
+ interactionStartEventKind: string | null;
60
+ originFrontendInteractionId: string | null;
61
+ viewId: string | null;
62
+ requestHeaders: HeaderRecord;
63
+ queryKeys: string[];
64
+ /** 値を持たない入れ物。顧客が名指しした項目だけ、方針が届いた時に値を持つ。 */
65
+ requestParametersSnapshot: ParameterSnapshotBundle;
66
+ requestMetadataSummary: RequestMetadataSummary;
67
+ requestShapeEvidence: RequestShapeEvidence;
68
+ requestSizeBytes?: number;
69
+ pageUrl: string | null;
70
+ };
71
+ export type FetchInstrumentationFailure = {
72
+ status?: number;
73
+ message?: string;
74
+ name?: string;
75
+ };
76
+ export type FetchInstrumentationResult = Response | FetchInstrumentationFailure;
77
+ export declare const headersToRecord: (headers: HeadersInit | undefined) => HeaderRecord;
78
+ export declare const buildParameterSource: (input: {
79
+ headers?: HeaderRecord;
80
+ query?: QueryValues;
81
+ body?: unknown;
82
+ }) => RequestParameterSource;
83
+ export declare const buildResponseShapeEvidence: (input: {
84
+ normalizedResponseBody: unknown;
85
+ statusCode?: number | null;
86
+ failureType?: string | null;
87
+ }) => ResponseShapeEvidence;
88
+ export declare const deriveFailureTypeFromStatus: (status: number) => string | null;
89
+ export declare const getXhrResponseBody: (xhr: XMLHttpRequest) => unknown;
90
+ export declare const parseXhrHeaders: (rawHeaders: string) => HeaderRecord;
91
+ export declare const createRequestMeta: (input: {
92
+ context: CaptureModuleContext;
93
+ method: string;
94
+ rawUrl: string;
95
+ requestHeaders: HeaderRecord;
96
+ body: BodyInit | null;
97
+ defaultRequestKind: "fetch" | "xhr";
98
+ traceContext: RequestTraceContext;
99
+ startedAtMs?: number;
100
+ /** 顧客が init で足した落とす項目名。既定の落とす表は下の組み立てが自分で読む。 */
101
+ deniedKeys?: readonly string[];
102
+ }) => NetworkRequestMeta;
103
+ export {};