@swifty.js/sentry 0.0.6 → 0.0.7
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/package.json.cjs +1 -1
- package/dist/package.json.js +1 -1
- package/package.json +2 -1
- package/skills/swifty-sentry/SKILL.md +1293 -0
package/dist/package.json.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e="0.0.
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e="0.0.7",r={version:e};exports.default=r,exports.version=e;
|
package/dist/package.json.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var a="0.0.
|
|
1
|
+
var a="0.0.7",e={version:a};export{e as default,a as version};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swifty.js/sentry",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Framework-agnostic sentry sdk with first-class support for react and vue3+",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"error-tracking",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
|
+
"skills",
|
|
26
27
|
"README.md"
|
|
27
28
|
],
|
|
28
29
|
"type": "module",
|
|
@@ -0,0 +1,1293 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: swifty-sentry
|
|
3
|
+
description: >-
|
|
4
|
+
Integration guide for @swifty.js/sentry, a browser monitoring and analytics SDK.
|
|
5
|
+
Use this skill whenever the user mentions @swifty.js/sentry, swifty-sentry, frontend monitoring,
|
|
6
|
+
frontend error tracking, browser performance monitoring, declarative click tracking,
|
|
7
|
+
exposure tracking, white-screen detection, screen recording, Web Vitals, PV/dwell-time,
|
|
8
|
+
offline report caching, or any task involving integrating browser observability into
|
|
9
|
+
a React, Vue, or vanilla TypeScript/JavaScript project. Also trigger when the user
|
|
10
|
+
asks about swifty-sentry-* attributes, ReactErrorBoundary from this SDK, vuePlugin, the
|
|
11
|
+
Vite dev-server mock plugin (sentryPlugin / sentryPlugin7), the webpack dev-server mock
|
|
12
|
+
plugin (SentryWebpackPlugin / sentryMiddleware), or dev-time source map resolution of
|
|
13
|
+
reported errors. Even if the user simply says "add monitoring" or "add tracking" in a
|
|
14
|
+
frontend context, consult this skill first.
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# @swifty.js/sentry -- Integration and Usage Guide
|
|
18
|
+
|
|
19
|
+
This skill teaches how to integrate, configure, and use `@swifty.js/sentry` (npm package `@swifty.js/sentry`, current version `0.0.5`) in browser applications. All code facts are derived from the SDK source code at `sentry/src/`.
|
|
20
|
+
|
|
21
|
+
## Package Overview
|
|
22
|
+
|
|
23
|
+
`@swifty.js/sentry` is a framework-agnostic browser monitoring SDK that captures errors, HTTP requests, page views, performance metrics, declarative clicks, exposure durations, white-screen events, and screen recordings. React and Vue integrations are published as dedicated subpath exports so non-framework users do not load framework dependencies.
|
|
24
|
+
|
|
25
|
+
## Package Exports
|
|
26
|
+
|
|
27
|
+
The package exposes six entry points:
|
|
28
|
+
|
|
29
|
+
| Subpath | Purpose |
|
|
30
|
+
| --------------------------- | --------------------------------------------------------------------------------------- |
|
|
31
|
+
| `@swifty.js/sentry` | Core SDK, all types, enums, and the `SentryPlugin` base class |
|
|
32
|
+
| `@swifty.js/sentry/plugins` | Plugins: PerformancePlugin, ScreenRecordPlugin, ExposurePlugin, unzipScreenRecord |
|
|
33
|
+
| `@swifty.js/sentry/react` | ReactErrorBoundary component |
|
|
34
|
+
| `@swifty.js/sentry/vue` | Vue 3 plugin (vuePlugin) |
|
|
35
|
+
| `@swifty.js/sentry/vite` | Vite dev-server mock plugin with source map resolution (sentryPlugin / sentryPlugin7) |
|
|
36
|
+
| `@swifty.js/sentry/webpack` | Webpack dev-server mock plugin (sentryPlugin / SentryWebpackPlugin / sentryMiddleware) |
|
|
37
|
+
|
|
38
|
+
Each public export provides ESM, CJS, and TypeScript declaration files.
|
|
39
|
+
|
|
40
|
+
The core entry re-exports everything from `src/types` (`export * from "./types"`), so `EventType`, `Status`, `BreadcrumbType`, the abstract `SentryPlugin` class, the hook types (`BeforeSendHook`, `BeforeSendBatchHook`, `AfterSendHook`, `BeforeBreadcrumbHook`), and all `I*`/`T*` interfaces are importable from `@swifty.js/sentry` directly.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @swifty.js/sentry
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
React (`^16 || ^17 || ^18 || ^19`), Vue (`^3`), Vite (`^7 || ^8`), and webpack (`^4 || ^5`) are optional peer dependencies. Install them only when the matching integration is used.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install react # for @swifty.js/sentry/react
|
|
52
|
+
npm install vue # for @swifty.js/sentry/vue
|
|
53
|
+
npm install -D vite # for @swifty.js/sentry/vite
|
|
54
|
+
npm install -D webpack webpack-dev-server # for @swifty.js/sentry/webpack
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
The minimum viable integration requires calling `init` with a non-empty `dsn` string. All other options fall back to SDK defaults. Plugins are **instantiated** by the caller and passed to `enablePlugin`, which accepts any number of plugin instances.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { init, enablePlugin } from "@swifty.js/sentry";
|
|
63
|
+
import {
|
|
64
|
+
PerformancePlugin,
|
|
65
|
+
ScreenRecordPlugin,
|
|
66
|
+
ExposurePlugin,
|
|
67
|
+
} from "@swifty.js/sentry/plugins";
|
|
68
|
+
|
|
69
|
+
init({ dsn: "/api/log" });
|
|
70
|
+
|
|
71
|
+
const exposure = new ExposurePlugin();
|
|
72
|
+
enablePlugin(new PerformancePlugin(), new ScreenRecordPlugin(), exposure);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `dsn` value must be a non-empty string. If `dsn` is empty or `disabled` is `true`, initialization is rejected silently (the SDK logs the reason but does not throw).
|
|
76
|
+
|
|
77
|
+
## Core Public API
|
|
78
|
+
|
|
79
|
+
All core APIs are exported from `@swifty.js/sentry`.
|
|
80
|
+
|
|
81
|
+
### init
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { init } from "@swifty.js/sentry";
|
|
85
|
+
|
|
86
|
+
init({
|
|
87
|
+
dsn: "/api/log",
|
|
88
|
+
projectId: "checkout-web",
|
|
89
|
+
userId: "user-001",
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Behavior, in source order:
|
|
94
|
+
|
|
95
|
+
1. If `isInitialized()` is already `true`, log and return. The SDK can only be initialized once per lifecycle.
|
|
96
|
+
2. Validate `{ ...DEFAULT_OPTIONS, ...options }` with zod (`optionsSchema`) and write the result to the `sentry` singleton via `setOptions`. A schema violation **throws** a `ZodError`.
|
|
97
|
+
3. If `disabled` is `true`, return without installing any listeners. Options are still applied.
|
|
98
|
+
4. If `dsn` is `""`, log an error and return. Options are still applied.
|
|
99
|
+
5. Set the breadcrumb buffer capacity from `maxBreadcrumbs`.
|
|
100
|
+
6. Call `setup()`, which installs bus subscriptions plus capture decorators for every enabled event type, starts white-screen detection when `enableWhiteScreen` is `true`, starts page-view lifecycle tracking, and registers the `pagehide` dwell flush.
|
|
101
|
+
7. Kick off `initIdentity()` (FingerprintJS) without awaiting it.
|
|
102
|
+
|
|
103
|
+
The internal event bus isolates handler exceptions: if one handler throws, the remaining handlers for that event type still execute.
|
|
104
|
+
|
|
105
|
+
### destroy
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { destroy } from "@swifty.js/sentry";
|
|
109
|
+
|
|
110
|
+
destroy();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Calls `plugin.destroy?.()` on every registered plugin and clears the registry, runs the `setup()` cleanup in reverse order (reversing all capture decorators, stopping white-screen sampling, removing the `pagehide` listener, resetting page-view state, clearing all bus subscriptions), destroys the batch-error manager, resets the `DataReporter` singleton (clearing its timers, removing its online/offline listeners, and dropping queued events), and resets per-session state: the breadcrumb buffer, the error-deduplication set, and the `shouldScreenRecord` flag. A later `init` therefore starts completely clean. Use this when resetting tests, unloading a micro-frontend, or dynamically disabling monitoring.
|
|
114
|
+
|
|
115
|
+
### isInitialized
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { isInitialized } from "@swifty.js/sentry";
|
|
119
|
+
|
|
120
|
+
if (!isInitialized()) {
|
|
121
|
+
init({ dsn: "/api/log" });
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Returns `true` after `init` has successfully completed `setup()`. Resets to `false` after `destroy`. Note that a `disabled: true` or empty-`dsn` init leaves this `false`.
|
|
126
|
+
|
|
127
|
+
### enablePlugin
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
enablePlugin(...plugins: SentryPlugin[]): void
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { enablePlugin } from "@swifty.js/sentry";
|
|
135
|
+
import { PerformancePlugin, ScreenRecordPlugin } from "@swifty.js/sentry/plugins";
|
|
136
|
+
|
|
137
|
+
// Single plugin
|
|
138
|
+
enablePlugin(new PerformancePlugin());
|
|
139
|
+
|
|
140
|
+
// Several at once, with constructor options
|
|
141
|
+
enablePlugin(
|
|
142
|
+
new PerformancePlugin(),
|
|
143
|
+
new ScreenRecordPlugin({ durationMs: 5000 }),
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
For each argument it calls `plugin.init()` and adds the instance to an internal `Set<SentryPlugin>`. It returns `void`, so keep your own reference to any plugin whose instance methods you need later:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const exposure = new ExposurePlugin();
|
|
151
|
+
enablePlugin(exposure);
|
|
152
|
+
exposure.observe({ target: element });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Call `enablePlugin` after `init`, so plugin initialization sees the parsed options.
|
|
156
|
+
|
|
157
|
+
## Configuration Options
|
|
158
|
+
|
|
159
|
+
`init` accepts an `InitOptions` object (`Partial<Options> & Pick<Options, "dsn">` -- every field optional except `dsn`, which is required at the type level). `Options` is `z.input<typeof optionsSchema>`; the resolved runtime shape is `IOptions`. Values not provided use SDK defaults from `DEFAULT_OPTIONS`.
|
|
160
|
+
|
|
161
|
+
### Required Options
|
|
162
|
+
|
|
163
|
+
| Option | Type | Default | Description |
|
|
164
|
+
| ------ | -------- | ------- | --------------------------------------------------------------------- |
|
|
165
|
+
| `dsn` | `string` | `""` | Report endpoint URL. Must be non-empty for initialization to succeed. |
|
|
166
|
+
|
|
167
|
+
### Feature Toggle Options
|
|
168
|
+
|
|
169
|
+
| Option | Type | Default | Description |
|
|
170
|
+
| -------------------------- | --------- | ----------- | -------------------------------------------------------------- |
|
|
171
|
+
| `projectId` | `string` | `"unknown"` | Frontend project identifier. |
|
|
172
|
+
| `userId` | `string` | `"unknown"` | Current user identifier. |
|
|
173
|
+
| `disabled` | `boolean` | `false` | Disable the SDK entirely. |
|
|
174
|
+
| `enableXhr` | `boolean` | `true` | Capture XMLHttpRequest requests. |
|
|
175
|
+
| `enableFetch` | `boolean` | `true` | Capture fetch requests. |
|
|
176
|
+
| `enableClick` | `boolean` | `true` | Capture declarative click events. |
|
|
177
|
+
| `enableError` | `boolean` | `true` | Capture runtime, `console.error`, and resource errors. |
|
|
178
|
+
| `enableUnhandledRejection` | `boolean` | `true` | Capture unhandled promise rejections. |
|
|
179
|
+
| `enableHashChange` | `boolean` | `true` | Capture hash navigation. |
|
|
180
|
+
| `enableHistory` | `boolean` | `true` | Capture history (pushState/replaceState/popstate) navigation. |
|
|
181
|
+
| `enableWhiteScreen` | `boolean` | `true` | Enable white-screen detection. |
|
|
182
|
+
| `enableFingerprint` | `boolean` | `false` | Enable FingerprintJS anonymous visitor identity. |
|
|
183
|
+
| `enableHttpPerformance` | `boolean` | `false` | Report successful HTTP requests as performance events. |
|
|
184
|
+
| `repeatCodeError` | `boolean` | `false` | Report duplicate errors (deduplication is on by default). |
|
|
185
|
+
| `debug` | `boolean` | `false` | Enable SDK debug logging in the browser console. |
|
|
186
|
+
|
|
187
|
+
### Tuning Options
|
|
188
|
+
|
|
189
|
+
| Option | Type | Default | Description |
|
|
190
|
+
| ---------------------------- | ---------------------- | --------------------------------------------------- | ----------------------------------------------------- |
|
|
191
|
+
| `anonymousId` | `string` | `"unknown"` | SDK-generated anonymous visitor id. |
|
|
192
|
+
| `visitorId` | `string` | `"unknown"` | Backend-bound visitor id. |
|
|
193
|
+
| `screenRecordDurationMs` | `number` | `3000` | Rolling screen record window length in ms. |
|
|
194
|
+
| `screenRecordEventTypes` | `EventType[]` | `[Error, Xhr, Fetch, Resource, UnhandledRejection]` | Event types that trigger screen record reporting. |
|
|
195
|
+
| `hasSkeleton` | `boolean` | `false` | Whether the page has a skeleton screen. |
|
|
196
|
+
| `rootCssSelectors` | `string[]` | `["html", "body", "#app", "#root"]` | Root selectors used by white-screen detection. |
|
|
197
|
+
| `clickThrottleDelay` | `number` | `0` | Click capture throttle delay in milliseconds. |
|
|
198
|
+
| `maxBreadcrumbs` | `number` | `30` | Breadcrumb capacity (FIFO buffer of the newest items). |
|
|
199
|
+
| `ignoreErrors` | `(string \| RegExp)[]` | `[]` | Runtime error ignore rules. |
|
|
200
|
+
| `excludeApis` | `(string \| RegExp)[]` | `[]` | HTTP request ignore rules. |
|
|
201
|
+
| `cacheMaxLength` | `number` | `10` | Maximum batch size before flush. |
|
|
202
|
+
| `cacheWaitingTime` | `number` | `2000` | Batch wait time in milliseconds. |
|
|
203
|
+
| `maxQueueLength` | `number` | `200` | Maximum queued events while offline or retrying. |
|
|
204
|
+
| `retryIntervalMilliseconds` | `number` | `60000` | Server recovery probe interval. |
|
|
205
|
+
| `offlineCacheKey` | `string` | `"swifty_sentry_offline_cache"` | localStorage key for offline cache. |
|
|
206
|
+
| `tracesSampleRate` | `number` | `1` | Sampling rate from 0 to 1. |
|
|
207
|
+
|
|
208
|
+
Schema constraints enforced by zod: `maxBreadcrumbs`, `cacheMaxLength`, and `maxQueueLength` must be positive integers; `screenRecordDurationMs`, `clickThrottleDelay`, `cacheWaitingTime`, and `retryIntervalMilliseconds` must be non-negative; `tracesSampleRate` must be between 0 and 1.
|
|
209
|
+
|
|
210
|
+
### Hook Options
|
|
211
|
+
|
|
212
|
+
| Option | Type | Default | Description |
|
|
213
|
+
| ------------------ | ---------- | ----------- | ------------------------------------------------------------------------------------------------------------- |
|
|
214
|
+
| `beforeBreadcrumb` | `function` | `undefined` | Hook before storing a breadcrumb. Receives `IBreadcrumbItem`, returns the (possibly modified) item. |
|
|
215
|
+
| `beforeSend` | `function` | `undefined` | Hook before one event enters the Reporter queue. Receives `IReportData`, returns the data or `false` to drop. |
|
|
216
|
+
| `beforeSendBatch` | `function` | `undefined` | Hook before a batch enters transport. Receives `readonly IReportData[]`, returns the array or `false`. |
|
|
217
|
+
| `afterSend` | `function` | `undefined` | Hook after a batch is sent successfully. Receives `readonly IReportData[]`. |
|
|
218
|
+
|
|
219
|
+
## Event Types
|
|
220
|
+
|
|
221
|
+
The SDK reports events with the following `EventType` enum values:
|
|
222
|
+
|
|
223
|
+
| Enum Value | String Value | Description |
|
|
224
|
+
| ------------------------------ | ---------------------------- | ------------------------------- |
|
|
225
|
+
| `EventType.Xhr` | `"XMLHttpRequest"` | XHR request. |
|
|
226
|
+
| `EventType.Fetch` | `"fetch"` | fetch request. |
|
|
227
|
+
| `EventType.Click` | `"Click"` | Declarative click. |
|
|
228
|
+
| `EventType.HashChange` | `"Event hashchange"` | Hash navigation. |
|
|
229
|
+
| `EventType.History` | `"History"` | History navigation. |
|
|
230
|
+
| `EventType.Resource` | `"Resource"` | Static resource load failure. |
|
|
231
|
+
| `EventType.UnhandledRejection` | `"Event unhandledrejection"` | Unhandled promise rejection. |
|
|
232
|
+
| `EventType.Error` | `"Error"` | JavaScript runtime error. |
|
|
233
|
+
| `EventType.Vue` | `"Vue"` | Vue error. |
|
|
234
|
+
| `EventType.React` | `"React"` | React error. |
|
|
235
|
+
| `EventType.OtherFrameworks` | `"OtherFrameworks"` | Other framework error (via `reportFrameworkError`). |
|
|
236
|
+
| `EventType.Performance` | `"Performance"` | Performance metric. |
|
|
237
|
+
| `EventType.ScreenRecord` | `"ScreenRecord"` | Screen record payload. |
|
|
238
|
+
| `EventType.Exposure` | `"Exposure"` | Exposure duration event. |
|
|
239
|
+
| `EventType.WhiteScreen` | `"WhiteScreen"` | White-screen event. |
|
|
240
|
+
| `EventType.Custom` | `"Custom"` | Custom business event. |
|
|
241
|
+
| `EventType.PV` | `"PV"` | Page view and dwell-time event. |
|
|
242
|
+
|
|
243
|
+
## Error Capture
|
|
244
|
+
|
|
245
|
+
The SDK captures errors from multiple sources, all routed through the `handleError` handler:
|
|
246
|
+
|
|
247
|
+
1. **`window` `error` events** -- captured via `globalThis.addEventListener("error", listener, true)`. `ErrorEvent` instances are dispatched to `handleCodeError`; plain `Event`s whose target exposes `localName` plus `src` or `href` are dispatched to the resource-error path.
|
|
248
|
+
|
|
249
|
+
2. **Resource load errors** -- a failed `<img>`, `<script>`, or `<link>` dispatches a plain `Event` (not an `ErrorEvent`) whose target is the failed element. `<img>`/`<script>` expose `src`, `<link>` exposes `href` -- never both, so both fields are optional in `IExtendedErrorEvent`. Reported as `EventType.Resource` with `name` set to the `localName`, `src`/`href` fields, and a synthesized `message` of `Failed to load <localName>: <src|href>`.
|
|
250
|
+
|
|
251
|
+
3. **`console.error`** -- the SDK decorates `console.error`, publishing the first `Error` argument or, if none, all arguments stringified and joined by a space. A reentrancy flag prevents the SDK's own `console.error` output from re-triggering capture, and the SDK's debug logger uses a native `console.error` reference captured before decoration so debug output never self-reports. The original `console.error` is always called afterwards.
|
|
252
|
+
|
|
253
|
+
4. **Unhandled promise rejections** -- captured via `globalThis.addEventListener("unhandledrejection", listener)`. The handler unwraps the event's `reason` and classifies that value: an `ErrorEvent` reason (carrying filename/line/column) goes to `handleCodeError`; every other reason -- `Error` instances, strings, plain objects -- goes through the generic `handleError` pipeline with the reason as `extra`.
|
|
254
|
+
|
|
255
|
+
5. **React ErrorBoundary errors** -- reported as `EventType.React` via `reportFrameworkError` with the error, its stack, and React's `ErrorInfo` as `context`.
|
|
256
|
+
|
|
257
|
+
6. **Vue `app.config.errorHandler` errors** -- reported as `EventType.Vue` via `reportFrameworkError` with `context: { vueInstance, info }`.
|
|
258
|
+
|
|
259
|
+
### Error Classification
|
|
260
|
+
|
|
261
|
+
`handleError` dispatches on the payload's `extra` value:
|
|
262
|
+
|
|
263
|
+
| `extra` is | Path | Reported type |
|
|
264
|
+
| ------------------------------------- | ------------------- | ---------------------- |
|
|
265
|
+
| `ErrorEvent` | `handleCodeError` | `Error` (with line/column; `extra` = the underlying `Error`'s stack when present) |
|
|
266
|
+
| Plain `Event` with resource-like target | `reportResourceError` | `Resource` |
|
|
267
|
+
| `Error` | `reportRuntimeError` | `Error` (`extra` = `stack \|\| error`) |
|
|
268
|
+
| Anything else | `reportUnknownError` | `Error` with `name: "Unknown Error"` |
|
|
269
|
+
|
|
270
|
+
### Error Deduplication
|
|
271
|
+
|
|
272
|
+
All three error paths deduplicate by default using a raw string key stored in a `BoundedSet<string>` (LRU-style, capacity 1000) on the `sentry` singleton, preventing unbounded memory growth in long-running SPAs:
|
|
273
|
+
|
|
274
|
+
| Path | Dedup key |
|
|
275
|
+
| --------------- | ----------------------------------------------------- |
|
|
276
|
+
| Code error | `Error-<message>-<filename>-<line>-<column>` |
|
|
277
|
+
| Resource error | `Resource-<localName>-<src\|href>` |
|
|
278
|
+
| Runtime/unknown | `Error-<name>-<message>` |
|
|
279
|
+
|
|
280
|
+
Code errors whose source filename is empty or `"unknown"` bypass deduplication and are always reported. Set `repeatCodeError: true` to disable deduplication entirely.
|
|
281
|
+
|
|
282
|
+
### Batch Error Aggregation
|
|
283
|
+
|
|
284
|
+
Only **code errors** (the `handleCodeError` path) are pushed to a `BatchErrorManager`. It debounces for 2 seconds after the last error, then groups the buffered errors by `type-name-message`. Groups of fewer than 5 errors are reported individually; groups of 5 or more collapse into a single `IBatchErrorData` report built from the first item plus `batchError: true`, `batchErrorLength`, and `batchErrorLastHappenTime`. Resource, runtime, and framework errors bypass the manager and report immediately.
|
|
285
|
+
|
|
286
|
+
### Error Ignoring
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
init({
|
|
290
|
+
dsn: "/api/log",
|
|
291
|
+
ignoreErrors: ["Script error.", /ResizeObserver loop limit exceeded/],
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`ignoreErrors` accepts strings and RegExp patterns. A string pattern matches when the error message **includes** it; a RegExp matches when `pattern.test(message)` is true. It is checked on code errors, runtime errors, and unknown errors, but not on resource errors.
|
|
296
|
+
|
|
297
|
+
## HTTP Capture
|
|
298
|
+
|
|
299
|
+
The SDK decorates `XMLHttpRequest.prototype.open`, `XMLHttpRequest.prototype.send`, and `globalThis.fetch` to capture HTTP requests.
|
|
300
|
+
|
|
301
|
+
### XHR Capture
|
|
302
|
+
|
|
303
|
+
- `open()` decoration stores method (uppercased), URL, and base data on the XHR instance under the `__sentry__` property.
|
|
304
|
+
- `send()` decoration refreshes the payload timestamp (so `elapsedTime` measures from `send()`, not `open()`) and adds a **once-only** `loadend` listener that records status code, the parsed `server-timing` header, and elapsed time, then publishes via the event bus. Requests filtered by `shouldIgnoreRequest` are skipped inside the listener.
|
|
305
|
+
- `requestData` and `responseData` (`{ responseType, response }`) are captured **only for error statuses** (`0` or `>= 400`); string responses are truncated to 8 KB.
|
|
306
|
+
|
|
307
|
+
### Fetch Capture
|
|
308
|
+
|
|
309
|
+
- `globalThis.fetch` decoration accepts `string`, `URL`, and `Request` inputs: the URL comes from the string itself, `URL.href`, or `Request.url`; the method comes from `RequestInit.method`, else `Request.method`, else `GET` (always uppercased).
|
|
310
|
+
- Requests matching `shouldIgnoreRequest` (including the SDK's own report POSTs) bypass instrumentation entirely -- the original fetch is called with zero overhead.
|
|
311
|
+
- The response status, `Server-Timing` headers, and elapsed time are recorded for every captured request.
|
|
312
|
+
- The response body is read via `res.clone().text()` **only for error statuses** (`0` or `>= 400`), truncated to 8 KB, and read in the background so the caller's response is never delayed; if the clone read fails (e.g., streaming responses), the event is still published without `responseData`.
|
|
313
|
+
- Network errors (fetch rejection) publish with `statusCode: 0` and `message` set to the error message, or `"Network error"` for non-`Error` rejections. The original error is re-thrown to preserve caller behavior.
|
|
314
|
+
|
|
315
|
+
### Status Classification
|
|
316
|
+
|
|
317
|
+
`transformHttpData` returns a **new** object with derived `status` and `message` rather than mutating the input:
|
|
318
|
+
|
|
319
|
+
| Status Code Range | SDK Status | Derived message |
|
|
320
|
+
| ----------------- | -------------- | ---------------------------- |
|
|
321
|
+
| 0 | `Status.Error` | Original network-error message, else `"Network error"` |
|
|
322
|
+
| 100 - 199 | `Status.OK` | `"Informational response"` |
|
|
323
|
+
| 200 - 299 | `Status.OK` | `"Successful responses"` |
|
|
324
|
+
| 300 - 399 | `Status.OK` | `"Redirection messages"` |
|
|
325
|
+
| 400 - 499 | `Status.Error` | `"Client error responses"` |
|
|
326
|
+
| 500 - 599 | `Status.Error` | `"Server error responses"` |
|
|
327
|
+
| Other values | `Status.Error` | `"Invalid status code"` |
|
|
328
|
+
|
|
329
|
+
Only requests with `Status.Error` are reported by default.
|
|
330
|
+
|
|
331
|
+
### Successful Requests as Performance Events
|
|
332
|
+
|
|
333
|
+
Set `enableHttpPerformance: true` to also report successful requests. `handleHttp` then sends an `EventType.Performance` event with `name: "HTTP <METHOD>"`, `message` set to the API path, `value` set to `elapsedTime`, and `extra: { method, statusCode, serverTiming }`.
|
|
334
|
+
|
|
335
|
+
### Request Filtering
|
|
336
|
+
|
|
337
|
+
`shouldIgnoreRequest` skips a request when it is a `POST` to the exact configured `dsn`, or when `isExcludedApi` matches. `excludeApis` uses **exact string equality** for string entries and `pattern.test(api)` for RegExp entries -- note this differs from `ignoreErrors`, which uses substring matching.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
init({
|
|
341
|
+
dsn: "/api/log",
|
|
342
|
+
excludeApis: ["/api/log", /\/health$/],
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Separately, `handleHttp` omits the breadcrumb for any request whose API contains the `dsn`, so the SDK's own traffic never pollutes the breadcrumb trail.
|
|
347
|
+
|
|
348
|
+
## Page Views and Dwell Time
|
|
349
|
+
|
|
350
|
+
The SDK reports PV (page view) events through the `pv-lifecycle` module. All PV events use `EventType.PV` and are distinguished by `name`.
|
|
351
|
+
|
|
352
|
+
### Automatic Page Views
|
|
353
|
+
|
|
354
|
+
1. **`PageLoad`** -- reported immediately (`immediate: true`) during `initPageView()`, called from `setup()`. `extra` is `{ url, referrer, entryTime }`.
|
|
355
|
+
2. **Route change PV** -- on hash or history route changes, `recordRoutePageView` runs:
|
|
356
|
+
- URLs are normalized against `location.href`. If `currentPage.url === normalizedTo`, the event is skipped.
|
|
357
|
+
- `PageDwell` is reported for the previous page, with `extra: { url, referrer, duration }`. Durations of 100 ms or less are dropped to reduce noise.
|
|
358
|
+
- A new PV is reported, named `"HistoryChange"` or `"HashChange"` depending on the source.
|
|
359
|
+
3. **`pagehide`** -- flushes the current dwell time via `flushCurrentPageDwell(true)` (`pagehide` fires reliably on mobile where `beforeunload` does not).
|
|
360
|
+
|
|
361
|
+
### Manual Page View
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import { tracePageView } from "@swifty.js/sentry";
|
|
365
|
+
|
|
366
|
+
tracePageView({
|
|
367
|
+
name: "ProductDetail",
|
|
368
|
+
message: location.href,
|
|
369
|
+
extra: {
|
|
370
|
+
productId: "sku-001",
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`tracePageView` accepts an optional object with `name` (default `"ManualPageView"`), `message` (default `location.href`), and `extra` (default `{ url, referrer }`).
|
|
376
|
+
|
|
377
|
+
## Declarative Click Tracking
|
|
378
|
+
|
|
379
|
+
Declarative click tracking uses `swifty-sentry-*` HTML attributes. Plain clicks are **not** reported: `getDeclarativeClickData` walks the composed path (falling back to a `parentElement` walk when `composedPath()` yields no `HTMLElement`) and returns `null` unless some element carries `swifty-sentry-el`, `swifty-sentry-ev`, or `swifty-sentry-msg`.
|
|
380
|
+
|
|
381
|
+
### Reserved Attributes
|
|
382
|
+
|
|
383
|
+
| Attribute | Description |
|
|
384
|
+
| ------------------- | ------------------------------------------------------------------------- |
|
|
385
|
+
| `swifty-sentry-ev` | Explicit event ID. First priority for event identification. |
|
|
386
|
+
| `swifty-sentry-msg` | Human-readable message. Highest priority for the reported `msg` field. |
|
|
387
|
+
| `swifty-sentry-el` | View/container ID. Fallback for event ID if `swifty-sentry-ev` is absent. |
|
|
388
|
+
|
|
389
|
+
### Custom Attributes
|
|
390
|
+
|
|
391
|
+
Any `swifty-sentry-*` attribute other than the reserved suffixes (`view`, `msg`, `ev`) becomes a param in the reported payload. Params are collected from the **nearest single element in the path** that has any `swifty-sentry-*` attribute -- they are not merged across ancestors. Empty attribute values become `null`.
|
|
392
|
+
|
|
393
|
+
```html
|
|
394
|
+
<a
|
|
395
|
+
swifty-sentry-ev="open-banner"
|
|
396
|
+
swifty-sentry-msg="Open campaign banner"
|
|
397
|
+
swifty-sentry-campaign="spring"
|
|
398
|
+
swifty-sentry-rank="1"
|
|
399
|
+
>
|
|
400
|
+
Campaign
|
|
401
|
+
</a>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The `params` field will contain `{ campaign: "spring", rank: "1" }`.
|
|
405
|
+
|
|
406
|
+
### Event ID Resolution
|
|
407
|
+
|
|
408
|
+
The event ID (`ev`) is resolved by searching the path in this order:
|
|
409
|
+
|
|
410
|
+
1. `swifty-sentry-ev` attribute on any element in the path.
|
|
411
|
+
2. `title` attribute on any element in the path.
|
|
412
|
+
3. `swifty-sentry-el` attribute on any element in the path.
|
|
413
|
+
4. The nearest element's tag name (lowercased), else `"unknown"`.
|
|
414
|
+
|
|
415
|
+
### Message Resolution
|
|
416
|
+
|
|
417
|
+
The `msg` field is resolved from the nearest element carrying a tracking attribute, in this order:
|
|
418
|
+
|
|
419
|
+
1. Its `swifty-sentry-msg` attribute.
|
|
420
|
+
2. Its `title` attribute.
|
|
421
|
+
3. Its trimmed `textContent`.
|
|
422
|
+
4. Its `aria-label` attribute.
|
|
423
|
+
5. Its tag name (lowercased).
|
|
424
|
+
|
|
425
|
+
### Click Payload
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
interface DeclarativeClickData {
|
|
429
|
+
readonly ev: string; // event ID
|
|
430
|
+
readonly msg: string; // human-readable message
|
|
431
|
+
readonly triggerPageUrl: string; // location.href
|
|
432
|
+
readonly x: number; // element bounding-rect left + documentElement.scrollLeft
|
|
433
|
+
readonly y: number; // element bounding-rect top + documentElement.scrollTop
|
|
434
|
+
readonly params: Readonly<Record<string, string | null>>; // custom swifty-sentry-* attributes
|
|
435
|
+
readonly elementPath: string; // CSS-selector-like ancestor path
|
|
436
|
+
readonly triggerTime: number; // Date.now() at click time
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
`elementPath` is produced by `dom2str`: a `" > "`-joined selector chain like `body > div#app > button.btn.primary`, traversing at most 5 levels up, stopping at `html`, capped at 128 characters (whole selectors are dropped rather than truncated), and returning `"<unknown>"` on any exception.
|
|
441
|
+
|
|
442
|
+
`handleClick` sets the report `name` to `clickData.ev || clickData.msg` and `message` to `clickData.msg || clickData.ev`. The full `DeclarativeClickData` object is stored in the `extra` field. The handler pushes a breadcrumb and reports the event; the whole click pipeline is only installed when `enableClick` is `true` at `init` time.
|
|
443
|
+
|
|
444
|
+
### Click Throttling
|
|
445
|
+
|
|
446
|
+
Set `clickThrottleDelay` to a positive number of milliseconds to throttle click capture. A value of `0` (default) means no throttling. The throttle is bound once when the listener is installed, so changing the option after `init` has no effect.
|
|
447
|
+
|
|
448
|
+
## White-Screen Detection
|
|
449
|
+
|
|
450
|
+
White-screen detection samples viewport points after the page is ready and checks whether those points still resolve to configured root elements. It is started directly by `setup()` when `enableWhiteScreen` is `true` (it does not go through the event bus) and is stopped by `destroy()`.
|
|
451
|
+
|
|
452
|
+
### Algorithm
|
|
453
|
+
|
|
454
|
+
1. Waits for `document.readyState === "complete"` or the `load` event.
|
|
455
|
+
2. Starts a `setInterval` at `WHITE_SCREEN_SAMPLE_INTERVAL` (1000 ms), wrapping each sample in `requestIdleCallback` (with a 1000 ms timeout) when available.
|
|
456
|
+
3. Each sample probes 9 points on the horizontal center line and 9 on the vertical center line (18 total) with `document.elementFromPoint`.
|
|
457
|
+
4. A point counts as "empty" when it resolves to `null` or to an element whose id, class+attribute, or tag selector is listed in `rootCssSelectors`.
|
|
458
|
+
5. A sample is "white" when all 18 points are empty.
|
|
459
|
+
6. Sampling stops as soon as a non-white sample is observed (real content rendered). A white screen is reported only when the page stays white for `MAX_WHITE_SCREEN_SAMPLE_COUNT` (10) consecutive samples, after which sampling stops.
|
|
460
|
+
|
|
461
|
+
The reported event is `EventType.WhiteScreen` with `status: Status.Error`, `name: "WhiteScreen"`, `message: "sample count <n>"`, and `extra: { sampleCount }`.
|
|
462
|
+
|
|
463
|
+
### Skeleton Screen Mode
|
|
464
|
+
|
|
465
|
+
When `hasSkeleton: true`, the first sample records the CSS selectors it encountered as a baseline and reports nothing. Each subsequent sample compares its selector set against that baseline: a difference means the skeleton transitioned to content and sampling stops; if the set is still identical at the `MAX_WHITE_SCREEN_SAMPLE_COUNT`th sample, the skeleton never transitioned and a white screen is reported.
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
init({
|
|
469
|
+
dsn: "/api/log",
|
|
470
|
+
enableWhiteScreen: true,
|
|
471
|
+
rootCssSelectors: ["html", "body", "#app"],
|
|
472
|
+
hasSkeleton: true,
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Visitor Identity
|
|
477
|
+
|
|
478
|
+
The SDK tracks three identity values:
|
|
479
|
+
|
|
480
|
+
| Field | Source |
|
|
481
|
+
| ------------- | ---------------------------------------------------------------------------------- |
|
|
482
|
+
| `anonymousId` | FingerprintJS visitor id, stored in localStorage key `swifty_sentry_anonymous_id`. |
|
|
483
|
+
| `visitorId` | Backend-bound visitor id, set via `setVisitorId()`. |
|
|
484
|
+
| `userId` | Current user id, set via `setUserId()` or `init({ userId })`. |
|
|
485
|
+
|
|
486
|
+
All three identity values are attached to every report envelope: `IReportData` carries `userId`, `anonymousId`, and `visitorId` on each event.
|
|
487
|
+
|
|
488
|
+
### Enable FingerprintJS
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
init({
|
|
492
|
+
dsn: "/api/log",
|
|
493
|
+
enableFingerprint: true,
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
When enabled, `initIdentity()` reuses the stored localStorage value if present; otherwise it dynamically imports `@fingerprintjs/fingerprintjs`, generates a visitor id, and persists it. Errors during fingerprint generation are logged but do not block initialization. When `enableFingerprint` is `false`, `initIdentity()` returns immediately and `anonymousId` stays `"unknown"`.
|
|
498
|
+
|
|
499
|
+
### Update Identity
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
import { setUserId, setVisitorId, getIdentity } from "@swifty.js/sentry";
|
|
503
|
+
|
|
504
|
+
setUserId("user-001");
|
|
505
|
+
setVisitorId("visitor-001");
|
|
506
|
+
|
|
507
|
+
const identity = getIdentity();
|
|
508
|
+
// { anonymousId, visitorId, userId, hasAnonymousId, hasVisitorId }
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
`hasAnonymousId` and `hasVisitorId` are simply `value !== "unknown"`.
|
|
512
|
+
|
|
513
|
+
## Manual APIs
|
|
514
|
+
|
|
515
|
+
All manual APIs are exported from `@swifty.js/sentry`.
|
|
516
|
+
|
|
517
|
+
### traceError
|
|
518
|
+
|
|
519
|
+
Manually report an error. The error is routed through the full `handleError` pipeline, which classifies it as a code error, resource error, runtime error, or unknown error.
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import { traceError } from "@swifty.js/sentry";
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
throw new Error("Unexpected state");
|
|
526
|
+
} catch (error) {
|
|
527
|
+
traceError(error);
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### tracePerformance
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
import { tracePerformance } from "@swifty.js/sentry";
|
|
535
|
+
|
|
536
|
+
tracePerformance({
|
|
537
|
+
name: "SearchLatency",
|
|
538
|
+
message: "/api/search",
|
|
539
|
+
value: 128,
|
|
540
|
+
});
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Requires `name` (string), `message` (string), and `value` (number). Reported as `EventType.Performance` with `Status.OK`.
|
|
544
|
+
|
|
545
|
+
### traceCustomEvent
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
import { traceCustomEvent } from "@swifty.js/sentry";
|
|
549
|
+
|
|
550
|
+
traceCustomEvent({
|
|
551
|
+
name: "CheckoutSuccess",
|
|
552
|
+
message: "Submit order",
|
|
553
|
+
extra: {
|
|
554
|
+
orderId: "order-001",
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Requires `name` and `message`; `extra` is optional. Reported as `EventType.Custom` with `Status.OK`.
|
|
560
|
+
|
|
561
|
+
### tracePageView
|
|
562
|
+
|
|
563
|
+
Manually report a page view event. See "Page Views and Dwell Time".
|
|
564
|
+
|
|
565
|
+
### reportFrameworkError
|
|
566
|
+
|
|
567
|
+
Report a framework-level error with an explicit event type and context. The React and Vue integrations use it internally; call it directly to integrate any other framework.
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
import { reportFrameworkError, EventType } from "@swifty.js/sentry";
|
|
571
|
+
|
|
572
|
+
reportFrameworkError({
|
|
573
|
+
type: EventType.OtherFrameworks, // or EventType.React / EventType.Vue
|
|
574
|
+
error: someError,
|
|
575
|
+
context: { component: "svelte-root" },
|
|
576
|
+
});
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
`type` must be `EventType.React`, `EventType.Vue`, or `EventType.OtherFrameworks`. All three fields are required. The reported `name` comes from `error.name`, else the prototype constructor name (or `"Object"`), else `"null"`/`"undefined"`, else `typeof`. The `message` comes from `error.message`, the string itself, `"null"`/`"undefined"`, or JSON serialization (falling back to `String(error)`). The payload `extra` is `{ error, stack, context }`, where `stack` reads `error.stack` for `Error` instances or a string `stack` property on plain objects.
|
|
580
|
+
|
|
581
|
+
## Reporter Hooks
|
|
582
|
+
|
|
583
|
+
Register hooks after initialization or provide equivalent hooks in `init` options. Both forms write to the same option fields, so the later call wins.
|
|
584
|
+
|
|
585
|
+
### Programmatic Hook Registration
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
import { beforeSend, beforeSendBatch, afterSend } from "@swifty.js/sentry";
|
|
589
|
+
|
|
590
|
+
beforeSend((data) => {
|
|
591
|
+
if (data.type === "Click") return false; // drop click events
|
|
592
|
+
return data;
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
beforeSendBatch((eventList) => {
|
|
596
|
+
return eventList.filter((item) => item.status !== "OK");
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
afterSend((eventList) => {
|
|
600
|
+
console.log("reported", eventList.length);
|
|
601
|
+
});
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Equivalent Initialization Form
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
init({
|
|
608
|
+
dsn: "/api/log",
|
|
609
|
+
beforeSend(data) {
|
|
610
|
+
return data;
|
|
611
|
+
},
|
|
612
|
+
beforeSendBatch(eventList) {
|
|
613
|
+
return eventList;
|
|
614
|
+
},
|
|
615
|
+
afterSend(eventList) {
|
|
616
|
+
console.log(eventList.length);
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Hook Behavior
|
|
622
|
+
|
|
623
|
+
- `beforeSend` (`BeforeSendHook`): Receives a single `IReportData`. Return the (possibly modified) data to proceed, or `false` to drop the event. May return a Promise.
|
|
624
|
+
- `beforeSendBatch` (`BeforeSendBatchHook`): Receives the batch array before transport. Return the (possibly filtered) array, or `false` to drop the whole batch. May return a Promise. Returning an empty array (or `false`) schedules another flush instead of sending.
|
|
625
|
+
- `afterSend` (`AfterSendHook`): Receives the batch array after successful transport. The return value is ignored and not awaited.
|
|
626
|
+
- `beforeBreadcrumb` (`BeforeBreadcrumbHook`): Receives `IBreadcrumbItem` before it is stored in the bounded breadcrumb buffer. Must return the (possibly modified) item synchronously. Breadcrumb `userAction` is determined by `event2breadcrumb`: `Error`/`Vue`/`React`/`UnhandledRejection` map to `BreadcrumbType.CodeError`; `Xhr`/`Fetch` to `Http`; `Click` to `Click`; `HashChange`/`History` to `Route`; `Resource` to `Resource`; everything else to `Custom`.
|
|
627
|
+
|
|
628
|
+
## Reporter
|
|
629
|
+
|
|
630
|
+
Reporter is the unified data outlet (`DataReporter` singleton, lazily instantiated on first use). It transforms captured payloads into `IReportData` objects and sends batches to the configured `dsn`. The module-level export uses a `Proxy` to defer singleton construction until the first property access, avoiding side effects at import time.
|
|
631
|
+
|
|
632
|
+
### Report Flow
|
|
633
|
+
|
|
634
|
+
`send(payload, immediate = false)` is called by all handlers and manual APIs:
|
|
635
|
+
|
|
636
|
+
1. `shouldQueuePayload(payload)` -- preflight check:
|
|
637
|
+
- Rejects if `dsn` is empty.
|
|
638
|
+
- Rejects if `Math.random() > tracesSampleRate` (sampling).
|
|
639
|
+
- Sets `sentry.shouldScreenRecord = true` if the payload type is in `screenRecordEventTypes`.
|
|
640
|
+
2. `runBeforeReportHook(id, payload)` -- builds the `IReportData` envelope and applies the `beforeSend` hook (awaiting it if it returns a Promise).
|
|
641
|
+
3. If the hook returned `false`, the event is dropped.
|
|
642
|
+
4. The event is pushed onto the internal `events` array.
|
|
643
|
+
5. If offline, the queue is capped to `maxQueueLength`, persisted to localStorage, and the call returns.
|
|
644
|
+
6. If `immediate` is `true` or `events.length >= cacheMaxLength`, flush immediately.
|
|
645
|
+
7. Otherwise, schedule a flush after `cacheWaitingTime` milliseconds.
|
|
646
|
+
|
|
647
|
+
### Flush Behavior
|
|
648
|
+
|
|
649
|
+
1. Returns early if the queue is empty; an `isFlushing` guard prevents concurrent flush races.
|
|
650
|
+
2. If offline, the queue is capped to `maxQueueLength`, persisted, and the flush aborts.
|
|
651
|
+
3. A batch of up to `cacheMaxLength` items is spliced off the queue head and passed through `beforeSendBatch` (Promise results are awaited). An empty result schedules the next flush.
|
|
652
|
+
4. The batch is JSON-serialized **once**, then sent by transport priority:
|
|
653
|
+
- `navigator.sendBeacon` for bodies up to 60 KB.
|
|
654
|
+
- `fetch` POST with `Content-Type: application/json`. `keepalive: true` is set only when the body is at most 60 KB, because Chromium rejects larger keepalive fetches and the queue head would stall forever.
|
|
655
|
+
5. On transport failure, the batch is prepended back onto the queue, capped, and persisted; the server-recovery probe is armed.
|
|
656
|
+
6. On success, the `afterSend` hook is called.
|
|
657
|
+
7. If events remain, another flush is scheduled after 100 ms.
|
|
658
|
+
|
|
659
|
+
### Offline Cache
|
|
660
|
+
|
|
661
|
+
- Events are persisted to `localStorage` under `offlineCacheKey` (default `"swifty_sentry_offline_cache"`), trimmed to the last `maxQueueLength` entries.
|
|
662
|
+
- On load, cached events are validated against `reportDataListSchema` (zod). Only a valid cache is removed from localStorage; an invalid one is left in place for debugging rather than silently discarded (a `JSON.parse` throw does remove it).
|
|
663
|
+
- The `online` event reloads the cache and flushes; the `offline` event marks the reporter offline.
|
|
664
|
+
- After a failed fetch report the reporter goes offline and probes recovery with `HEAD` requests to `dsn` every `retryIntervalMilliseconds`, re-arming on each failure. The retry timer is unref'd so it never keeps a Node process alive.
|
|
665
|
+
|
|
666
|
+
### Manual Offline Cache Flush
|
|
667
|
+
|
|
668
|
+
```ts
|
|
669
|
+
import { flushOfflineCache } from "@swifty.js/sentry";
|
|
670
|
+
|
|
671
|
+
await flushOfflineCache();
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
`flushOfflineCache` loads the offline cache into the queue and flushes it.
|
|
675
|
+
|
|
676
|
+
## Report Data Schema
|
|
677
|
+
|
|
678
|
+
Each reported event is an `IReportData` object:
|
|
679
|
+
|
|
680
|
+
| Field | Type | Description |
|
|
681
|
+
| ------------- | ------------------- | -------------------------------------------------------- |
|
|
682
|
+
| `id` | `string` | Reporter instance id (`generateUUID()`, secure-context safe), shared by every event from one reporter. |
|
|
683
|
+
| `type` | `EventType` | Event type enum value. |
|
|
684
|
+
| `name` | `string` | Event name. |
|
|
685
|
+
| `message` | `string` | Event message. |
|
|
686
|
+
| `status` | `Status` | `"OK"` or `"Error"`. |
|
|
687
|
+
| `time` | `string` | ISO 8601 formatted time. |
|
|
688
|
+
| `timestamp` | `number` | Numeric timestamp (`Date.now()`). |
|
|
689
|
+
| `url` | `string` | Current page URL (`location.href`). |
|
|
690
|
+
| `userId` | `string` | User identifier. |
|
|
691
|
+
| `anonymousId` | `string` | FingerprintJS anonymous visitor id (`"unknown"` when disabled). |
|
|
692
|
+
| `visitorId` | `string` | Backend-bound visitor id (`"unknown"` until `setVisitorId`). |
|
|
693
|
+
| `projectId` | `string` | Project identifier. |
|
|
694
|
+
| `sdkVersion` | `string` | SDK version from package.json. |
|
|
695
|
+
| `breadcrumbs` | `IBreadcrumbItem[]` | Present **only** for error-class types (see below). |
|
|
696
|
+
| `deviceInfo` | `IDeviceInfo` | Device, browser, OS, language, and screen data (lazily collected on first report). |
|
|
697
|
+
| `payload` | `TReportPayload` | Original event payload, including its own `id`. |
|
|
698
|
+
|
|
699
|
+
Breadcrumbs are the trail leading up to a failure, so they are attached only to `Error`, `UnhandledRejection`, `Resource`, `Vue`, `React`, and `OtherFrameworks` events. Attaching them to every batched event would multiply payload size for no diagnostic value.
|
|
700
|
+
|
|
701
|
+
## Plugin System
|
|
702
|
+
|
|
703
|
+
Plugins extend the SDK without coupling optional capabilities to the core entry. A plugin class extends the abstract `SentryPlugin` base class (exported from `@swifty.js/sentry`), implements `init()`, and optionally implements `destroy()` for cleanup.
|
|
704
|
+
|
|
705
|
+
```ts
|
|
706
|
+
abstract class SentryPlugin {
|
|
707
|
+
abstract init(): void;
|
|
708
|
+
destroy?(): void;
|
|
709
|
+
}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
Custom plugin:
|
|
713
|
+
|
|
714
|
+
```ts
|
|
715
|
+
import { SentryPlugin, enablePlugin } from "@swifty.js/sentry";
|
|
716
|
+
|
|
717
|
+
class HeartbeatPlugin extends SentryPlugin {
|
|
718
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
719
|
+
|
|
720
|
+
init(): void {
|
|
721
|
+
this.timer = setInterval(() => {
|
|
722
|
+
/* traceCustomEvent(...) */
|
|
723
|
+
}, 30_000);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
override destroy(): void {
|
|
727
|
+
if (this.timer) clearInterval(this.timer);
|
|
728
|
+
this.timer = null;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
enablePlugin(new HeartbeatPlugin());
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
Enabled plugins live in an internal `Set<SentryPlugin>`. `destroy()` calls each plugin's `destroy()` when available and clears the set.
|
|
736
|
+
|
|
737
|
+
## PerformancePlugin
|
|
738
|
+
|
|
739
|
+
```ts
|
|
740
|
+
import { enablePlugin } from "@swifty.js/sentry";
|
|
741
|
+
import { PerformancePlugin } from "@swifty.js/sentry/plugins";
|
|
742
|
+
|
|
743
|
+
enablePlugin(new PerformancePlugin());
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Takes no constructor options. Every metric is reported as an `EventType.Performance` event; the `name` field identifies the metric:
|
|
747
|
+
|
|
748
|
+
| Reported `name` | Source |
|
|
749
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------- |
|
|
750
|
+
| `LCP`, `FCP`, `CLS`, `INP`, `TTFB` | Web Vitals via the `web-vitals` library, carrying `value` and `rating`. The metric's own `id` overwrites the payload `id`. |
|
|
751
|
+
| `FSP` | First Screen Paint -- a `MutationObserver` tracks the latest in-viewport DOM mutation timestamp (excluding `link`/`script`/`style`), resolved via a `requestAnimationFrame` loop once `document.readyState === "complete"`; a pending observation is cancelled by `destroy()`. |
|
|
752
|
+
| `NavigationTiming` | Page-load metrics in `extra`: paintTime, domInteractive, domContentLoaded, loadEvent, firstByte, dnsLookup, tcpConnection, tlsHandshake, timeToFirstByte, contentTransfer, domProcessing, resourceLoad, redirect, unloadTime, triggerPageUrl. Reported on page ready. |
|
|
753
|
+
| `ResourceList` | Snapshot of all buffered `resource` entries at page ready, in `resourceList`. |
|
|
754
|
+
| `ResourceTiming` | One event per live `resource` entry from `PerformanceObserver`, with `value` = duration and `extra.resource`. |
|
|
755
|
+
| `LongTask` | `PerformanceObserver` for `longtask`, entries in `longTasks`. |
|
|
756
|
+
| `Memory` | `performance.measureUserAgentSpecificMemory()` result in `memory`, when supported. |
|
|
757
|
+
|
|
758
|
+
Resource collection excludes `fetch`, `xmlhttprequest`, and `beacon` initiator types, and any URL containing the SDK `dsn`. `fromCache` is derived from `transferSize === 0` or an empty `encodedBodySize`.
|
|
759
|
+
|
|
760
|
+
When `PerformanceObserver` does not support the `resource` entry type, a `MutationObserver` fallback watches for inserted `<img>`, `<script>`, and `<link>` elements and reports on their `load`/`error` (once per URL), reusing real `PerformanceResourceTiming` data when it exists or a zero-duration fallback object otherwise.
|
|
761
|
+
|
|
762
|
+
All capability checks go through `supportsPerformanceEntryType()`, which reads `PerformanceObserver.supportedEntryTypes`. Unsupported capabilities are skipped safely. `destroy()` runs all registered cleanups in reverse order.
|
|
763
|
+
|
|
764
|
+
## ScreenRecordPlugin
|
|
765
|
+
|
|
766
|
+
```ts
|
|
767
|
+
import { enablePlugin } from "@swifty.js/sentry";
|
|
768
|
+
import {
|
|
769
|
+
ScreenRecordPlugin,
|
|
770
|
+
unzipScreenRecord,
|
|
771
|
+
type ScreenRecordPluginOptions,
|
|
772
|
+
} from "@swifty.js/sentry/plugins";
|
|
773
|
+
|
|
774
|
+
enablePlugin(new ScreenRecordPlugin());
|
|
775
|
+
|
|
776
|
+
// With custom options
|
|
777
|
+
enablePlugin(new ScreenRecordPlugin({ durationMs: 5000 }));
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Screen recording is based on `@rrweb/record`. The plugin keeps a rolling record window; when a selected error or network event occurs, the recent window is reported as a `ScreenRecord` event.
|
|
781
|
+
|
|
782
|
+
### Constructor Options (`ScreenRecordPluginOptions`)
|
|
783
|
+
|
|
784
|
+
| Option | Type | Default | Description |
|
|
785
|
+
| ------------ | ------------- | --------------------------------------------------- | ----------------------------------- |
|
|
786
|
+
| `durationMs` | `number` | `3000` | Rolling record window length in ms. |
|
|
787
|
+
| `eventTypes` | `EventType[]` | `[Error, Xhr, Fetch, Resource, UnhandledRejection]` | Event types that trigger reporting. |
|
|
788
|
+
|
|
789
|
+
### How It Works
|
|
790
|
+
|
|
791
|
+
1. `init()` writes `screenRecordEventTypes` and `screenRecordDurationMs` from the constructor options into the SDK options via `sentry.setOptions` (arrays are copied, so plugin instances never share option array references). Pass `eventTypes`/`durationMs` to the constructor to configure the trigger set and window length.
|
|
792
|
+
2. `@rrweb/record` and `pako` are dynamically imported, then `record()` starts with `recordCanvas: true` and `checkoutEveryNms` set to `durationMs`. A load failure is logged and the plugin degrades to a no-op.
|
|
793
|
+
3. Emitted events are validated (`{ timestamp: number }`, loose object) and kept in a rolling window pruned in place to the last `screenRecordDurationMs` milliseconds.
|
|
794
|
+
4. When `sentry.shouldScreenRecord` is `true` (set by `shouldQueuePayload` for matching event types) and the window is non-empty, the window is JSON-serialized, gzip-compressed with `pako.gzip`, base64-encoded (in 32 KB chunks) into the payload's `event` field, and reported with `name: "ScreenRecord"` and `eventCount`.
|
|
795
|
+
5. `sentry.shouldScreenRecord` is reset to `false` after reporting.
|
|
796
|
+
6. `destroy()` calls the `stopRecord` function returned by rrweb.
|
|
797
|
+
|
|
798
|
+
### Decode Record Payload
|
|
799
|
+
|
|
800
|
+
```ts
|
|
801
|
+
const events = await unzipScreenRecord(recordPayload);
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
`unzipScreenRecord(data: string): Promise<unknown>` base64-decodes, `pako.ungzip`-decompresses, then JSON-parses. It returns `null` for empty input and dynamically imports `pako` when the plugin has not loaded it yet, so it works in any context.
|
|
805
|
+
|
|
806
|
+
## ExposurePlugin
|
|
807
|
+
|
|
808
|
+
```ts
|
|
809
|
+
import { enablePlugin } from "@swifty.js/sentry";
|
|
810
|
+
import { ExposurePlugin } from "@swifty.js/sentry/plugins";
|
|
811
|
+
|
|
812
|
+
const exposure = new ExposurePlugin();
|
|
813
|
+
enablePlugin(exposure);
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
Exposure tracking uses `IntersectionObserver` to measure how long elements are visible in the viewport. Because `enablePlugin` returns `void`, keep your own reference to the instance.
|
|
817
|
+
|
|
818
|
+
### Observe a Single Element
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
const element = document.querySelector("#banner");
|
|
822
|
+
|
|
823
|
+
if (element) {
|
|
824
|
+
exposure.observe({
|
|
825
|
+
target: element,
|
|
826
|
+
threshold: 0.5,
|
|
827
|
+
params: {
|
|
828
|
+
bannerId: "spring-001",
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Observe Multiple Elements
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
const first = document.querySelector("#first");
|
|
838
|
+
const second = document.querySelector("#second");
|
|
839
|
+
|
|
840
|
+
if (first && second) {
|
|
841
|
+
exposure.observe([
|
|
842
|
+
{
|
|
843
|
+
target: first,
|
|
844
|
+
threshold: 0.5,
|
|
845
|
+
params: { position: "first" },
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
target: second,
|
|
849
|
+
threshold: 0.75,
|
|
850
|
+
params: { position: "second" },
|
|
851
|
+
},
|
|
852
|
+
]);
|
|
853
|
+
}
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### Observe Parameters
|
|
857
|
+
|
|
858
|
+
| Parameter | Type | Default | Description |
|
|
859
|
+
| ----------- | ------------------------- | -------- | ----------------------------------------- |
|
|
860
|
+
| `target` | `Element` | required | The DOM element to observe. |
|
|
861
|
+
| `threshold` | `number` (0-1) | `0.5` | Intersection ratio threshold. |
|
|
862
|
+
| `params` | `Record<string, unknown>` | `{}` | Custom parameters included in the report. |
|
|
863
|
+
|
|
864
|
+
All inputs are validated with zod (`exposureTargetSchema`), so an invalid `target` or out-of-range `threshold` throws. An explicit `threshold` of `0` is respected (the code uses `item.threshold ?? 0.5`, so only an omitted threshold falls back to `0.5`). Re-observing an element already in the internal map is a no-op, so the original `threshold` and `params` are kept.
|
|
865
|
+
|
|
866
|
+
### Cancel Observation
|
|
867
|
+
|
|
868
|
+
```ts
|
|
869
|
+
exposure.unobserve(element);
|
|
870
|
+
exposure.unobserve([first, second]);
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Exposure Event Payload
|
|
874
|
+
|
|
875
|
+
An exposure event is reported when an observed element leaves the viewport after having been visible. Reported with `name: "Exposure"`, `message: "Element Exposure"`, and `Status.OK`; the payload `extra` contains:
|
|
876
|
+
|
|
877
|
+
| Field | Type | Description |
|
|
878
|
+
| ------------- | ------------------------- | -------------------------------------- |
|
|
879
|
+
| `threshold` | `number` | Intersection ratio threshold. |
|
|
880
|
+
| `observeTime` | `number` | Timestamp when observation started. |
|
|
881
|
+
| `showTime` | `number` | Timestamp when element became visible. |
|
|
882
|
+
| `showEndTime` | `number` | Timestamp when element left viewport. |
|
|
883
|
+
| `duration` | `number` | `showEndTime - showTime` in ms. |
|
|
884
|
+
| `params` | `Record<string, unknown>` | User-provided custom parameters. |
|
|
885
|
+
|
|
886
|
+
An element that is observed but never becomes visible reports nothing, and elements still visible at teardown are not flushed.
|
|
887
|
+
|
|
888
|
+
### IntersectionObserver Management
|
|
889
|
+
|
|
890
|
+
The plugin creates one `IntersectionObserver` per unique `threshold` value and reuses it for all elements with that threshold. `unobserve` calls the matching observer's `unobserve()` and removes the element from the internal `targetMap`. `destroy()` disconnects all observers and clears both maps.
|
|
891
|
+
|
|
892
|
+
## React Integration
|
|
893
|
+
|
|
894
|
+
```tsx
|
|
895
|
+
import { init } from "@swifty.js/sentry";
|
|
896
|
+
import { ReactErrorBoundary } from "@swifty.js/sentry/react";
|
|
897
|
+
|
|
898
|
+
init({ dsn: "/api/log" });
|
|
899
|
+
|
|
900
|
+
export function App() {
|
|
901
|
+
return (
|
|
902
|
+
<ReactErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
903
|
+
<Page />
|
|
904
|
+
</ReactErrorBoundary>
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### Fallback Prop
|
|
910
|
+
|
|
911
|
+
`fallback` can be a ReactNode or a render function. `errorInfo` is **optional** in the render function: the boundary renders the fallback from `getDerivedStateFromError` during the render phase, before React delivers `ErrorInfo` in `componentDidCatch`, so the function may be called once with `errorInfo` undefined and again once it is available.
|
|
912
|
+
|
|
913
|
+
```tsx
|
|
914
|
+
<ReactErrorBoundary
|
|
915
|
+
fallback={(error, errorInfo) => (
|
|
916
|
+
<div>
|
|
917
|
+
{error.message}
|
|
918
|
+
{errorInfo?.componentStack}
|
|
919
|
+
</div>
|
|
920
|
+
)}
|
|
921
|
+
>
|
|
922
|
+
<Page />
|
|
923
|
+
</ReactErrorBoundary>
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
### ReactErrorBoundaryProps
|
|
927
|
+
|
|
928
|
+
| Prop | Type | Description |
|
|
929
|
+
| ---------- | ------------------------------------------------------------------- | -------------------------------------------- |
|
|
930
|
+
| `children` | `ReactNode` (optional) | Child components to render. |
|
|
931
|
+
| `fallback` | `ReactNode \| ((error: Error, errorInfo?: ErrorInfo) => ReactNode)` (optional) | Error UI to display when an error is caught. |
|
|
932
|
+
|
|
933
|
+
### Behavior
|
|
934
|
+
|
|
935
|
+
- `static displayName = "ReactErrorBoundary"` keeps the React 16 component stack readable.
|
|
936
|
+
- `static getDerivedStateFromError(error)` sets `{ error }` in the render phase so the fallback appears immediately.
|
|
937
|
+
- `componentDidCatch(error, errorInfo)` merges `{ error, errorInfo }` into state and reports an `EventType.React` event via `reportFrameworkError` with `context: errorInfo`.
|
|
938
|
+
- `render()` returns the fallback (or `null` when no `fallback` is provided) while in the error state, otherwise `children ?? null`.
|
|
939
|
+
|
|
940
|
+
**Important limitation**: React ErrorBoundary does not catch asynchronous callback errors, event handler errors, or errors in server-side rendering. Use `traceError` for those cases.
|
|
941
|
+
|
|
942
|
+
## Vue 3 Integration
|
|
943
|
+
|
|
944
|
+
```ts
|
|
945
|
+
import { createApp } from "vue";
|
|
946
|
+
import { vuePlugin } from "@swifty.js/sentry/vue";
|
|
947
|
+
import App from "./app.vue";
|
|
948
|
+
|
|
949
|
+
const app = createApp(App);
|
|
950
|
+
|
|
951
|
+
app.use(vuePlugin, {
|
|
952
|
+
dsn: "/api/log",
|
|
953
|
+
projectId: "vue-app",
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
app.mount("#app");
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### Behavior
|
|
960
|
+
|
|
961
|
+
`vuePlugin` is a Vue `Plugin` that:
|
|
962
|
+
|
|
963
|
+
1. Captures the existing `app.config.errorHandler`.
|
|
964
|
+
2. Installs a new `app.config.errorHandler` that reports an `EventType.Vue` event via `reportFrameworkError` with `context: { vueInstance, info }`.
|
|
965
|
+
3. Calls the previous error handler if one existed.
|
|
966
|
+
4. Calls `init(options)` with the provided options.
|
|
967
|
+
|
|
968
|
+
The plugin accepts the same `InitOptions` as `init()`.
|
|
969
|
+
|
|
970
|
+
## Vite Dev-Server Plugin
|
|
971
|
+
|
|
972
|
+
The SDK provides a Vite plugin that creates a mock report endpoint during development, writing reported data to log files instead of sending it to a real server.
|
|
973
|
+
|
|
974
|
+
```ts
|
|
975
|
+
// vite.config.ts
|
|
976
|
+
import { defineConfig } from "vite";
|
|
977
|
+
import { sentryPlugin } from "@swifty.js/sentry/vite";
|
|
978
|
+
|
|
979
|
+
export default defineConfig({
|
|
980
|
+
// `dsn` should match the @swifty.js/sentry `init({ dsn: "/api/log" })` dsn value
|
|
981
|
+
plugins: [sentryPlugin({ dsn: "/api/log" })],
|
|
982
|
+
});
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
### Available Exports
|
|
986
|
+
|
|
987
|
+
| Export | Vite Version | Description |
|
|
988
|
+
| --------------- | ------------ | --------------------------------------- |
|
|
989
|
+
| `sentryPlugin` | Vite 8 | Default export. For current Vite. |
|
|
990
|
+
| `sentryPlugin7` | Vite 7 | For projects using Vite 7 specifically. |
|
|
991
|
+
|
|
992
|
+
`sentryPlugin` and `sentryPlugin7` both default their options to `{}` and share the same `ISentryPluginOptions` type.
|
|
993
|
+
|
|
994
|
+
### Options
|
|
995
|
+
|
|
996
|
+
| Option | Type | Default | Description |
|
|
997
|
+
| ------ | -------- | ----------- | --------------------------------------------------- |
|
|
998
|
+
| `dsn` | `string` | `undefined` | URL path to intercept. Falls back to `"/sentry"`. |
|
|
999
|
+
|
|
1000
|
+
### Behavior
|
|
1001
|
+
|
|
1002
|
+
- Only active for the dev server (`apply: "serve"`); `vite build` is untouched and never creates a `logs/` directory.
|
|
1003
|
+
- When the dev server starts (`configureServer`), creates a `logs/` directory in `process.cwd()` and appends to a timestamped `sentry_YYYYMMDDHHMMSS.jsonl` file.
|
|
1004
|
+
- Intercepts POST requests whose `req.url` equals the resolved dsn exactly.
|
|
1005
|
+
- Parses the request body with `JSON.parse` and enriches error records with original source positions resolved from the dev server's in-memory module graph source maps (see "Dev-Time Source Map Resolution").
|
|
1006
|
+
- Writes each enriched report batch as one JSON line. If parsing or enrichment throws, the raw body is written unmodified.
|
|
1007
|
+
- Always responds `200` with `{ code: 0, message: "success" }`.
|
|
1008
|
+
- Closes the log stream in the `closeBundle` hook when one was created.
|
|
1009
|
+
|
|
1010
|
+
## Webpack Dev-Server Plugin
|
|
1011
|
+
|
|
1012
|
+
The `@swifty.js/sentry/webpack` subpath provides the same mock report endpoint for webpack-dev-server, plus source map resolution based on emitted `.map` assets. Requires `webpack` and `webpack-dev-server` as dev dependencies.
|
|
1013
|
+
|
|
1014
|
+
### Available Exports
|
|
1015
|
+
|
|
1016
|
+
| Export | Description |
|
|
1017
|
+
| --------------------- | ----------------------------------------------------------------------------- |
|
|
1018
|
+
| `sentryPlugin` | Factory returning a `SentryWebpackPlugin` instance. Default export. |
|
|
1019
|
+
| `SentryWebpackPlugin` | Webpack plugin class (`WebpackPluginInstance`). |
|
|
1020
|
+
| `sentryMiddleware` | Connect/express-style middleware for manual mounting (no source map support). |
|
|
1021
|
+
| `SentryDevMiddleware` | Type of the middleware function. |
|
|
1022
|
+
|
|
1023
|
+
All accept `{ dsn?: string }` (`ISentryWebpackPluginOptions`). The dsn resolves like the Vite plugin: option value, else `"/sentry"`.
|
|
1024
|
+
|
|
1025
|
+
### Plugin Usage (recommended)
|
|
1026
|
+
|
|
1027
|
+
```ts
|
|
1028
|
+
// webpack.config.mjs
|
|
1029
|
+
import { sentryPlugin } from "@swifty.js/sentry/webpack";
|
|
1030
|
+
|
|
1031
|
+
export default {
|
|
1032
|
+
plugins: [sentryPlugin({ dsn: "/api/log" })],
|
|
1033
|
+
devServer: {
|
|
1034
|
+
// ...
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
Behavior:
|
|
1040
|
+
|
|
1041
|
+
- No-op unless `compiler.options.devServer` exists, so production builds remain untouched.
|
|
1042
|
+
- Wraps `devServer.setupMiddlewares` (calling any user-provided setup first) and unshifts the mock middleware named `"sentry-mock"`. The middleware entry deliberately omits `path` because webpack-dev-server's `{ name, path, middleware }` form delegates to `app.use(path, middleware)`, which strips the prefix from `req.url` and would break the `req.url === dsn` match.
|
|
1043
|
+
- Taps `compiler.hooks.assetEmitted` to collect emitted `.map` assets (works with the in-memory dev-server file system) into an asset map store. Reported script URLs resolve to `<path>.map`; unmatched URLs fall back to basename matching to tolerate unknown `publicPath` prefixes.
|
|
1044
|
+
- Writes `logs/sentry_YYYYMMDDHHMMSS.jsonl` and responds `{ code: 0, message: "success" }`, same as the Vite plugin.
|
|
1045
|
+
- Closes the log stream on `compiler.hooks.shutdown`.
|
|
1046
|
+
|
|
1047
|
+
### Middleware Usage (manual)
|
|
1048
|
+
|
|
1049
|
+
```ts
|
|
1050
|
+
import { sentryMiddleware } from "@swifty.js/sentry/webpack";
|
|
1051
|
+
|
|
1052
|
+
export default {
|
|
1053
|
+
devServer: {
|
|
1054
|
+
setupMiddlewares(middlewares) {
|
|
1055
|
+
middlewares.unshift({
|
|
1056
|
+
name: "sentry-mock",
|
|
1057
|
+
middleware: sentryMiddleware({ dsn: "/api/log" }),
|
|
1058
|
+
});
|
|
1059
|
+
return middlewares;
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
};
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
`sentryMiddleware` writes raw reports without source map enrichment (it has no access to compiler assets).
|
|
1066
|
+
|
|
1067
|
+
## Dev-Time Source Map Resolution
|
|
1068
|
+
|
|
1069
|
+
Both dev-server plugins enrich reported error records with original source positions before writing them to the log file. The shared resolver lives in `src/source-map/` (Node-only, never bundled into the browser SDK) and uses the `source-map` library.
|
|
1070
|
+
|
|
1071
|
+
### Which Records Are Enriched
|
|
1072
|
+
|
|
1073
|
+
For each record in a reported batch, the first matching rule applies:
|
|
1074
|
+
|
|
1075
|
+
1. `type === "Error"` with a string `name` and numeric `payload.line` / `payload.column` -- resolves a single frame using the record `name`, which holds the script URL for code errors.
|
|
1076
|
+
2. `payload.extra` is a stack-like string (matches `at url:line:col` or `fn@url:line:col`) -- parses and resolves up to 30 frames.
|
|
1077
|
+
3. `type === "React"`, `"Vue"`, or `"OtherFrameworks"` -- reads the stack from `payload.extra.stack` (where `reportFrameworkError` nests it), falling back to a string `payload.stack` for older payload shapes, then resolves up to 30 frames.
|
|
1078
|
+
|
|
1079
|
+
Records that produce at least one frame gain a `sourcemap: { frames: ResolvedFrame[] }` field; all other records pass through unchanged. A non-array batch body is returned as-is.
|
|
1080
|
+
|
|
1081
|
+
### ResolvedFrame Fields
|
|
1082
|
+
|
|
1083
|
+
| Field | Description |
|
|
1084
|
+
| -------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
1085
|
+
| `resolved` | `false` when no source map matched (raw frame passthrough). |
|
|
1086
|
+
| `url`, `line`, `column`, `func` | Raw frame parsed from the stack (Chrome and Firefox stack formats). |
|
|
1087
|
+
| `source`, `originalLine`, `originalColumn`, `name` | Original position resolved from the source map. |
|
|
1088
|
+
| `snippet` | `SnippetLine[]` -- original source lines, the error line plus 3 lines of context on each side, with `highlight: true` on the error line, when `sourcesContent` is available. |
|
|
1089
|
+
|
|
1090
|
+
### Map Loading
|
|
1091
|
+
|
|
1092
|
+
- **Vite**: source maps come from `server.moduleGraph.getModuleByUrl(...).transformResult.map`, trying `pathname + search` first, then `pathname` alone.
|
|
1093
|
+
- **Webpack**: source maps come from `.map` assets collected via the `assetEmitted` compiler hook.
|
|
1094
|
+
- Candidate maps are validated with a zod schema (`version`, `sources`, `names`, `mappings`) before use.
|
|
1095
|
+
- Browser stack line/column values are 1-based; the resolver converts columns to 0-based before querying the source map.
|
|
1096
|
+
- All resolution failures are silent: the frame is kept with `resolved: false`, and a record-level failure writes the raw body.
|
|
1097
|
+
|
|
1098
|
+
## Debug Logging
|
|
1099
|
+
|
|
1100
|
+
SDK console output is disabled by default. Set `debug: true` to enable styled, collapsed console groups for all SDK activity (event capture, report queueing, transport results with elapsed time, plugin initialization, and so on).
|
|
1101
|
+
|
|
1102
|
+
```ts
|
|
1103
|
+
init({
|
|
1104
|
+
dsn: "/api/log",
|
|
1105
|
+
debug: true, // enable console output
|
|
1106
|
+
});
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
The logger reads `globalThis.__sentry__.options.debug` on every call, so toggling `debug` at runtime takes effect immediately:
|
|
1110
|
+
|
|
1111
|
+
```ts
|
|
1112
|
+
globalThis.__sentry__?.setOptions({ debug: false });
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
The logger's error output uses a native `console.error` reference captured before the SDK decorates `console.error`, so enabling `debug` never causes the SDK to report its own log lines as errors.
|
|
1116
|
+
|
|
1117
|
+
The `sentry` singleton is assigned to `globalThis.__sentry__` on first access, which also makes it a convenient debugging handle for inspecting live options and `deviceInfo`.
|
|
1118
|
+
|
|
1119
|
+
## Browser Compatibility
|
|
1120
|
+
|
|
1121
|
+
- `sendBeacon` is preferred for batches up to 60 KB; `fetch` POST is the fallback, using `keepalive` only for bodies up to 60 KB.
|
|
1122
|
+
- `PerformanceObserver` powers Web Vitals, long task, and resource timing when available.
|
|
1123
|
+
- `MutationObserver` powers first-screen paint and the dynamic-resource fallback.
|
|
1124
|
+
- `IntersectionObserver` is required by `ExposurePlugin`.
|
|
1125
|
+
- `requestIdleCallback` is used opportunistically by white-screen sampling, with a direct-call fallback.
|
|
1126
|
+
- `performance.measureUserAgentSpecificMemory` is optional (Chrome-only).
|
|
1127
|
+
- `@rrweb/record` and `pako` are dynamically imported only by `ScreenRecordPlugin`.
|
|
1128
|
+
- `@fingerprintjs/fingerprintjs` is dynamically imported only when `enableFingerprint: true`.
|
|
1129
|
+
- UUIDs come from `generateUUID()`: `crypto.randomUUID` when available, else a `crypto.getRandomValues`-based v4 fallback, so the SDK works on insecure (plain-http) contexts.
|
|
1130
|
+
- `localStorage`/`sessionStorage` access is wrapped in try/catch, so private-mode or blocked-storage browsers fall back to per-call UUIDs.
|
|
1131
|
+
|
|
1132
|
+
## Session and Device Identity
|
|
1133
|
+
|
|
1134
|
+
The SDK automatically generates and persists:
|
|
1135
|
+
|
|
1136
|
+
| Key | Storage | Description |
|
|
1137
|
+
| ---------------------------- | -------------- | ----------------------------------------- |
|
|
1138
|
+
| `swifty_sentry_device_id` | localStorage | Persistent device identifier (UUID). |
|
|
1139
|
+
| `swifty_sentry_session_id` | sessionStorage | Session identifier (UUID, reset per tab). |
|
|
1140
|
+
| `swifty_sentry_anonymous_id` | localStorage | FingerprintJS visitor id (when enabled). |
|
|
1141
|
+
|
|
1142
|
+
## Production Configuration Example
|
|
1143
|
+
|
|
1144
|
+
```ts
|
|
1145
|
+
import { init, enablePlugin, beforeSend } from "@swifty.js/sentry";
|
|
1146
|
+
import {
|
|
1147
|
+
PerformancePlugin,
|
|
1148
|
+
ScreenRecordPlugin,
|
|
1149
|
+
ExposurePlugin,
|
|
1150
|
+
} from "@swifty.js/sentry/plugins";
|
|
1151
|
+
|
|
1152
|
+
init({
|
|
1153
|
+
dsn: "https://example.com/api/log",
|
|
1154
|
+
projectId: "production-web",
|
|
1155
|
+
userId: "unknown",
|
|
1156
|
+
enableFingerprint: true,
|
|
1157
|
+
enableHttpPerformance: true,
|
|
1158
|
+
tracesSampleRate: 1,
|
|
1159
|
+
debug: false, // set true for dev troubleshooting
|
|
1160
|
+
excludeApis: ["https://example.com/api/log"],
|
|
1161
|
+
ignoreErrors: [/ResizeObserver loop limit exceeded/],
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
const exposure = new ExposurePlugin();
|
|
1165
|
+
enablePlugin(new PerformancePlugin(), new ScreenRecordPlugin(), exposure);
|
|
1166
|
+
|
|
1167
|
+
beforeSend((data) => {
|
|
1168
|
+
// Inspect or transform every report; return false to drop it
|
|
1169
|
+
return data;
|
|
1170
|
+
});
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
## Common Integration Patterns
|
|
1174
|
+
|
|
1175
|
+
### SPA with React Router
|
|
1176
|
+
|
|
1177
|
+
```tsx
|
|
1178
|
+
import { init, enablePlugin } from "@swifty.js/sentry";
|
|
1179
|
+
import { ReactErrorBoundary } from "@swifty.js/sentry/react";
|
|
1180
|
+
import { PerformancePlugin } from "@swifty.js/sentry/plugins";
|
|
1181
|
+
|
|
1182
|
+
init({
|
|
1183
|
+
dsn: "/api/log",
|
|
1184
|
+
projectId: "spa-app",
|
|
1185
|
+
enableHistory: true, // track pushState/replaceState/popstate
|
|
1186
|
+
enableHashChange: true, // track hash navigation
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
enablePlugin(new PerformancePlugin());
|
|
1190
|
+
|
|
1191
|
+
export function App() {
|
|
1192
|
+
return (
|
|
1193
|
+
<ReactErrorBoundary fallback={<div>Error occurred</div>}>
|
|
1194
|
+
<Router />
|
|
1195
|
+
</ReactErrorBoundary>
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
### Vue 3 Application
|
|
1201
|
+
|
|
1202
|
+
```ts
|
|
1203
|
+
import { createApp } from "vue";
|
|
1204
|
+
import { vuePlugin } from "@swifty.js/sentry/vue";
|
|
1205
|
+
import { enablePlugin } from "@swifty.js/sentry";
|
|
1206
|
+
import { PerformancePlugin } from "@swifty.js/sentry/plugins";
|
|
1207
|
+
import App from "./app.vue";
|
|
1208
|
+
|
|
1209
|
+
const app = createApp(App);
|
|
1210
|
+
|
|
1211
|
+
app.use(vuePlugin, {
|
|
1212
|
+
dsn: "/api/log",
|
|
1213
|
+
projectId: "vue-app",
|
|
1214
|
+
enableHistory: true,
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
app.mount("#app");
|
|
1218
|
+
|
|
1219
|
+
enablePlugin(new PerformancePlugin());
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
### Micro-Frontend Setup
|
|
1223
|
+
|
|
1224
|
+
```ts
|
|
1225
|
+
import { init, destroy, isInitialized } from "@swifty.js/sentry";
|
|
1226
|
+
|
|
1227
|
+
// Mount
|
|
1228
|
+
if (!isInitialized()) {
|
|
1229
|
+
init({ dsn: "/api/log", projectId: "micro-frontend" });
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Unmount
|
|
1233
|
+
destroy();
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
### E-Commerce with Exposure Tracking
|
|
1237
|
+
|
|
1238
|
+
```ts
|
|
1239
|
+
import { init, enablePlugin } from "@swifty.js/sentry";
|
|
1240
|
+
import { ExposurePlugin } from "@swifty.js/sentry/plugins";
|
|
1241
|
+
|
|
1242
|
+
init({ dsn: "/api/log" });
|
|
1243
|
+
|
|
1244
|
+
const exposure = new ExposurePlugin();
|
|
1245
|
+
enablePlugin(exposure);
|
|
1246
|
+
|
|
1247
|
+
// Track product card visibility
|
|
1248
|
+
document.querySelectorAll(".product-card").forEach((card) => {
|
|
1249
|
+
exposure.observe({
|
|
1250
|
+
target: card,
|
|
1251
|
+
threshold: 0.5,
|
|
1252
|
+
params: {
|
|
1253
|
+
productId: card.getAttribute("data-product-id"),
|
|
1254
|
+
position: card.getAttribute("data-position"),
|
|
1255
|
+
},
|
|
1256
|
+
});
|
|
1257
|
+
});
|
|
1258
|
+
```
|
|
1259
|
+
|
|
1260
|
+
### Declarative Click Tracking in Templates
|
|
1261
|
+
|
|
1262
|
+
```html
|
|
1263
|
+
<nav swifty-sentry-el="main-nav">
|
|
1264
|
+
<a swifty-sentry-ev="nav-home" swifty-sentry-msg="Go to homepage" href="/"
|
|
1265
|
+
>Home</a
|
|
1266
|
+
>
|
|
1267
|
+
<a
|
|
1268
|
+
swifty-sentry-ev="nav-products"
|
|
1269
|
+
swifty-sentry-msg="Browse products"
|
|
1270
|
+
href="/products"
|
|
1271
|
+
>Products</a
|
|
1272
|
+
>
|
|
1273
|
+
<button
|
|
1274
|
+
swifty-sentry-ev="nav-search"
|
|
1275
|
+
swifty-sentry-msg="Open search"
|
|
1276
|
+
swifty-sentry-type="icon"
|
|
1277
|
+
>
|
|
1278
|
+
Search
|
|
1279
|
+
</button>
|
|
1280
|
+
</nav>
|
|
1281
|
+
|
|
1282
|
+
<section swifty-sentry-el="product-list" swifty-sentry-category="electronics">
|
|
1283
|
+
<article
|
|
1284
|
+
swifty-sentry-ev="product-click"
|
|
1285
|
+
swifty-sentry-msg="View product"
|
|
1286
|
+
swifty-sentry-sku="SKU-001"
|
|
1287
|
+
>
|
|
1288
|
+
Product Name
|
|
1289
|
+
</article>
|
|
1290
|
+
</section>
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Note that `params` are read from the nearest single element carrying `swifty-sentry-*` attributes, so a click on the `<article>` above reports `{ sku: "SKU-001" }` -- not the section's `category`. Duplicate any attribute you need on the element that will actually be clicked.
|