@loggerjs/browser 0.5.0 → 0.5.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.
- package/dist/broadcast-channel-transport.cjs +115 -0
- package/dist/broadcast-channel-transport.cjs.map +1 -0
- package/dist/broadcast-channel-transport.js +114 -0
- package/dist/broadcast-channel-transport.js.map +1 -0
- package/dist/console-integration.cjs +98 -0
- package/dist/console-integration.cjs.map +1 -0
- package/dist/console-integration.js +97 -0
- package/dist/console-integration.js.map +1 -0
- package/dist/context-propagation-integration.cjs +78 -0
- package/dist/context-propagation-integration.cjs.map +1 -0
- package/dist/context-propagation-integration.js +77 -0
- package/dist/context-propagation-integration.js.map +1 -0
- package/dist/error-integration.cjs +128 -0
- package/dist/error-integration.cjs.map +1 -0
- package/dist/error-integration.js +127 -0
- package/dist/error-integration.js.map +1 -0
- package/dist/fetch-integration.cjs +120 -0
- package/dist/fetch-integration.cjs.map +1 -0
- package/dist/fetch-integration.js +119 -0
- package/dist/fetch-integration.js.map +1 -0
- package/dist/framework-error-integration.cjs +102 -0
- package/dist/framework-error-integration.cjs.map +1 -0
- package/dist/framework-error-integration.js +101 -0
- package/dist/framework-error-integration.js.map +1 -0
- package/dist/framework-router-integrations.cjs +88 -0
- package/dist/framework-router-integrations.cjs.map +1 -0
- package/dist/framework-router-integrations.js +84 -0
- package/dist/framework-router-integrations.js.map +1 -0
- package/dist/http-transport.cjs +301 -0
- package/dist/http-transport.cjs.map +1 -0
- package/dist/http-transport.js +299 -0
- package/dist/http-transport.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-offline-queue.cjs +105 -0
- package/dist/indexeddb-offline-queue.cjs.map +1 -0
- package/dist/indexeddb-offline-queue.js +104 -0
- package/dist/indexeddb-offline-queue.js.map +1 -0
- package/dist/indexeddb-transport.cjs +531 -0
- package/dist/indexeddb-transport.cjs.map +1 -0
- package/dist/indexeddb-transport.js +530 -0
- package/dist/indexeddb-transport.js.map +1 -0
- package/dist/offline-first-transport.cjs +675 -0
- package/dist/offline-first-transport.cjs.map +1 -0
- package/dist/offline-first-transport.js +674 -0
- package/dist/offline-first-transport.js.map +1 -0
- package/dist/page-lifecycle.cjs +39 -0
- package/dist/page-lifecycle.cjs.map +1 -0
- package/dist/page-lifecycle.js +38 -0
- package/dist/page-lifecycle.js.map +1 -0
- package/dist/payload-transforms.cjs +30 -0
- package/dist/payload-transforms.cjs.map +1 -0
- package/dist/payload-transforms.js +29 -0
- package/dist/payload-transforms.js.map +1 -0
- package/dist/performance-integration.cjs +135 -0
- package/dist/performance-integration.cjs.map +1 -0
- package/dist/performance-integration.js +133 -0
- package/dist/performance-integration.js.map +1 -0
- package/dist/reporting-integration.cjs +110 -0
- package/dist/reporting-integration.cjs.map +1 -0
- package/dist/reporting-integration.js +109 -0
- package/dist/reporting-integration.js.map +1 -0
- package/dist/router-integration.cjs +97 -0
- package/dist/router-integration.cjs.map +1 -0
- package/dist/router-integration.js +96 -0
- package/dist/router-integration.js.map +1 -0
- package/dist/runtime-host-integration.cjs +181 -0
- package/dist/runtime-host-integration.cjs.map +1 -0
- package/dist/runtime-host-integration.js +180 -0
- package/dist/runtime-host-integration.js.map +1 -0
- package/dist/service-worker-integration.cjs +109 -0
- package/dist/service-worker-integration.cjs.map +1 -0
- package/dist/service-worker-integration.js +108 -0
- package/dist/service-worker-integration.js.map +1 -0
- package/dist/service-worker-transport.cjs +184 -0
- package/dist/service-worker-transport.cjs.map +1 -0
- package/dist/service-worker-transport.js +183 -0
- package/dist/service-worker-transport.js.map +1 -0
- package/dist/user-action-integration.cjs +100 -0
- package/dist/user-action-integration.cjs.map +1 -0
- package/dist/user-action-integration.js +99 -0
- package/dist/user-action-integration.js.map +1 -0
- package/dist/web-vitals-integration.cjs +157 -0
- package/dist/web-vitals-integration.cjs.map +1 -0
- package/dist/web-vitals-integration.js +156 -0
- package/dist/web-vitals-integration.js.map +1 -0
- package/dist/websocket-integration.cjs +158 -0
- package/dist/websocket-integration.cjs.map +1 -0
- package/dist/websocket-integration.js +157 -0
- package/dist/websocket-integration.js.map +1 -0
- package/dist/websocket-transport.cjs +187 -0
- package/dist/websocket-transport.cjs.map +1 -0
- package/dist/websocket-transport.js +186 -0
- package/dist/websocket-transport.js.map +1 -0
- package/dist/xhr-integration.cjs +107 -0
- package/dist/xhr-integration.cjs.map +1 -0
- package/dist/xhr-integration.js +106 -0
- package/dist/xhr-integration.js.map +1 -0
- package/dist/zip-export.cjs +241 -0
- package/dist/zip-export.cjs.map +1 -0
- package/dist/zip-export.js +238 -0
- package/dist/zip-export.js.map +1 -0
- package/package.json +104 -77
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _loggerjs_core = require("@loggerjs/core");
|
|
3
|
+
//#region src/reporting-integration.ts
|
|
4
|
+
const defaultReportTypes = [
|
|
5
|
+
"csp-violation",
|
|
6
|
+
"deprecation",
|
|
7
|
+
"intervention",
|
|
8
|
+
"crash"
|
|
9
|
+
];
|
|
10
|
+
function levelFor(level, report) {
|
|
11
|
+
if (typeof level === "function") return level(report);
|
|
12
|
+
if (level) return level;
|
|
13
|
+
return report.type === "deprecation" ? "info" : "warn";
|
|
14
|
+
}
|
|
15
|
+
function maybeSanitize(url, sanitizeUrl) {
|
|
16
|
+
return url && sanitizeUrl ? sanitizeUrl(url) : url;
|
|
17
|
+
}
|
|
18
|
+
function normalizeReport(report, sanitizeUrl) {
|
|
19
|
+
const raw = typeof report.toJSON === "function" ? report.toJSON() : report;
|
|
20
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
21
|
+
const body = "body" in record ? record.body : report.body;
|
|
22
|
+
const url = typeof record.url === "string" ? record.url : report.url;
|
|
23
|
+
return {
|
|
24
|
+
type: typeof record.type === "string" ? record.type : report.type ?? "unknown",
|
|
25
|
+
url: maybeSanitize(url, sanitizeUrl),
|
|
26
|
+
body: (0, _loggerjs_core.normalizeValue)(body, {
|
|
27
|
+
maxDepth: 6,
|
|
28
|
+
maxObjectKeys: 80
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function normalizeCspViolation(event, sanitizeUrl) {
|
|
33
|
+
return {
|
|
34
|
+
type: "securitypolicyviolation",
|
|
35
|
+
blockedURI: maybeSanitize(event.blockedURI, sanitizeUrl),
|
|
36
|
+
documentURI: maybeSanitize(event.documentURI, sanitizeUrl),
|
|
37
|
+
effectiveDirective: event.effectiveDirective,
|
|
38
|
+
violatedDirective: event.violatedDirective,
|
|
39
|
+
disposition: event.disposition,
|
|
40
|
+
sourceFile: maybeSanitize(event.sourceFile, sanitizeUrl),
|
|
41
|
+
lineNumber: event.lineNumber,
|
|
42
|
+
columnNumber: event.columnNumber,
|
|
43
|
+
statusCode: event.statusCode,
|
|
44
|
+
sample: event.sample
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function captureReportingIntegration(options = {}) {
|
|
48
|
+
const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? true;
|
|
49
|
+
const captureReportingObserver = options.captureReportingObserver ?? true;
|
|
50
|
+
const reportTypes = options.reportTypes ?? defaultReportTypes;
|
|
51
|
+
const buffered = options.buffered ?? true;
|
|
52
|
+
return {
|
|
53
|
+
name: "capture-reporting",
|
|
54
|
+
setup(api) {
|
|
55
|
+
const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
|
|
56
|
+
const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
|
|
57
|
+
const Observer = options.ReportingObserver ?? globalThis.ReportingObserver;
|
|
58
|
+
const disposers = [];
|
|
59
|
+
let disposed = false;
|
|
60
|
+
const captureReport = api.guard((report) => {
|
|
61
|
+
if (disposed) return;
|
|
62
|
+
api.capture({
|
|
63
|
+
level: levelFor(options.level, report),
|
|
64
|
+
message: `Browser report ${report.type}`,
|
|
65
|
+
props: { browser: {
|
|
66
|
+
kind: "report",
|
|
67
|
+
report
|
|
68
|
+
} }
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
if (captureSecurityPolicyViolation && add && remove) {
|
|
72
|
+
const onViolation = api.guard((event) => {
|
|
73
|
+
const report = normalizeCspViolation(event, options.sanitizeUrl);
|
|
74
|
+
api.capture({
|
|
75
|
+
level: levelFor(options.level, report),
|
|
76
|
+
message: `Security policy violation: ${report.effectiveDirective ?? report.violatedDirective ?? "unknown"}`,
|
|
77
|
+
props: { browser: {
|
|
78
|
+
kind: "securitypolicyviolation",
|
|
79
|
+
report
|
|
80
|
+
} }
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
add("securitypolicyviolation", onViolation);
|
|
84
|
+
disposers.push(() => remove("securitypolicyviolation", onViolation));
|
|
85
|
+
}
|
|
86
|
+
if (captureReportingObserver && Observer) try {
|
|
87
|
+
const observer = new Observer((reports) => {
|
|
88
|
+
for (const report of reports) captureReport(normalizeReport(report, options.sanitizeUrl));
|
|
89
|
+
}, {
|
|
90
|
+
buffered,
|
|
91
|
+
types: reportTypes
|
|
92
|
+
});
|
|
93
|
+
observer.observe();
|
|
94
|
+
disposers.push(() => {
|
|
95
|
+
for (const report of observer.takeRecords?.() ?? []) captureReport(normalizeReport(report, options.sanitizeUrl));
|
|
96
|
+
observer.disconnect();
|
|
97
|
+
});
|
|
98
|
+
} catch {}
|
|
99
|
+
return () => {
|
|
100
|
+
if (disposed) return;
|
|
101
|
+
for (const dispose of disposers) dispose();
|
|
102
|
+
disposed = true;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
exports.captureReportingIntegration = captureReportingIntegration;
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=reporting-integration.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting-integration.cjs","names":[],"sources":["../src/reporting-integration.ts"],"sourcesContent":["import {\n normalizeValue,\n type Integration,\n type IntegrationSetupContext,\n type LoggerLevel,\n} from \"@loggerjs/core\";\n\nexport interface BrowserReportLike {\n type?: string;\n url?: string;\n body?: unknown;\n toJSON?: () => unknown;\n}\n\nexport interface BrowserReportingObserverLike {\n observe: () => void;\n disconnect: () => void;\n takeRecords?: () => BrowserReportLike[];\n}\n\nexport interface BrowserReportingObserverConstructor {\n new (\n callback: (reports: BrowserReportLike[], observer: BrowserReportingObserverLike) => void,\n options?: { buffered?: boolean; types?: readonly string[] },\n ): BrowserReportingObserverLike;\n}\n\nexport interface BrowserReportPayload {\n type: string;\n url?: string;\n body?: unknown;\n}\n\nexport interface BrowserCspViolationPayload {\n type: \"securitypolicyviolation\";\n blockedURI?: string;\n documentURI?: string;\n effectiveDirective?: string;\n violatedDirective?: string;\n disposition?: string;\n sourceFile?: string;\n lineNumber?: number;\n columnNumber?: number;\n statusCode?: number;\n sample?: string;\n}\n\nexport interface CaptureReportingOptions {\n captureSecurityPolicyViolation?: boolean;\n captureReportingObserver?: boolean;\n reportTypes?: readonly string[];\n level?:\n | LoggerLevel\n | ((report: BrowserReportPayload | BrowserCspViolationPayload) => LoggerLevel);\n buffered?: boolean;\n sanitizeUrl?: (url: string) => string;\n ReportingObserver?: BrowserReportingObserverConstructor;\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n}\n\nconst defaultReportTypes = [\"csp-violation\", \"deprecation\", \"intervention\", \"crash\"] as const;\n\nfunction levelFor(\n level: CaptureReportingOptions[\"level\"],\n report: BrowserReportPayload | BrowserCspViolationPayload,\n): LoggerLevel {\n if (typeof level === \"function\") return level(report);\n if (level) return level;\n return report.type === \"deprecation\" ? \"info\" : \"warn\";\n}\n\nfunction maybeSanitize(\n url: string | undefined,\n sanitizeUrl: ((url: string) => string) | undefined,\n) {\n return url && sanitizeUrl ? sanitizeUrl(url) : url;\n}\n\nfunction normalizeReport(\n report: BrowserReportLike,\n sanitizeUrl: ((url: string) => string) | undefined,\n): BrowserReportPayload {\n const raw = typeof report.toJSON === \"function\" ? report.toJSON() : report;\n const record = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const body = \"body\" in record ? record.body : report.body;\n const url = typeof record.url === \"string\" ? record.url : report.url;\n const type = typeof record.type === \"string\" ? record.type : (report.type ?? \"unknown\");\n return {\n type,\n url: maybeSanitize(url, sanitizeUrl),\n body: normalizeValue(body, { maxDepth: 6, maxObjectKeys: 80 }),\n };\n}\n\nfunction normalizeCspViolation(\n event: SecurityPolicyViolationEvent,\n sanitizeUrl: ((url: string) => string) | undefined,\n): BrowserCspViolationPayload {\n return {\n type: \"securitypolicyviolation\",\n blockedURI: maybeSanitize(event.blockedURI, sanitizeUrl),\n documentURI: maybeSanitize(event.documentURI, sanitizeUrl),\n effectiveDirective: event.effectiveDirective,\n violatedDirective: event.violatedDirective,\n disposition: event.disposition,\n sourceFile: maybeSanitize(event.sourceFile, sanitizeUrl),\n lineNumber: event.lineNumber,\n columnNumber: event.columnNumber,\n statusCode: event.statusCode,\n sample: event.sample,\n };\n}\n\nexport function captureReportingIntegration(options: CaptureReportingOptions = {}): Integration {\n const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? true;\n const captureReportingObserver = options.captureReportingObserver ?? true;\n const reportTypes = options.reportTypes ?? defaultReportTypes;\n const buffered = options.buffered ?? true;\n\n return {\n name: \"capture-reporting\",\n setup(api: IntegrationSetupContext) {\n const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);\n const remove =\n options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);\n const Observer =\n options.ReportingObserver ??\n (globalThis as unknown as { ReportingObserver?: BrowserReportingObserverConstructor })\n .ReportingObserver;\n const disposers: Array<() => void> = [];\n let disposed = false;\n\n const captureReport = api.guard((report: BrowserReportPayload) => {\n if (disposed) return;\n api.capture({\n level: levelFor(options.level, report),\n message: `Browser report ${report.type}`,\n props: { browser: { kind: \"report\", report } },\n });\n });\n\n if (captureSecurityPolicyViolation && add && remove) {\n const onViolation = api.guard((event: SecurityPolicyViolationEvent) => {\n const report = normalizeCspViolation(event, options.sanitizeUrl);\n api.capture({\n level: levelFor(options.level, report),\n message: `Security policy violation: ${report.effectiveDirective ?? report.violatedDirective ?? \"unknown\"}`,\n props: { browser: { kind: \"securitypolicyviolation\", report } },\n });\n });\n add(\"securitypolicyviolation\", onViolation);\n disposers.push(() => remove(\"securitypolicyviolation\", onViolation));\n }\n\n if (captureReportingObserver && Observer) {\n try {\n const observer = new Observer(\n (reports) => {\n for (const report of reports)\n captureReport(normalizeReport(report, options.sanitizeUrl));\n },\n { buffered, types: reportTypes },\n );\n observer.observe();\n disposers.push(() => {\n for (const report of observer.takeRecords?.() ?? [])\n captureReport(normalizeReport(report, options.sanitizeUrl));\n observer.disconnect();\n });\n } catch {\n // ReportingObserver is still uneven across browsers; failures should not break setup.\n }\n }\n\n return () => {\n if (disposed) return;\n for (const dispose of disposers) dispose();\n disposed = true;\n };\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { normalizeValue } from "@loggerjs/core";
|
|
2
|
+
//#region src/reporting-integration.ts
|
|
3
|
+
const defaultReportTypes = [
|
|
4
|
+
"csp-violation",
|
|
5
|
+
"deprecation",
|
|
6
|
+
"intervention",
|
|
7
|
+
"crash"
|
|
8
|
+
];
|
|
9
|
+
function levelFor(level, report) {
|
|
10
|
+
if (typeof level === "function") return level(report);
|
|
11
|
+
if (level) return level;
|
|
12
|
+
return report.type === "deprecation" ? "info" : "warn";
|
|
13
|
+
}
|
|
14
|
+
function maybeSanitize(url, sanitizeUrl) {
|
|
15
|
+
return url && sanitizeUrl ? sanitizeUrl(url) : url;
|
|
16
|
+
}
|
|
17
|
+
function normalizeReport(report, sanitizeUrl) {
|
|
18
|
+
const raw = typeof report.toJSON === "function" ? report.toJSON() : report;
|
|
19
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
20
|
+
const body = "body" in record ? record.body : report.body;
|
|
21
|
+
const url = typeof record.url === "string" ? record.url : report.url;
|
|
22
|
+
return {
|
|
23
|
+
type: typeof record.type === "string" ? record.type : report.type ?? "unknown",
|
|
24
|
+
url: maybeSanitize(url, sanitizeUrl),
|
|
25
|
+
body: normalizeValue(body, {
|
|
26
|
+
maxDepth: 6,
|
|
27
|
+
maxObjectKeys: 80
|
|
28
|
+
})
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function normalizeCspViolation(event, sanitizeUrl) {
|
|
32
|
+
return {
|
|
33
|
+
type: "securitypolicyviolation",
|
|
34
|
+
blockedURI: maybeSanitize(event.blockedURI, sanitizeUrl),
|
|
35
|
+
documentURI: maybeSanitize(event.documentURI, sanitizeUrl),
|
|
36
|
+
effectiveDirective: event.effectiveDirective,
|
|
37
|
+
violatedDirective: event.violatedDirective,
|
|
38
|
+
disposition: event.disposition,
|
|
39
|
+
sourceFile: maybeSanitize(event.sourceFile, sanitizeUrl),
|
|
40
|
+
lineNumber: event.lineNumber,
|
|
41
|
+
columnNumber: event.columnNumber,
|
|
42
|
+
statusCode: event.statusCode,
|
|
43
|
+
sample: event.sample
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function captureReportingIntegration(options = {}) {
|
|
47
|
+
const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? true;
|
|
48
|
+
const captureReportingObserver = options.captureReportingObserver ?? true;
|
|
49
|
+
const reportTypes = options.reportTypes ?? defaultReportTypes;
|
|
50
|
+
const buffered = options.buffered ?? true;
|
|
51
|
+
return {
|
|
52
|
+
name: "capture-reporting",
|
|
53
|
+
setup(api) {
|
|
54
|
+
const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
|
|
55
|
+
const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
|
|
56
|
+
const Observer = options.ReportingObserver ?? globalThis.ReportingObserver;
|
|
57
|
+
const disposers = [];
|
|
58
|
+
let disposed = false;
|
|
59
|
+
const captureReport = api.guard((report) => {
|
|
60
|
+
if (disposed) return;
|
|
61
|
+
api.capture({
|
|
62
|
+
level: levelFor(options.level, report),
|
|
63
|
+
message: `Browser report ${report.type}`,
|
|
64
|
+
props: { browser: {
|
|
65
|
+
kind: "report",
|
|
66
|
+
report
|
|
67
|
+
} }
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
if (captureSecurityPolicyViolation && add && remove) {
|
|
71
|
+
const onViolation = api.guard((event) => {
|
|
72
|
+
const report = normalizeCspViolation(event, options.sanitizeUrl);
|
|
73
|
+
api.capture({
|
|
74
|
+
level: levelFor(options.level, report),
|
|
75
|
+
message: `Security policy violation: ${report.effectiveDirective ?? report.violatedDirective ?? "unknown"}`,
|
|
76
|
+
props: { browser: {
|
|
77
|
+
kind: "securitypolicyviolation",
|
|
78
|
+
report
|
|
79
|
+
} }
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
add("securitypolicyviolation", onViolation);
|
|
83
|
+
disposers.push(() => remove("securitypolicyviolation", onViolation));
|
|
84
|
+
}
|
|
85
|
+
if (captureReportingObserver && Observer) try {
|
|
86
|
+
const observer = new Observer((reports) => {
|
|
87
|
+
for (const report of reports) captureReport(normalizeReport(report, options.sanitizeUrl));
|
|
88
|
+
}, {
|
|
89
|
+
buffered,
|
|
90
|
+
types: reportTypes
|
|
91
|
+
});
|
|
92
|
+
observer.observe();
|
|
93
|
+
disposers.push(() => {
|
|
94
|
+
for (const report of observer.takeRecords?.() ?? []) captureReport(normalizeReport(report, options.sanitizeUrl));
|
|
95
|
+
observer.disconnect();
|
|
96
|
+
});
|
|
97
|
+
} catch {}
|
|
98
|
+
return () => {
|
|
99
|
+
if (disposed) return;
|
|
100
|
+
for (const dispose of disposers) dispose();
|
|
101
|
+
disposed = true;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { captureReportingIntegration };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=reporting-integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting-integration.js","names":[],"sources":["../src/reporting-integration.ts"],"sourcesContent":["import {\n normalizeValue,\n type Integration,\n type IntegrationSetupContext,\n type LoggerLevel,\n} from \"@loggerjs/core\";\n\nexport interface BrowserReportLike {\n type?: string;\n url?: string;\n body?: unknown;\n toJSON?: () => unknown;\n}\n\nexport interface BrowserReportingObserverLike {\n observe: () => void;\n disconnect: () => void;\n takeRecords?: () => BrowserReportLike[];\n}\n\nexport interface BrowserReportingObserverConstructor {\n new (\n callback: (reports: BrowserReportLike[], observer: BrowserReportingObserverLike) => void,\n options?: { buffered?: boolean; types?: readonly string[] },\n ): BrowserReportingObserverLike;\n}\n\nexport interface BrowserReportPayload {\n type: string;\n url?: string;\n body?: unknown;\n}\n\nexport interface BrowserCspViolationPayload {\n type: \"securitypolicyviolation\";\n blockedURI?: string;\n documentURI?: string;\n effectiveDirective?: string;\n violatedDirective?: string;\n disposition?: string;\n sourceFile?: string;\n lineNumber?: number;\n columnNumber?: number;\n statusCode?: number;\n sample?: string;\n}\n\nexport interface CaptureReportingOptions {\n captureSecurityPolicyViolation?: boolean;\n captureReportingObserver?: boolean;\n reportTypes?: readonly string[];\n level?:\n | LoggerLevel\n | ((report: BrowserReportPayload | BrowserCspViolationPayload) => LoggerLevel);\n buffered?: boolean;\n sanitizeUrl?: (url: string) => string;\n ReportingObserver?: BrowserReportingObserverConstructor;\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n}\n\nconst defaultReportTypes = [\"csp-violation\", \"deprecation\", \"intervention\", \"crash\"] as const;\n\nfunction levelFor(\n level: CaptureReportingOptions[\"level\"],\n report: BrowserReportPayload | BrowserCspViolationPayload,\n): LoggerLevel {\n if (typeof level === \"function\") return level(report);\n if (level) return level;\n return report.type === \"deprecation\" ? \"info\" : \"warn\";\n}\n\nfunction maybeSanitize(\n url: string | undefined,\n sanitizeUrl: ((url: string) => string) | undefined,\n) {\n return url && sanitizeUrl ? sanitizeUrl(url) : url;\n}\n\nfunction normalizeReport(\n report: BrowserReportLike,\n sanitizeUrl: ((url: string) => string) | undefined,\n): BrowserReportPayload {\n const raw = typeof report.toJSON === \"function\" ? report.toJSON() : report;\n const record = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const body = \"body\" in record ? record.body : report.body;\n const url = typeof record.url === \"string\" ? record.url : report.url;\n const type = typeof record.type === \"string\" ? record.type : (report.type ?? \"unknown\");\n return {\n type,\n url: maybeSanitize(url, sanitizeUrl),\n body: normalizeValue(body, { maxDepth: 6, maxObjectKeys: 80 }),\n };\n}\n\nfunction normalizeCspViolation(\n event: SecurityPolicyViolationEvent,\n sanitizeUrl: ((url: string) => string) | undefined,\n): BrowserCspViolationPayload {\n return {\n type: \"securitypolicyviolation\",\n blockedURI: maybeSanitize(event.blockedURI, sanitizeUrl),\n documentURI: maybeSanitize(event.documentURI, sanitizeUrl),\n effectiveDirective: event.effectiveDirective,\n violatedDirective: event.violatedDirective,\n disposition: event.disposition,\n sourceFile: maybeSanitize(event.sourceFile, sanitizeUrl),\n lineNumber: event.lineNumber,\n columnNumber: event.columnNumber,\n statusCode: event.statusCode,\n sample: event.sample,\n };\n}\n\nexport function captureReportingIntegration(options: CaptureReportingOptions = {}): Integration {\n const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? true;\n const captureReportingObserver = options.captureReportingObserver ?? true;\n const reportTypes = options.reportTypes ?? defaultReportTypes;\n const buffered = options.buffered ?? true;\n\n return {\n name: \"capture-reporting\",\n setup(api: IntegrationSetupContext) {\n const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);\n const remove =\n options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);\n const Observer =\n options.ReportingObserver ??\n (globalThis as unknown as { ReportingObserver?: BrowserReportingObserverConstructor })\n .ReportingObserver;\n const disposers: Array<() => void> = [];\n let disposed = false;\n\n const captureReport = api.guard((report: BrowserReportPayload) => {\n if (disposed) return;\n api.capture({\n level: levelFor(options.level, report),\n message: `Browser report ${report.type}`,\n props: { browser: { kind: \"report\", report } },\n });\n });\n\n if (captureSecurityPolicyViolation && add && remove) {\n const onViolation = api.guard((event: SecurityPolicyViolationEvent) => {\n const report = normalizeCspViolation(event, options.sanitizeUrl);\n api.capture({\n level: levelFor(options.level, report),\n message: `Security policy violation: ${report.effectiveDirective ?? report.violatedDirective ?? \"unknown\"}`,\n props: { browser: { kind: \"securitypolicyviolation\", report } },\n });\n });\n add(\"securitypolicyviolation\", onViolation);\n disposers.push(() => remove(\"securitypolicyviolation\", onViolation));\n }\n\n if (captureReportingObserver && Observer) {\n try {\n const observer = new Observer(\n (reports) => {\n for (const report of reports)\n captureReport(normalizeReport(report, options.sanitizeUrl));\n },\n { buffered, types: reportTypes },\n );\n observer.observe();\n disposers.push(() => {\n for (const report of observer.takeRecords?.() ?? [])\n captureReport(normalizeReport(report, options.sanitizeUrl));\n observer.disconnect();\n });\n } catch {\n // ReportingObserver is still uneven across browsers; failures should not break setup.\n }\n }\n\n return () => {\n if (disposed) return;\n for (const dispose of disposers) dispose();\n disposed = true;\n };\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _loggerjs_core = require("@loggerjs/core");
|
|
3
|
+
//#region src/router-integration.ts
|
|
4
|
+
function locationHref(location) {
|
|
5
|
+
if (!location) return "http://localhost/";
|
|
6
|
+
if (location.href) return location.href;
|
|
7
|
+
return `${location.pathname ?? "/"}${location.search ?? ""}${location.hash ?? ""}`;
|
|
8
|
+
}
|
|
9
|
+
function normalizeRouteUrl(url, location, mode, sanitizeUrl) {
|
|
10
|
+
const fallback = locationHref(location);
|
|
11
|
+
const raw = url === void 0 || url === null ? fallback : String(url);
|
|
12
|
+
let normalized = raw;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = new URL(raw, fallback);
|
|
15
|
+
normalized = mode === "href" ? parsed.href : `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
16
|
+
} catch {
|
|
17
|
+
normalized = raw;
|
|
18
|
+
}
|
|
19
|
+
return sanitizeUrl ? sanitizeUrl(normalized) : normalized;
|
|
20
|
+
}
|
|
21
|
+
function routeState(includeState, state, maxDepth) {
|
|
22
|
+
return includeState ? (0, _loggerjs_core.normalizeValue)(state, {
|
|
23
|
+
maxDepth,
|
|
24
|
+
maxObjectKeys: 80
|
|
25
|
+
}) : void 0;
|
|
26
|
+
}
|
|
27
|
+
function captureRouterIntegration(options = {}) {
|
|
28
|
+
const level = options.level ?? "info";
|
|
29
|
+
const captureInitial = options.captureInitial ?? true;
|
|
30
|
+
const includeState = options.includeState ?? false;
|
|
31
|
+
const stateMaxDepth = options.stateMaxDepth ?? 4;
|
|
32
|
+
const urlMode = options.urlMode ?? "path";
|
|
33
|
+
return {
|
|
34
|
+
name: "capture-router",
|
|
35
|
+
setup(api) {
|
|
36
|
+
const history = options.history ?? globalThis.history;
|
|
37
|
+
const location = options.location ?? globalThis.location;
|
|
38
|
+
const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
|
|
39
|
+
const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
|
|
40
|
+
const originalPushState = history?.pushState;
|
|
41
|
+
const originalReplaceState = history?.replaceState;
|
|
42
|
+
let current = normalizeRouteUrl(void 0, location, urlMode, options.sanitizeUrl);
|
|
43
|
+
let disposed = false;
|
|
44
|
+
const capture = (trigger, to, state) => {
|
|
45
|
+
if (disposed) return;
|
|
46
|
+
const previous = current;
|
|
47
|
+
current = to;
|
|
48
|
+
if (trigger !== "initial" && previous === to) return;
|
|
49
|
+
const route = {
|
|
50
|
+
trigger,
|
|
51
|
+
from: trigger === "initial" ? void 0 : previous,
|
|
52
|
+
to,
|
|
53
|
+
state: routeState(includeState, state, stateMaxDepth)
|
|
54
|
+
};
|
|
55
|
+
api.capture({
|
|
56
|
+
level,
|
|
57
|
+
message: trigger === "initial" ? `Route initial ${to}` : `Route change ${to}`,
|
|
58
|
+
props: { browser: {
|
|
59
|
+
kind: "route-change",
|
|
60
|
+
route
|
|
61
|
+
} }
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
if (captureInitial) capture("initial", current, history?.state);
|
|
65
|
+
if (history && originalPushState) history.pushState = function pushState(data, unused, url) {
|
|
66
|
+
const result = originalPushState.call(this, data, unused, url);
|
|
67
|
+
capture("pushState", normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl), data);
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
if (history && originalReplaceState) history.replaceState = function replaceState(data, unused, url) {
|
|
71
|
+
const result = originalReplaceState.call(this, data, unused, url);
|
|
72
|
+
capture("replaceState", normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl), data);
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
const onPopState = api.guard((event) => {
|
|
76
|
+
capture("popstate", normalizeRouteUrl(void 0, location, urlMode, options.sanitizeUrl), event.state);
|
|
77
|
+
});
|
|
78
|
+
const onHashChange = api.guard((event) => {
|
|
79
|
+
capture("hashchange", normalizeRouteUrl(event.newURL, location, urlMode, options.sanitizeUrl), history?.state);
|
|
80
|
+
});
|
|
81
|
+
add?.("popstate", onPopState);
|
|
82
|
+
add?.("hashchange", onHashChange);
|
|
83
|
+
return () => {
|
|
84
|
+
if (disposed) return;
|
|
85
|
+
disposed = true;
|
|
86
|
+
if (history && originalPushState) history.pushState = originalPushState;
|
|
87
|
+
if (history && originalReplaceState) history.replaceState = originalReplaceState;
|
|
88
|
+
remove?.("popstate", onPopState);
|
|
89
|
+
remove?.("hashchange", onHashChange);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
exports.captureRouterIntegration = captureRouterIntegration;
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=router-integration.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router-integration.cjs","names":[],"sources":["../src/router-integration.ts"],"sourcesContent":["import {\n normalizeValue,\n type Integration,\n type IntegrationSetupContext,\n type LoggerLevel,\n} from \"@loggerjs/core\";\n\nexport interface BrowserHistoryLike {\n state?: unknown;\n pushState?: (data: unknown, unused: string, url?: string | URL | null) => unknown;\n replaceState?: (data: unknown, unused: string, url?: string | URL | null) => unknown;\n}\n\nexport interface BrowserLocationLike {\n href?: string;\n pathname?: string;\n search?: string;\n hash?: string;\n}\n\nexport type BrowserRouteTrigger =\n | \"initial\"\n | \"pushState\"\n | \"replaceState\"\n | \"popstate\"\n | \"hashchange\";\nexport type BrowserRouteUrlMode = \"path\" | \"href\";\n\nexport interface BrowserRouteChangePayload {\n trigger: BrowserRouteTrigger;\n from?: string;\n to: string;\n state?: unknown;\n}\n\nexport interface CaptureRouterOptions {\n level?: LoggerLevel;\n captureInitial?: boolean;\n includeState?: boolean;\n stateMaxDepth?: number;\n urlMode?: BrowserRouteUrlMode;\n sanitizeUrl?: (url: string) => string;\n history?: BrowserHistoryLike;\n location?: BrowserLocationLike;\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n}\n\nfunction locationHref(location: BrowserLocationLike | undefined): string {\n if (!location) return \"http://localhost/\";\n if (location.href) return location.href;\n return `${location.pathname ?? \"/\"}${location.search ?? \"\"}${location.hash ?? \"\"}`;\n}\n\nfunction normalizeRouteUrl(\n url: string | URL | null | undefined,\n location: BrowserLocationLike | undefined,\n mode: BrowserRouteUrlMode,\n sanitizeUrl: ((url: string) => string) | undefined,\n): string {\n const fallback = locationHref(location);\n const raw = url === undefined || url === null ? fallback : String(url);\n let normalized = raw;\n try {\n const parsed = new URL(raw, fallback);\n normalized = mode === \"href\" ? parsed.href : `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n normalized = raw;\n }\n return sanitizeUrl ? sanitizeUrl(normalized) : normalized;\n}\n\nfunction routeState(includeState: boolean, state: unknown, maxDepth: number): unknown {\n return includeState ? normalizeValue(state, { maxDepth, maxObjectKeys: 80 }) : undefined;\n}\n\nexport function captureRouterIntegration(options: CaptureRouterOptions = {}): Integration {\n const level = options.level ?? \"info\";\n const captureInitial = options.captureInitial ?? true;\n const includeState = options.includeState ?? false;\n const stateMaxDepth = options.stateMaxDepth ?? 4;\n const urlMode = options.urlMode ?? \"path\";\n\n return {\n name: \"capture-router\",\n setup(api: IntegrationSetupContext) {\n const history = options.history ?? globalThis.history;\n const location = options.location ?? globalThis.location;\n const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);\n const remove =\n options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);\n const originalPushState = history?.pushState;\n const originalReplaceState = history?.replaceState;\n let current = normalizeRouteUrl(undefined, location, urlMode, options.sanitizeUrl);\n let disposed = false;\n\n const capture = (trigger: BrowserRouteTrigger, to: string, state?: unknown) => {\n if (disposed) return;\n const previous = current;\n current = to;\n if (trigger !== \"initial\" && previous === to) return;\n const route: BrowserRouteChangePayload = {\n trigger,\n from: trigger === \"initial\" ? undefined : previous,\n to,\n state: routeState(includeState, state, stateMaxDepth),\n };\n api.capture({\n level,\n message: trigger === \"initial\" ? `Route initial ${to}` : `Route change ${to}`,\n props: { browser: { kind: \"route-change\", route } },\n });\n };\n\n if (captureInitial) capture(\"initial\", current, history?.state);\n\n if (history && originalPushState) {\n history.pushState = function pushState(\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ) {\n const result = originalPushState.call(this, data, unused, url);\n capture(\n \"pushState\",\n normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl),\n data,\n );\n return result;\n };\n }\n\n if (history && originalReplaceState) {\n history.replaceState = function replaceState(\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ) {\n const result = originalReplaceState.call(this, data, unused, url);\n capture(\n \"replaceState\",\n normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl),\n data,\n );\n return result;\n };\n }\n\n const onPopState = api.guard((event: PopStateEvent) => {\n capture(\n \"popstate\",\n normalizeRouteUrl(undefined, location, urlMode, options.sanitizeUrl),\n event.state,\n );\n });\n const onHashChange = api.guard((event: HashChangeEvent) => {\n capture(\n \"hashchange\",\n normalizeRouteUrl(event.newURL, location, urlMode, options.sanitizeUrl),\n history?.state,\n );\n });\n\n add?.(\"popstate\", onPopState);\n add?.(\"hashchange\", onHashChange);\n\n return () => {\n if (disposed) return;\n disposed = true;\n if (history && originalPushState) history.pushState = originalPushState;\n if (history && originalReplaceState) history.replaceState = originalReplaceState;\n remove?.(\"popstate\", onPopState);\n remove?.(\"hashchange\", onHashChange);\n };\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { normalizeValue } from "@loggerjs/core";
|
|
2
|
+
//#region src/router-integration.ts
|
|
3
|
+
function locationHref(location) {
|
|
4
|
+
if (!location) return "http://localhost/";
|
|
5
|
+
if (location.href) return location.href;
|
|
6
|
+
return `${location.pathname ?? "/"}${location.search ?? ""}${location.hash ?? ""}`;
|
|
7
|
+
}
|
|
8
|
+
function normalizeRouteUrl(url, location, mode, sanitizeUrl) {
|
|
9
|
+
const fallback = locationHref(location);
|
|
10
|
+
const raw = url === void 0 || url === null ? fallback : String(url);
|
|
11
|
+
let normalized = raw;
|
|
12
|
+
try {
|
|
13
|
+
const parsed = new URL(raw, fallback);
|
|
14
|
+
normalized = mode === "href" ? parsed.href : `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
15
|
+
} catch {
|
|
16
|
+
normalized = raw;
|
|
17
|
+
}
|
|
18
|
+
return sanitizeUrl ? sanitizeUrl(normalized) : normalized;
|
|
19
|
+
}
|
|
20
|
+
function routeState(includeState, state, maxDepth) {
|
|
21
|
+
return includeState ? normalizeValue(state, {
|
|
22
|
+
maxDepth,
|
|
23
|
+
maxObjectKeys: 80
|
|
24
|
+
}) : void 0;
|
|
25
|
+
}
|
|
26
|
+
function captureRouterIntegration(options = {}) {
|
|
27
|
+
const level = options.level ?? "info";
|
|
28
|
+
const captureInitial = options.captureInitial ?? true;
|
|
29
|
+
const includeState = options.includeState ?? false;
|
|
30
|
+
const stateMaxDepth = options.stateMaxDepth ?? 4;
|
|
31
|
+
const urlMode = options.urlMode ?? "path";
|
|
32
|
+
return {
|
|
33
|
+
name: "capture-router",
|
|
34
|
+
setup(api) {
|
|
35
|
+
const history = options.history ?? globalThis.history;
|
|
36
|
+
const location = options.location ?? globalThis.location;
|
|
37
|
+
const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
|
|
38
|
+
const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
|
|
39
|
+
const originalPushState = history?.pushState;
|
|
40
|
+
const originalReplaceState = history?.replaceState;
|
|
41
|
+
let current = normalizeRouteUrl(void 0, location, urlMode, options.sanitizeUrl);
|
|
42
|
+
let disposed = false;
|
|
43
|
+
const capture = (trigger, to, state) => {
|
|
44
|
+
if (disposed) return;
|
|
45
|
+
const previous = current;
|
|
46
|
+
current = to;
|
|
47
|
+
if (trigger !== "initial" && previous === to) return;
|
|
48
|
+
const route = {
|
|
49
|
+
trigger,
|
|
50
|
+
from: trigger === "initial" ? void 0 : previous,
|
|
51
|
+
to,
|
|
52
|
+
state: routeState(includeState, state, stateMaxDepth)
|
|
53
|
+
};
|
|
54
|
+
api.capture({
|
|
55
|
+
level,
|
|
56
|
+
message: trigger === "initial" ? `Route initial ${to}` : `Route change ${to}`,
|
|
57
|
+
props: { browser: {
|
|
58
|
+
kind: "route-change",
|
|
59
|
+
route
|
|
60
|
+
} }
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
if (captureInitial) capture("initial", current, history?.state);
|
|
64
|
+
if (history && originalPushState) history.pushState = function pushState(data, unused, url) {
|
|
65
|
+
const result = originalPushState.call(this, data, unused, url);
|
|
66
|
+
capture("pushState", normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl), data);
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
if (history && originalReplaceState) history.replaceState = function replaceState(data, unused, url) {
|
|
70
|
+
const result = originalReplaceState.call(this, data, unused, url);
|
|
71
|
+
capture("replaceState", normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl), data);
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
const onPopState = api.guard((event) => {
|
|
75
|
+
capture("popstate", normalizeRouteUrl(void 0, location, urlMode, options.sanitizeUrl), event.state);
|
|
76
|
+
});
|
|
77
|
+
const onHashChange = api.guard((event) => {
|
|
78
|
+
capture("hashchange", normalizeRouteUrl(event.newURL, location, urlMode, options.sanitizeUrl), history?.state);
|
|
79
|
+
});
|
|
80
|
+
add?.("popstate", onPopState);
|
|
81
|
+
add?.("hashchange", onHashChange);
|
|
82
|
+
return () => {
|
|
83
|
+
if (disposed) return;
|
|
84
|
+
disposed = true;
|
|
85
|
+
if (history && originalPushState) history.pushState = originalPushState;
|
|
86
|
+
if (history && originalReplaceState) history.replaceState = originalReplaceState;
|
|
87
|
+
remove?.("popstate", onPopState);
|
|
88
|
+
remove?.("hashchange", onHashChange);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { captureRouterIntegration };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=router-integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router-integration.js","names":[],"sources":["../src/router-integration.ts"],"sourcesContent":["import {\n normalizeValue,\n type Integration,\n type IntegrationSetupContext,\n type LoggerLevel,\n} from \"@loggerjs/core\";\n\nexport interface BrowserHistoryLike {\n state?: unknown;\n pushState?: (data: unknown, unused: string, url?: string | URL | null) => unknown;\n replaceState?: (data: unknown, unused: string, url?: string | URL | null) => unknown;\n}\n\nexport interface BrowserLocationLike {\n href?: string;\n pathname?: string;\n search?: string;\n hash?: string;\n}\n\nexport type BrowserRouteTrigger =\n | \"initial\"\n | \"pushState\"\n | \"replaceState\"\n | \"popstate\"\n | \"hashchange\";\nexport type BrowserRouteUrlMode = \"path\" | \"href\";\n\nexport interface BrowserRouteChangePayload {\n trigger: BrowserRouteTrigger;\n from?: string;\n to: string;\n state?: unknown;\n}\n\nexport interface CaptureRouterOptions {\n level?: LoggerLevel;\n captureInitial?: boolean;\n includeState?: boolean;\n stateMaxDepth?: number;\n urlMode?: BrowserRouteUrlMode;\n sanitizeUrl?: (url: string) => string;\n history?: BrowserHistoryLike;\n location?: BrowserLocationLike;\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n}\n\nfunction locationHref(location: BrowserLocationLike | undefined): string {\n if (!location) return \"http://localhost/\";\n if (location.href) return location.href;\n return `${location.pathname ?? \"/\"}${location.search ?? \"\"}${location.hash ?? \"\"}`;\n}\n\nfunction normalizeRouteUrl(\n url: string | URL | null | undefined,\n location: BrowserLocationLike | undefined,\n mode: BrowserRouteUrlMode,\n sanitizeUrl: ((url: string) => string) | undefined,\n): string {\n const fallback = locationHref(location);\n const raw = url === undefined || url === null ? fallback : String(url);\n let normalized = raw;\n try {\n const parsed = new URL(raw, fallback);\n normalized = mode === \"href\" ? parsed.href : `${parsed.pathname}${parsed.search}${parsed.hash}`;\n } catch {\n normalized = raw;\n }\n return sanitizeUrl ? sanitizeUrl(normalized) : normalized;\n}\n\nfunction routeState(includeState: boolean, state: unknown, maxDepth: number): unknown {\n return includeState ? normalizeValue(state, { maxDepth, maxObjectKeys: 80 }) : undefined;\n}\n\nexport function captureRouterIntegration(options: CaptureRouterOptions = {}): Integration {\n const level = options.level ?? \"info\";\n const captureInitial = options.captureInitial ?? true;\n const includeState = options.includeState ?? false;\n const stateMaxDepth = options.stateMaxDepth ?? 4;\n const urlMode = options.urlMode ?? \"path\";\n\n return {\n name: \"capture-router\",\n setup(api: IntegrationSetupContext) {\n const history = options.history ?? globalThis.history;\n const location = options.location ?? globalThis.location;\n const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);\n const remove =\n options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);\n const originalPushState = history?.pushState;\n const originalReplaceState = history?.replaceState;\n let current = normalizeRouteUrl(undefined, location, urlMode, options.sanitizeUrl);\n let disposed = false;\n\n const capture = (trigger: BrowserRouteTrigger, to: string, state?: unknown) => {\n if (disposed) return;\n const previous = current;\n current = to;\n if (trigger !== \"initial\" && previous === to) return;\n const route: BrowserRouteChangePayload = {\n trigger,\n from: trigger === \"initial\" ? undefined : previous,\n to,\n state: routeState(includeState, state, stateMaxDepth),\n };\n api.capture({\n level,\n message: trigger === \"initial\" ? `Route initial ${to}` : `Route change ${to}`,\n props: { browser: { kind: \"route-change\", route } },\n });\n };\n\n if (captureInitial) capture(\"initial\", current, history?.state);\n\n if (history && originalPushState) {\n history.pushState = function pushState(\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ) {\n const result = originalPushState.call(this, data, unused, url);\n capture(\n \"pushState\",\n normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl),\n data,\n );\n return result;\n };\n }\n\n if (history && originalReplaceState) {\n history.replaceState = function replaceState(\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ) {\n const result = originalReplaceState.call(this, data, unused, url);\n capture(\n \"replaceState\",\n normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl),\n data,\n );\n return result;\n };\n }\n\n const onPopState = api.guard((event: PopStateEvent) => {\n capture(\n \"popstate\",\n normalizeRouteUrl(undefined, location, urlMode, options.sanitizeUrl),\n event.state,\n );\n });\n const onHashChange = api.guard((event: HashChangeEvent) => {\n capture(\n \"hashchange\",\n normalizeRouteUrl(event.newURL, location, urlMode, options.sanitizeUrl),\n history?.state,\n );\n });\n\n add?.(\"popstate\", onPopState);\n add?.(\"hashchange\", onHashChange);\n\n return () => {\n if (disposed) return;\n disposed = true;\n if (history && originalPushState) history.pushState = originalPushState;\n if (history && originalReplaceState) history.replaceState = originalReplaceState;\n remove?.(\"popstate\", onPopState);\n remove?.(\"hashchange\", onHashChange);\n };\n },\n };\n}\n"],"mappings":"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"}
|