nuxt-error-tracker 0.1.17 → 0.1.19
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/LICENSE +91 -0
- package/README.md +263 -64
- package/README.uk.md +388 -0
- package/dist/module.d.mts +32 -7
- package/dist/module.d.ts +32 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +14 -0
- package/dist/runtime/plugin.client.js +24 -2
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# PolyForm Shield License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/shield/1.0.0>
|
|
4
|
+
|
|
5
|
+
Required Notice: Copyright ray_of_goodness_
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncompete
|
|
34
|
+
|
|
35
|
+
Any purpose is a permitted purpose, except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.
|
|
36
|
+
|
|
37
|
+
## Competition
|
|
38
|
+
|
|
39
|
+
Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if they're written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge. If you market a product as a practical substitute for the software or another product, it definitely competes.
|
|
40
|
+
|
|
41
|
+
## New Products
|
|
42
|
+
|
|
43
|
+
If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
|
|
44
|
+
|
|
45
|
+
## Discontinued Products
|
|
46
|
+
|
|
47
|
+
You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plain-text line beginning with `Licensor Line of Business:` with the software that mentions that line of business. For example:
|
|
48
|
+
|
|
49
|
+
> Licensor Line of Business: YoyodyneCMS Content Management System (http://example.com/cms)
|
|
50
|
+
|
|
51
|
+
## Sales of Business
|
|
52
|
+
|
|
53
|
+
If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce [Noncompete](#noncompete) for that product.
|
|
54
|
+
|
|
55
|
+
## Fair Use
|
|
56
|
+
|
|
57
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
58
|
+
|
|
59
|
+
## No Other Rights
|
|
60
|
+
|
|
61
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
62
|
+
|
|
63
|
+
## Patent Defense
|
|
64
|
+
|
|
65
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
66
|
+
|
|
67
|
+
## Violations
|
|
68
|
+
|
|
69
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
70
|
+
|
|
71
|
+
## No Liability
|
|
72
|
+
|
|
73
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
74
|
+
|
|
75
|
+
## Definitions
|
|
76
|
+
|
|
77
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
78
|
+
|
|
79
|
+
A **product** can be a good or service, or a combination of them.
|
|
80
|
+
|
|
81
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
82
|
+
|
|
83
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all its affiliates.
|
|
84
|
+
|
|
85
|
+
**Affiliates** means the other organizations than an organization has control over, is under the control of, or is under common control with.
|
|
86
|
+
|
|
87
|
+
**Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
88
|
+
|
|
89
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
90
|
+
|
|
91
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
package/README.md
CHANGED
|
@@ -1,20 +1,67 @@
|
|
|
1
1
|
# nuxt-error-tracker
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
errors, failed requests, and a breadcrumb trail of user actions, then ships them
|
|
5
|
-
to your Observer ingest endpoint. Client-only, batched, and built to **never
|
|
6
|
-
break the host app** — if it isn't configured, it does nothing.
|
|
7
|
-
|
|
8
|
-
## Features
|
|
3
|
+
**English** · [Українська](./README.uk.md)
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
Lightweight frontend error tracker for **Nuxt 3 / 4**. It captures unhandled
|
|
6
|
+
errors, failed requests, and a breadcrumb trail of user actions, then ships them
|
|
7
|
+
to **your own** Observer ingest endpoint. Client-only, batched, and built to
|
|
8
|
+
**never break the host app** — if it isn't configured, it does nothing.
|
|
9
|
+
|
|
10
|
+
You bring the URL and a public key; the package does the capturing. Everything
|
|
11
|
+
you send lands under your project in your Observer dashboard, where you view,
|
|
12
|
+
search and manage the logs yourself.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Table of contents
|
|
17
|
+
|
|
18
|
+
- [How it works (the moving parts)](#how-it-works-the-moving-parts)
|
|
19
|
+
- [Install](#install)
|
|
20
|
+
- [Getting your endpoint and key](#getting-your-endpoint-and-key)
|
|
21
|
+
- [Setup](#setup)
|
|
22
|
+
- [Configuration via environment variables](#configuration-via-environment-variables)
|
|
23
|
+
- [Options](#options)
|
|
24
|
+
- [Runtime helpers](#runtime-helpers)
|
|
25
|
+
- [What gets sent to the API](#what-gets-sent-to-the-api)
|
|
26
|
+
- [Wire format & data types](#wire-format--data-types)
|
|
27
|
+
- [Viewing and controlling your logs](#viewing-and-controlling-your-logs)
|
|
28
|
+
- [Privacy](#privacy)
|
|
29
|
+
- [License](#license)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## How it works (the moving parts)
|
|
34
|
+
|
|
35
|
+
There are three pieces. This package is only the first one.
|
|
36
|
+
|
|
37
|
+
| Piece | What it is | Responsibility |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| **This package** (`nuxt-error-tracker`) | A Nuxt module you install in your app | Hooks into the browser, captures errors + breadcrumbs, batches them, and POSTs to your endpoint. Runs **client-side only**. |
|
|
40
|
+
| **Your ingest endpoint** (`.../v1/ingest`) | The Observer API you host (or are given access to) | Authenticates the request by public key + origin, validates and rate-limits it, deduplicates errors by fingerprint, and stores them. |
|
|
41
|
+
| **Your dashboard** | The Observer console UI | Where **you** browse, search, and manage the logs — stack traces, occurrences, breadcrumb trails, retention. |
|
|
42
|
+
|
|
43
|
+
The package never decides *where* the data goes — **you do**, by pointing
|
|
44
|
+
`endpoint` at your host. It ships no default URL. Swap the host and your logs
|
|
45
|
+
follow. This is what makes it "bring your own backend": the same package works
|
|
46
|
+
against localhost during development and your production Observer instance in
|
|
47
|
+
release, just by changing the endpoint.
|
|
48
|
+
|
|
49
|
+
Inside the package itself:
|
|
50
|
+
|
|
51
|
+
- **`module.ts`** — the Nuxt module. Reads your options, and **only registers
|
|
52
|
+
the capture plugin if `enabled` + `endpoint` + `publicKey` are all present**.
|
|
53
|
+
Missing any of them → the plugin is never added and your app runs untouched.
|
|
54
|
+
It also normalizes options (clamps `maxBatch ≤ 20`, `maxBreadcrumbs ≤ 50` to
|
|
55
|
+
match server limits) and injects TypeScript types for the helpers.
|
|
56
|
+
- **`plugin.client.ts`** — the actual capture engine (browser only). Listens for
|
|
57
|
+
errors, wraps `fetch`, patches `console.error`, records breadcrumbs, and
|
|
58
|
+
manages the send queue.
|
|
59
|
+
- **`plugin.server.ts`** — a tiny SSR stub. Provides no-op `$trackError` /
|
|
60
|
+
`$addBreadcrumb` so calling them during server-side rendering never throws.
|
|
61
|
+
- **`ingest.codec.ts`** — a hand-rolled protobuf encoder (no dependencies) that
|
|
62
|
+
turns a batch into the binary wire format the API expects.
|
|
63
|
+
|
|
64
|
+
---
|
|
18
65
|
|
|
19
66
|
## Install
|
|
20
67
|
|
|
@@ -23,31 +70,47 @@ npm i nuxt-error-tracker
|
|
|
23
70
|
# or: bun add nuxt-error-tracker
|
|
24
71
|
```
|
|
25
72
|
|
|
26
|
-
|
|
73
|
+
---
|
|
27
74
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
75
|
+
## Getting your endpoint and key
|
|
76
|
+
|
|
77
|
+
The module needs exactly two things to start reporting: an **ingest endpoint**
|
|
78
|
+
(your URL) and a **public key** (which project the errors belong to). Both come
|
|
79
|
+
from your Observer dashboard.
|
|
31
80
|
|
|
32
81
|
1. Open the dashboard at `https://<your-observer-host>/dashboard`.
|
|
33
|
-
2. **Create account** — enter an email and a password (min 8 chars), submit.
|
|
34
|
-
stay signed in via a secure cookie.
|
|
82
|
+
2. **Create an account** — enter an email and a password (min 8 chars), submit.
|
|
83
|
+
You stay signed in via a secure httpOnly cookie.
|
|
35
84
|
3. Click **New project**, give it a name (e.g. `my-app`), and optionally list the
|
|
36
|
-
origins your site runs on (comma-separated, e.g.
|
|
37
|
-
http://localhost:3000`). Empty = accept any origin. This
|
|
38
|
-
allowlist — requests from other origins are rejected.
|
|
39
|
-
4. The project now shows a **public key** (`pk_…`) and a ready-made
|
|
40
|
-
|
|
85
|
+
origins your site runs on (comma-separated, e.g.
|
|
86
|
+
`https://my-app.com, http://localhost:3000`). Empty = accept any origin. This
|
|
87
|
+
is the **server-side allowlist** — requests from other origins are rejected.
|
|
88
|
+
4. The project now shows a **public key** (`pk_…`) and a ready-made `nuxt.config`
|
|
89
|
+
snippet. Use **Copy key** / **Copy config**.
|
|
41
90
|
5. The **endpoint** is your host + `/v1/ingest`, i.e.
|
|
42
91
|
`https://<your-observer-host>/v1/ingest`.
|
|
43
92
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
93
|
+
### What the two values actually do
|
|
94
|
+
|
|
95
|
+
- **`endpoint`** — *where* to send. This is your URL. Point it at whatever host
|
|
96
|
+
runs the Observer API. There is no hosted default; you are always sending to
|
|
97
|
+
your own backend.
|
|
98
|
+
- **`publicKey`** — *whose project* the errors are. On every request the API
|
|
99
|
+
looks up the project by this key (`X-Public-Key` header, or `?key=` query for
|
|
100
|
+
the beacon path), then enforces that project's origin allowlist and rate
|
|
101
|
+
limit. It is a **public** value — safe to ship in client bundles. It grants
|
|
102
|
+
*write-only* ingest for one project; it does **not** let anyone read your logs.
|
|
103
|
+
Reading requires your dashboard login.
|
|
104
|
+
|
|
105
|
+
> **Origins matter.** If you set an allowlist, it must include **every** origin
|
|
106
|
+
> your app is served from — production domains **and** `http://localhost:3000`
|
|
107
|
+
> for local dev — or the browser's requests get a `401`. Leave it empty while
|
|
108
|
+
> experimenting, tighten it for production.
|
|
47
109
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
110
|
+
Once configured (below) and deployed, captured errors appear under that project
|
|
111
|
+
in the dashboard. Open **Logs** on the project to browse them.
|
|
112
|
+
|
|
113
|
+
---
|
|
51
114
|
|
|
52
115
|
## Setup
|
|
53
116
|
|
|
@@ -65,16 +128,16 @@ export default defineNuxtConfig({
|
|
|
65
128
|
})
|
|
66
129
|
```
|
|
67
130
|
|
|
68
|
-
|
|
69
|
-
> value — safe to ship in client bundles. Origin allowlisting and rate limiting
|
|
70
|
-
> are enforced server-side.
|
|
131
|
+
That's the minimum. Everything else has sensible defaults.
|
|
71
132
|
|
|
72
133
|
### No keys? No problem
|
|
73
134
|
|
|
74
135
|
If `endpoint` or `publicKey` is empty (or `enabled: false`), the plugin is never
|
|
75
136
|
registered and your app runs untouched. This makes it safe to leave the module
|
|
76
137
|
installed while wiring keys per-environment — dev builds without a key simply
|
|
77
|
-
don't track.
|
|
138
|
+
don't track. There is no crash, no console noise, no network call.
|
|
139
|
+
|
|
140
|
+
---
|
|
78
141
|
|
|
79
142
|
## Configuration via environment variables
|
|
80
143
|
|
|
@@ -104,21 +167,32 @@ export default defineNuxtConfig({
|
|
|
104
167
|
})
|
|
105
168
|
```
|
|
106
169
|
|
|
170
|
+
This is the recommended way to point the same build at a **different endpoint**
|
|
171
|
+
per environment (staging vs. production) without touching code.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
107
175
|
## Options
|
|
108
176
|
|
|
109
|
-
| Option
|
|
110
|
-
|
|
|
111
|
-
| `endpoint`
|
|
112
|
-
| `publicKey`
|
|
113
|
-
| `appVersion`
|
|
114
|
-
| `enabled`
|
|
115
|
-
| `captureFetch`
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
177
|
+
| Option | Type | Default | Description |
|
|
178
|
+
| --------------------- | --------- | ----------- | ------------------------------------------------------------------ |
|
|
179
|
+
| `endpoint` | `string` | `''` | Ingest URL (`.../v1/ingest`). **Required** — your host. |
|
|
180
|
+
| `publicKey` | `string` | `''` | Your project's public key (`pk_…`). **Required**. |
|
|
181
|
+
| `appVersion` | `string` | `'unknown'` | Tags every batch with a release/version so you can filter by it. |
|
|
182
|
+
| `enabled` | `boolean` | `true` | Master switch. `false` = fully disabled, plugin not registered. |
|
|
183
|
+
| `captureFetch` | `boolean` | `true` | Wrap `fetch` to breadcrumb requests and report failures / 5xx. |
|
|
184
|
+
| `captureFetchDetails` | `boolean` | `true` | Attach full request/response detail (method, status, timing, headers, request payload, response body) to each fetch breadcrumb. Sensitive headers redacted, bodies truncated to 2 KB. Set `false` to keep fetch crumbs message-only (avoids capturing payloads / PII). |
|
|
185
|
+
| `captureConsole` | `boolean` | `true` | Patch `console.error` to capture logged-but-not-thrown errors. |
|
|
186
|
+
| `captureInputs` | `boolean` | `false` | Record form input values as breadcrumbs (on change/blur). Sensitive fields masked. **Opt-in** — captures user-entered data. |
|
|
187
|
+
| `flushDelay` | `number` | `2000` | Debounce (ms) before a queued batch is sent. |
|
|
188
|
+
| `minFlushInterval` | `number` | `0` | Floor (ms) between consecutive flushes — throttles request rate during an error storm. `0` = off. |
|
|
189
|
+
| `sampleRate` | `number` | `1` | Fraction (0..1) of captured errors actually sent; the rest are dropped at capture. `0.3` ≈ keep 30%. Thins volume before the server rate limit. |
|
|
190
|
+
| `maxPerMinute` | `number` | `0` | Client-side cap on errors queued per rolling minute (fixed window); excess dropped. Mirrors the server per-project rate limit. `0` = unlimited. |
|
|
191
|
+
| `maxPerSession` | `number` | `5` | Max occurrences of the *same* error captured per session. |
|
|
192
|
+
| `maxBatch` | `number` | `20` | Max errors per request (clamped to server hard cap of 20). |
|
|
193
|
+
| `maxBreadcrumbs` | `number` | `30` | Breadcrumbs kept in the ring buffer / sent per error (clamped to 50). |
|
|
194
|
+
|
|
195
|
+
---
|
|
122
196
|
|
|
123
197
|
## Runtime helpers
|
|
124
198
|
|
|
@@ -153,19 +227,141 @@ augmentation for `NuxtApp` and Vue's `ComponentCustomProperties`, so both
|
|
|
153
227
|
`useNuxtApp()` and template usage resolve them as functions (no `unknown` /
|
|
154
228
|
TS18046). No manual `.d.ts` needed.
|
|
155
229
|
|
|
156
|
-
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## What gets sent to the API
|
|
233
|
+
|
|
234
|
+
Nothing is sent until an error occurs. The package keeps an in-memory breadcrumb
|
|
235
|
+
ring buffer as the user interacts; when an error is captured, it snapshots that
|
|
236
|
+
trail, queues the error, and (after a debounce) POSTs a **batch**.
|
|
237
|
+
|
|
238
|
+
A batch has two parts: **`meta`** (sent once per batch, applies to all errors in
|
|
239
|
+
it) and **`errors`** (the array of captured errors).
|
|
240
|
+
|
|
241
|
+
### `meta` (once per batch)
|
|
242
|
+
|
|
243
|
+
| Field | Type | Source | Notes |
|
|
244
|
+
| ------------ | -------- | ------ | ----- |
|
|
245
|
+
| `device` | `object` | `navigator` / `window` | `userAgent`, `language`, `platform`, `screen` (`WxH`), `viewport` (`WxH`), `online` (bool), `memory` (`deviceMemory`, GB or `null`), `connection` (`effectiveType` or `null`). Server caps the JSON at 2 KB. |
|
|
246
|
+
| `userId` | `string` \| `null` | `user_id` cookie | Read from a cookie named `user_id` if present, else `null`. The package never invents an id. Max 100 chars. |
|
|
247
|
+
| `appVersion` | `string` | your `appVersion` option | Defaults to `'unknown'`. Max 50 chars. |
|
|
248
|
+
|
|
249
|
+
### `errors[]` (per captured error)
|
|
250
|
+
|
|
251
|
+
| Field | Type | Description | Limit |
|
|
252
|
+
| ------------- | --------- | ----------- | ----- |
|
|
253
|
+
| `message` | `string` | Error message. | ≤ 1000 chars |
|
|
254
|
+
| `stack` | `string` | Stack trace. | ≤ 8000 chars |
|
|
255
|
+
| `source` | `string` | Where it came from: `vue`, `window`, `promise`, `fetch`, or `console`. | ≤ 20 chars |
|
|
256
|
+
| `level` | `string` | Severity: `fatal` \| `error` \| `warning` \| `info` \| `debug`. | one of |
|
|
257
|
+
| `url` | `string` | `window.location.href` at capture time. | ≤ 2000 chars |
|
|
258
|
+
| `route` | `string` | Nuxt route `fullPath`. | ≤ 500 chars |
|
|
259
|
+
| `occurredAt` | `string` | ISO-8601 timestamp. | ISO-8601 |
|
|
260
|
+
| `breadcrumbs` | `Breadcrumb[]` | The trail leading up to the error (see below). | ≤ 50 items |
|
|
261
|
+
|
|
262
|
+
Each **`Breadcrumb`**:
|
|
263
|
+
|
|
264
|
+
| Field | Type | Description | Limit |
|
|
265
|
+
| ----------- | -------- | ----------- | ----- |
|
|
266
|
+
| `type` | `string` | `click`, `input`, `navigation`, `fetch`, `console`, or your custom type. | ≤ 30 chars |
|
|
267
|
+
| `category` | `string` | e.g. `ui.click`, `ui.input`, `navigation`, `http`, `console`. | ≤ 60 chars |
|
|
268
|
+
| `message` | `string` | Human-readable line, e.g. `POST /api/pay → 500`. | ≤ 500 chars |
|
|
269
|
+
| `level` | `string` | `info` \| `error` \| … | ≤ 20 chars |
|
|
270
|
+
| `timestamp` | `string` | ISO-8601. | ISO-8601 |
|
|
271
|
+
| `data` | `object` | Optional structured payload (fetch details: method, status, timing, headers, request/response bodies). | JSON |
|
|
272
|
+
|
|
273
|
+
**Batch caps:** at most **20 errors per request**; the client also re-queues
|
|
274
|
+
failed batches (bounded to 200 in memory) and dedups the *same* error to
|
|
275
|
+
`maxPerSession` (5) occurrences per session, so one recurring error can't flood
|
|
276
|
+
your quota.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Wire format & data types
|
|
281
|
+
|
|
282
|
+
Payloads are encoded as **protobuf** (proto3) and POSTed as
|
|
283
|
+
`application/x-protobuf`. The encoder is hand-rolled and has **zero
|
|
284
|
+
dependencies** — no `protobufjs`, so consumers need no Vite `optimizeDeps` or
|
|
285
|
+
CJS-interop config.
|
|
286
|
+
|
|
287
|
+
The schema (field numbers are load-bearing — they must match the API decoder):
|
|
288
|
+
|
|
289
|
+
```proto
|
|
290
|
+
message Breadcrumb {
|
|
291
|
+
string type = 1;
|
|
292
|
+
string category = 2;
|
|
293
|
+
string message = 3;
|
|
294
|
+
string level = 4;
|
|
295
|
+
string timestamp = 5;
|
|
296
|
+
string data = 6; // JSON-stringified object
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
message Error {
|
|
300
|
+
string message = 1;
|
|
301
|
+
string stack = 2;
|
|
302
|
+
string source = 3;
|
|
303
|
+
string url = 4;
|
|
304
|
+
string route = 5;
|
|
305
|
+
string userId = 6; // per-error override (unused by this client)
|
|
306
|
+
string appVersion = 7; // per-error override (unused by this client)
|
|
307
|
+
string device = 8; // per-error override (unused by this client)
|
|
308
|
+
string occurredAt = 9;
|
|
309
|
+
repeated Breadcrumb breadcrumbs = 10;
|
|
310
|
+
string level = 11;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
message Meta { string device = 1; string userId = 2; string appVersion = 3; }
|
|
314
|
+
message Batch { Meta meta = 1; repeated Error errors = 2; }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`device` and breadcrumb `data` are objects, so they travel as
|
|
318
|
+
**JSON-stringified strings** inside the protobuf. Empty strings are omitted
|
|
319
|
+
(proto3 default). The binary framing just keeps the network panel tidy — it is
|
|
320
|
+
**not** encryption; the schema is public, so anyone with it can decode a payload.
|
|
321
|
+
|
|
322
|
+
### How it's transmitted
|
|
323
|
+
|
|
324
|
+
- Normal path: `fetch(endpoint, { method: 'POST', headers: { 'Content-Type':
|
|
325
|
+
'application/x-protobuf', 'X-Public-Key': <key> }, body: <bytes>, keepalive:
|
|
326
|
+
true })`.
|
|
327
|
+
- Page-hide path: the queue is force-flushed via `navigator.sendBeacon(endpoint
|
|
328
|
+
+ '?key=<key>', Blob)` — because a beacon can't set headers, the key rides in
|
|
329
|
+
the `?key=` query param. Falls back to `fetch(..., { keepalive: true })` if
|
|
330
|
+
`sendBeacon` is unavailable or refuses.
|
|
331
|
+
|
|
332
|
+
The API also accepts plain JSON at the same endpoint (same validation), which is
|
|
333
|
+
handy for scripted test posts, but the SDK always sends protobuf.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Viewing and controlling your logs
|
|
338
|
+
|
|
339
|
+
The point of bringing your own endpoint: after the package sends, **you** own the
|
|
340
|
+
data and control it entirely from your dashboard — no third-party console.
|
|
341
|
+
|
|
342
|
+
- **Browse** — open **Logs** on the project to see error groups, each with its
|
|
343
|
+
message, count, first/last seen, affected routes, and per-occurrence stack
|
|
344
|
+
trace + breadcrumb trail (including captured fetch payloads/responses when
|
|
345
|
+
`captureFetchDetails` is on).
|
|
346
|
+
- **Deduplication** — the API groups occurrences by a **fingerprint** (a hash of
|
|
347
|
+
the normalized message + top stack frames), so a recurring error is one row
|
|
348
|
+
with a count, not thousands of duplicates.
|
|
349
|
+
- **Rate limit & origins** — each project has its own per-minute rate limit and
|
|
350
|
+
origin allowlist. Tighten origins to reject stray traffic; the key alone is not
|
|
351
|
+
enough to get data in from an unlisted origin.
|
|
352
|
+
- **Retention** — occurrences are purged after a retention window (7 days by
|
|
353
|
+
default on the server), so storage stays bounded without any action from you.
|
|
354
|
+
|
|
355
|
+
Because the key is write-only and reading is gated by your login, shipping the
|
|
356
|
+
key in your client bundle exposes no stored data.
|
|
157
357
|
|
|
158
|
-
|
|
159
|
-
`fetch` | `console`), `url`, `route`, timestamp, and the breadcrumb trail. Once
|
|
160
|
-
per batch: `device` info (user agent, language, screen/viewport, connection),
|
|
161
|
-
`userId` (read from a `user_id` cookie if present), and `appVersion`. Field
|
|
162
|
-
lengths are truncated client-side to match server limits.
|
|
358
|
+
---
|
|
163
359
|
|
|
164
|
-
|
|
360
|
+
## Privacy
|
|
165
361
|
|
|
166
|
-
Click breadcrumbs record the element (tag / id / class / short text
|
|
167
|
-
input values**. Nothing is captured until an error occurs;
|
|
168
|
-
in memory and ship attached to an error.
|
|
362
|
+
Click breadcrumbs record the element (tag / id / class / short text / `data-*`
|
|
363
|
+
attributes) — **never input values**. Nothing is captured until an error occurs;
|
|
364
|
+
breadcrumbs live only in memory and ship attached to an error.
|
|
169
365
|
|
|
170
366
|
Input values are **only** captured when you opt in with `captureInputs: true`.
|
|
171
367
|
Even then, sensitive fields are masked (stored as `[masked, N chars]`, never in
|
|
@@ -173,13 +369,16 @@ the clear): any `type="password"`, plus fields whose `name` / `id` /
|
|
|
173
369
|
`autocomplete` match `pass`, `secret`, `token`, `otp`, `cvv`, `card`, `iban`,
|
|
174
370
|
`ssn`, `pin`, `auth`, etc. Non-sensitive values are truncated to 100 chars.
|
|
175
371
|
|
|
176
|
-
|
|
372
|
+
Fetch detail capture (`captureFetchDetails`, on by default) redacts sensitive
|
|
373
|
+
request/response headers (`authorization`, `cookie`, `x-api-key`, CSRF tokens, …)
|
|
374
|
+
and truncates bodies to 2 KB. Set it to `false` if you don't want request
|
|
375
|
+
payloads or response bodies leaving the browser at all.
|
|
177
376
|
|
|
178
|
-
|
|
179
|
-
page-hide the queue is force-flushed via `navigator.sendBeacon`, falling back to
|
|
180
|
-
`fetch(..., { keepalive: true })`. Binary framing keeps the network panel tidy —
|
|
181
|
-
it is **not** encryption; the schema is public.
|
|
377
|
+
---
|
|
182
378
|
|
|
183
379
|
## License
|
|
184
380
|
|
|
185
|
-
|
|
381
|
+
[PolyForm Shield 1.0.0](./LICENSE) — source-available. Free to install and use
|
|
382
|
+
for any purpose **except** building a product that competes with this software or
|
|
383
|
+
with what the licensor offers using it. You may not fork it into a rival. Full
|
|
384
|
+
terms in [`LICENSE`](./LICENSE); copyright remains with the author.
|
package/README.uk.md
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# nuxt-error-tracker
|
|
2
|
+
|
|
3
|
+
[English](./README.md) · **Українська**
|
|
4
|
+
|
|
5
|
+
Легкий трекер frontend-помилок для **Nuxt 3 / 4**. Перехоплює неопрацьовані
|
|
6
|
+
помилки, невдалі запити та ланцюжок дій користувача (breadcrumbs), після чого
|
|
7
|
+
відправляє їх на **ваш власний** Observer ingest-endpoint. Працює лише на
|
|
8
|
+
клієнті, батчами, і збудований так, щоб **ніколи не ламати застосунок-хост** —
|
|
9
|
+
якщо його не налаштовано, він не робить нічого.
|
|
10
|
+
|
|
11
|
+
Ви даєте URL і публічний ключ; пакет робить перехоплення. Усе відправлене
|
|
12
|
+
потрапляє під ваш проєкт у вашому Observer-дашборді, де ви самі переглядаєте,
|
|
13
|
+
шукаєте та керуєте логами.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Зміст
|
|
18
|
+
|
|
19
|
+
- [Як це працює (складові)](#як-це-працює-складові)
|
|
20
|
+
- [Встановлення](#встановлення)
|
|
21
|
+
- [Отримати endpoint і ключ](#отримати-endpoint-і-ключ)
|
|
22
|
+
- [Налаштування](#налаштування)
|
|
23
|
+
- [Конфігурація через змінні середовища](#конфігурація-через-змінні-середовища)
|
|
24
|
+
- [Опції](#опції)
|
|
25
|
+
- [Runtime-хелпери](#runtime-хелпери)
|
|
26
|
+
- [Що відправляється на API](#що-відправляється-на-api)
|
|
27
|
+
- [Формат передачі та типи даних](#формат-передачі-та-типи-даних)
|
|
28
|
+
- [Перегляд і контроль ваших логів](#перегляд-і-контроль-ваших-логів)
|
|
29
|
+
- [Приватність](#приватність)
|
|
30
|
+
- [Ліцензія](#ліцензія)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Як це працює (складові)
|
|
35
|
+
|
|
36
|
+
Складових три. Цей пакет — лише перша.
|
|
37
|
+
|
|
38
|
+
| Складова | Що це | За що відповідає |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| **Цей пакет** (`nuxt-error-tracker`) | Nuxt-модуль, який ви ставите у застосунок | Підключається до браузера, перехоплює помилки + breadcrumbs, батчить їх і POSTить на ваш endpoint. Працює **лише на клієнті**. |
|
|
41
|
+
| **Ваш ingest-endpoint** (`.../v1/ingest`) | Observer API, який ви хостите (або маєте доступ) | Автентифікує запит за публічним ключем + origin, валідує та rate-лімітує, дедуплікує помилки за fingerprint і зберігає. |
|
|
42
|
+
| **Ваш дашборд** | Консоль Observer | Де **ви** переглядаєте, шукаєте та керуєте логами — стек-трейси, occurrences, breadcrumb-ланцюжки, retention. |
|
|
43
|
+
|
|
44
|
+
Пакет ніколи не вирішує, *куди* йдуть дані — **вирішуєте ви**, вказавши
|
|
45
|
+
`endpoint` на свій хост. Дефолтного URL немає. Змініть хост — логи підуть за
|
|
46
|
+
ним. Це і є «bring your own backend»: той самий пакет працює проти localhost під
|
|
47
|
+
час розробки та проти вашого прод-Observer у релізі, лише зміною endpoint.
|
|
48
|
+
|
|
49
|
+
Усередині самого пакета:
|
|
50
|
+
|
|
51
|
+
- **`module.ts`** — Nuxt-модуль. Читає ваші опції та **реєструє плагін
|
|
52
|
+
перехоплення лише коли `enabled` + `endpoint` + `publicKey` присутні всі**.
|
|
53
|
+
Немає будь-чого → плагін не додається, застосунок працює недоторканим. Також
|
|
54
|
+
нормалізує опції (обрізає `maxBatch ≤ 20`, `maxBreadcrumbs ≤ 50` під серверні
|
|
55
|
+
ліміти) і додає TypeScript-типи для хелперів.
|
|
56
|
+
- **`plugin.client.ts`** — власне рушій перехоплення (лише браузер). Слухає
|
|
57
|
+
помилки, обгортає `fetch`, патчить `console.error`, пише breadcrumbs і керує
|
|
58
|
+
чергою відправки.
|
|
59
|
+
- **`plugin.server.ts`** — маленька SSR-заглушка. Дає no-op `$trackError` /
|
|
60
|
+
`$addBreadcrumb`, щоб виклик під час серверного рендеру ніколи не кидав
|
|
61
|
+
помилку.
|
|
62
|
+
- **`ingest.codec.ts`** — саморобний protobuf-енкодер (без залежностей), що
|
|
63
|
+
перетворює батч на бінарний формат, який очікує API.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Встановлення
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm i nuxt-error-tracker
|
|
71
|
+
# або: bun add nuxt-error-tracker
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Отримати endpoint і ключ
|
|
77
|
+
|
|
78
|
+
Модулю потрібні рівно дві речі, щоб почати звітувати: **ingest-endpoint** (ваш
|
|
79
|
+
URL) і **публічний ключ** (якому проєкту належать помилки). Обидва беруться з
|
|
80
|
+
вашого Observer-дашборда.
|
|
81
|
+
|
|
82
|
+
1. Відкрийте дашборд за адресою `https://<ваш-observer-хост>/dashboard`.
|
|
83
|
+
2. **Створіть акаунт** — введіть email і пароль (мін. 8 символів), надішліть.
|
|
84
|
+
Ви лишаєтесь залогіненим через захищений httpOnly-cookie.
|
|
85
|
+
3. Натисніть **New project**, дайте назву (напр. `my-app`) і за бажанням
|
|
86
|
+
перелічіть origins, на яких працює ваш сайт (через кому, напр.
|
|
87
|
+
`https://my-app.com, http://localhost:3000`). Порожньо = приймати будь-який
|
|
88
|
+
origin. Це **серверний allowlist** — запити з інших origins відхиляються.
|
|
89
|
+
4. Проєкт тепер показує **публічний ключ** (`pk_…`) і готовий сніпет
|
|
90
|
+
`nuxt.config`. Використайте **Copy key** / **Copy config**.
|
|
91
|
+
5. **Endpoint** — це ваш хост + `/v1/ingest`, тобто
|
|
92
|
+
`https://<ваш-observer-хост>/v1/ingest`.
|
|
93
|
+
|
|
94
|
+
### Що насправді роблять ці два значення
|
|
95
|
+
|
|
96
|
+
- **`endpoint`** — *куди* відправляти. Це ваш URL. Вказуйте на будь-який хост, де
|
|
97
|
+
крутиться Observer API. Хостованого дефолту немає; ви завжди шлете на свій
|
|
98
|
+
бекенд.
|
|
99
|
+
- **`publicKey`** — *чий проєкт* помилки. На кожному запиті API шукає проєкт за
|
|
100
|
+
цим ключем (заголовок `X-Public-Key` або query `?key=` для beacon-шляху), потім
|
|
101
|
+
застосовує allowlist origins і rate-ліміт цього проєкту. Це **публічне**
|
|
102
|
+
значення — безпечно класти у клієнтські бандли. Воно дає *лише запис* (ingest)
|
|
103
|
+
в один проєкт; воно **не** дає нікому читати ваші логи. Читання вимагає логіну в
|
|
104
|
+
дашборд.
|
|
105
|
+
|
|
106
|
+
> **Origins важливі.** Якщо ви задали allowlist, він мусить містити **кожен**
|
|
107
|
+
> origin, з якого віддається застосунок — прод-домени **та** `http://localhost:3000`
|
|
108
|
+
> для локальної розробки — інакше запити браузера отримають `401`. Лишіть порожнім
|
|
109
|
+
> під час експериментів, звузьте для продакшену.
|
|
110
|
+
|
|
111
|
+
Після налаштування (нижче) і деплою перехоплені помилки з'являються під цим
|
|
112
|
+
проєктом у дашборді. Відкрийте **Logs** на проєкті, щоб їх переглянути.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Налаштування
|
|
117
|
+
|
|
118
|
+
Додайте модуль і вкажіть йому ingest-endpoint з публічним ключем вашого проєкту:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// nuxt.config.ts
|
|
122
|
+
export default defineNuxtConfig({
|
|
123
|
+
modules: ['nuxt-error-tracker'],
|
|
124
|
+
errorTracker: {
|
|
125
|
+
endpoint: 'https://<ваш-observer-хост>/v1/ingest',
|
|
126
|
+
publicKey: 'pk_xxxxxxxxxxxxxxxxxxxxxxxx',
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Це мінімум. Усе решта має розумні дефолти.
|
|
132
|
+
|
|
133
|
+
### Немає ключів? Не проблема
|
|
134
|
+
|
|
135
|
+
Якщо `endpoint` чи `publicKey` порожні (або `enabled: false`), плагін не
|
|
136
|
+
реєструється, і застосунок працює недоторканим. Тому модуль можна лишати
|
|
137
|
+
встановленим, підключаючи ключі по-середовищах — dev-збірки без ключа просто не
|
|
138
|
+
трекають. Ні падіння, ні шуму в консолі, ні мережевого виклику.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Конфігурація через змінні середовища
|
|
143
|
+
|
|
144
|
+
Кожну опцію можна задати зі змінних середовища замість хардкоду, за конвенцією
|
|
145
|
+
публічних env у Nuxt `runtimeConfig`:
|
|
146
|
+
|
|
147
|
+
```ini
|
|
148
|
+
NUXT_PUBLIC_ERROR_TRACKER_ENDPOINT=https://<ваш-observer-хост>/v1/ingest
|
|
149
|
+
NUXT_PUBLIC_ERROR_TRACKER_PUBLIC_KEY=pk_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
150
|
+
NUXT_PUBLIC_ERROR_TRACKER_APP_VERSION=1.4.2
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Підключіть їх у `nuxt.config.ts`:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
export default defineNuxtConfig({
|
|
157
|
+
modules: ['nuxt-error-tracker'],
|
|
158
|
+
runtimeConfig: {
|
|
159
|
+
public: {
|
|
160
|
+
errorTracker: {
|
|
161
|
+
endpoint: process.env.NUXT_PUBLIC_ERROR_TRACKER_ENDPOINT,
|
|
162
|
+
publicKey: process.env.NUXT_PUBLIC_ERROR_TRACKER_PUBLIC_KEY,
|
|
163
|
+
appVersion: process.env.NUXT_PUBLIC_ERROR_TRACKER_APP_VERSION,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Це рекомендований спосіб націлити ту саму збірку на **різний endpoint** за
|
|
171
|
+
середовищем (staging vs. production) без правки коду.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Опції
|
|
176
|
+
|
|
177
|
+
| Опція | Тип | Дефолт | Опис |
|
|
178
|
+
| --------------------- | --------- | ----------- | ------------------------------------------------------------------ |
|
|
179
|
+
| `endpoint` | `string` | `''` | Ingest URL (`.../v1/ingest`). **Обовʼязково** — ваш хост. |
|
|
180
|
+
| `publicKey` | `string` | `''` | Публічний ключ вашого проєкту (`pk_…`). **Обовʼязково**. |
|
|
181
|
+
| `appVersion` | `string` | `'unknown'` | Тегує кожен батч релізом/версією для фільтрації. |
|
|
182
|
+
| `enabled` | `boolean` | `true` | Головний вимикач. `false` = повністю вимкнено, плагін не реєструється. |
|
|
183
|
+
| `captureFetch` | `boolean` | `true` | Обгортає `fetch`, щоб breadcrumbʼити запити і звітувати про збої / 5xx. |
|
|
184
|
+
| `captureFetchDetails` | `boolean` | `true` | Додає повну деталь запиту/відповіді (метод, статус, тайминг, заголовки, тіло запиту, тіло відповіді) до кожного fetch-breadcrumb. Чутливі заголовки редагуються, тіла обрізаються до 2 КБ. `false` — лишити fetch-крихти лише-повідомленням (не захоплювати payload / PII). |
|
|
185
|
+
| `captureConsole` | `boolean` | `true` | Патчить `console.error`, щоб ловити залоговані-але-не-кинуті помилки. |
|
|
186
|
+
| `captureInputs` | `boolean` | `false` | Записує значення полів форм як breadcrumbs (на change/blur). Чутливі поля маскуються. **Opt-in** — захоплює введені користувачем дані. |
|
|
187
|
+
| `flushDelay` | `number` | `2000` | Debounce (мс) перед відправкою черги-батча. |
|
|
188
|
+
| `minFlushInterval` | `number` | `0` | Мін. пауза (мс) між флашами — throttle частоти запитів під час шторму помилок. `0` = вимкнено. |
|
|
189
|
+
| `sampleRate` | `number` | `1` | Частка (0..1) захоплених помилок, що реально шлються; решта дропається при capture. `0.3` ≈ 30%. Ріже обʼєм до серверного rate limit. |
|
|
190
|
+
| `maxPerMinute` | `number` | `0` | Клієнтський ліміт помилок у чергу за rolling-хвилину (fixed window); понад — дроп. Дзеркалить серверний per-project rate limit. `0` = без ліміту. |
|
|
191
|
+
| `maxPerSession` | `number` | `5` | Макс. кількість occurrences *тієї самої* помилки за сесію. |
|
|
192
|
+
| `maxBatch` | `number` | `20` | Макс. помилок на запит (обрізається серверним лімітом 20). |
|
|
193
|
+
| `maxBreadcrumbs` | `number` | `30` | Breadcrumbs у ring buffer / на помилку (обрізається до 50). |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Runtime-хелпери
|
|
198
|
+
|
|
199
|
+
Плагін інʼєктить два хелпери для ручного використання:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const { $trackError, $addBreadcrumb } = useNuxtApp()
|
|
203
|
+
|
|
204
|
+
// Звітувати про опрацьовану помилку самому — опційний рівень серйозності
|
|
205
|
+
// ('fatal' | 'error' | 'warning' | 'info' | 'debug', дефолт 'error')
|
|
206
|
+
try {
|
|
207
|
+
await mayFail()
|
|
208
|
+
} catch (e) {
|
|
209
|
+
$trackError(e, 'warning')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Додати власний breadcrumb у ланцюжок
|
|
213
|
+
$addBreadcrumb({
|
|
214
|
+
type: 'custom',
|
|
215
|
+
category: 'checkout',
|
|
216
|
+
message: 'user clicked pay',
|
|
217
|
+
level: 'info',
|
|
218
|
+
})
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Обидва хелпери завжди викликаються. Реальне перехоплення — **лише на клієнті**; на
|
|
222
|
+
сервері (SSR — напр. верхньорівневий `<script setup>`) це безпечні no-opʼи, тож
|
|
223
|
+
виклик ніколи не кидає помилку.
|
|
224
|
+
|
|
225
|
+
`$trackError` і `$addBreadcrumb` **повністю типізовані** — модуль постачає
|
|
226
|
+
augmentation для `NuxtApp` і Vue `ComponentCustomProperties`, тож і
|
|
227
|
+
`useNuxtApp()`, і використання в шаблоні бачать їх як функції (без `unknown` /
|
|
228
|
+
TS18046). Ручний `.d.ts` не потрібен.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Що відправляється на API
|
|
233
|
+
|
|
234
|
+
Нічого не відправляється, доки не станеться помилка. Пакет тримає в памʼяті
|
|
235
|
+
ring buffer breadcrumbs, поки користувач взаємодіє; при перехопленні помилки він
|
|
236
|
+
знімає снапшот цього ланцюжка, ставить помилку в чергу і (після debounce) POSTить
|
|
237
|
+
**батч**.
|
|
238
|
+
|
|
239
|
+
Батч має дві частини: **`meta`** (раз на батч, застосовується до всіх помилок у
|
|
240
|
+
ньому) і **`errors`** (масив перехоплених помилок).
|
|
241
|
+
|
|
242
|
+
### `meta` (раз на батч)
|
|
243
|
+
|
|
244
|
+
| Поле | Тип | Джерело | Нотатки |
|
|
245
|
+
| ------------ | -------- | ------- | ------- |
|
|
246
|
+
| `device` | `object` | `navigator` / `window` | `userAgent`, `language`, `platform`, `screen` (`ШxВ`), `viewport` (`ШxВ`), `online` (bool), `memory` (`deviceMemory`, ГБ або `null`), `connection` (`effectiveType` або `null`). Сервер обрізає JSON до 2 КБ. |
|
|
247
|
+
| `userId` | `string` \| `null` | cookie `user_id` | Читається з cookie `user_id`, якщо є, інакше `null`. Пакет ніколи не вигадує id. Макс. 100 символів. |
|
|
248
|
+
| `appVersion` | `string` | ваша опція `appVersion` | Дефолт `'unknown'`. Макс. 50 символів. |
|
|
249
|
+
|
|
250
|
+
### `errors[]` (на кожну перехоплену помилку)
|
|
251
|
+
|
|
252
|
+
| Поле | Тип | Опис | Ліміт |
|
|
253
|
+
| ------------- | --------- | ---- | ----- |
|
|
254
|
+
| `message` | `string` | Повідомлення помилки. | ≤ 1000 симв. |
|
|
255
|
+
| `stack` | `string` | Стек-трейс. | ≤ 8000 симв. |
|
|
256
|
+
| `source` | `string` | Звідки: `vue`, `window`, `promise`, `fetch` чи `console`. | ≤ 20 симв. |
|
|
257
|
+
| `level` | `string` | Серйозність: `fatal` \| `error` \| `warning` \| `info` \| `debug`. | одне зі списку |
|
|
258
|
+
| `url` | `string` | `window.location.href` на момент перехоплення. | ≤ 2000 симв. |
|
|
259
|
+
| `route` | `string` | Nuxt-route `fullPath`. | ≤ 500 симв. |
|
|
260
|
+
| `occurredAt` | `string` | ISO-8601 timestamp. | ISO-8601 |
|
|
261
|
+
| `breadcrumbs` | `Breadcrumb[]` | Ланцюжок, що привів до помилки (нижче). | ≤ 50 елементів |
|
|
262
|
+
|
|
263
|
+
Кожен **`Breadcrumb`**:
|
|
264
|
+
|
|
265
|
+
| Поле | Тип | Опис | Ліміт |
|
|
266
|
+
| ----------- | -------- | ---- | ----- |
|
|
267
|
+
| `type` | `string` | `click`, `input`, `navigation`, `fetch`, `console` чи ваш кастомний тип. | ≤ 30 симв. |
|
|
268
|
+
| `category` | `string` | напр. `ui.click`, `ui.input`, `navigation`, `http`, `console`. | ≤ 60 симв. |
|
|
269
|
+
| `message` | `string` | Людиночитний рядок, напр. `POST /api/pay → 500`. | ≤ 500 симв. |
|
|
270
|
+
| `level` | `string` | `info` \| `error` \| … | ≤ 20 симв. |
|
|
271
|
+
| `timestamp` | `string` | ISO-8601. | ISO-8601 |
|
|
272
|
+
| `data` | `object` | Опційний структурований payload (деталі fetch: метод, статус, тайминг, заголовки, тіла запиту/відповіді). | JSON |
|
|
273
|
+
|
|
274
|
+
**Ліміти батча:** щонайбільше **20 помилок на запит**; клієнт також повертає в
|
|
275
|
+
чергу невдалі батчі (обмежено 200 у памʼяті) і дедуплікує *ту саму* помилку до
|
|
276
|
+
`maxPerSession` (5) occurrences за сесію, тож одна повторювана помилка не
|
|
277
|
+
затопить вашу квоту.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Формат передачі та типи даних
|
|
282
|
+
|
|
283
|
+
Payloadʼи кодуються у **protobuf** (proto3) і POSTяться як
|
|
284
|
+
`application/x-protobuf`. Енкодер саморобний і **без залежностей** — жодного
|
|
285
|
+
`protobufjs`, тож споживачам не потрібні ні Vite `optimizeDeps`, ні
|
|
286
|
+
CJS-interop-конфіг.
|
|
287
|
+
|
|
288
|
+
Схема (номери полів визначальні — вони мусять збігатися з декодером API):
|
|
289
|
+
|
|
290
|
+
```proto
|
|
291
|
+
message Breadcrumb {
|
|
292
|
+
string type = 1;
|
|
293
|
+
string category = 2;
|
|
294
|
+
string message = 3;
|
|
295
|
+
string level = 4;
|
|
296
|
+
string timestamp = 5;
|
|
297
|
+
string data = 6; // обʼєкт у вигляді JSON-рядка
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
message Error {
|
|
301
|
+
string message = 1;
|
|
302
|
+
string stack = 2;
|
|
303
|
+
string source = 3;
|
|
304
|
+
string url = 4;
|
|
305
|
+
string route = 5;
|
|
306
|
+
string userId = 6; // per-error override (цим клієнтом не використовується)
|
|
307
|
+
string appVersion = 7; // per-error override (цим клієнтом не використовується)
|
|
308
|
+
string device = 8; // per-error override (цим клієнтом не використовується)
|
|
309
|
+
string occurredAt = 9;
|
|
310
|
+
repeated Breadcrumb breadcrumbs = 10;
|
|
311
|
+
string level = 11;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
message Meta { string device = 1; string userId = 2; string appVersion = 3; }
|
|
315
|
+
message Batch { Meta meta = 1; repeated Error errors = 2; }
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`device` і breadcrumb `data` — обʼєкти, тож їдуть як **JSON-рядки** всередині
|
|
319
|
+
protobuf. Порожні рядки опускаються (дефолт proto3). Бінарне обрамлення лише
|
|
320
|
+
тримає мережеву панель охайною — це **не** шифрування; схема публічна, тож
|
|
321
|
+
будь-хто з нею може декодувати payload.
|
|
322
|
+
|
|
323
|
+
### Як передається
|
|
324
|
+
|
|
325
|
+
- Звичайний шлях: `fetch(endpoint, { method: 'POST', headers: { 'Content-Type':
|
|
326
|
+
'application/x-protobuf', 'X-Public-Key': <ключ> }, body: <bytes>, keepalive:
|
|
327
|
+
true })`.
|
|
328
|
+
- Шлях page-hide: черга примусово скидається через `navigator.sendBeacon(endpoint
|
|
329
|
+
+ '?key=<ключ>', Blob)` — оскільки beacon не може ставити заголовки, ключ їде в
|
|
330
|
+
query-параметрі `?key=`. Відкат до `fetch(..., { keepalive: true })`, якщо
|
|
331
|
+
`sendBeacon` недоступний чи відмовив.
|
|
332
|
+
|
|
333
|
+
API також приймає звичайний JSON на тому самому endpoint (та сама валідація), що
|
|
334
|
+
зручно для скриптових тестових POSTів, але SDK завжди шле protobuf.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Перегляд і контроль ваших логів
|
|
339
|
+
|
|
340
|
+
Сенс власного endpoint: після відправки пакетом **ви** володієте даними і керуєте
|
|
341
|
+
ними повністю зі свого дашборда — без сторонньої консолі.
|
|
342
|
+
|
|
343
|
+
- **Перегляд** — відкрийте **Logs** на проєкті, щоб побачити групи помилок, кожну
|
|
344
|
+
з повідомленням, лічильником, first/last seen, зачепленими routes і
|
|
345
|
+
стек-трейсом + breadcrumb-ланцюжком на кожну occurrence (включно з захопленими
|
|
346
|
+
fetch payload/response, коли `captureFetchDetails` увімкнено).
|
|
347
|
+
- **Дедуплікація** — API групує occurrences за **fingerprint** (хеш нормалізованого
|
|
348
|
+
повідомлення + верхні стек-фрейми), тож повторювана помилка — це один рядок із
|
|
349
|
+
лічильником, а не тисячі дублікатів.
|
|
350
|
+
- **Rate-ліміт і origins** — кожен проєкт має власний per-minute rate-ліміт і
|
|
351
|
+
allowlist origins. Звузьте origins, щоб відкидати сторонній трафік; самого
|
|
352
|
+
ключа недостатньо, щоб дані зайшли з неперелічeного origin.
|
|
353
|
+
- **Retention** — occurrences видаляються після вікна retention (за замовчуванням
|
|
354
|
+
7 днів на сервері), тож сховище лишається обмеженим без жодних дій з вашого
|
|
355
|
+
боку.
|
|
356
|
+
|
|
357
|
+
Оскільки ключ — лише на запис, а читання під логіном, публікація ключа у
|
|
358
|
+
клієнтському бандлі не відкриває жодних збережених даних.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Приватність
|
|
363
|
+
|
|
364
|
+
Click-breadcrumbs записують елемент (tag / id / class / короткий текст / `data-*`
|
|
365
|
+
атрибути) — **ніколи значення полів**. Нічого не захоплюється, доки не станеться
|
|
366
|
+
помилка; breadcrumbs живуть лише в памʼяті і їдуть прикріпленими до помилки.
|
|
367
|
+
|
|
368
|
+
Значення полів захоплюються **лише** коли ви явно вмикаєте `captureInputs: true`.
|
|
369
|
+
Навіть тоді чутливі поля маскуються (зберігаються як `[masked, N chars]`, ніколи
|
|
370
|
+
у відкритому вигляді): будь-який `type="password"`, а також поля, чиї `name` /
|
|
371
|
+
`id` / `autocomplete` збігаються з `pass`, `secret`, `token`, `otp`, `cvv`,
|
|
372
|
+
`card`, `iban`, `ssn`, `pin`, `auth` тощо. Нечутливі значення обрізаються до 100
|
|
373
|
+
символів.
|
|
374
|
+
|
|
375
|
+
Захоплення деталей fetch (`captureFetchDetails`, увімкнено за замовчуванням)
|
|
376
|
+
редагує чутливі заголовки запиту/відповіді (`authorization`, `cookie`,
|
|
377
|
+
`x-api-key`, CSRF-токени, …) і обрізає тіла до 2 КБ. Поставте `false`, якщо не
|
|
378
|
+
хочете, щоб payloadʼи запитів чи тіла відповідей взагалі покидали браузер.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Ліцензія
|
|
383
|
+
|
|
384
|
+
[PolyForm Shield 1.0.0](./LICENSE) — source-available. Безкоштовно ставити й
|
|
385
|
+
використовувати для будь-якої мети, **окрім** створення продукту, що конкурує з
|
|
386
|
+
цим ПЗ або з тим, що ліцензіар надає з його допомогою. Не можна форкнути у
|
|
387
|
+
конкурента. Повні умови у [`LICENSE`](./LICENSE); авторські права лишаються за
|
|
388
|
+
автором.
|
package/dist/module.d.mts
CHANGED
|
@@ -6,12 +6,11 @@ interface ModuleOptions {
|
|
|
6
6
|
/** Wrap window.fetch to capture failed requests (network errors + 5xx). Default true. */
|
|
7
7
|
captureFetch?: boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* fetch breadcrumbs message-only (e.g. to avoid capturing request payloads).
|
|
9
|
+
* Attach full request/response detail (method, url, status, timing, headers,
|
|
10
|
+
* request payload, response body) to every fetch breadcrumb's `data` for
|
|
11
|
+
* precise debugging. Sensitive headers (authorization/cookie/…) are redacted;
|
|
12
|
+
* bodies are truncated. Default true; set false to keep fetch breadcrumbs
|
|
13
|
+
* message-only (e.g. to avoid capturing request payloads / PII).
|
|
15
14
|
*/
|
|
16
15
|
captureFetchDetails?: boolean;
|
|
17
16
|
/** Patch console.error to capture logged-but-not-thrown errors. Default true. */
|
|
@@ -22,8 +21,34 @@ interface ModuleOptions {
|
|
|
22
21
|
* masked. Default false — opt in, since it can capture user-entered data.
|
|
23
22
|
*/
|
|
24
23
|
captureInputs?: boolean;
|
|
25
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* How often batches leave the browser (debounce before a flush), in ms.
|
|
26
|
+
* A flush is scheduled `flushDelay` after the first queued error; more errors
|
|
27
|
+
* arriving in that window ride the same batch. Default 2000.
|
|
28
|
+
*/
|
|
26
29
|
flushDelay?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Floor on the spacing between consecutive flushes, in ms. Unlike `flushDelay`
|
|
32
|
+
* (a per-batch debounce), this throttles the *rate* of network requests during
|
|
33
|
+
* a sustained error storm: a flush never fires sooner than `minFlushInterval`
|
|
34
|
+
* after the previous one. Default 0 (off — `flushDelay` alone governs cadence).
|
|
35
|
+
*/
|
|
36
|
+
minFlushInterval?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Client-side sampling: fraction (0..1) of captured errors to actually send;
|
|
39
|
+
* the rest are dropped before they ever queue. 1 = send everything (default),
|
|
40
|
+
* 0.3 = keep ~30%. Thins volume at the source so a high-traffic app doesn't
|
|
41
|
+
* flood the per-project server rate limit. Applied per occurrence, before the
|
|
42
|
+
* per-session dedup — so with a low rate even a rare error may be dropped.
|
|
43
|
+
*/
|
|
44
|
+
sampleRate?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Client-side hard cap on errors *queued* per rolling minute (fixed window);
|
|
47
|
+
* anything over is dropped. Mirrors the server's per-project rate limit so the
|
|
48
|
+
* browser stops emitting instead of shipping batches that get rejected.
|
|
49
|
+
* Default 0 (off — unlimited).
|
|
50
|
+
*/
|
|
51
|
+
maxPerMinute?: number;
|
|
27
52
|
/** Max occurrences of the same error captured per session. Default 5. */
|
|
28
53
|
maxPerSession?: number;
|
|
29
54
|
/** Max errors per flushed batch. Default 20 (server also caps at 20). */
|
package/dist/module.d.ts
CHANGED
|
@@ -6,12 +6,11 @@ interface ModuleOptions {
|
|
|
6
6
|
/** Wrap window.fetch to capture failed requests (network errors + 5xx). Default true. */
|
|
7
7
|
captureFetch?: boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* fetch breadcrumbs message-only (e.g. to avoid capturing request payloads).
|
|
9
|
+
* Attach full request/response detail (method, url, status, timing, headers,
|
|
10
|
+
* request payload, response body) to every fetch breadcrumb's `data` for
|
|
11
|
+
* precise debugging. Sensitive headers (authorization/cookie/…) are redacted;
|
|
12
|
+
* bodies are truncated. Default true; set false to keep fetch breadcrumbs
|
|
13
|
+
* message-only (e.g. to avoid capturing request payloads / PII).
|
|
15
14
|
*/
|
|
16
15
|
captureFetchDetails?: boolean;
|
|
17
16
|
/** Patch console.error to capture logged-but-not-thrown errors. Default true. */
|
|
@@ -22,8 +21,34 @@ interface ModuleOptions {
|
|
|
22
21
|
* masked. Default false — opt in, since it can capture user-entered data.
|
|
23
22
|
*/
|
|
24
23
|
captureInputs?: boolean;
|
|
25
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* How often batches leave the browser (debounce before a flush), in ms.
|
|
26
|
+
* A flush is scheduled `flushDelay` after the first queued error; more errors
|
|
27
|
+
* arriving in that window ride the same batch. Default 2000.
|
|
28
|
+
*/
|
|
26
29
|
flushDelay?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Floor on the spacing between consecutive flushes, in ms. Unlike `flushDelay`
|
|
32
|
+
* (a per-batch debounce), this throttles the *rate* of network requests during
|
|
33
|
+
* a sustained error storm: a flush never fires sooner than `minFlushInterval`
|
|
34
|
+
* after the previous one. Default 0 (off — `flushDelay` alone governs cadence).
|
|
35
|
+
*/
|
|
36
|
+
minFlushInterval?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Client-side sampling: fraction (0..1) of captured errors to actually send;
|
|
39
|
+
* the rest are dropped before they ever queue. 1 = send everything (default),
|
|
40
|
+
* 0.3 = keep ~30%. Thins volume at the source so a high-traffic app doesn't
|
|
41
|
+
* flood the per-project server rate limit. Applied per occurrence, before the
|
|
42
|
+
* per-session dedup — so with a low rate even a rare error may be dropped.
|
|
43
|
+
*/
|
|
44
|
+
sampleRate?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Client-side hard cap on errors *queued* per rolling minute (fixed window);
|
|
47
|
+
* anything over is dropped. Mirrors the server's per-project rate limit so the
|
|
48
|
+
* browser stops emitting instead of shipping batches that get rejected.
|
|
49
|
+
* Default 0 (off — unlimited).
|
|
50
|
+
*/
|
|
51
|
+
maxPerMinute?: number;
|
|
27
52
|
/** Max occurrences of the same error captured per session. Default 5. */
|
|
28
53
|
maxPerSession?: number;
|
|
29
54
|
/** Max errors per flushed batch. Default 20 (server also caps at 20). */
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -4,6 +4,14 @@ function posInt(value, fallback) {
|
|
|
4
4
|
const n = Number(value);
|
|
5
5
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
6
6
|
}
|
|
7
|
+
function clamp01(value, fallback) {
|
|
8
|
+
const n = Number(value);
|
|
9
|
+
return Number.isFinite(n) ? Math.min(Math.max(n, 0), 1) : fallback;
|
|
10
|
+
}
|
|
11
|
+
function nonNegInt(value, fallback) {
|
|
12
|
+
const n = Number(value);
|
|
13
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
14
|
+
}
|
|
7
15
|
const module = defineNuxtModule({
|
|
8
16
|
meta: {
|
|
9
17
|
name: "nuxt-error-tracker",
|
|
@@ -19,6 +27,9 @@ const module = defineNuxtModule({
|
|
|
19
27
|
captureConsole: true,
|
|
20
28
|
captureInputs: false,
|
|
21
29
|
flushDelay: 2e3,
|
|
30
|
+
minFlushInterval: 0,
|
|
31
|
+
sampleRate: 1,
|
|
32
|
+
maxPerMinute: 0,
|
|
22
33
|
maxPerSession: 5,
|
|
23
34
|
maxBatch: 20,
|
|
24
35
|
maxBreadcrumbs: 30
|
|
@@ -35,6 +46,9 @@ const module = defineNuxtModule({
|
|
|
35
46
|
captureConsole: options.captureConsole !== false,
|
|
36
47
|
captureInputs: options.captureInputs === true,
|
|
37
48
|
flushDelay: posInt(options.flushDelay, 2e3),
|
|
49
|
+
minFlushInterval: nonNegInt(options.minFlushInterval, 0),
|
|
50
|
+
sampleRate: clamp01(options.sampleRate, 1),
|
|
51
|
+
maxPerMinute: nonNegInt(options.maxPerMinute, 0),
|
|
38
52
|
maxPerSession: posInt(options.maxPerSession, 5),
|
|
39
53
|
// Server hard-caps batches at 20 — never emit larger, even if misconfigured.
|
|
40
54
|
maxBatch: Math.min(posInt(options.maxBatch, 20), 20),
|
|
@@ -11,12 +11,29 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
11
11
|
if (!config || !config.endpoint || !config.publicKey) return;
|
|
12
12
|
const route = useRoute();
|
|
13
13
|
const FLUSH_DELAY = config.flushDelay ?? 2e3;
|
|
14
|
+
const MIN_FLUSH_INTERVAL = config.minFlushInterval ?? 0;
|
|
15
|
+
const SAMPLE_RATE = config.sampleRate ?? 1;
|
|
16
|
+
const MAX_PER_MINUTE = config.maxPerMinute ?? 0;
|
|
14
17
|
const MAX_PER_SESSION = config.maxPerSession ?? 5;
|
|
15
18
|
const MAX_BATCH = config.maxBatch ?? 20;
|
|
16
19
|
const MAX_CRUMBS = config.maxBreadcrumbs ?? 30;
|
|
17
20
|
const queue = [];
|
|
18
21
|
const sessionCounts = /* @__PURE__ */ new Map();
|
|
19
22
|
let flushTimer = null;
|
|
23
|
+
let lastFlushAt = 0;
|
|
24
|
+
let rateWindowStart = 0;
|
|
25
|
+
let rateWindowCount = 0;
|
|
26
|
+
function rateLimited() {
|
|
27
|
+
if (!MAX_PER_MINUTE) return false;
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
if (now - rateWindowStart >= 6e4) {
|
|
30
|
+
rateWindowStart = now;
|
|
31
|
+
rateWindowCount = 0;
|
|
32
|
+
}
|
|
33
|
+
if (rateWindowCount >= MAX_PER_MINUTE) return true;
|
|
34
|
+
rateWindowCount++;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
20
37
|
const crumbs = [];
|
|
21
38
|
function addCrumb(c) {
|
|
22
39
|
crumbs.push({ ...c, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -43,6 +60,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
43
60
|
const MAX_QUEUE = 200;
|
|
44
61
|
function flush(useBeacon = false) {
|
|
45
62
|
if (!queue.length) return;
|
|
63
|
+
lastFlushAt = Date.now();
|
|
46
64
|
try {
|
|
47
65
|
const batch = queue.splice(0, MAX_BATCH);
|
|
48
66
|
const body = {
|
|
@@ -78,18 +96,22 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
78
96
|
}
|
|
79
97
|
function scheduleFlush() {
|
|
80
98
|
if (flushTimer) return;
|
|
99
|
+
const sinceLast = Date.now() - lastFlushAt;
|
|
100
|
+
const wait = Math.max(FLUSH_DELAY, MIN_FLUSH_INTERVAL - sinceLast);
|
|
81
101
|
flushTimer = setTimeout(() => {
|
|
82
102
|
flushTimer = null;
|
|
83
103
|
flush();
|
|
84
|
-
},
|
|
104
|
+
}, wait);
|
|
85
105
|
}
|
|
86
106
|
function capture(err, source, level = "error") {
|
|
87
107
|
try {
|
|
88
108
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
89
109
|
if (error.message === "Script error.") return;
|
|
110
|
+
if (SAMPLE_RATE < 1 && Math.random() >= SAMPLE_RATE) return;
|
|
90
111
|
const key = sessionKey(error.message, error.stack ?? "");
|
|
91
112
|
const count = sessionCounts.get(key) ?? 0;
|
|
92
113
|
if (count >= MAX_PER_SESSION) return;
|
|
114
|
+
if (rateLimited()) return;
|
|
93
115
|
sessionCounts.set(key, count + 1);
|
|
94
116
|
queue.push({
|
|
95
117
|
message: error.message.slice(0, 1e3),
|
|
@@ -266,7 +288,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
266
288
|
if (!isOwn) {
|
|
267
289
|
const failed = res.status >= 400;
|
|
268
290
|
let data;
|
|
269
|
-
if (
|
|
291
|
+
if (config.captureFetchDetails) {
|
|
270
292
|
data = {
|
|
271
293
|
method,
|
|
272
294
|
url: reqUrl,
|