@monzori/browser 0.1.1 → 0.1.2

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
@@ -4,7 +4,22 @@ Captura erros globais, promises rejeitadas, reporte manual e console error/warn.
4
4
 
5
5
  Instale com npm install @monzori/browser. Versão inicial para piloto privado.
6
6
 
7
- O endpoint hospedado depende da implantação do serviço. Configure um endpoint explícito para testes locais.
7
+ A partir da versão 0.1.2, falhas recuperáveis de importação, inicialização e envio afetam somente a coleta. Recursos de navegador indisponíveis devolvem um cliente inativo; inicialização parcial desfaz os hooks instalados. A fila é limitada e o envio tem prazo de três segundos, backoff e tratamento terminal de rejeições.
8
+
9
+ Para isolar também falhas de download/avaliação do módulo, carregue o SDK após iniciar o React, sem aguardá-lo no caminho de renderização:
10
+
11
+ ```js
12
+ // Execute após iniciar o frontend; falhas encerram somente a coleta.
13
+ void import('@monzori/browser')
14
+ .then(({default: Monzori}) => {
15
+ Monzori.start({apiKey: 'IDENTIFICADOR_PUBLICO', release: 'web-2026.11.14'});
16
+ })
17
+ .catch(() => {});
18
+ ```
19
+
20
+ Não condicione login, navegação ou ações do usuário ao resultado de flush(). Um beforeSend fornecido pela aplicação deve ser curto: código executado no mesmo contexto JavaScript não pode ser isolado de callbacks que nunca retornam ou do esgotamento de recursos do navegador.
21
+
22
+ O piloto hospedado usa o endereço padrão abaixo. Para obter a chave, abra **Integração** na sua aplicação frontend no painel. Configure um endpoint explícito somente para testes locais.
8
23
 
9
24
  ```ts
10
25
  import Monzori from '@monzori/browser';
@@ -32,7 +47,7 @@ import { MonzoriErrorBoundary } from '@monzori/browser/react';
32
47
  </MonzoriErrorBoundary>
33
48
  ```
34
49
 
35
- Cadastre a origem HTTPS exata no painel; HTTP é aceito apenas em loopback local. O identificador público só admite eventos naquele vínculo, sem permitir leitura ou upload de mapas. A URL de ingresso deve aceitar CORS para a origem cadastrada; a instalação local usa http://localhost:3333/v1/web/events. Release precisa coincidir com o upload privado dos source maps. Identificadores de release devem ser únicos por versão; não reutilizá-los para substituir mapas de um mesmo artefato.
50
+ Novas integrações podem receber eventos de qualquer site. A restrição opcional fica em **Integração → Configurações → Domínios permitidos**; integrações anteriores mantêm suas restrições. HTTP é aceito apenas em loopback local. O identificador público só admite eventos naquele vínculo, sem permitir leitura ou upload de mapas. A instalação local usa http://localhost:3333/v1/web/events. Release precisa coincidir com o upload privado dos source maps. Identificadores de release devem ser únicos por versão; não reutilizá-los para substituir mapas de um mesmo artefato.
36
51
 
37
52
  Buffer somente em memória, sem cookies/storage/DOM/formulários/identificação de visitantes automáticos. Limites: 100 eventos/256 KiB, flush a cada 5 segundos ou 20 eventos, até 50 eventos e 32 KiB por pedido. O SDK reserva espaço para contexto do servidor e descarta um evento serializado acima de 6 KiB; o teto canônico na API é 8 KiB. Reenvio com o mesmo ID, backoff de 1–60 segundos com jitter, até 24 horas de idade; fechar a página pode perder eventos não admitidos. No máximo 60 entradas de console por minuto/página. O SDK não registra seus próprios diagnósticos no console monitorado.
38
53
 
package/dist/index.js CHANGED
@@ -1,10 +1,28 @@
1
1
  import { cleanText, cleanUrl, safeValue } from './sanitize.js';
2
2
  export const DEFAULT_ENDPOINT = 'https://ingest.monzori.com.br/v1/web/events';
3
3
  let active;
4
- const encode = new TextEncoder();
5
4
  export function start(options) {
6
5
  if (active)
7
6
  return active;
7
+ const restorers = [];
8
+ try {
9
+ return createClient(options, restorers);
10
+ }
11
+ catch {
12
+ for (const restore of restorers.reverse()) {
13
+ try {
14
+ restore();
15
+ }
16
+ catch { /* best effort cleanup */ }
17
+ }
18
+ const client = { captureException: () => { }, captureMessage: () => { }, flush: async () => { }, close: () => { if (active === client)
19
+ active = undefined; }, stats: () => ({ pending: 0, bytes: 0, dropped: 0, retries: 0, confirmed: 0, disabled: true }) };
20
+ active = client;
21
+ return client;
22
+ }
23
+ }
24
+ function createClient(options, restorers) {
25
+ const encode = new TextEncoder();
8
26
  let endpoint = '', apiKey = '', disabled = false;
9
27
  try {
10
28
  apiKey = options.apiKey ?? options.publicKey ?? '';
@@ -26,7 +44,7 @@ export function start(options) {
26
44
  let pending;
27
45
  let controller;
28
46
  const protectedIds = new Set();
29
- const restorers = [];
47
+ const disable = () => { disabled = true; queue = []; bytes = 0; protectedIds.clear(); };
30
48
  const expired = () => { const keep = queue.filter(entry => { if (Date.now() - Date.parse(entry.event.occurred_at) > 86400000) {
31
49
  bytes -= entry.size;
32
50
  dropped++;
@@ -92,6 +110,15 @@ export function start(options) {
92
110
  }
93
111
  }
94
112
  function flush() {
113
+ try {
114
+ return send();
115
+ }
116
+ catch {
117
+ disable();
118
+ return Promise.resolve();
119
+ }
120
+ }
121
+ function send() {
95
122
  if (pending)
96
123
  return pending;
97
124
  if (disabled || closed || Date.now() < nextAttempt)
@@ -111,10 +138,16 @@ export function start(options) {
111
138
  if (!batch.length)
112
139
  return Promise.resolve();
113
140
  pending = Promise.resolve().then(async () => {
114
- controller = new AbortController();
115
- const timeout = setTimeout(() => controller?.abort(), 3000);
141
+ let timeout;
116
142
  try {
117
- const response = await fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json', 'x-monzori-key': apiKey }, body: JSON.stringify({ events: batch.map(e => e.event) }), credentials: 'omit', referrerPolicy: 'no-referrer', keepalive: true, signal: controller.signal });
143
+ const requestController = new AbortController();
144
+ controller = requestController;
145
+ const deadline = new Promise((_, reject) => { timeout = setTimeout(() => { try {
146
+ requestController.abort();
147
+ }
148
+ catch { } reject(Error('timeout')); }, 3000); });
149
+ const request = Promise.resolve().then(() => fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json', 'x-monzori-key': apiKey }, body: JSON.stringify({ events: batch.map(e => e.event) }), credentials: 'omit', referrerPolicy: 'no-referrer', keepalive: true, signal: requestController.signal }));
150
+ const response = await Promise.race([request, deadline]);
118
151
  if (response.status === 202 || [400, 409, 413, 415, 422].includes(response.status)) {
119
152
  const ids = new Set(batch.map(e => e.event.event_id));
120
153
  queue = queue.filter(e => { if (ids.has(e.event.event_id)) {
@@ -145,31 +178,53 @@ export function start(options) {
145
178
  }
146
179
  }
147
180
  finally {
148
- clearTimeout(timeout);
181
+ try {
182
+ clearTimeout(timeout);
183
+ }
184
+ catch { }
149
185
  protectedIds.clear();
150
186
  pending = undefined;
151
187
  controller = undefined;
152
188
  }
153
- });
189
+ }).catch(() => { disable(); pending = undefined; });
154
190
  return pending;
155
191
  }
156
192
  const client = { captureException: exception, captureMessage: (message, severity = 'info', context) => capture(message, '', 'manual', severity, context), flush,
157
193
  stats: () => ({ pending: queue.length, bytes, dropped, retries, confirmed, disabled }),
158
- close: () => { closed = true; controller?.abort(); restorers.forEach(fn => fn()); queue = []; bytes = 0; if (active === client)
194
+ close: () => { closed = true; try {
195
+ controller?.abort();
196
+ }
197
+ catch { } for (const restore of restorers.splice(0).reverse()) {
198
+ try {
199
+ restore();
200
+ }
201
+ catch { }
202
+ } queue = []; bytes = 0; if (active === client)
159
203
  active = undefined; }
160
204
  };
161
205
  if (!disabled) {
162
- const onError = (event) => { if (event.error)
163
- exception(event.error, {}, 'js_error');
164
- else if (event.message)
165
- capture(event.message, event.filename ? `${event.filename}:${event.lineno}:${event.colno}` : '', 'js_error', 'error'); };
166
- const onRejection = (event) => exception(event.reason, {}, 'promise_rejection');
167
- const onVisibility = () => { if (document.visibilityState === 'hidden')
168
- void flush(); };
206
+ const onError = (event) => { try {
207
+ if (event.error)
208
+ exception(event.error, {}, 'js_error');
209
+ else if (event.message)
210
+ capture(event.message, event.filename ? `${event.filename}:${event.lineno}:${event.colno}` : '', 'js_error', 'error');
211
+ }
212
+ catch { } };
213
+ const onRejection = (event) => { try {
214
+ exception(event.reason, {}, 'promise_rejection');
215
+ }
216
+ catch { } };
217
+ const onVisibility = () => { try {
218
+ if (document.visibilityState === 'hidden')
219
+ void flush();
220
+ }
221
+ catch { } };
222
+ restorers.push(() => window.removeEventListener('error', onError));
169
223
  window.addEventListener('error', onError);
224
+ restorers.push(() => window.removeEventListener('unhandledrejection', onRejection));
170
225
  window.addEventListener('unhandledrejection', onRejection);
226
+ restorers.push(() => document.removeEventListener('visibilitychange', onVisibility));
171
227
  document.addEventListener('visibilitychange', onVisibility);
172
- restorers.push(() => { window.removeEventListener('error', onError); window.removeEventListener('unhandledrejection', onRejection); document.removeEventListener('visibilitychange', onVisibility); });
173
228
  if (options.console !== false) {
174
229
  let minute = 0, count = 0;
175
230
  const levels = ['error', 'warn', ...(options.console?.log ? ['log'] : []), ...(options.console?.info ? ['info'] : []), ...(options.console?.debug ? ['debug'] : [])];
@@ -202,9 +257,9 @@ export function start(options) {
202
257
  }
203
258
  return result;
204
259
  };
205
- console[level] = wrapped;
206
260
  restorers.push(() => { if (console[level] === wrapped)
207
261
  console[level] = original; });
262
+ console[level] = wrapped;
208
263
  }
209
264
  }
210
265
  const timer = setInterval(() => void flush(), 5000);
package/dist/react.js CHANGED
@@ -5,6 +5,9 @@ export class MonzoriErrorBoundary extends Component {
5
5
  this.state = { failed: false };
6
6
  }
7
7
  static getDerivedStateFromError() { return { failed: true }; }
8
- componentDidCatch(error, info) { this.props.client.captureException(error, { componentStack: info.componentStack ?? '' }, 'react_error'); }
8
+ componentDidCatch(error, info) { try {
9
+ this.props.client.captureException(error, { componentStack: info.componentStack ?? '' }, 'react_error');
10
+ }
11
+ catch { /* Telemetry must not break React's error handling. */ } }
9
12
  render() { return this.state.failed ? this.props.fallback ?? createElement('p', null, 'Não foi possível exibir este conteúdo.') : this.props.children; }
10
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monzori/browser",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Browser error and console collection SDK for Monzori",
5
5
  "publishConfig": {
6
6
  "access": "public",