nuxt-error-tracker 0.1.15 → 0.1.17

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/module.d.mts CHANGED
@@ -5,6 +5,15 @@ interface ModuleOptions {
5
5
  enabled?: boolean;
6
6
  /** Wrap window.fetch to capture failed requests (network errors + 5xx). Default true. */
7
7
  captureFetch?: boolean;
8
+ /**
9
+ * On a failed request (HTTP >= 400 or a network error), attach full request/
10
+ * response detail (method, url, status, timing, headers, bodies) to the fetch
11
+ * breadcrumb's `data` for precise debugging. Sensitive headers (authorization/
12
+ * cookie/…) are redacted; bodies are truncated. Only failed requests carry
13
+ * detail — successful ones stay lightweight. Default true; set false to keep
14
+ * fetch breadcrumbs message-only (e.g. to avoid capturing request payloads).
15
+ */
16
+ captureFetchDetails?: boolean;
8
17
  /** Patch console.error to capture logged-but-not-thrown errors. Default true. */
9
18
  captureConsole?: boolean;
10
19
  /**
package/dist/module.d.ts CHANGED
@@ -5,6 +5,15 @@ interface ModuleOptions {
5
5
  enabled?: boolean;
6
6
  /** Wrap window.fetch to capture failed requests (network errors + 5xx). Default true. */
7
7
  captureFetch?: boolean;
8
+ /**
9
+ * On a failed request (HTTP >= 400 or a network error), attach full request/
10
+ * response detail (method, url, status, timing, headers, bodies) to the fetch
11
+ * breadcrumb's `data` for precise debugging. Sensitive headers (authorization/
12
+ * cookie/…) are redacted; bodies are truncated. Only failed requests carry
13
+ * detail — successful ones stay lightweight. Default true; set false to keep
14
+ * fetch breadcrumbs message-only (e.g. to avoid capturing request payloads).
15
+ */
16
+ captureFetchDetails?: boolean;
8
17
  /** Patch console.error to capture logged-but-not-thrown errors. Default true. */
9
18
  captureConsole?: boolean;
10
19
  /**
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-error-tracker",
3
3
  "configKey": "errorTracker",
4
- "version": "0.1.15",
4
+ "version": "0.1.17",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -15,6 +15,7 @@ const module = defineNuxtModule({
15
15
  appVersion: "unknown",
16
16
  enabled: true,
17
17
  captureFetch: true,
18
+ captureFetchDetails: true,
18
19
  captureConsole: true,
19
20
  captureInputs: false,
20
21
  flushDelay: 2e3,
@@ -30,6 +31,7 @@ const module = defineNuxtModule({
30
31
  publicKey: options.publicKey,
31
32
  appVersion: options.appVersion ?? "unknown",
32
33
  captureFetch: options.captureFetch !== false,
34
+ captureFetchDetails: options.captureFetchDetails !== false,
33
35
  captureConsole: options.captureConsole !== false,
34
36
  captureInputs: options.captureInputs === true,
35
37
  flushDelay: posInt(options.flushDelay, 2e3),
@@ -116,7 +116,16 @@ export default defineNuxtPlugin((nuxtApp) => {
116
116
  (event) => capture(event.reason, "promise")
117
117
  );
118
118
  const formatDataAttrs = (el) => {
119
- const dataset = el.dataset;
119
+ let node = el;
120
+ let dataset;
121
+ for (let depth = 0; node && depth < 5; depth++) {
122
+ const ds = node.dataset;
123
+ if (ds && Object.keys(ds).length > 0) {
124
+ dataset = ds;
125
+ break;
126
+ }
127
+ node = node.parentElement;
128
+ }
120
129
  if (!dataset) return "";
121
130
  return Object.entries(dataset).slice(0, 5).map(
122
131
  ([k, v]) => `data-${k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}=${(v ?? "").slice(0, 40)}`
@@ -190,18 +199,92 @@ export default defineNuxtPlugin((nuxtApp) => {
190
199
  });
191
200
  if (config.captureFetch && typeof window.fetch === "function") {
192
201
  const originalFetch = window.fetch.bind(window);
202
+ const REDACTED = "[redacted]";
203
+ const SENSITIVE_HEADER = /^(authorization|cookie|set-cookie|proxy-authorization|x-api-key|x-auth-token|x-csrf-token|x-xsrf-token)$/i;
204
+ const MAX_BODY = 2048;
205
+ const MAX_HEADER_VAL = 256;
206
+ const trunc = (s, max) => s.length > max ? `${s.slice(0, max)}\u2026[+${s.length - max}]` : s;
207
+ const headersToObj = (h) => {
208
+ const out = {};
209
+ if (!h) return out;
210
+ try {
211
+ const entries = h instanceof Headers ? [...h.entries()] : Array.isArray(h) ? h : Object.entries(h);
212
+ for (const [k, v] of entries) {
213
+ out[k] = SENSITIVE_HEADER.test(k) ? REDACTED : trunc(String(v), MAX_HEADER_VAL);
214
+ }
215
+ } catch {
216
+ }
217
+ return out;
218
+ };
219
+ const reqHeaders = (input, init) => {
220
+ const base = input instanceof Request ? headersToObj(input.headers) : {};
221
+ return { ...base, ...headersToObj(init?.headers) };
222
+ };
223
+ const readReqBody = async (input, init) => {
224
+ try {
225
+ const body = init?.body;
226
+ if (body != null) {
227
+ if (typeof body === "string") return trunc(body, MAX_BODY);
228
+ if (body instanceof URLSearchParams)
229
+ return trunc(body.toString(), MAX_BODY);
230
+ if (typeof FormData !== "undefined" && body instanceof FormData)
231
+ return "[FormData]";
232
+ if (typeof Blob !== "undefined" && body instanceof Blob)
233
+ return `[Blob ${body.size}b]`;
234
+ if (body instanceof ArrayBuffer)
235
+ return `[ArrayBuffer ${body.byteLength}b]`;
236
+ return "[stream]";
237
+ }
238
+ if (input instanceof Request) {
239
+ const t = await input.clone().text();
240
+ return t ? trunc(t, MAX_BODY) : void 0;
241
+ }
242
+ } catch {
243
+ }
244
+ return void 0;
245
+ };
246
+ const readResBody = async (res) => {
247
+ try {
248
+ const ct = res.headers.get("content-type") ?? "";
249
+ if (!/json|text|xml|form-urlencoded/i.test(ct))
250
+ return `[${ct || "binary"}]`;
251
+ const len = Number(res.headers.get("content-length") ?? 0);
252
+ if (len > MAX_BODY * 8) return `[too large ${len}b]`;
253
+ const t = await res.clone().text();
254
+ return t ? trunc(t, MAX_BODY) : void 0;
255
+ } catch {
256
+ }
257
+ return void 0;
258
+ };
193
259
  window.fetch = async (input, init) => {
194
260
  const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
195
261
  const reqUrl = input instanceof Request ? input.url : String(input);
196
262
  const isOwn = reqUrl.startsWith(config.endpoint);
263
+ const startedAt = Date.now();
197
264
  try {
198
265
  const res = await originalFetch(input, init);
199
266
  if (!isOwn) {
267
+ const failed = res.status >= 400;
268
+ let data;
269
+ if (failed && config.captureFetchDetails) {
270
+ data = {
271
+ method,
272
+ url: reqUrl,
273
+ status: res.status,
274
+ statusText: res.statusText,
275
+ durationMs: Date.now() - startedAt,
276
+ requestHeaders: reqHeaders(input, init),
277
+ requestBody: await readReqBody(input, init),
278
+ responseHeaders: headersToObj(res.headers),
279
+ responseBody: await readResBody(res)
280
+ };
281
+ }
200
282
  addCrumb({
201
283
  type: "fetch",
202
284
  category: "http",
203
- level: res.status >= 400 ? "error" : "info",
204
- message: `${method} ${reqUrl} \u2192 ${res.status}`
285
+ level: failed ? "error" : "info",
286
+ message: `${method} ${reqUrl} \u2192 ${res.status}`,
287
+ data
205
288
  });
206
289
  if (res.status >= 500) {
207
290
  capture(
@@ -218,7 +301,15 @@ export default defineNuxtPlugin((nuxtApp) => {
218
301
  type: "fetch",
219
302
  category: "http",
220
303
  level: "error",
221
- message: `${method} ${reqUrl} \u2192 failed: ${reason}`
304
+ message: `${method} ${reqUrl} \u2192 failed: ${reason}`,
305
+ data: config.captureFetchDetails ? {
306
+ method,
307
+ url: reqUrl,
308
+ error: reason,
309
+ durationMs: Date.now() - startedAt,
310
+ requestHeaders: reqHeaders(input, init),
311
+ requestBody: await readReqBody(input, init)
312
+ } : void 0
222
313
  });
223
314
  capture(
224
315
  new Error(`Fetch failed ${method} ${reqUrl}: ${reason}`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-error-tracker",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {