@inso_web/els-react 0.4.1 → 0.5.0

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/README.md CHANGED
@@ -5,73 +5,62 @@
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![license MIT](https://img.shields.io/npm/l/@inso_web/els-react.svg)](./LICENSE)
7
7
 
8
- React-обёртка для **Error Logs Service (ELS)**: `<ELSProvider>` для DI клиента, hook `useELS()`, готовый `<ErrorBoundary>` с автоматической отправкой ошибок render-фазы. React 17+.
8
+ React bindings for the **Inso Error Logs Service (ELS)** — a managed SaaS for centralised event logging (debug → fatal) with AI-assisted error triage. Ships `<ELSProvider>` for DI, `useELS()` hook, a ready-made `<ELSErrorBoundary>` that auto-reports render-phase errors, and a `useGlobalErrorHandlers()` hook for `window` listeners. React 17+.
9
9
 
10
- ## Что внутри
11
-
12
- - `<ELSProvider client={...}>` — кладёт клиент в контекст.
13
- - `useELS()` — hook возвращает logger.
14
- - `<ELSErrorBoundary>` — error boundary который ловит render-ошибки и шлёт в ELS со stack trace + componentStack.
15
- - `useGlobalErrorHandlers()` — hook для подписки на `window.error` и `unhandledrejection`.
10
+ > 🇷🇺 [Русская версия → README_RU.md](README_RU.md)
16
11
 
17
12
  ---
18
13
 
19
- ## UI: что вы получаете
20
-
21
- ELS из коробки даёт админ-панель — все события из вашего React приложения попадают в неё.
22
-
23
- ### Список логов с фильтрами
24
-
25
- ![Список логов](./docs/screenshots/01-error-logs-list.png)
26
-
27
- Виртуальная таблица всех событий: trace ID, приложение, источник (client/server), уровень, сообщение, страница, IP. Левый сайдбар — фасеты по приложению, окружению, **версии**, источнику, уровню, браузеру, языку, IP, категории ошибки.
28
-
29
- ### Детальная карточка с метаданными
30
-
31
- ![Детальная карточка](./docs/screenshots/02-event-detail-info.png)
32
-
33
- Время сервера/клиента, IP с гео, окружение, **версия приложения**, fingerprint, session ID. Карточки повторений и корреляция событий справа.
34
-
35
- ### AI-диагностика ошибок
14
+ ## Table of contents
15
+
16
+ - [What you get](#what-you-get)
17
+ - [Install](#install)
18
+ - [Quick Start](#quick-start)
19
+ - [When to use what](#when-to-use-what)
20
+ - [Core concepts](#core-concepts)
21
+ - [Configuration](#configuration)
22
+ - [Migration](#migration)
23
+ - [From @sentry/react](#from-sentryreact)
24
+ - [From LogRocket React SDK](#from-logrocket-react-sdk)
25
+ - [Versioning](#versioning)
26
+ - [Quick reference](#quick-reference)
27
+ - [Why ELS](#why-els)
28
+ - [API](#api)
29
+ - [FAQ](#faq)
30
+ - [Other ELS SDKs](#other-els-sdks)
31
+ - [Pricing](#pricing)
32
+ - [License](#license)
36
33
 
37
- ![AI диагностика](./docs/screenshots/03-error-detail-ai.png)
38
-
39
- Stack trace с распарсенными фреймами + AI-анализ что именно сломалось и как чинить. Для React-ошибок дополнительно сохраняется `componentStack`.
40
-
41
- ### Аналитика и регрессии по версиям
42
-
43
- ![Аналитика](./docs/screenshots/04-analytics-dashboard.png)
44
-
45
- Total / critical+errors / warnings / error rate. AI-обзор слева, timeline в центре, donut'ы по приложению/источнику/уровню. **Виджет «Регрессии»**: какие fingerprint'ы появились впервые в свежей версии и какие пропали.
46
-
47
- ### Управление API-ключами
48
-
49
- ![API ключи](./docs/screenshots/05-api-keys.png)
50
- ![Действия с ключом](./docs/screenshots/06-api-key-actions.png)
51
-
52
- Scoped-ключи (write/read/read-any), live/test environments, ротация без даунтайма.
34
+ ---
53
35
 
54
- ### Избранные события
36
+ ## What you get
55
37
 
56
- ![Избранные](./docs/screenshots/07-favorites.png)
38
+ ELS ships with a built-in admin dashboard. Every event captured by this SDK lands there with full-text search, faceted filtering, AI-assisted diagnosis, and version-aware regression detection. React render-phase errors carry `componentStack` alongside the regular stack trace.
57
39
 
58
- Закладки на конкретные trace ID — для расследований, не теряются между сессиями.
40
+ | | |
41
+ |---|---|
42
+ | ![Logs list](https://raw.githubusercontent.com/official-inso/els-go/main/docs/screenshots/01-error-logs-list.png) | ![Event detail](https://raw.githubusercontent.com/official-inso/els-go/main/docs/screenshots/02-event-detail-info.png) |
43
+ | Virtual table with facet sidebar (app, env, **version**, source, level, browser, IP, category). Live mode auto-refreshes every 5s. | Full event metadata: timestamps, geo, env, **app version**, fingerprint, session, repetition cards, in-session correlation. |
44
+ | ![AI diagnosis](https://raw.githubusercontent.com/official-inso/els-go/main/docs/screenshots/03-error-detail-ai.png) | ![Analytics](https://raw.githubusercontent.com/official-inso/els-go/main/docs/screenshots/04-analytics-dashboard.png) |
45
+ | Parsed stack trace + AI-assisted diagnosis: what broke, where, how to fix. | Timeline, donuts, top URLs/IPs, hourly heatmap, **version-regression widget**. |
59
46
 
60
47
  ---
61
48
 
62
- ## Установка
49
+ ## Install
63
50
 
64
51
  ```bash
65
52
  npm install @inso_web/els-client @inso_web/els-react
66
53
  ```
67
54
 
55
+ **Requirements:** React 17+, Node.js 18+ at build time. Works with Vite, CRA, Webpack, Parcel.
56
+
68
57
  ---
69
58
 
70
59
  ## Quick Start
71
60
 
72
- ### 1. Подключите Provider
61
+ ### 1. Wrap your app in `<ELSProvider>`
73
62
 
74
- `main.tsx` (Vite) или `index.tsx` (CRA):
63
+ `main.tsx` (Vite) or `index.tsx` (CRA):
75
64
 
76
65
  ```tsx
77
66
  import { ELSClient } from '@inso_web/els-client';
@@ -95,7 +84,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
95
84
  );
96
85
  ```
97
86
 
98
- ### 2. Логируйте через `useELS()`
87
+ Don't have an API key yet? **[Sign up at lk.insoweb.ru](https://lk.insoweb.ru)** — takes under a minute.
88
+
89
+ ### 2. Log via `useELS()`
99
90
 
100
91
  ```tsx
101
92
  import { useELS } from '@inso_web/els-react';
@@ -114,19 +105,19 @@ export function CheckoutButton() {
114
105
  }
115
106
  ```
116
107
 
117
- ### 3. Оберните корень в `<ELSErrorBoundary>`
108
+ ### 3. Wrap the root in `<ELSErrorBoundary>`
118
109
 
119
110
  ```tsx
120
111
  import { ELSErrorBoundary } from '@inso_web/els-react';
121
112
 
122
- <ELSErrorBoundary fallback={<div>Что-то пошло не так</div>}>
113
+ <ELSErrorBoundary fallback={<div>Something went wrong</div>}>
123
114
  <App />
124
115
  </ELSErrorBoundary>
125
116
  ```
126
117
 
127
- Render-ошибки автоматически пойдут в ELS с stack trace + componentStack.
118
+ Render-phase errors are captured automatically with stack trace + `componentStack`.
128
119
 
129
- ### 4. Глобальные обработчики (опционально)
120
+ ### 4. Global handlers (optional)
130
121
 
131
122
  ```tsx
132
123
  import { useGlobalErrorHandlers } from '@inso_web/els-react';
@@ -137,11 +128,200 @@ export function ErrorReporter() {
137
128
  }
138
129
  ```
139
130
 
131
+ Mount `<ErrorReporter />` once inside the provider.
132
+
140
133
  ---
141
134
 
142
- ## Версионирование
135
+ ## When to use what
136
+
137
+ | Scenario | Use |
138
+ |---|---|
139
+ | Catch render-phase errors | Wrap a subtree in `<ELSErrorBoundary>` |
140
+ | Handle async errors in event handlers | `useELS()` + `try/catch` |
141
+ | Background `window.error` / `unhandledrejection` | Mount `<ErrorReporter />` with `useGlobalErrorHandlers()` |
142
+ | Multiple sub-apps with different configs | Multiple `<ELSProvider>` instances (innermost wins) |
143
+ | Outside React (utility module) | Import the `ELSClient` directly |
144
+ | SSR (Next.js) | Use [`@inso_web/els-next`](https://github.com/official-inso/els-next) instead |
145
+
146
+ ---
147
+
148
+ ## Core concepts
149
+
150
+ ### `<ELSErrorBoundary>`
151
+
152
+ A regular React error boundary that calls `client.error(err, ..., { meta: { componentStack } })` in `componentDidCatch`. Render-phase exceptions are captured; event-handler errors are not (React itself doesn't surface them to boundaries — wrap with `try/catch`).
143
153
 
144
- Прокидывайте версию через переменные сборки. Для Vite — `VITE_BUILD_VERSION` (Vite инлайнит на этапе build):
154
+ ### `useELS()`
155
+
156
+ Returns the same `Logger` interface as the base client: `info`, `warn`, `error`, `debug`, `trace`, `fatal`, `child`, `flush`. The reference is stable across renders.
157
+
158
+ ### Bindings & child loggers
159
+
160
+ ```tsx
161
+ const log = useELS();
162
+ const userLog = log.child({ userId: 42, role: 'admin' });
163
+ userLog.info('viewed profile');
164
+ ```
165
+
166
+ `child` is cheap — create one per user, per workflow, per route as needed.
167
+
168
+ ---
169
+
170
+ ## Configuration
171
+
172
+ `ELSConfig` matches the base client — see [@inso_web/els-client](https://github.com/official-inso/els-client). Key fields:
173
+
174
+ | Option | Description |
175
+ |---|---|
176
+ | `endpoint` | ELS URL (required) |
177
+ | `apiKey` | API key (required) |
178
+ | `appSlug` | App slug (required) |
179
+ | `serviceName` | Service / module name |
180
+ | `deploymentEnv` | `DEV` / `STAGING` / `PRODUCTION` |
181
+ | `appVersion` | Version (≤128 chars) |
182
+ | `minLevel` | Minimum level to send |
183
+
184
+ ---
185
+
186
+ ## Migration
187
+
188
+ ### From @sentry/react
189
+
190
+ **Before:**
191
+
192
+ ```tsx
193
+ import * as Sentry from '@sentry/react';
194
+
195
+ Sentry.init({
196
+ dsn: 'https://public@sentry.example.com/1',
197
+ environment: process.env.NODE_ENV,
198
+ release: import.meta.env.VITE_BUILD_VERSION,
199
+ });
200
+
201
+ const SentryRoute = Sentry.withSentryReactRouterV6Routing(Route);
202
+
203
+ ReactDOM.createRoot(document.getElementById('root')!).render(
204
+ <Sentry.ErrorBoundary fallback={<p>Crashed</p>}>
205
+ <App />
206
+ </Sentry.ErrorBoundary>,
207
+ );
208
+
209
+ function MyButton() {
210
+ return (
211
+ <button onClick={() => {
212
+ Sentry.captureMessage('clicked');
213
+ doStuff().catch(Sentry.captureException);
214
+ }}>Click</button>
215
+ );
216
+ }
217
+ ```
218
+
219
+ **After:**
220
+
221
+ ```tsx
222
+ import { ELSClient } from '@inso_web/els-client';
223
+ import { ELSProvider, ELSErrorBoundary, useELS } from '@inso_web/els-react';
224
+
225
+ const client = new ELSClient({
226
+ endpoint: import.meta.env.VITE_ELS_URL,
227
+ apiKey: import.meta.env.VITE_ELS_API_KEY,
228
+ appSlug: 'my-react-app',
229
+ deploymentEnv: import.meta.env.PROD ? 'PRODUCTION' : 'DEV',
230
+ appVersion: import.meta.env.VITE_BUILD_VERSION,
231
+ });
232
+
233
+ ReactDOM.createRoot(document.getElementById('root')!).render(
234
+ <ELSProvider client={client}>
235
+ <ELSErrorBoundary fallback={<p>Crashed</p>}>
236
+ <App />
237
+ </ELSErrorBoundary>
238
+ </ELSProvider>,
239
+ );
240
+
241
+ function MyButton() {
242
+ const log = useELS();
243
+ return (
244
+ <button onClick={() => {
245
+ log.info('clicked');
246
+ doStuff().catch((err) => log.error(err, 'click failed'));
247
+ }}>Click</button>
248
+ );
249
+ }
250
+ ```
251
+
252
+ | Sentry | ELS | Notes |
253
+ |---|---|---|
254
+ | `Sentry.init({ dsn })` | `new ELSClient({ endpoint, apiKey, appSlug })` | Three explicit fields |
255
+ | `<Sentry.ErrorBoundary>` | `<ELSErrorBoundary>` | Same role, same API |
256
+ | `Sentry.captureException(err)` | `log.error(err)` | Via `useELS()` |
257
+ | `Sentry.captureMessage(msg, level)` | `log.<level>(msg)` | |
258
+ | `Sentry.setUser({ id })` | `log.child({ user: { id } })` | Or via `loggerDefaults` |
259
+ | `release` | `appVersion` | Any string ≤128 chars |
260
+ | `environment` | `deploymentEnv` | Fixed enum |
261
+ | `Sentry.withSentryReactRouterV6Routing` | Not provided | Routing telemetry stays in Sentry if needed |
262
+ | Source maps upload | Not provided | Pair with another tool if critical |
263
+ | Session replay | Not provided | LogRocket / Sentry Replay if needed |
264
+
265
+ **Gotchas:**
266
+
267
+ - React event-handler errors are not caught by error boundaries — wrap async logic in `try/catch` and call `log.error(...)` explicitly.
268
+ - Sentry's `breadcrumbs` have no direct equivalent — use `log.child({ ... })` to carry context.
269
+
270
+ ---
271
+
272
+ ### From LogRocket React SDK
273
+
274
+ **Before:**
275
+
276
+ ```tsx
277
+ import LogRocket from 'logrocket';
278
+ import setupLogRocketReact from 'logrocket-react';
279
+
280
+ LogRocket.init('app/123');
281
+ setupLogRocketReact(LogRocket);
282
+
283
+ LogRocket.identify('user-42', { email: 'a@b.com' });
284
+ LogRocket.captureException(new Error('boom'));
285
+ ```
286
+
287
+ **After:**
288
+
289
+ ```tsx
290
+ import { ELSClient } from '@inso_web/els-client';
291
+ import { ELSProvider, ELSErrorBoundary, useELS } from '@inso_web/els-react';
292
+
293
+ const client = new ELSClient({
294
+ endpoint: import.meta.env.VITE_ELS_URL,
295
+ apiKey: import.meta.env.VITE_ELS_API_KEY,
296
+ appSlug: 'my-react-app',
297
+ });
298
+
299
+ // usage
300
+ const log = useELS();
301
+ log.child({ user: { id: 'user-42', email: 'a@b.com' } }).info('identified');
302
+ log.error(new Error('boom'));
303
+ ```
304
+
305
+ | LogRocket | ELS | Notes |
306
+ |---|---|---|
307
+ | `LogRocket.init('app/123')` | `new ELSClient({ endpoint, apiKey, appSlug })` | Three explicit fields |
308
+ | `LogRocket.identify(id, meta)` | `log.child({ user: { id, ...meta } })` | Bindings travel with the logger |
309
+ | `LogRocket.captureException(err)` | `log.error(err)` | |
310
+ | `LogRocket.log/info/warn/error` | `log.<level>(...)` | |
311
+ | Session replay | Not provided | Stay on LogRocket if replay is critical |
312
+ | Network request capture | Not provided | Log explicit `log.info({ url, status })` instead |
313
+ | Redux integration | Not provided | Call `log.info(action.type, ...)` in middleware |
314
+
315
+ **Gotchas:**
316
+
317
+ - LogRocket records full DOM sessions; ELS only stores discrete events. If session replay is the reason you chose LogRocket, keep it.
318
+ - LogRocket's request/response interception is opaque — replicate with explicit log calls in your fetch wrapper.
319
+
320
+ ---
321
+
322
+ ## Versioning
323
+
324
+ Pass via build-time env. For Vite — `VITE_BUILD_VERSION` (Vite inlines at `npm run build`):
145
325
 
146
326
  ```Dockerfile
147
327
  ARG VITE_BUILD_VERSION=dev
@@ -159,7 +339,59 @@ RUN npm run build
159
339
  new ELSClient({ ..., appVersion: import.meta.env.VITE_BUILD_VERSION });
160
340
  ```
161
341
 
162
- ELS принимает любой формат до 128 символов: semver, CalVer, date-compact, git SHA, opaque. Парсер на стороне сервера автоматически распознаёт тип и сортирует timeline.
342
+ ELS accepts any format 128 chars: semver, CalVer, date-compact, git SHA, opaque. The server auto-detects the format and sorts timelines.
343
+
344
+ ---
345
+
346
+ ## Quick reference
347
+
348
+ | Need | Use |
349
+ |---|---|
350
+ | Logger in components | `const log = useELS()` |
351
+ | Catch render crashes | `<ELSErrorBoundary>` |
352
+ | Catch event-handler errors | `try/catch` + `log.error(err)` |
353
+ | Global browser errors | Mount `<ErrorReporter />` with `useGlobalErrorHandlers()` |
354
+ | Identify user | `log.child({ user: { id, email } })` |
355
+ | Per-route context | `log.child({ route })` in a layout component |
356
+ | Suppress noisy levels | `minLevel: 'warn'` |
357
+
358
+ ---
359
+
360
+ ## Why ELS
361
+
362
+ ELS for Node.js is a focused logging SaaS, not a full observability suite. It optimises for capture speed, AI-driven triage, and a low integration cost.
363
+
364
+ - **Lower weight.** ~3 KB gzip in the browser, no transitive deps.
365
+ - **Zero external API calls.** Only `POST /errors[/batch]` and `GET /health`.
366
+ - **AI-assisted diagnosis** on every stack trace — `componentStack` included for React render errors.
367
+ - **5-minute integration.** Provider + boundary + hook, done.
368
+ - **Predictable price.** Tariffs in the dashboard.
369
+
370
+ ### Detailed comparison
371
+
372
+ | Category | ELS | Sentry | Datadog / New Relic | Grafana Loki | LogRocket / Logtail / BetterStack |
373
+ |---|---|---|---|---|---|
374
+ | Hosting model | Managed SaaS | SaaS or self-hosted | SaaS only | Self-hosted / Grafana Cloud | SaaS |
375
+ | SDK runtime deps | Zero | Medium (sub-SDKs, integrations) | Heavy (agent + tracing) | Promtail / agent | Medium |
376
+ | Typical integration time | ~5 min | 10–20 min | 30–60 min | Hours to days | 10–20 min |
377
+ | AI-assisted triage | Built-in | Paid add-on | Paid add-on | None | None |
378
+ | Error grouping / fingerprint | Yes | Yes | Yes | Manual via LogQL | Partial |
379
+ | Source-map upload | No | Yes | Yes | n/a | Partial |
380
+ | Session replay (frontend) | No | Paid | Paid | n/a | Yes (core) |
381
+ | Distributed tracing / APM | No | Partial | Yes (core) | Yes with Tempo | No |
382
+ | Infrastructure metrics | No | No | Yes (core) | Yes with Mimir | No |
383
+ | Free tier log retention | 24 hours | 30 days (limited volume) | Trial only | Self-cost | 3–30 days |
384
+ | Russian-language support / docs | Native | Community | Limited | Community | None |
385
+
386
+ ### When ELS is the wrong choice
387
+
388
+ - You need a single vendor for **APM + logs + metrics** under one bill — go Datadog or New Relic.
389
+ - Your frontend bug triage relies on **DOM session replay** — go LogRocket or Sentry Replay.
390
+ - You ship a **public mobile app** and need crash symbolication + ANR detection — Firebase Crashlytics or Sentry Mobile.
391
+
392
+ For everything else — backend errors, frontend JS errors, request logs, structured app events with version-aware analytics — ELS is built to be the cheapest path to a working dashboard.
393
+
394
+ → **Sign up at [lk.insoweb.ru](https://lk.insoweb.ru)** to grab an API key.
163
395
 
164
396
  ---
165
397
 
@@ -179,23 +411,52 @@ class ELSErrorBoundary extends React.Component<{
179
411
  function useGlobalErrorHandlers(opts?: { errors?: boolean; rejections?: boolean }): void;
180
412
  ```
181
413
 
414
+ Full `ELSConfig` reference — see [@inso_web/els-client](https://github.com/official-inso/els-client).
415
+
182
416
  ---
183
417
 
184
418
  ## FAQ
185
419
 
186
- **Совместимо с React 18 / 19?** Да. Поддерживается React 17+.
420
+ **React 18 / 19?** Yes. Supported on React 17+.
187
421
 
188
- **А `apiKey` для клиентского bundle это безопасно?** Да. ELS-ключи scoped (только write для приложения), и они всё равно видны в bundle (как у Sentry public DSN).
422
+ **Is the API key safe in the client bundle?** Yes. ELS keys are scoped a write key cannot read events. Same model as Sentry public DSN. If you still want to hide it, run an internal `/api/log` proxy.
189
423
 
190
- **Что если `apiKey` пустой?** Если передать `apiKey: ''` в `new ELSClient({...})` конструктор throw'нёт. Используйте guard в коде:
424
+ **What if `apiKey` is empty?** `new ELSClient({ ..., apiKey: '' })` throws. Guard in the entry file:
191
425
 
192
426
  ```ts
193
427
  const client = apiKey
194
428
  ? new ELSClient({ ..., apiKey })
195
- : { info: () => {}, warn: () => {}, error: () => {}, /* ... */ };
429
+ : ({ info: () => {}, warn: () => {}, error: () => {}, /* ... */ } as any);
196
430
  ```
197
431
 
198
- Либо используйте обёртки next/nest которые уже имеют такой guard.
432
+ Or use [`@inso_web/els-next`](https://github.com/official-inso/els-next) which has the silent-no-op guard built-in.
433
+
434
+ **SSR with Next.js?** Use [`@inso_web/els-next`](https://github.com/official-inso/els-next) — single config for server + client + edge.
435
+
436
+ ---
437
+
438
+ ## Other ELS SDKs
439
+
440
+ Same wire format, same dashboard — pick by stack.
441
+
442
+ **Node.js family**
443
+ - [`@inso_web/els-client`](https://github.com/official-inso/els-client) — base TS / Node / browser client
444
+ - [`@inso_web/els-express`](https://github.com/official-inso/els-express) — Express middleware
445
+ - [`@inso_web/els-next`](https://github.com/official-inso/els-next) — Next.js helpers (App + Pages router)
446
+ - [`@inso_web/els-nest`](https://github.com/official-inso/els-nest) — NestJS module
447
+ - [`@inso_web/els-react`](https://github.com/official-inso/els-react) — React Provider, hooks, ErrorBoundary (this repo)
448
+ - [`@inso_web/els-vue`](https://github.com/official-inso/els-vue) — Vue 3 plugin
449
+
450
+ **Other stacks**
451
+ - [`Inso.Els`](https://github.com/official-inso/els-csharp) — .NET (Core + ASP.NET Core + ILogger)
452
+ - [`io.github.official-inso:els-core`](https://github.com/official-inso/els-java) — Java + Spring Boot starter + SLF4J
453
+ - [`github.com/official-inso/els-go`](https://github.com/official-inso/els-go) — Go
454
+
455
+ ---
456
+
457
+ ## Pricing
458
+
459
+ Free tier — **24-hour log retention**. See **[lk.insoweb.ru](https://lk.insoweb.ru)** for the full tariff matrix.
199
460
 
200
461
  ---
201
462
 
package/dist/index.cjs CHANGED
@@ -62,10 +62,8 @@ var ELSProvider = ({
62
62
  const onError = (event) => {
63
63
  const entry = {
64
64
  message: event.message || "window.onerror",
65
- url: typeof location !== "undefined" ? location.href : "",
66
65
  stack: event.error?.stack,
67
- level: "error",
68
- source: "client"
66
+ level: "error"
69
67
  };
70
68
  if (value.queue) value.queue.enqueue(entry);
71
69
  else void value.client.sendError(entry);
@@ -74,10 +72,8 @@ var ELSProvider = ({
74
72
  const reason = event.reason;
75
73
  const entry = {
76
74
  message: String(reason?.message ?? reason ?? "unhandledrejection"),
77
- url: typeof location !== "undefined" ? location.href : "",
78
75
  stack: reason?.stack,
79
- level: "error",
80
- source: "client"
76
+ level: "error"
81
77
  };
82
78
  if (value.queue) value.queue.enqueue(entry);
83
79
  else void value.client.sendError(entry);
@@ -103,7 +99,7 @@ function useELS() {
103
99
  const ctx = React2.useContext(ELSContext);
104
100
  if (!ctx) {
105
101
  throw new Error(
106
- "useELS: ELSProvider \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u041E\u0431\u0435\u0440\u043D\u0438\u0442\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0432 <ELSProvider>."
102
+ "useELS: no ELSProvider found. Wrap your app in <ELSProvider>."
107
103
  );
108
104
  }
109
105
  return ctx;
@@ -119,9 +115,7 @@ function useErrorReporter() {
119
115
  const entry = {
120
116
  message: e?.message ?? String(err),
121
117
  stack: e?.stack,
122
- url: typeof location !== "undefined" ? location.href : extra?.url ?? "",
123
118
  level: "error",
124
- source: "client",
125
119
  ...extra
126
120
  };
127
121
  if (queue) queue.enqueue(entry);
@@ -149,9 +143,7 @@ var ELSErrorBoundary = class extends React4.Component {
149
143
  message: error.message,
150
144
  stack: error.stack,
151
145
  componentStack: info.componentStack ?? void 0,
152
- url: typeof location !== "undefined" ? location.href : "",
153
- level: "error",
154
- source: "client"
146
+ level: "error"
155
147
  };
156
148
  if (ctx.queue) ctx.queue.enqueue(entry);
157
149
  else void ctx.client.sendError(entry);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ELSProvider.tsx","../src/useELS.ts","../src/useErrorReporter.ts","../src/ELSErrorBoundary.tsx","../src/withErrorReporting.tsx"],"sourcesContent":["export { ELSProvider, ELSContext } from \"./ELSProvider.js\";\nexport { useELS } from \"./useELS.js\";\nexport { useErrorReporter } from \"./useErrorReporter.js\";\nexport { ELSErrorBoundary } from \"./ELSErrorBoundary.js\";\nexport { withErrorReporting } from \"./withErrorReporting.js\";\nexport type { ELSProviderProps } from \"./ELSProvider.js\";\n","import * as React from \"react\";\nimport { ELSClient, ELSQueue } from \"@inso_web/els-client\";\nimport type { ELSConfig } from \"@inso_web/els-client\";\n\nexport interface ELSContextValue {\n client: ELSClient;\n queue: ELSQueue | null;\n}\n\nexport const ELSContext = React.createContext<ELSContextValue | null>(null);\n\nexport interface ELSProviderProps {\n config: ELSConfig;\n /** Включить очередь с батчингом. По умолчанию true. */\n useQueue?: boolean;\n /** Интервал авто‑флаша очереди в мс. */\n flushIntervalMs?: number;\n /** Максимальный размер батча. */\n maxBatchSize?: number;\n /** Авто‑подписка на window.onerror и unhandledrejection. По умолчанию true. */\n captureGlobalErrors?: boolean;\n children?: React.ReactNode;\n}\n\nexport const ELSProvider: React.FC<ELSProviderProps> = ({\n config,\n useQueue = true,\n flushIntervalMs,\n maxBatchSize,\n captureGlobalErrors = true,\n children,\n}) => {\n const value = React.useMemo<ELSContextValue>(() => {\n const client = new ELSClient(config);\n const queue = useQueue\n ? new ELSQueue(client, { flushIntervalMs, maxBatchSize })\n : null;\n return { client, queue };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n React.useEffect(() => {\n if (!captureGlobalErrors || typeof window === \"undefined\") return;\n\n const onError = (event: ErrorEvent) => {\n const entry = {\n message: event.message || \"window.onerror\",\n url: typeof location !== \"undefined\" ? location.href : \"\",\n stack: event.error?.stack,\n level: \"error\" as const,\n source: \"client\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n const onRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const entry = {\n message: String(reason?.message ?? reason ?? \"unhandledrejection\"),\n url: typeof location !== \"undefined\" ? location.href : \"\",\n stack: reason?.stack,\n level: \"error\" as const,\n source: \"client\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n return () => {\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n };\n }, [captureGlobalErrors, value]);\n\n React.useEffect(() => {\n return () => {\n value.queue?.stop();\n };\n }, [value]);\n\n return <ELSContext.Provider value={value}>{children}</ELSContext.Provider>;\n};\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\nimport type { ELSContextValue } from \"./ELSProvider.js\";\n\nexport function useELS(): ELSContextValue {\n const ctx = React.useContext(ELSContext);\n if (!ctx) {\n throw new Error(\n \"useELS: ELSProvider не найден. Оберните приложение в <ELSProvider>.\",\n );\n }\n return ctx;\n}\n","import * as React from \"react\";\nimport type { ErrorEntry } from \"@inso_web/els-client\";\nimport { useELS } from \"./useELS.js\";\n\nexport function useErrorReporter() {\n const { client, queue } = useELS();\n\n return React.useCallback(\n (err: unknown, extra?: Partial<ErrorEntry>) => {\n const e = err as Error | undefined;\n const entry: ErrorEntry = {\n message: e?.message ?? String(err),\n stack: e?.stack,\n url:\n typeof location !== \"undefined\" ? location.href : extra?.url ?? \"\",\n level: \"error\",\n source: \"client\",\n ...extra,\n };\n if (queue) queue.enqueue(entry);\n else void client.sendError(entry);\n },\n [client, queue],\n );\n}\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\n\nexport interface ELSErrorBoundaryProps {\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class ELSErrorBoundary extends React.Component<\n ELSErrorBoundaryProps,\n State\n> {\n static contextType = ELSContext;\n declare context: React.ContextType<typeof ELSContext>;\n\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n this.props.onError?.(error, info);\n const ctx = this.context;\n if (!ctx) return;\n const entry = {\n message: error.message,\n stack: error.stack,\n componentStack: info.componentStack ?? undefined,\n url: typeof location !== \"undefined\" ? location.href : \"\",\n level: \"error\" as const,\n source: \"client\" as const,\n };\n if (ctx.queue) ctx.queue.enqueue(entry);\n else void ctx.client.sendError(entry);\n }\n\n render() {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") return fallback(this.state.error);\n return fallback ?? null;\n }\n return this.props.children;\n }\n}\n","import * as React from \"react\";\nimport { ELSErrorBoundary } from \"./ELSErrorBoundary.js\";\nimport type { ELSErrorBoundaryProps } from \"./ELSErrorBoundary.js\";\n\nexport function withErrorReporting<P extends object>(\n Component: React.ComponentType<P>,\n boundaryProps?: Omit<ELSErrorBoundaryProps, \"children\">,\n): React.FC<P> {\n const Wrapped: React.FC<P> = (props) => (\n <ELSErrorBoundary {...boundaryProps}>\n <Component {...props} />\n </ELSErrorBoundary>\n );\n Wrapped.displayName = `withErrorReporting(${\n Component.displayName ?? Component.name ?? \"Component\"\n })`;\n return Wrapped;\n}\n"],"mappings":"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","names":["React","React","React","import_jsx_runtime","Component"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ELSProvider.tsx","../src/useELS.ts","../src/useErrorReporter.ts","../src/ELSErrorBoundary.tsx","../src/withErrorReporting.tsx"],"sourcesContent":["export { ELSProvider, ELSContext } from \"./ELSProvider.js\";\nexport { useELS } from \"./useELS.js\";\nexport { useErrorReporter } from \"./useErrorReporter.js\";\nexport { ELSErrorBoundary } from \"./ELSErrorBoundary.js\";\nexport { withErrorReporting } from \"./withErrorReporting.js\";\nexport type { ELSProviderProps } from \"./ELSProvider.js\";\n","import * as React from \"react\";\nimport { ELSClient, ELSQueue } from \"@inso_web/els-client\";\nimport type { ELSConfig } from \"@inso_web/els-client\";\n\n/** Value provided by {@link ELSProvider} and read via {@link useELS}. */\nexport interface ELSContextValue {\n /** The shared ELS client. */\n client: ELSClient;\n /** The batching queue, or `null` when `useQueue` is disabled. */\n queue: ELSQueue | null;\n}\n\n/** React context holding the ELS client/queue. Prefer the {@link useELS} hook. */\nexport const ELSContext = React.createContext<ELSContextValue | null>(null);\n\n/** Props for {@link ELSProvider}. */\nexport interface ELSProviderProps {\n /** ELS client configuration (only `apiKey` + `appSlug` are required). */\n config: ELSConfig;\n /** Use a batching queue. Default: `true`. */\n useQueue?: boolean;\n /** Queue auto-flush interval, in ms. */\n flushIntervalMs?: number;\n /** Max entries buffered before an early flush. */\n maxBatchSize?: number;\n /** Auto-subscribe to `window.onerror` and `unhandledrejection`. Default: `true`. */\n captureGlobalErrors?: boolean;\n children?: React.ReactNode;\n}\n\n/**\n * Provides a single ELS client (and optional queue) to the React tree and, by\n * default, captures uncaught errors and unhandled promise rejections. Wrap your\n * app once near the root.\n *\n * @example\n * <ELSProvider config={{ apiKey: \"els_live_…\", appSlug: \"web\" }}>\n * <App />\n * </ELSProvider>\n */\nexport const ELSProvider: React.FC<ELSProviderProps> = ({\n config,\n useQueue = true,\n flushIntervalMs,\n maxBatchSize,\n captureGlobalErrors = true,\n children,\n}) => {\n const value = React.useMemo<ELSContextValue>(() => {\n const client = new ELSClient(config);\n const queue = useQueue\n ? new ELSQueue(client, { flushIntervalMs, maxBatchSize })\n : null;\n return { client, queue };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n React.useEffect(() => {\n if (!captureGlobalErrors || typeof window === \"undefined\") return;\n\n const onError = (event: ErrorEvent) => {\n const entry = {\n message: event.message || \"window.onerror\",\n stack: event.error?.stack,\n level: \"error\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n const onRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const entry = {\n message: String(reason?.message ?? reason ?? \"unhandledrejection\"),\n stack: reason?.stack,\n level: \"error\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n return () => {\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n };\n }, [captureGlobalErrors, value]);\n\n React.useEffect(() => {\n return () => {\n value.queue?.stop();\n };\n }, [value]);\n\n return <ELSContext.Provider value={value}>{children}</ELSContext.Provider>;\n};\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\nimport type { ELSContextValue } from \"./ELSProvider.js\";\n\n/**\n * Returns the ELS `client` and `queue` from the nearest {@link ELSProvider}.\n * Throws if no provider is present.\n *\n * @example\n * const { client } = useELS();\n * client.info(\"dashboard opened\");\n */\nexport function useELS(): ELSContextValue {\n const ctx = React.useContext(ELSContext);\n if (!ctx) {\n throw new Error(\n \"useELS: no ELSProvider found. Wrap your app in <ELSProvider>.\",\n );\n }\n return ctx;\n}\n","import * as React from \"react\";\nimport type { ErrorEntry } from \"@inso_web/els-client\";\nimport { useELS } from \"./useELS.js\";\n\n/**\n * Returns a stable `report(error, extra?)` callback that sends an error to ELS\n * (through the provider's queue when enabled). Use it for handled/caught errors.\n *\n * @example\n * const report = useErrorReporter();\n * try { await save(); } catch (e) { report(e, { url: \"/save\" }); }\n */\nexport function useErrorReporter() {\n const { client, queue } = useELS();\n\n return React.useCallback(\n (err: unknown, extra?: Partial<ErrorEntry>) => {\n const e = err as Error | undefined;\n const entry: ErrorEntry = {\n message: e?.message ?? String(err),\n stack: e?.stack,\n level: \"error\",\n ...extra,\n };\n if (queue) queue.enqueue(entry);\n else void client.sendError(entry);\n },\n [client, queue],\n );\n}\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\n\n/** Props for {@link ELSErrorBoundary}. */\nexport interface ELSErrorBoundaryProps {\n /** Rendered when a child throws. A function receives the caught error. */\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n /** Called in addition to reporting, e.g. for custom logging. */\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\n/**\n * Error boundary that reports render errors (with component stack) to ELS via\n * the surrounding {@link ELSProvider} and shows an optional `fallback`.\n *\n * @example\n * <ELSErrorBoundary fallback={<p>Something went wrong</p>}>\n * <Dashboard />\n * </ELSErrorBoundary>\n */\nexport class ELSErrorBoundary extends React.Component<\n ELSErrorBoundaryProps,\n State\n> {\n static contextType = ELSContext;\n declare context: React.ContextType<typeof ELSContext>;\n\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n this.props.onError?.(error, info);\n const ctx = this.context;\n if (!ctx) return;\n const entry = {\n message: error.message,\n stack: error.stack,\n componentStack: info.componentStack ?? undefined,\n level: \"error\" as const,\n };\n if (ctx.queue) ctx.queue.enqueue(entry);\n else void ctx.client.sendError(entry);\n }\n\n render() {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") return fallback(this.state.error);\n return fallback ?? null;\n }\n return this.props.children;\n }\n}\n","import * as React from \"react\";\nimport { ELSErrorBoundary } from \"./ELSErrorBoundary.js\";\nimport type { ELSErrorBoundaryProps } from \"./ELSErrorBoundary.js\";\n\n/**\n * HOC that wraps a component in an {@link ELSErrorBoundary}.\n *\n * @example\n * export default withErrorReporting(Dashboard, { fallback: <ErrorPage /> });\n */\nexport function withErrorReporting<P extends object>(\n Component: React.ComponentType<P>,\n boundaryProps?: Omit<ELSErrorBoundaryProps, \"children\">,\n): React.FC<P> {\n const Wrapped: React.FC<P> = (props) => (\n <ELSErrorBoundary {...boundaryProps}>\n <Component {...props} />\n </ELSErrorBoundary>\n );\n Wrapped.displayName = `withErrorReporting(${\n Component.displayName ?? Component.name ?? \"Component\"\n })`;\n return Wrapped;\n}\n"],"mappings":"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","names":["React","React","React","import_jsx_runtime","Component"]}
package/dist/index.d.cts CHANGED
@@ -1,37 +1,81 @@
1
1
  import * as React from 'react';
2
2
  import { ELSClient, ELSQueue, ELSConfig, ErrorEntry } from '@inso_web/els-client';
3
3
 
4
+ /** Value provided by {@link ELSProvider} and read via {@link useELS}. */
4
5
  interface ELSContextValue {
6
+ /** The shared ELS client. */
5
7
  client: ELSClient;
8
+ /** The batching queue, or `null` when `useQueue` is disabled. */
6
9
  queue: ELSQueue | null;
7
10
  }
11
+ /** React context holding the ELS client/queue. Prefer the {@link useELS} hook. */
8
12
  declare const ELSContext: React.Context<ELSContextValue | null>;
13
+ /** Props for {@link ELSProvider}. */
9
14
  interface ELSProviderProps {
15
+ /** ELS client configuration (only `apiKey` + `appSlug` are required). */
10
16
  config: ELSConfig;
11
- /** Включить очередь с батчингом. По умолчанию true. */
17
+ /** Use a batching queue. Default: `true`. */
12
18
  useQueue?: boolean;
13
- /** Интервал авто‑флаша очереди в мс. */
19
+ /** Queue auto-flush interval, in ms. */
14
20
  flushIntervalMs?: number;
15
- /** Максимальный размер батча. */
21
+ /** Max entries buffered before an early flush. */
16
22
  maxBatchSize?: number;
17
- /** Авто‑подписка на window.onerror и unhandledrejection. По умолчанию true. */
23
+ /** Auto-subscribe to `window.onerror` and `unhandledrejection`. Default: `true`. */
18
24
  captureGlobalErrors?: boolean;
19
25
  children?: React.ReactNode;
20
26
  }
27
+ /**
28
+ * Provides a single ELS client (and optional queue) to the React tree and, by
29
+ * default, captures uncaught errors and unhandled promise rejections. Wrap your
30
+ * app once near the root.
31
+ *
32
+ * @example
33
+ * <ELSProvider config={{ apiKey: "els_live_…", appSlug: "web" }}>
34
+ * <App />
35
+ * </ELSProvider>
36
+ */
21
37
  declare const ELSProvider: React.FC<ELSProviderProps>;
22
38
 
39
+ /**
40
+ * Returns the ELS `client` and `queue` from the nearest {@link ELSProvider}.
41
+ * Throws if no provider is present.
42
+ *
43
+ * @example
44
+ * const { client } = useELS();
45
+ * client.info("dashboard opened");
46
+ */
23
47
  declare function useELS(): ELSContextValue;
24
48
 
49
+ /**
50
+ * Returns a stable `report(error, extra?)` callback that sends an error to ELS
51
+ * (through the provider's queue when enabled). Use it for handled/caught errors.
52
+ *
53
+ * @example
54
+ * const report = useErrorReporter();
55
+ * try { await save(); } catch (e) { report(e, { url: "/save" }); }
56
+ */
25
57
  declare function useErrorReporter(): (err: unknown, extra?: Partial<ErrorEntry>) => void;
26
58
 
59
+ /** Props for {@link ELSErrorBoundary}. */
27
60
  interface ELSErrorBoundaryProps {
61
+ /** Rendered when a child throws. A function receives the caught error. */
28
62
  fallback?: React.ReactNode | ((error: Error) => React.ReactNode);
63
+ /** Called in addition to reporting, e.g. for custom logging. */
29
64
  onError?: (error: Error, info: React.ErrorInfo) => void;
30
65
  children?: React.ReactNode;
31
66
  }
32
67
  interface State {
33
68
  error: Error | null;
34
69
  }
70
+ /**
71
+ * Error boundary that reports render errors (with component stack) to ELS via
72
+ * the surrounding {@link ELSProvider} and shows an optional `fallback`.
73
+ *
74
+ * @example
75
+ * <ELSErrorBoundary fallback={<p>Something went wrong</p>}>
76
+ * <Dashboard />
77
+ * </ELSErrorBoundary>
78
+ */
35
79
  declare class ELSErrorBoundary extends React.Component<ELSErrorBoundaryProps, State> {
36
80
  static contextType: React.Context<ELSContextValue | null>;
37
81
  context: React.ContextType<typeof ELSContext>;
@@ -41,6 +85,12 @@ declare class ELSErrorBoundary extends React.Component<ELSErrorBoundaryProps, St
41
85
  render(): React.ReactNode;
42
86
  }
43
87
 
88
+ /**
89
+ * HOC that wraps a component in an {@link ELSErrorBoundary}.
90
+ *
91
+ * @example
92
+ * export default withErrorReporting(Dashboard, { fallback: <ErrorPage /> });
93
+ */
44
94
  declare function withErrorReporting<P extends object>(Component: React.ComponentType<P>, boundaryProps?: Omit<ELSErrorBoundaryProps, "children">): React.FC<P>;
45
95
 
46
96
  export { ELSContext, ELSErrorBoundary, ELSProvider, type ELSProviderProps, useELS, useErrorReporter, withErrorReporting };
package/dist/index.d.ts CHANGED
@@ -1,37 +1,81 @@
1
1
  import * as React from 'react';
2
2
  import { ELSClient, ELSQueue, ELSConfig, ErrorEntry } from '@inso_web/els-client';
3
3
 
4
+ /** Value provided by {@link ELSProvider} and read via {@link useELS}. */
4
5
  interface ELSContextValue {
6
+ /** The shared ELS client. */
5
7
  client: ELSClient;
8
+ /** The batching queue, or `null` when `useQueue` is disabled. */
6
9
  queue: ELSQueue | null;
7
10
  }
11
+ /** React context holding the ELS client/queue. Prefer the {@link useELS} hook. */
8
12
  declare const ELSContext: React.Context<ELSContextValue | null>;
13
+ /** Props for {@link ELSProvider}. */
9
14
  interface ELSProviderProps {
15
+ /** ELS client configuration (only `apiKey` + `appSlug` are required). */
10
16
  config: ELSConfig;
11
- /** Включить очередь с батчингом. По умолчанию true. */
17
+ /** Use a batching queue. Default: `true`. */
12
18
  useQueue?: boolean;
13
- /** Интервал авто‑флаша очереди в мс. */
19
+ /** Queue auto-flush interval, in ms. */
14
20
  flushIntervalMs?: number;
15
- /** Максимальный размер батча. */
21
+ /** Max entries buffered before an early flush. */
16
22
  maxBatchSize?: number;
17
- /** Авто‑подписка на window.onerror и unhandledrejection. По умолчанию true. */
23
+ /** Auto-subscribe to `window.onerror` and `unhandledrejection`. Default: `true`. */
18
24
  captureGlobalErrors?: boolean;
19
25
  children?: React.ReactNode;
20
26
  }
27
+ /**
28
+ * Provides a single ELS client (and optional queue) to the React tree and, by
29
+ * default, captures uncaught errors and unhandled promise rejections. Wrap your
30
+ * app once near the root.
31
+ *
32
+ * @example
33
+ * <ELSProvider config={{ apiKey: "els_live_…", appSlug: "web" }}>
34
+ * <App />
35
+ * </ELSProvider>
36
+ */
21
37
  declare const ELSProvider: React.FC<ELSProviderProps>;
22
38
 
39
+ /**
40
+ * Returns the ELS `client` and `queue` from the nearest {@link ELSProvider}.
41
+ * Throws if no provider is present.
42
+ *
43
+ * @example
44
+ * const { client } = useELS();
45
+ * client.info("dashboard opened");
46
+ */
23
47
  declare function useELS(): ELSContextValue;
24
48
 
49
+ /**
50
+ * Returns a stable `report(error, extra?)` callback that sends an error to ELS
51
+ * (through the provider's queue when enabled). Use it for handled/caught errors.
52
+ *
53
+ * @example
54
+ * const report = useErrorReporter();
55
+ * try { await save(); } catch (e) { report(e, { url: "/save" }); }
56
+ */
25
57
  declare function useErrorReporter(): (err: unknown, extra?: Partial<ErrorEntry>) => void;
26
58
 
59
+ /** Props for {@link ELSErrorBoundary}. */
27
60
  interface ELSErrorBoundaryProps {
61
+ /** Rendered when a child throws. A function receives the caught error. */
28
62
  fallback?: React.ReactNode | ((error: Error) => React.ReactNode);
63
+ /** Called in addition to reporting, e.g. for custom logging. */
29
64
  onError?: (error: Error, info: React.ErrorInfo) => void;
30
65
  children?: React.ReactNode;
31
66
  }
32
67
  interface State {
33
68
  error: Error | null;
34
69
  }
70
+ /**
71
+ * Error boundary that reports render errors (with component stack) to ELS via
72
+ * the surrounding {@link ELSProvider} and shows an optional `fallback`.
73
+ *
74
+ * @example
75
+ * <ELSErrorBoundary fallback={<p>Something went wrong</p>}>
76
+ * <Dashboard />
77
+ * </ELSErrorBoundary>
78
+ */
35
79
  declare class ELSErrorBoundary extends React.Component<ELSErrorBoundaryProps, State> {
36
80
  static contextType: React.Context<ELSContextValue | null>;
37
81
  context: React.ContextType<typeof ELSContext>;
@@ -41,6 +85,12 @@ declare class ELSErrorBoundary extends React.Component<ELSErrorBoundaryProps, St
41
85
  render(): React.ReactNode;
42
86
  }
43
87
 
88
+ /**
89
+ * HOC that wraps a component in an {@link ELSErrorBoundary}.
90
+ *
91
+ * @example
92
+ * export default withErrorReporting(Dashboard, { fallback: <ErrorPage /> });
93
+ */
44
94
  declare function withErrorReporting<P extends object>(Component: React.ComponentType<P>, boundaryProps?: Omit<ELSErrorBoundaryProps, "children">): React.FC<P>;
45
95
 
46
96
  export { ELSContext, ELSErrorBoundary, ELSProvider, type ELSProviderProps, useELS, useErrorReporter, withErrorReporting };
package/dist/index.js CHANGED
@@ -21,10 +21,8 @@ var ELSProvider = ({
21
21
  const onError = (event) => {
22
22
  const entry = {
23
23
  message: event.message || "window.onerror",
24
- url: typeof location !== "undefined" ? location.href : "",
25
24
  stack: event.error?.stack,
26
- level: "error",
27
- source: "client"
25
+ level: "error"
28
26
  };
29
27
  if (value.queue) value.queue.enqueue(entry);
30
28
  else void value.client.sendError(entry);
@@ -33,10 +31,8 @@ var ELSProvider = ({
33
31
  const reason = event.reason;
34
32
  const entry = {
35
33
  message: String(reason?.message ?? reason ?? "unhandledrejection"),
36
- url: typeof location !== "undefined" ? location.href : "",
37
34
  stack: reason?.stack,
38
- level: "error",
39
- source: "client"
35
+ level: "error"
40
36
  };
41
37
  if (value.queue) value.queue.enqueue(entry);
42
38
  else void value.client.sendError(entry);
@@ -62,7 +58,7 @@ function useELS() {
62
58
  const ctx = React2.useContext(ELSContext);
63
59
  if (!ctx) {
64
60
  throw new Error(
65
- "useELS: ELSProvider \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u041E\u0431\u0435\u0440\u043D\u0438\u0442\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0432 <ELSProvider>."
61
+ "useELS: no ELSProvider found. Wrap your app in <ELSProvider>."
66
62
  );
67
63
  }
68
64
  return ctx;
@@ -78,9 +74,7 @@ function useErrorReporter() {
78
74
  const entry = {
79
75
  message: e?.message ?? String(err),
80
76
  stack: e?.stack,
81
- url: typeof location !== "undefined" ? location.href : extra?.url ?? "",
82
77
  level: "error",
83
- source: "client",
84
78
  ...extra
85
79
  };
86
80
  if (queue) queue.enqueue(entry);
@@ -108,9 +102,7 @@ var ELSErrorBoundary = class extends React4.Component {
108
102
  message: error.message,
109
103
  stack: error.stack,
110
104
  componentStack: info.componentStack ?? void 0,
111
- url: typeof location !== "undefined" ? location.href : "",
112
- level: "error",
113
- source: "client"
105
+ level: "error"
114
106
  };
115
107
  if (ctx.queue) ctx.queue.enqueue(entry);
116
108
  else void ctx.client.sendError(entry);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ELSProvider.tsx","../src/useELS.ts","../src/useErrorReporter.ts","../src/ELSErrorBoundary.tsx","../src/withErrorReporting.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { ELSClient, ELSQueue } from \"@inso_web/els-client\";\nimport type { ELSConfig } from \"@inso_web/els-client\";\n\nexport interface ELSContextValue {\n client: ELSClient;\n queue: ELSQueue | null;\n}\n\nexport const ELSContext = React.createContext<ELSContextValue | null>(null);\n\nexport interface ELSProviderProps {\n config: ELSConfig;\n /** Включить очередь с батчингом. По умолчанию true. */\n useQueue?: boolean;\n /** Интервал авто‑флаша очереди в мс. */\n flushIntervalMs?: number;\n /** Максимальный размер батча. */\n maxBatchSize?: number;\n /** Авто‑подписка на window.onerror и unhandledrejection. По умолчанию true. */\n captureGlobalErrors?: boolean;\n children?: React.ReactNode;\n}\n\nexport const ELSProvider: React.FC<ELSProviderProps> = ({\n config,\n useQueue = true,\n flushIntervalMs,\n maxBatchSize,\n captureGlobalErrors = true,\n children,\n}) => {\n const value = React.useMemo<ELSContextValue>(() => {\n const client = new ELSClient(config);\n const queue = useQueue\n ? new ELSQueue(client, { flushIntervalMs, maxBatchSize })\n : null;\n return { client, queue };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n React.useEffect(() => {\n if (!captureGlobalErrors || typeof window === \"undefined\") return;\n\n const onError = (event: ErrorEvent) => {\n const entry = {\n message: event.message || \"window.onerror\",\n url: typeof location !== \"undefined\" ? location.href : \"\",\n stack: event.error?.stack,\n level: \"error\" as const,\n source: \"client\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n const onRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const entry = {\n message: String(reason?.message ?? reason ?? \"unhandledrejection\"),\n url: typeof location !== \"undefined\" ? location.href : \"\",\n stack: reason?.stack,\n level: \"error\" as const,\n source: \"client\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n return () => {\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n };\n }, [captureGlobalErrors, value]);\n\n React.useEffect(() => {\n return () => {\n value.queue?.stop();\n };\n }, [value]);\n\n return <ELSContext.Provider value={value}>{children}</ELSContext.Provider>;\n};\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\nimport type { ELSContextValue } from \"./ELSProvider.js\";\n\nexport function useELS(): ELSContextValue {\n const ctx = React.useContext(ELSContext);\n if (!ctx) {\n throw new Error(\n \"useELS: ELSProvider не найден. Оберните приложение в <ELSProvider>.\",\n );\n }\n return ctx;\n}\n","import * as React from \"react\";\nimport type { ErrorEntry } from \"@inso_web/els-client\";\nimport { useELS } from \"./useELS.js\";\n\nexport function useErrorReporter() {\n const { client, queue } = useELS();\n\n return React.useCallback(\n (err: unknown, extra?: Partial<ErrorEntry>) => {\n const e = err as Error | undefined;\n const entry: ErrorEntry = {\n message: e?.message ?? String(err),\n stack: e?.stack,\n url:\n typeof location !== \"undefined\" ? location.href : extra?.url ?? \"\",\n level: \"error\",\n source: \"client\",\n ...extra,\n };\n if (queue) queue.enqueue(entry);\n else void client.sendError(entry);\n },\n [client, queue],\n );\n}\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\n\nexport interface ELSErrorBoundaryProps {\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class ELSErrorBoundary extends React.Component<\n ELSErrorBoundaryProps,\n State\n> {\n static contextType = ELSContext;\n declare context: React.ContextType<typeof ELSContext>;\n\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n this.props.onError?.(error, info);\n const ctx = this.context;\n if (!ctx) return;\n const entry = {\n message: error.message,\n stack: error.stack,\n componentStack: info.componentStack ?? undefined,\n url: typeof location !== \"undefined\" ? location.href : \"\",\n level: \"error\" as const,\n source: \"client\" as const,\n };\n if (ctx.queue) ctx.queue.enqueue(entry);\n else void ctx.client.sendError(entry);\n }\n\n render() {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") return fallback(this.state.error);\n return fallback ?? null;\n }\n return this.props.children;\n }\n}\n","import * as React from \"react\";\nimport { ELSErrorBoundary } from \"./ELSErrorBoundary.js\";\nimport type { ELSErrorBoundaryProps } from \"./ELSErrorBoundary.js\";\n\nexport function withErrorReporting<P extends object>(\n Component: React.ComponentType<P>,\n boundaryProps?: Omit<ELSErrorBoundaryProps, \"children\">,\n): React.FC<P> {\n const Wrapped: React.FC<P> = (props) => (\n <ELSErrorBoundary {...boundaryProps}>\n <Component {...props} />\n </ELSErrorBoundary>\n );\n Wrapped.displayName = `withErrorReporting(${\n Component.displayName ?? Component.name ?? \"Component\"\n })`;\n return Wrapped;\n}\n"],"mappings":"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","names":["React","React","React","jsx","Component"]}
1
+ {"version":3,"sources":["../src/ELSProvider.tsx","../src/useELS.ts","../src/useErrorReporter.ts","../src/ELSErrorBoundary.tsx","../src/withErrorReporting.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { ELSClient, ELSQueue } from \"@inso_web/els-client\";\nimport type { ELSConfig } from \"@inso_web/els-client\";\n\n/** Value provided by {@link ELSProvider} and read via {@link useELS}. */\nexport interface ELSContextValue {\n /** The shared ELS client. */\n client: ELSClient;\n /** The batching queue, or `null` when `useQueue` is disabled. */\n queue: ELSQueue | null;\n}\n\n/** React context holding the ELS client/queue. Prefer the {@link useELS} hook. */\nexport const ELSContext = React.createContext<ELSContextValue | null>(null);\n\n/** Props for {@link ELSProvider}. */\nexport interface ELSProviderProps {\n /** ELS client configuration (only `apiKey` + `appSlug` are required). */\n config: ELSConfig;\n /** Use a batching queue. Default: `true`. */\n useQueue?: boolean;\n /** Queue auto-flush interval, in ms. */\n flushIntervalMs?: number;\n /** Max entries buffered before an early flush. */\n maxBatchSize?: number;\n /** Auto-subscribe to `window.onerror` and `unhandledrejection`. Default: `true`. */\n captureGlobalErrors?: boolean;\n children?: React.ReactNode;\n}\n\n/**\n * Provides a single ELS client (and optional queue) to the React tree and, by\n * default, captures uncaught errors and unhandled promise rejections. Wrap your\n * app once near the root.\n *\n * @example\n * <ELSProvider config={{ apiKey: \"els_live_…\", appSlug: \"web\" }}>\n * <App />\n * </ELSProvider>\n */\nexport const ELSProvider: React.FC<ELSProviderProps> = ({\n config,\n useQueue = true,\n flushIntervalMs,\n maxBatchSize,\n captureGlobalErrors = true,\n children,\n}) => {\n const value = React.useMemo<ELSContextValue>(() => {\n const client = new ELSClient(config);\n const queue = useQueue\n ? new ELSQueue(client, { flushIntervalMs, maxBatchSize })\n : null;\n return { client, queue };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n React.useEffect(() => {\n if (!captureGlobalErrors || typeof window === \"undefined\") return;\n\n const onError = (event: ErrorEvent) => {\n const entry = {\n message: event.message || \"window.onerror\",\n stack: event.error?.stack,\n level: \"error\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n const onRejection = (event: PromiseRejectionEvent) => {\n const reason = event.reason;\n const entry = {\n message: String(reason?.message ?? reason ?? \"unhandledrejection\"),\n stack: reason?.stack,\n level: \"error\" as const,\n };\n if (value.queue) value.queue.enqueue(entry);\n else void value.client.sendError(entry);\n };\n\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n return () => {\n window.removeEventListener(\"error\", onError);\n window.removeEventListener(\"unhandledrejection\", onRejection);\n };\n }, [captureGlobalErrors, value]);\n\n React.useEffect(() => {\n return () => {\n value.queue?.stop();\n };\n }, [value]);\n\n return <ELSContext.Provider value={value}>{children}</ELSContext.Provider>;\n};\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\nimport type { ELSContextValue } from \"./ELSProvider.js\";\n\n/**\n * Returns the ELS `client` and `queue` from the nearest {@link ELSProvider}.\n * Throws if no provider is present.\n *\n * @example\n * const { client } = useELS();\n * client.info(\"dashboard opened\");\n */\nexport function useELS(): ELSContextValue {\n const ctx = React.useContext(ELSContext);\n if (!ctx) {\n throw new Error(\n \"useELS: no ELSProvider found. Wrap your app in <ELSProvider>.\",\n );\n }\n return ctx;\n}\n","import * as React from \"react\";\nimport type { ErrorEntry } from \"@inso_web/els-client\";\nimport { useELS } from \"./useELS.js\";\n\n/**\n * Returns a stable `report(error, extra?)` callback that sends an error to ELS\n * (through the provider's queue when enabled). Use it for handled/caught errors.\n *\n * @example\n * const report = useErrorReporter();\n * try { await save(); } catch (e) { report(e, { url: \"/save\" }); }\n */\nexport function useErrorReporter() {\n const { client, queue } = useELS();\n\n return React.useCallback(\n (err: unknown, extra?: Partial<ErrorEntry>) => {\n const e = err as Error | undefined;\n const entry: ErrorEntry = {\n message: e?.message ?? String(err),\n stack: e?.stack,\n level: \"error\",\n ...extra,\n };\n if (queue) queue.enqueue(entry);\n else void client.sendError(entry);\n },\n [client, queue],\n );\n}\n","import * as React from \"react\";\nimport { ELSContext } from \"./ELSProvider.js\";\n\n/** Props for {@link ELSErrorBoundary}. */\nexport interface ELSErrorBoundaryProps {\n /** Rendered when a child throws. A function receives the caught error. */\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n /** Called in addition to reporting, e.g. for custom logging. */\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\n/**\n * Error boundary that reports render errors (with component stack) to ELS via\n * the surrounding {@link ELSProvider} and shows an optional `fallback`.\n *\n * @example\n * <ELSErrorBoundary fallback={<p>Something went wrong</p>}>\n * <Dashboard />\n * </ELSErrorBoundary>\n */\nexport class ELSErrorBoundary extends React.Component<\n ELSErrorBoundaryProps,\n State\n> {\n static contextType = ELSContext;\n declare context: React.ContextType<typeof ELSContext>;\n\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n this.props.onError?.(error, info);\n const ctx = this.context;\n if (!ctx) return;\n const entry = {\n message: error.message,\n stack: error.stack,\n componentStack: info.componentStack ?? undefined,\n level: \"error\" as const,\n };\n if (ctx.queue) ctx.queue.enqueue(entry);\n else void ctx.client.sendError(entry);\n }\n\n render() {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") return fallback(this.state.error);\n return fallback ?? null;\n }\n return this.props.children;\n }\n}\n","import * as React from \"react\";\nimport { ELSErrorBoundary } from \"./ELSErrorBoundary.js\";\nimport type { ELSErrorBoundaryProps } from \"./ELSErrorBoundary.js\";\n\n/**\n * HOC that wraps a component in an {@link ELSErrorBoundary}.\n *\n * @example\n * export default withErrorReporting(Dashboard, { fallback: <ErrorPage /> });\n */\nexport function withErrorReporting<P extends object>(\n Component: React.ComponentType<P>,\n boundaryProps?: Omit<ELSErrorBoundaryProps, \"children\">,\n): React.FC<P> {\n const Wrapped: React.FC<P> = (props) => (\n <ELSErrorBoundary {...boundaryProps}>\n <Component {...props} />\n </ELSErrorBoundary>\n );\n Wrapped.displayName = `withErrorReporting(${\n Component.displayName ?? Component.name ?? \"Component\"\n })`;\n return Wrapped;\n}\n"],"mappings":"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","names":["React","React","React","jsx","Component"]}
@@ -8,7 +8,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
8
8
  <React.StrictMode>
9
9
  <ELSProvider
10
10
  config={{
11
- endpoint: import.meta.env.VITE_ELS_URL || 'https://api.insoweb.ru/els',
12
11
  apiKey: import.meta.env.VITE_ELS_API_KEY || 'els_live_xxxxxxxx',
13
12
  appSlug: 'examples',
14
13
  deploymentEnv: 'DEV',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inso_web/els-react",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "React‑обёртка для Error Logs Service (ELS): Provider, hooks, ErrorBoundary и HOC для автоматического репорта ошибок компонентов.",
5
5
  "homepage": "https://api.insoweb.ru/els",
6
6
  "author": "INSOWEB",
@@ -51,7 +51,7 @@
51
51
  "react": ">=17.0.0"
52
52
  },
53
53
  "dependencies": {
54
- "@inso_web/els-client": "^0.4.1"
54
+ "@inso_web/els-client": "^0.5.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@testing-library/react": "^14.1.2",