@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.
Files changed (103) hide show
  1. package/dist/broadcast-channel-transport.cjs +115 -0
  2. package/dist/broadcast-channel-transport.cjs.map +1 -0
  3. package/dist/broadcast-channel-transport.js +114 -0
  4. package/dist/broadcast-channel-transport.js.map +1 -0
  5. package/dist/console-integration.cjs +98 -0
  6. package/dist/console-integration.cjs.map +1 -0
  7. package/dist/console-integration.js +97 -0
  8. package/dist/console-integration.js.map +1 -0
  9. package/dist/context-propagation-integration.cjs +78 -0
  10. package/dist/context-propagation-integration.cjs.map +1 -0
  11. package/dist/context-propagation-integration.js +77 -0
  12. package/dist/context-propagation-integration.js.map +1 -0
  13. package/dist/error-integration.cjs +128 -0
  14. package/dist/error-integration.cjs.map +1 -0
  15. package/dist/error-integration.js +127 -0
  16. package/dist/error-integration.js.map +1 -0
  17. package/dist/fetch-integration.cjs +120 -0
  18. package/dist/fetch-integration.cjs.map +1 -0
  19. package/dist/fetch-integration.js +119 -0
  20. package/dist/fetch-integration.js.map +1 -0
  21. package/dist/framework-error-integration.cjs +102 -0
  22. package/dist/framework-error-integration.cjs.map +1 -0
  23. package/dist/framework-error-integration.js +101 -0
  24. package/dist/framework-error-integration.js.map +1 -0
  25. package/dist/framework-router-integrations.cjs +88 -0
  26. package/dist/framework-router-integrations.cjs.map +1 -0
  27. package/dist/framework-router-integrations.js +84 -0
  28. package/dist/framework-router-integrations.js.map +1 -0
  29. package/dist/http-transport.cjs +301 -0
  30. package/dist/http-transport.cjs.map +1 -0
  31. package/dist/http-transport.js +299 -0
  32. package/dist/http-transport.js.map +1 -0
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexeddb-offline-queue.cjs +105 -0
  36. package/dist/indexeddb-offline-queue.cjs.map +1 -0
  37. package/dist/indexeddb-offline-queue.js +104 -0
  38. package/dist/indexeddb-offline-queue.js.map +1 -0
  39. package/dist/indexeddb-transport.cjs +531 -0
  40. package/dist/indexeddb-transport.cjs.map +1 -0
  41. package/dist/indexeddb-transport.js +530 -0
  42. package/dist/indexeddb-transport.js.map +1 -0
  43. package/dist/offline-first-transport.cjs +675 -0
  44. package/dist/offline-first-transport.cjs.map +1 -0
  45. package/dist/offline-first-transport.js +674 -0
  46. package/dist/offline-first-transport.js.map +1 -0
  47. package/dist/page-lifecycle.cjs +39 -0
  48. package/dist/page-lifecycle.cjs.map +1 -0
  49. package/dist/page-lifecycle.js +38 -0
  50. package/dist/page-lifecycle.js.map +1 -0
  51. package/dist/payload-transforms.cjs +30 -0
  52. package/dist/payload-transforms.cjs.map +1 -0
  53. package/dist/payload-transforms.js +29 -0
  54. package/dist/payload-transforms.js.map +1 -0
  55. package/dist/performance-integration.cjs +135 -0
  56. package/dist/performance-integration.cjs.map +1 -0
  57. package/dist/performance-integration.js +133 -0
  58. package/dist/performance-integration.js.map +1 -0
  59. package/dist/reporting-integration.cjs +110 -0
  60. package/dist/reporting-integration.cjs.map +1 -0
  61. package/dist/reporting-integration.js +109 -0
  62. package/dist/reporting-integration.js.map +1 -0
  63. package/dist/router-integration.cjs +97 -0
  64. package/dist/router-integration.cjs.map +1 -0
  65. package/dist/router-integration.js +96 -0
  66. package/dist/router-integration.js.map +1 -0
  67. package/dist/runtime-host-integration.cjs +181 -0
  68. package/dist/runtime-host-integration.cjs.map +1 -0
  69. package/dist/runtime-host-integration.js +180 -0
  70. package/dist/runtime-host-integration.js.map +1 -0
  71. package/dist/service-worker-integration.cjs +109 -0
  72. package/dist/service-worker-integration.cjs.map +1 -0
  73. package/dist/service-worker-integration.js +108 -0
  74. package/dist/service-worker-integration.js.map +1 -0
  75. package/dist/service-worker-transport.cjs +184 -0
  76. package/dist/service-worker-transport.cjs.map +1 -0
  77. package/dist/service-worker-transport.js +183 -0
  78. package/dist/service-worker-transport.js.map +1 -0
  79. package/dist/user-action-integration.cjs +100 -0
  80. package/dist/user-action-integration.cjs.map +1 -0
  81. package/dist/user-action-integration.js +99 -0
  82. package/dist/user-action-integration.js.map +1 -0
  83. package/dist/web-vitals-integration.cjs +157 -0
  84. package/dist/web-vitals-integration.cjs.map +1 -0
  85. package/dist/web-vitals-integration.js +156 -0
  86. package/dist/web-vitals-integration.js.map +1 -0
  87. package/dist/websocket-integration.cjs +158 -0
  88. package/dist/websocket-integration.cjs.map +1 -0
  89. package/dist/websocket-integration.js +157 -0
  90. package/dist/websocket-integration.js.map +1 -0
  91. package/dist/websocket-transport.cjs +187 -0
  92. package/dist/websocket-transport.cjs.map +1 -0
  93. package/dist/websocket-transport.js +186 -0
  94. package/dist/websocket-transport.js.map +1 -0
  95. package/dist/xhr-integration.cjs +107 -0
  96. package/dist/xhr-integration.cjs.map +1 -0
  97. package/dist/xhr-integration.js +106 -0
  98. package/dist/xhr-integration.js.map +1 -0
  99. package/dist/zip-export.cjs +241 -0
  100. package/dist/zip-export.cjs.map +1 -0
  101. package/dist/zip-export.js +238 -0
  102. package/dist/zip-export.js.map +1 -0
  103. package/package.json +104 -77
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-propagation-integration.js","names":[],"sources":["../src/context-propagation-integration.ts"],"sourcesContent":["import {\n addContextProvider,\n formatBaggage,\n formatTraceparent,\n parseBaggage,\n parseTraceparent,\n type Baggage,\n type Integration,\n type TraceContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserContextActionEventName = \"change\" | \"click\" | \"input\" | \"keydown\" | \"submit\";\n\nexport interface BrowserContextEventTargetLike {\n addEventListener: typeof globalThis.addEventListener;\n removeEventListener: typeof globalThis.removeEventListener;\n}\n\nexport interface BrowserContextPropagationOptions {\n sessionId?: string | (() => string | undefined);\n requestId?: () => string | undefined;\n trace?: TraceContext | (() => TraceContext | undefined);\n traceparent?: string | (() => string | undefined);\n baggage?: Baggage | string | (() => Baggage | string | undefined);\n actionEvents?: readonly BrowserContextActionEventName[];\n actionTtlMs?: number;\n root?: BrowserContextEventTargetLike;\n clock?: () => number;\n idFactory?: () => string;\n}\n\nconst defaultActionEvents: readonly BrowserContextActionEventName[] = [\n \"click\",\n \"input\",\n \"change\",\n \"submit\",\n];\n\nlet fallbackIdSeq = 0;\n\nfunction defaultIdFactory(): string {\n const cryptoRandomUUID = globalThis.crypto?.randomUUID;\n if (cryptoRandomUUID) return cryptoRandomUUID.call(globalThis.crypto);\n return `${Date.now().toString(36)}-${(fallbackIdSeq++).toString(36)}`;\n}\n\nfunction resolveValue<T>(value: T | (() => T | undefined) | undefined): T | undefined {\n return typeof value === \"function\" ? (value as () => T | undefined)() : value;\n}\n\nfunction normalizeBaggage(value: Baggage | string | undefined): Baggage | undefined {\n if (typeof value === \"string\") return parseBaggage(value);\n return value;\n}\n\nfunction actionTargetName(target: EventTarget | null): string | undefined {\n const item = target as { tagName?: string; id?: string; name?: string } | null;\n if (!item) return undefined;\n return item.id || item.name || item.tagName?.toLowerCase();\n}\n\nexport function browserContextPropagationIntegration(\n options: BrowserContextPropagationOptions = {},\n): Integration {\n const actionEvents = options.actionEvents ?? defaultActionEvents;\n const actionTtlMs = Math.max(0, Math.floor(options.actionTtlMs ?? 5000));\n const clock = options.clock ?? Date.now;\n const idFactory = options.idFactory ?? defaultIdFactory;\n let lastAction:\n | {\n id: string;\n type: string;\n target?: string;\n time: number;\n }\n | undefined;\n\n return {\n name: \"browser-context-propagation\",\n setup() {\n const root = options.root ?? globalThis.document;\n const disposeProvider = addContextProvider(() => {\n const sessionId = resolveValue(options.sessionId);\n const requestId = options.requestId?.();\n const explicitTrace = resolveValue(options.trace);\n const trace = explicitTrace ?? parseTraceparent(resolveValue(options.traceparent));\n const baggage = normalizeBaggage(resolveValue(options.baggage)) ?? trace?.baggage;\n const traceparent = formatTraceparent(trace);\n const now = clock();\n const action =\n lastAction && now - lastAction.time <= actionTtlMs\n ? {\n actionId: lastAction.id,\n actionType: lastAction.type,\n actionTarget: lastAction.target,\n }\n : undefined;\n\n return {\n sessionId,\n requestId,\n traceparent,\n baggage: formatBaggage(baggage),\n ...action,\n };\n });\n\n if (!root?.addEventListener || !root.removeEventListener || actionEvents.length === 0) {\n return disposeProvider;\n }\n\n const onAction = (event: Event) => {\n lastAction = {\n id: idFactory(),\n type: event.type,\n target: actionTargetName(event.target),\n time: clock(),\n };\n };\n\n for (const event of actionEvents) root.addEventListener(event, onAction, true);\n\n return () => {\n for (const event of actionEvents) root.removeEventListener(event, onAction, true);\n disposeProvider();\n };\n },\n };\n}\n"],"mappings":";;AA+BA,MAAM,sBAAgE;CACpE;CACA;CACA;CACA;AACF;AAEA,IAAI,gBAAgB;AAEpB,SAAS,mBAA2B;CAClC,MAAM,mBAAmB,WAAW,QAAQ;CAC5C,IAAI,kBAAkB,OAAO,iBAAiB,KAAK,WAAW,MAAM;CACpE,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,IAAI,gBAAA,CAAiB,SAAS,EAAE;AACpE;AAEA,SAAS,aAAgB,OAA6D;CACpF,OAAO,OAAO,UAAU,aAAc,MAA8B,IAAI;AAC1E;AAEA,SAAS,iBAAiB,OAA0D;CAClF,IAAI,OAAO,UAAU,UAAU,OAAO,aAAa,KAAK;CACxD,OAAO;AACT;AAEA,SAAS,iBAAiB,QAAgD;CACxE,MAAM,OAAO;CACb,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,OAAO,KAAK,MAAM,KAAK,QAAQ,KAAK,SAAS,YAAY;AAC3D;AAEA,SAAgB,qCACd,UAA4C,CAAC,GAChC;CACb,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,eAAe,GAAI,CAAC;CACvE,MAAM,QAAQ,QAAQ,SAAS,KAAK;CACpC,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI;CASJ,OAAO;EACL,MAAM;EACN,QAAQ;GACN,MAAM,OAAO,QAAQ,QAAQ,WAAW;GACxC,MAAM,kBAAkB,yBAAyB;IAC/C,MAAM,YAAY,aAAa,QAAQ,SAAS;IAChD,MAAM,YAAY,QAAQ,YAAY;IAEtC,MAAM,QADgB,aAAa,QAAQ,KACjB,KAAK,iBAAiB,aAAa,QAAQ,WAAW,CAAC;IACjF,MAAM,UAAU,iBAAiB,aAAa,QAAQ,OAAO,CAAC,KAAK,OAAO;IAC1E,MAAM,cAAc,kBAAkB,KAAK;IAC3C,MAAM,MAAM,MAAM;IAClB,MAAM,SACJ,cAAc,MAAM,WAAW,QAAQ,cACnC;KACE,UAAU,WAAW;KACrB,YAAY,WAAW;KACvB,cAAc,WAAW;IAC3B,IACA,KAAA;IAEN,OAAO;KACL;KACA;KACA;KACA,SAAS,cAAc,OAAO;KAC9B,GAAG;IACL;GACF,CAAC;GAED,IAAI,CAAC,MAAM,oBAAoB,CAAC,KAAK,uBAAuB,aAAa,WAAW,GAClF,OAAO;GAGT,MAAM,YAAY,UAAiB;IACjC,aAAa;KACX,IAAI,UAAU;KACd,MAAM,MAAM;KACZ,QAAQ,iBAAiB,MAAM,MAAM;KACrC,MAAM,MAAM;IACd;GACF;GAEA,KAAK,MAAM,SAAS,cAAc,KAAK,iBAAiB,OAAO,UAAU,IAAI;GAE7E,aAAa;IACX,KAAK,MAAM,SAAS,cAAc,KAAK,oBAAoB,OAAO,UAAU,IAAI;IAChF,gBAAgB;GAClB;EACF;CACF;AACF"}
@@ -0,0 +1,128 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _loggerjs_core = require("@loggerjs/core");
3
+ //#region src/error-integration.ts
4
+ function resourceInfo(target) {
5
+ const element = target;
6
+ return {
7
+ tagName: element?.tagName,
8
+ url: element?.src || element?.currentSrc || element?.href,
9
+ html: element?.outerHTML?.slice(0, 500)
10
+ };
11
+ }
12
+ function reportDrop(reason) {
13
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("integration.dropped");
14
+ (0, _loggerjs_core.incrementLoggerMetaCounter)(`integration.dropped.${reason}`);
15
+ }
16
+ function captureBrowserErrorsIntegration(options = {}) {
17
+ const captureWindowError = options.captureWindowError ?? true;
18
+ const captureUnhandledRejection = options.captureUnhandledRejection ?? true;
19
+ const captureResourceErrors = options.captureResourceErrors ?? true;
20
+ const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? false;
21
+ const scriptErrorDedupeWindowMs = options.scriptErrorDedupeWindowMs ?? 1e3;
22
+ return {
23
+ name: "capture-browser-errors",
24
+ setup(api) {
25
+ const disposers = [];
26
+ let lastScriptErrorAt = 0;
27
+ let disposed = false;
28
+ const shouldDropScriptError = (message) => {
29
+ if (message !== "Script error.") return false;
30
+ const now = Date.now();
31
+ if (now - lastScriptErrorAt <= scriptErrorDedupeWindowMs) {
32
+ reportDrop("script-error-duplicate");
33
+ return true;
34
+ }
35
+ lastScriptErrorAt = now;
36
+ return false;
37
+ };
38
+ if (captureWindowError && typeof addEventListener !== "undefined") {
39
+ const onError = api.guard((event) => {
40
+ const errorEvent = event;
41
+ if ("message" in errorEvent && shouldDropScriptError(errorEvent.message)) return;
42
+ if ("error" in errorEvent && errorEvent.error) {
43
+ api.capture({
44
+ level: "error",
45
+ message: errorEvent.message || errorEvent.error.message,
46
+ error: errorEvent.error,
47
+ props: { browser: {
48
+ kind: "script-error",
49
+ file: errorEvent.filename,
50
+ line: errorEvent.lineno,
51
+ column: errorEvent.colno
52
+ } }
53
+ });
54
+ return;
55
+ }
56
+ if ("message" in errorEvent && errorEvent.message) {
57
+ api.capture({
58
+ level: "error",
59
+ message: errorEvent.message,
60
+ props: { browser: {
61
+ kind: "script-error",
62
+ file: errorEvent.filename,
63
+ line: errorEvent.lineno,
64
+ column: errorEvent.colno
65
+ } }
66
+ });
67
+ return;
68
+ }
69
+ if (captureResourceErrors) api.capture({
70
+ level: "error",
71
+ message: "Browser resource load error",
72
+ props: { browser: {
73
+ kind: "resource-error",
74
+ ...resourceInfo(event.target)
75
+ } }
76
+ });
77
+ });
78
+ addEventListener("error", onError, true);
79
+ disposers.push(() => removeEventListener("error", onError, true));
80
+ }
81
+ if (captureUnhandledRejection && typeof addEventListener !== "undefined") {
82
+ const onUnhandledRejection = api.guard((event) => {
83
+ const reason = event.reason;
84
+ api.capture({
85
+ level: "error",
86
+ message: reason instanceof Error ? reason.message : "Unhandled promise rejection",
87
+ error: reason,
88
+ props: {
89
+ browser: { kind: "unhandledrejection" },
90
+ reason: (0, _loggerjs_core.normalizeValue)(reason, { maxDepth: 5 })
91
+ }
92
+ });
93
+ });
94
+ addEventListener("unhandledrejection", onUnhandledRejection);
95
+ disposers.push(() => removeEventListener("unhandledrejection", onUnhandledRejection));
96
+ }
97
+ if (captureSecurityPolicyViolation && typeof addEventListener !== "undefined") {
98
+ const onSecurityPolicyViolation = api.guard((event) => {
99
+ api.capture({
100
+ level: "warn",
101
+ message: `Security policy violation: ${event.violatedDirective}`,
102
+ props: { browser: {
103
+ kind: "securitypolicyviolation",
104
+ blockedURI: event.blockedURI,
105
+ documentURI: event.documentURI,
106
+ file: event.sourceFile,
107
+ line: event.lineNumber,
108
+ column: event.columnNumber,
109
+ originalPolicy: event.originalPolicy,
110
+ effectiveDirective: event.effectiveDirective
111
+ } }
112
+ });
113
+ });
114
+ addEventListener("securitypolicyviolation", onSecurityPolicyViolation);
115
+ disposers.push(() => removeEventListener("securitypolicyviolation", onSecurityPolicyViolation));
116
+ }
117
+ return () => {
118
+ if (disposed) return;
119
+ disposed = true;
120
+ for (const dispose of disposers) dispose();
121
+ };
122
+ }
123
+ };
124
+ }
125
+ //#endregion
126
+ exports.captureBrowserErrorsIntegration = captureBrowserErrorsIntegration;
127
+
128
+ //# sourceMappingURL=error-integration.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-integration.cjs","names":[],"sources":["../src/error-integration.ts"],"sourcesContent":["import {\n incrementLoggerMetaCounter,\n normalizeValue,\n type Integration,\n type IntegrationSetupContext,\n} from \"@loggerjs/core\";\n\nexport interface CaptureBrowserErrorsOptions {\n captureWindowError?: boolean;\n captureUnhandledRejection?: boolean;\n captureResourceErrors?: boolean;\n captureSecurityPolicyViolation?: boolean;\n scriptErrorDedupeWindowMs?: number;\n}\n\nfunction resourceInfo(target: unknown): Record<string, unknown> {\n const element = target as {\n tagName?: string;\n src?: string;\n href?: string;\n currentSrc?: string;\n outerHTML?: string;\n } | null;\n return {\n tagName: element?.tagName,\n url: element?.src || element?.currentSrc || element?.href,\n html: element?.outerHTML?.slice(0, 500),\n };\n}\n\nfunction reportDrop(reason: string) {\n incrementLoggerMetaCounter(\"integration.dropped\");\n incrementLoggerMetaCounter(`integration.dropped.${reason}`);\n}\n\nexport function captureBrowserErrorsIntegration(\n options: CaptureBrowserErrorsOptions = {},\n): Integration {\n const captureWindowError = options.captureWindowError ?? true;\n const captureUnhandledRejection = options.captureUnhandledRejection ?? true;\n const captureResourceErrors = options.captureResourceErrors ?? true;\n const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? false;\n const scriptErrorDedupeWindowMs = options.scriptErrorDedupeWindowMs ?? 1000;\n\n return {\n name: \"capture-browser-errors\",\n setup(api: IntegrationSetupContext) {\n const disposers: Array<() => void> = [];\n let lastScriptErrorAt = 0;\n let disposed = false;\n\n const shouldDropScriptError = (message: string | undefined) => {\n if (message !== \"Script error.\") return false;\n const now = Date.now();\n if (now - lastScriptErrorAt <= scriptErrorDedupeWindowMs) {\n reportDrop(\"script-error-duplicate\");\n return true;\n }\n lastScriptErrorAt = now;\n return false;\n };\n\n if (captureWindowError && typeof addEventListener !== \"undefined\") {\n const onError = api.guard((event: ErrorEvent | Event) => {\n const errorEvent = event as ErrorEvent;\n if (\"message\" in errorEvent && shouldDropScriptError(errorEvent.message)) return;\n\n if (\"error\" in errorEvent && errorEvent.error) {\n api.capture({\n level: \"error\",\n message: errorEvent.message || errorEvent.error.message,\n error: errorEvent.error,\n props: {\n browser: {\n kind: \"script-error\",\n file: errorEvent.filename,\n line: errorEvent.lineno,\n column: errorEvent.colno,\n },\n },\n });\n return;\n }\n\n if (\"message\" in errorEvent && errorEvent.message) {\n api.capture({\n level: \"error\",\n message: errorEvent.message,\n props: {\n browser: {\n kind: \"script-error\",\n file: errorEvent.filename,\n line: errorEvent.lineno,\n column: errorEvent.colno,\n },\n },\n });\n return;\n }\n\n if (captureResourceErrors) {\n api.capture({\n level: \"error\",\n message: \"Browser resource load error\",\n props: {\n browser: {\n kind: \"resource-error\",\n ...resourceInfo((event as Event).target),\n },\n },\n });\n }\n });\n addEventListener(\"error\", onError, true);\n disposers.push(() => removeEventListener(\"error\", onError, true));\n }\n\n if (captureUnhandledRejection && typeof addEventListener !== \"undefined\") {\n const onUnhandledRejection = api.guard((event: PromiseRejectionEvent) => {\n const reason = event.reason;\n api.capture({\n level: \"error\",\n message: reason instanceof Error ? reason.message : \"Unhandled promise rejection\",\n error: reason,\n props: {\n browser: { kind: \"unhandledrejection\" },\n reason: normalizeValue(reason, { maxDepth: 5 }),\n },\n });\n });\n addEventListener(\"unhandledrejection\", onUnhandledRejection);\n disposers.push(() => removeEventListener(\"unhandledrejection\", onUnhandledRejection));\n }\n\n if (captureSecurityPolicyViolation && typeof addEventListener !== \"undefined\") {\n const onSecurityPolicyViolation = api.guard((event: SecurityPolicyViolationEvent) => {\n api.capture({\n level: \"warn\",\n message: `Security policy violation: ${event.violatedDirective}`,\n props: {\n browser: {\n kind: \"securitypolicyviolation\",\n blockedURI: event.blockedURI,\n documentURI: event.documentURI,\n file: event.sourceFile,\n line: event.lineNumber,\n column: event.columnNumber,\n originalPolicy: event.originalPolicy,\n effectiveDirective: event.effectiveDirective,\n },\n },\n });\n });\n addEventListener(\"securitypolicyviolation\", onSecurityPolicyViolation);\n disposers.push(() =>\n removeEventListener(\"securitypolicyviolation\", onSecurityPolicyViolation),\n );\n }\n\n return () => {\n if (disposed) return;\n disposed = true;\n for (const dispose of disposers) dispose();\n };\n },\n };\n}\n"],"mappings":";;;AAeA,SAAS,aAAa,QAA0C;CAC9D,MAAM,UAAU;CAOhB,OAAO;EACL,SAAS,SAAS;EAClB,KAAK,SAAS,OAAO,SAAS,cAAc,SAAS;EACrD,MAAM,SAAS,WAAW,MAAM,GAAG,GAAG;CACxC;AACF;AAEA,SAAS,WAAW,QAAgB;CAClC,CAAA,GAAA,eAAA,2BAAA,CAA2B,qBAAqB;CAChD,CAAA,GAAA,eAAA,2BAAA,CAA2B,uBAAuB,QAAQ;AAC5D;AAEA,SAAgB,gCACd,UAAuC,CAAC,GAC3B;CACb,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,MAAM,4BAA4B,QAAQ,6BAA6B;CACvE,MAAM,wBAAwB,QAAQ,yBAAyB;CAC/D,MAAM,iCAAiC,QAAQ,kCAAkC;CACjF,MAAM,4BAA4B,QAAQ,6BAA6B;CAEvE,OAAO;EACL,MAAM;EACN,MAAM,KAA8B;GAClC,MAAM,YAA+B,CAAC;GACtC,IAAI,oBAAoB;GACxB,IAAI,WAAW;GAEf,MAAM,yBAAyB,YAAgC;IAC7D,IAAI,YAAY,iBAAiB,OAAO;IACxC,MAAM,MAAM,KAAK,IAAI;IACrB,IAAI,MAAM,qBAAqB,2BAA2B;KACxD,WAAW,wBAAwB;KACnC,OAAO;IACT;IACA,oBAAoB;IACpB,OAAO;GACT;GAEA,IAAI,sBAAsB,OAAO,qBAAqB,aAAa;IACjE,MAAM,UAAU,IAAI,OAAO,UAA8B;KACvD,MAAM,aAAa;KACnB,IAAI,aAAa,cAAc,sBAAsB,WAAW,OAAO,GAAG;KAE1E,IAAI,WAAW,cAAc,WAAW,OAAO;MAC7C,IAAI,QAAQ;OACV,OAAO;OACP,SAAS,WAAW,WAAW,WAAW,MAAM;OAChD,OAAO,WAAW;OAClB,OAAO,EACL,SAAS;QACP,MAAM;QACN,MAAM,WAAW;QACjB,MAAM,WAAW;QACjB,QAAQ,WAAW;OACrB,EACF;MACF,CAAC;MACD;KACF;KAEA,IAAI,aAAa,cAAc,WAAW,SAAS;MACjD,IAAI,QAAQ;OACV,OAAO;OACP,SAAS,WAAW;OACpB,OAAO,EACL,SAAS;QACP,MAAM;QACN,MAAM,WAAW;QACjB,MAAM,WAAW;QACjB,QAAQ,WAAW;OACrB,EACF;MACF,CAAC;MACD;KACF;KAEA,IAAI,uBACF,IAAI,QAAQ;MACV,OAAO;MACP,SAAS;MACT,OAAO,EACL,SAAS;OACP,MAAM;OACN,GAAG,aAAc,MAAgB,MAAM;MACzC,EACF;KACF,CAAC;IAEL,CAAC;IACD,iBAAiB,SAAS,SAAS,IAAI;IACvC,UAAU,WAAW,oBAAoB,SAAS,SAAS,IAAI,CAAC;GAClE;GAEA,IAAI,6BAA6B,OAAO,qBAAqB,aAAa;IACxE,MAAM,uBAAuB,IAAI,OAAO,UAAiC;KACvE,MAAM,SAAS,MAAM;KACrB,IAAI,QAAQ;MACV,OAAO;MACP,SAAS,kBAAkB,QAAQ,OAAO,UAAU;MACpD,OAAO;MACP,OAAO;OACL,SAAS,EAAE,MAAM,qBAAqB;OACtC,SAAA,GAAA,eAAA,eAAA,CAAuB,QAAQ,EAAE,UAAU,EAAE,CAAC;MAChD;KACF,CAAC;IACH,CAAC;IACD,iBAAiB,sBAAsB,oBAAoB;IAC3D,UAAU,WAAW,oBAAoB,sBAAsB,oBAAoB,CAAC;GACtF;GAEA,IAAI,kCAAkC,OAAO,qBAAqB,aAAa;IAC7E,MAAM,4BAA4B,IAAI,OAAO,UAAwC;KACnF,IAAI,QAAQ;MACV,OAAO;MACP,SAAS,8BAA8B,MAAM;MAC7C,OAAO,EACL,SAAS;OACP,MAAM;OACN,YAAY,MAAM;OAClB,aAAa,MAAM;OACnB,MAAM,MAAM;OACZ,MAAM,MAAM;OACZ,QAAQ,MAAM;OACd,gBAAgB,MAAM;OACtB,oBAAoB,MAAM;MAC5B,EACF;KACF,CAAC;IACH,CAAC;IACD,iBAAiB,2BAA2B,yBAAyB;IACrE,UAAU,WACR,oBAAoB,2BAA2B,yBAAyB,CAC1E;GACF;GAEA,aAAa;IACX,IAAI,UAAU;IACd,WAAW;IACX,KAAK,MAAM,WAAW,WAAW,QAAQ;GAC3C;EACF;CACF;AACF"}
@@ -0,0 +1,127 @@
1
+ import { incrementLoggerMetaCounter, normalizeValue } from "@loggerjs/core";
2
+ //#region src/error-integration.ts
3
+ function resourceInfo(target) {
4
+ const element = target;
5
+ return {
6
+ tagName: element?.tagName,
7
+ url: element?.src || element?.currentSrc || element?.href,
8
+ html: element?.outerHTML?.slice(0, 500)
9
+ };
10
+ }
11
+ function reportDrop(reason) {
12
+ incrementLoggerMetaCounter("integration.dropped");
13
+ incrementLoggerMetaCounter(`integration.dropped.${reason}`);
14
+ }
15
+ function captureBrowserErrorsIntegration(options = {}) {
16
+ const captureWindowError = options.captureWindowError ?? true;
17
+ const captureUnhandledRejection = options.captureUnhandledRejection ?? true;
18
+ const captureResourceErrors = options.captureResourceErrors ?? true;
19
+ const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? false;
20
+ const scriptErrorDedupeWindowMs = options.scriptErrorDedupeWindowMs ?? 1e3;
21
+ return {
22
+ name: "capture-browser-errors",
23
+ setup(api) {
24
+ const disposers = [];
25
+ let lastScriptErrorAt = 0;
26
+ let disposed = false;
27
+ const shouldDropScriptError = (message) => {
28
+ if (message !== "Script error.") return false;
29
+ const now = Date.now();
30
+ if (now - lastScriptErrorAt <= scriptErrorDedupeWindowMs) {
31
+ reportDrop("script-error-duplicate");
32
+ return true;
33
+ }
34
+ lastScriptErrorAt = now;
35
+ return false;
36
+ };
37
+ if (captureWindowError && typeof addEventListener !== "undefined") {
38
+ const onError = api.guard((event) => {
39
+ const errorEvent = event;
40
+ if ("message" in errorEvent && shouldDropScriptError(errorEvent.message)) return;
41
+ if ("error" in errorEvent && errorEvent.error) {
42
+ api.capture({
43
+ level: "error",
44
+ message: errorEvent.message || errorEvent.error.message,
45
+ error: errorEvent.error,
46
+ props: { browser: {
47
+ kind: "script-error",
48
+ file: errorEvent.filename,
49
+ line: errorEvent.lineno,
50
+ column: errorEvent.colno
51
+ } }
52
+ });
53
+ return;
54
+ }
55
+ if ("message" in errorEvent && errorEvent.message) {
56
+ api.capture({
57
+ level: "error",
58
+ message: errorEvent.message,
59
+ props: { browser: {
60
+ kind: "script-error",
61
+ file: errorEvent.filename,
62
+ line: errorEvent.lineno,
63
+ column: errorEvent.colno
64
+ } }
65
+ });
66
+ return;
67
+ }
68
+ if (captureResourceErrors) api.capture({
69
+ level: "error",
70
+ message: "Browser resource load error",
71
+ props: { browser: {
72
+ kind: "resource-error",
73
+ ...resourceInfo(event.target)
74
+ } }
75
+ });
76
+ });
77
+ addEventListener("error", onError, true);
78
+ disposers.push(() => removeEventListener("error", onError, true));
79
+ }
80
+ if (captureUnhandledRejection && typeof addEventListener !== "undefined") {
81
+ const onUnhandledRejection = api.guard((event) => {
82
+ const reason = event.reason;
83
+ api.capture({
84
+ level: "error",
85
+ message: reason instanceof Error ? reason.message : "Unhandled promise rejection",
86
+ error: reason,
87
+ props: {
88
+ browser: { kind: "unhandledrejection" },
89
+ reason: normalizeValue(reason, { maxDepth: 5 })
90
+ }
91
+ });
92
+ });
93
+ addEventListener("unhandledrejection", onUnhandledRejection);
94
+ disposers.push(() => removeEventListener("unhandledrejection", onUnhandledRejection));
95
+ }
96
+ if (captureSecurityPolicyViolation && typeof addEventListener !== "undefined") {
97
+ const onSecurityPolicyViolation = api.guard((event) => {
98
+ api.capture({
99
+ level: "warn",
100
+ message: `Security policy violation: ${event.violatedDirective}`,
101
+ props: { browser: {
102
+ kind: "securitypolicyviolation",
103
+ blockedURI: event.blockedURI,
104
+ documentURI: event.documentURI,
105
+ file: event.sourceFile,
106
+ line: event.lineNumber,
107
+ column: event.columnNumber,
108
+ originalPolicy: event.originalPolicy,
109
+ effectiveDirective: event.effectiveDirective
110
+ } }
111
+ });
112
+ });
113
+ addEventListener("securitypolicyviolation", onSecurityPolicyViolation);
114
+ disposers.push(() => removeEventListener("securitypolicyviolation", onSecurityPolicyViolation));
115
+ }
116
+ return () => {
117
+ if (disposed) return;
118
+ disposed = true;
119
+ for (const dispose of disposers) dispose();
120
+ };
121
+ }
122
+ };
123
+ }
124
+ //#endregion
125
+ export { captureBrowserErrorsIntegration };
126
+
127
+ //# sourceMappingURL=error-integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-integration.js","names":[],"sources":["../src/error-integration.ts"],"sourcesContent":["import {\n incrementLoggerMetaCounter,\n normalizeValue,\n type Integration,\n type IntegrationSetupContext,\n} from \"@loggerjs/core\";\n\nexport interface CaptureBrowserErrorsOptions {\n captureWindowError?: boolean;\n captureUnhandledRejection?: boolean;\n captureResourceErrors?: boolean;\n captureSecurityPolicyViolation?: boolean;\n scriptErrorDedupeWindowMs?: number;\n}\n\nfunction resourceInfo(target: unknown): Record<string, unknown> {\n const element = target as {\n tagName?: string;\n src?: string;\n href?: string;\n currentSrc?: string;\n outerHTML?: string;\n } | null;\n return {\n tagName: element?.tagName,\n url: element?.src || element?.currentSrc || element?.href,\n html: element?.outerHTML?.slice(0, 500),\n };\n}\n\nfunction reportDrop(reason: string) {\n incrementLoggerMetaCounter(\"integration.dropped\");\n incrementLoggerMetaCounter(`integration.dropped.${reason}`);\n}\n\nexport function captureBrowserErrorsIntegration(\n options: CaptureBrowserErrorsOptions = {},\n): Integration {\n const captureWindowError = options.captureWindowError ?? true;\n const captureUnhandledRejection = options.captureUnhandledRejection ?? true;\n const captureResourceErrors = options.captureResourceErrors ?? true;\n const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? false;\n const scriptErrorDedupeWindowMs = options.scriptErrorDedupeWindowMs ?? 1000;\n\n return {\n name: \"capture-browser-errors\",\n setup(api: IntegrationSetupContext) {\n const disposers: Array<() => void> = [];\n let lastScriptErrorAt = 0;\n let disposed = false;\n\n const shouldDropScriptError = (message: string | undefined) => {\n if (message !== \"Script error.\") return false;\n const now = Date.now();\n if (now - lastScriptErrorAt <= scriptErrorDedupeWindowMs) {\n reportDrop(\"script-error-duplicate\");\n return true;\n }\n lastScriptErrorAt = now;\n return false;\n };\n\n if (captureWindowError && typeof addEventListener !== \"undefined\") {\n const onError = api.guard((event: ErrorEvent | Event) => {\n const errorEvent = event as ErrorEvent;\n if (\"message\" in errorEvent && shouldDropScriptError(errorEvent.message)) return;\n\n if (\"error\" in errorEvent && errorEvent.error) {\n api.capture({\n level: \"error\",\n message: errorEvent.message || errorEvent.error.message,\n error: errorEvent.error,\n props: {\n browser: {\n kind: \"script-error\",\n file: errorEvent.filename,\n line: errorEvent.lineno,\n column: errorEvent.colno,\n },\n },\n });\n return;\n }\n\n if (\"message\" in errorEvent && errorEvent.message) {\n api.capture({\n level: \"error\",\n message: errorEvent.message,\n props: {\n browser: {\n kind: \"script-error\",\n file: errorEvent.filename,\n line: errorEvent.lineno,\n column: errorEvent.colno,\n },\n },\n });\n return;\n }\n\n if (captureResourceErrors) {\n api.capture({\n level: \"error\",\n message: \"Browser resource load error\",\n props: {\n browser: {\n kind: \"resource-error\",\n ...resourceInfo((event as Event).target),\n },\n },\n });\n }\n });\n addEventListener(\"error\", onError, true);\n disposers.push(() => removeEventListener(\"error\", onError, true));\n }\n\n if (captureUnhandledRejection && typeof addEventListener !== \"undefined\") {\n const onUnhandledRejection = api.guard((event: PromiseRejectionEvent) => {\n const reason = event.reason;\n api.capture({\n level: \"error\",\n message: reason instanceof Error ? reason.message : \"Unhandled promise rejection\",\n error: reason,\n props: {\n browser: { kind: \"unhandledrejection\" },\n reason: normalizeValue(reason, { maxDepth: 5 }),\n },\n });\n });\n addEventListener(\"unhandledrejection\", onUnhandledRejection);\n disposers.push(() => removeEventListener(\"unhandledrejection\", onUnhandledRejection));\n }\n\n if (captureSecurityPolicyViolation && typeof addEventListener !== \"undefined\") {\n const onSecurityPolicyViolation = api.guard((event: SecurityPolicyViolationEvent) => {\n api.capture({\n level: \"warn\",\n message: `Security policy violation: ${event.violatedDirective}`,\n props: {\n browser: {\n kind: \"securitypolicyviolation\",\n blockedURI: event.blockedURI,\n documentURI: event.documentURI,\n file: event.sourceFile,\n line: event.lineNumber,\n column: event.columnNumber,\n originalPolicy: event.originalPolicy,\n effectiveDirective: event.effectiveDirective,\n },\n },\n });\n });\n addEventListener(\"securitypolicyviolation\", onSecurityPolicyViolation);\n disposers.push(() =>\n removeEventListener(\"securitypolicyviolation\", onSecurityPolicyViolation),\n );\n }\n\n return () => {\n if (disposed) return;\n disposed = true;\n for (const dispose of disposers) dispose();\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _loggerjs_core = require("@loggerjs/core");
3
+ //#region src/http-capture-utils.ts
4
+ function nowMs() {
5
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
6
+ }
7
+ function durationMs(started) {
8
+ return nowMs() - started;
9
+ }
10
+ function sanitizeHttpUrl(rawUrl, sanitizer) {
11
+ const stripped = stripSensitiveUrlParts(rawUrl);
12
+ return sanitizer ? sanitizer(stripped) : stripped;
13
+ }
14
+ function pickAllowedHeaders(headers, allowList) {
15
+ if (!headers || !allowList || allowList.length === 0) return void 0;
16
+ const out = {};
17
+ for (const name of allowList) {
18
+ const value = headers.get(name);
19
+ if (value !== null) out[name.toLowerCase()] = value;
20
+ }
21
+ return Object.keys(out).length > 0 ? out : void 0;
22
+ }
23
+ function headersFromInit(headers) {
24
+ if (!headers) return void 0;
25
+ try {
26
+ return new Headers(headers);
27
+ } catch {
28
+ return;
29
+ }
30
+ }
31
+ function shouldSample(sampleRate, random) {
32
+ if (sampleRate >= 1) return true;
33
+ if (sampleRate <= 0) return false;
34
+ return random() < sampleRate;
35
+ }
36
+ function stripSensitiveUrlParts(rawUrl) {
37
+ if (!/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(rawUrl)) return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
38
+ try {
39
+ const url = new URL(rawUrl);
40
+ url.search = "";
41
+ url.hash = "";
42
+ return url.toString();
43
+ } catch {
44
+ return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
45
+ }
46
+ }
47
+ //#endregion
48
+ //#region src/fetch-integration.ts
49
+ function requestUrl(input) {
50
+ if (typeof input === "string") return input;
51
+ if (input instanceof URL) return input.toString();
52
+ return input.url;
53
+ }
54
+ function requestMethod(input, init) {
55
+ if (init?.method) return init.method.toUpperCase();
56
+ if (typeof input === "object" && "method" in input && input.method) return input.method.toUpperCase();
57
+ return "GET";
58
+ }
59
+ function captureFetchIntegration(options = {}) {
60
+ const minStatus = options.minStatus ?? 400;
61
+ const captureAll = options.captureAll ?? options.captureSuccessful ?? false;
62
+ const sampleRate = options.sampleRate ?? 1;
63
+ const random = options.random ?? Math.random;
64
+ return {
65
+ name: "capture-fetch",
66
+ setup(api) {
67
+ const current = globalThis.fetch;
68
+ if (!current) return;
69
+ const original = api.unpatched.fetch ?? current.bind(globalThis);
70
+ api.unpatched.fetch ??= original;
71
+ const capture = api.guard((input) => api.capture(input));
72
+ globalThis.fetch = async (input, init) => {
73
+ const started = nowMs();
74
+ const url = sanitizeHttpUrl(requestUrl(input), options.sanitizeUrl);
75
+ const method = requestMethod(input, init);
76
+ try {
77
+ const response = await original(input, init);
78
+ if (response.status >= minStatus || captureAll && shouldSample(sampleRate, random)) capture({
79
+ level: response.status >= minStatus ? "warn" : "debug",
80
+ message: `Fetch ${response.status} ${method} ${url}`,
81
+ props: { http: {
82
+ kind: "fetch",
83
+ method,
84
+ url,
85
+ status: response.status,
86
+ ok: response.ok,
87
+ durationMs: durationMs(started),
88
+ requestHeaders: pickAllowedHeaders(headersFromInit(init?.headers), options.captureRequestHeaders),
89
+ responseHeaders: pickAllowedHeaders(response.headers, options.captureResponseHeaders)
90
+ } }
91
+ });
92
+ return response;
93
+ } catch (error) {
94
+ capture({
95
+ level: "error",
96
+ message: `Fetch network error ${method} ${url}`,
97
+ error,
98
+ props: {
99
+ http: {
100
+ kind: "fetch",
101
+ method,
102
+ url,
103
+ durationMs: durationMs(started)
104
+ },
105
+ input: (0, _loggerjs_core.normalizeValue)(input, { maxDepth: 3 })
106
+ }
107
+ });
108
+ throw error;
109
+ }
110
+ };
111
+ return () => {
112
+ globalThis.fetch = current;
113
+ };
114
+ }
115
+ };
116
+ }
117
+ //#endregion
118
+ exports.captureFetchIntegration = captureFetchIntegration;
119
+
120
+ //# sourceMappingURL=fetch-integration.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-integration.cjs","names":[],"sources":["../src/http-capture-utils.ts","../src/fetch-integration.ts"],"sourcesContent":["export function nowMs(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nexport function durationMs(started: number): number {\n return nowMs() - started;\n}\n\nexport function sanitizeHttpUrl(rawUrl: string, sanitizer?: (url: string) => string): string {\n const stripped = stripSensitiveUrlParts(rawUrl);\n return sanitizer ? sanitizer(stripped) : stripped;\n}\n\nexport function pickAllowedHeaders(\n headers: Headers | undefined,\n allowList: readonly string[] | undefined,\n): Record<string, string> | undefined {\n if (!headers || !allowList || allowList.length === 0) return undefined;\n const out: Record<string, string> = {};\n for (const name of allowList) {\n const value = headers.get(name);\n if (value !== null) out[name.toLowerCase()] = value;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nexport function headersFromInit(headers: HeadersInit | undefined): Headers | undefined {\n if (!headers) return undefined;\n try {\n return new Headers(headers);\n } catch {\n return undefined;\n }\n}\n\nexport function shouldSample(sampleRate: number, random: () => number): boolean {\n if (sampleRate >= 1) return true;\n if (sampleRate <= 0) return false;\n return random() < sampleRate;\n}\n\nfunction stripSensitiveUrlParts(rawUrl: string): string {\n if (!/^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(rawUrl)) {\n return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;\n }\n\n try {\n const url = new URL(rawUrl);\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n } catch {\n return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;\n }\n}\n","import {\n normalizeValue,\n type CaptureInput,\n type Integration,\n type IntegrationSetupContext,\n type UnpatchedFunction,\n} from \"@loggerjs/core\";\nimport {\n durationMs,\n headersFromInit,\n nowMs,\n pickAllowedHeaders,\n sanitizeHttpUrl,\n shouldSample,\n} from \"./http-capture-utils\";\n\nexport interface CaptureFetchOptions {\n minStatus?: number;\n captureRequestHeaders?: readonly string[];\n captureResponseHeaders?: readonly string[];\n captureAll?: boolean;\n captureSuccessful?: boolean;\n sampleRate?: number;\n random?: () => number;\n sanitizeUrl?: (url: string) => string;\n}\n\ntype BrowserFetch = typeof fetch;\n\nfunction requestUrl(input: RequestInfo | URL): string {\n if (typeof input === \"string\") return input;\n if (input instanceof URL) return input.toString();\n return input.url;\n}\n\nfunction requestMethod(input: RequestInfo | URL, init?: RequestInit): string {\n if (init?.method) return init.method.toUpperCase();\n if (typeof input === \"object\" && \"method\" in input && input.method)\n return input.method.toUpperCase();\n return \"GET\";\n}\n\nexport function captureFetchIntegration(options: CaptureFetchOptions = {}): Integration {\n const minStatus = options.minStatus ?? 400;\n const captureAll = options.captureAll ?? options.captureSuccessful ?? false;\n const sampleRate = options.sampleRate ?? 1;\n const random = options.random ?? Math.random;\n\n return {\n name: \"capture-fetch\",\n setup(api: IntegrationSetupContext) {\n const current = globalThis.fetch;\n if (!current) return;\n const original =\n (api.unpatched.fetch as BrowserFetch | undefined) ?? current.bind(globalThis);\n api.unpatched.fetch ??= original as UnpatchedFunction;\n\n const capture = api.guard((input: CaptureInput) => api.capture(input));\n\n globalThis.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const started = nowMs();\n const url = sanitizeHttpUrl(requestUrl(input), options.sanitizeUrl);\n const method = requestMethod(input, init);\n try {\n const response = await original(input, init);\n const shouldCapture =\n response.status >= minStatus || (captureAll && shouldSample(sampleRate, random));\n if (shouldCapture) {\n capture({\n level: response.status >= minStatus ? \"warn\" : \"debug\",\n message: `Fetch ${response.status} ${method} ${url}`,\n props: {\n http: {\n kind: \"fetch\",\n method,\n url,\n status: response.status,\n ok: response.ok,\n durationMs: durationMs(started),\n requestHeaders: pickAllowedHeaders(\n headersFromInit(init?.headers),\n options.captureRequestHeaders,\n ),\n responseHeaders: pickAllowedHeaders(\n response.headers,\n options.captureResponseHeaders,\n ),\n },\n },\n });\n }\n return response;\n } catch (error) {\n capture({\n level: \"error\",\n message: `Fetch network error ${method} ${url}`,\n error,\n props: {\n http: { kind: \"fetch\", method, url, durationMs: durationMs(started) },\n input: normalizeValue(input, { maxDepth: 3 }),\n },\n });\n throw error;\n }\n };\n\n return () => {\n globalThis.fetch = current;\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import { normalizeValue } from "@loggerjs/core";
2
+ //#region src/http-capture-utils.ts
3
+ function nowMs() {
4
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
5
+ }
6
+ function durationMs(started) {
7
+ return nowMs() - started;
8
+ }
9
+ function sanitizeHttpUrl(rawUrl, sanitizer) {
10
+ const stripped = stripSensitiveUrlParts(rawUrl);
11
+ return sanitizer ? sanitizer(stripped) : stripped;
12
+ }
13
+ function pickAllowedHeaders(headers, allowList) {
14
+ if (!headers || !allowList || allowList.length === 0) return void 0;
15
+ const out = {};
16
+ for (const name of allowList) {
17
+ const value = headers.get(name);
18
+ if (value !== null) out[name.toLowerCase()] = value;
19
+ }
20
+ return Object.keys(out).length > 0 ? out : void 0;
21
+ }
22
+ function headersFromInit(headers) {
23
+ if (!headers) return void 0;
24
+ try {
25
+ return new Headers(headers);
26
+ } catch {
27
+ return;
28
+ }
29
+ }
30
+ function shouldSample(sampleRate, random) {
31
+ if (sampleRate >= 1) return true;
32
+ if (sampleRate <= 0) return false;
33
+ return random() < sampleRate;
34
+ }
35
+ function stripSensitiveUrlParts(rawUrl) {
36
+ if (!/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(rawUrl)) return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
37
+ try {
38
+ const url = new URL(rawUrl);
39
+ url.search = "";
40
+ url.hash = "";
41
+ return url.toString();
42
+ } catch {
43
+ return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
44
+ }
45
+ }
46
+ //#endregion
47
+ //#region src/fetch-integration.ts
48
+ function requestUrl(input) {
49
+ if (typeof input === "string") return input;
50
+ if (input instanceof URL) return input.toString();
51
+ return input.url;
52
+ }
53
+ function requestMethod(input, init) {
54
+ if (init?.method) return init.method.toUpperCase();
55
+ if (typeof input === "object" && "method" in input && input.method) return input.method.toUpperCase();
56
+ return "GET";
57
+ }
58
+ function captureFetchIntegration(options = {}) {
59
+ const minStatus = options.minStatus ?? 400;
60
+ const captureAll = options.captureAll ?? options.captureSuccessful ?? false;
61
+ const sampleRate = options.sampleRate ?? 1;
62
+ const random = options.random ?? Math.random;
63
+ return {
64
+ name: "capture-fetch",
65
+ setup(api) {
66
+ const current = globalThis.fetch;
67
+ if (!current) return;
68
+ const original = api.unpatched.fetch ?? current.bind(globalThis);
69
+ api.unpatched.fetch ??= original;
70
+ const capture = api.guard((input) => api.capture(input));
71
+ globalThis.fetch = async (input, init) => {
72
+ const started = nowMs();
73
+ const url = sanitizeHttpUrl(requestUrl(input), options.sanitizeUrl);
74
+ const method = requestMethod(input, init);
75
+ try {
76
+ const response = await original(input, init);
77
+ if (response.status >= minStatus || captureAll && shouldSample(sampleRate, random)) capture({
78
+ level: response.status >= minStatus ? "warn" : "debug",
79
+ message: `Fetch ${response.status} ${method} ${url}`,
80
+ props: { http: {
81
+ kind: "fetch",
82
+ method,
83
+ url,
84
+ status: response.status,
85
+ ok: response.ok,
86
+ durationMs: durationMs(started),
87
+ requestHeaders: pickAllowedHeaders(headersFromInit(init?.headers), options.captureRequestHeaders),
88
+ responseHeaders: pickAllowedHeaders(response.headers, options.captureResponseHeaders)
89
+ } }
90
+ });
91
+ return response;
92
+ } catch (error) {
93
+ capture({
94
+ level: "error",
95
+ message: `Fetch network error ${method} ${url}`,
96
+ error,
97
+ props: {
98
+ http: {
99
+ kind: "fetch",
100
+ method,
101
+ url,
102
+ durationMs: durationMs(started)
103
+ },
104
+ input: normalizeValue(input, { maxDepth: 3 })
105
+ }
106
+ });
107
+ throw error;
108
+ }
109
+ };
110
+ return () => {
111
+ globalThis.fetch = current;
112
+ };
113
+ }
114
+ };
115
+ }
116
+ //#endregion
117
+ export { captureFetchIntegration };
118
+
119
+ //# sourceMappingURL=fetch-integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-integration.js","names":[],"sources":["../src/http-capture-utils.ts","../src/fetch-integration.ts"],"sourcesContent":["export function nowMs(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nexport function durationMs(started: number): number {\n return nowMs() - started;\n}\n\nexport function sanitizeHttpUrl(rawUrl: string, sanitizer?: (url: string) => string): string {\n const stripped = stripSensitiveUrlParts(rawUrl);\n return sanitizer ? sanitizer(stripped) : stripped;\n}\n\nexport function pickAllowedHeaders(\n headers: Headers | undefined,\n allowList: readonly string[] | undefined,\n): Record<string, string> | undefined {\n if (!headers || !allowList || allowList.length === 0) return undefined;\n const out: Record<string, string> = {};\n for (const name of allowList) {\n const value = headers.get(name);\n if (value !== null) out[name.toLowerCase()] = value;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nexport function headersFromInit(headers: HeadersInit | undefined): Headers | undefined {\n if (!headers) return undefined;\n try {\n return new Headers(headers);\n } catch {\n return undefined;\n }\n}\n\nexport function shouldSample(sampleRate: number, random: () => number): boolean {\n if (sampleRate >= 1) return true;\n if (sampleRate <= 0) return false;\n return random() < sampleRate;\n}\n\nfunction stripSensitiveUrlParts(rawUrl: string): string {\n if (!/^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(rawUrl)) {\n return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;\n }\n\n try {\n const url = new URL(rawUrl);\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n } catch {\n return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;\n }\n}\n","import {\n normalizeValue,\n type CaptureInput,\n type Integration,\n type IntegrationSetupContext,\n type UnpatchedFunction,\n} from \"@loggerjs/core\";\nimport {\n durationMs,\n headersFromInit,\n nowMs,\n pickAllowedHeaders,\n sanitizeHttpUrl,\n shouldSample,\n} from \"./http-capture-utils\";\n\nexport interface CaptureFetchOptions {\n minStatus?: number;\n captureRequestHeaders?: readonly string[];\n captureResponseHeaders?: readonly string[];\n captureAll?: boolean;\n captureSuccessful?: boolean;\n sampleRate?: number;\n random?: () => number;\n sanitizeUrl?: (url: string) => string;\n}\n\ntype BrowserFetch = typeof fetch;\n\nfunction requestUrl(input: RequestInfo | URL): string {\n if (typeof input === \"string\") return input;\n if (input instanceof URL) return input.toString();\n return input.url;\n}\n\nfunction requestMethod(input: RequestInfo | URL, init?: RequestInit): string {\n if (init?.method) return init.method.toUpperCase();\n if (typeof input === \"object\" && \"method\" in input && input.method)\n return input.method.toUpperCase();\n return \"GET\";\n}\n\nexport function captureFetchIntegration(options: CaptureFetchOptions = {}): Integration {\n const minStatus = options.minStatus ?? 400;\n const captureAll = options.captureAll ?? options.captureSuccessful ?? false;\n const sampleRate = options.sampleRate ?? 1;\n const random = options.random ?? Math.random;\n\n return {\n name: \"capture-fetch\",\n setup(api: IntegrationSetupContext) {\n const current = globalThis.fetch;\n if (!current) return;\n const original =\n (api.unpatched.fetch as BrowserFetch | undefined) ?? current.bind(globalThis);\n api.unpatched.fetch ??= original as UnpatchedFunction;\n\n const capture = api.guard((input: CaptureInput) => api.capture(input));\n\n globalThis.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const started = nowMs();\n const url = sanitizeHttpUrl(requestUrl(input), options.sanitizeUrl);\n const method = requestMethod(input, init);\n try {\n const response = await original(input, init);\n const shouldCapture =\n response.status >= minStatus || (captureAll && shouldSample(sampleRate, random));\n if (shouldCapture) {\n capture({\n level: response.status >= minStatus ? \"warn\" : \"debug\",\n message: `Fetch ${response.status} ${method} ${url}`,\n props: {\n http: {\n kind: \"fetch\",\n method,\n url,\n status: response.status,\n ok: response.ok,\n durationMs: durationMs(started),\n requestHeaders: pickAllowedHeaders(\n headersFromInit(init?.headers),\n options.captureRequestHeaders,\n ),\n responseHeaders: pickAllowedHeaders(\n response.headers,\n options.captureResponseHeaders,\n ),\n },\n },\n });\n }\n return response;\n } catch (error) {\n capture({\n level: \"error\",\n message: `Fetch network error ${method} ${url}`,\n error,\n props: {\n http: { kind: \"fetch\", method, url, durationMs: durationMs(started) },\n input: normalizeValue(input, { maxDepth: 3 }),\n },\n });\n throw error;\n }\n };\n\n return () => {\n globalThis.fetch = current;\n };\n },\n };\n}\n"],"mappings":"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"}