nuxt-error-tracker 0.1.16 → 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.16",
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),
@@ -199,18 +199,92 @@ export default defineNuxtPlugin((nuxtApp) => {
199
199
  });
200
200
  if (config.captureFetch && typeof window.fetch === "function") {
201
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
+ };
202
259
  window.fetch = async (input, init) => {
203
260
  const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
204
261
  const reqUrl = input instanceof Request ? input.url : String(input);
205
262
  const isOwn = reqUrl.startsWith(config.endpoint);
263
+ const startedAt = Date.now();
206
264
  try {
207
265
  const res = await originalFetch(input, init);
208
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
+ }
209
282
  addCrumb({
210
283
  type: "fetch",
211
284
  category: "http",
212
- level: res.status >= 400 ? "error" : "info",
213
- message: `${method} ${reqUrl} \u2192 ${res.status}`
285
+ level: failed ? "error" : "info",
286
+ message: `${method} ${reqUrl} \u2192 ${res.status}`,
287
+ data
214
288
  });
215
289
  if (res.status >= 500) {
216
290
  capture(
@@ -227,7 +301,15 @@ export default defineNuxtPlugin((nuxtApp) => {
227
301
  type: "fetch",
228
302
  category: "http",
229
303
  level: "error",
230
- 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
231
313
  });
232
314
  capture(
233
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.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {