@monzori/browser 0.1.1 → 0.1.3
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 +29 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +81 -18
- package/dist/react.js +4 -1
- package/package.json +1 -1
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
|
-
|
|
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';
|
|
@@ -20,6 +35,18 @@ monzori.captureException(new Error('Evento explícito de teste'));
|
|
|
20
35
|
|
|
21
36
|
O endereço padrão é `https://ingest.monzori.com.br/v1/web/events`. `apiKey` é o identificador público restrito à ingestão da fonte, não uma senha de administração. A inicialização devolve o client usado no Error Boundary e no reporte manual. Há também o export nomeado `start`.
|
|
22
37
|
|
|
38
|
+
Na versão 0.1.3, erros tratados podem ser enviados por `Monzori.notify(error, contextoOpcional)` ou pelo export nomeado `notify`. Aceita Error, string e os valores já suportados por captureException, usando o mesmo client ativo, sanitização, fila e transporte protegido. Não exige endpoint separado. Antes de start ou depois de close, notify não envia e não lança. O método client.captureException continua compatível.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
try {
|
|
42
|
+
// ação da aplicação
|
|
43
|
+
} catch (error) {
|
|
44
|
+
Monzori.notify(error, {flow: 'login'});
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Uma resposta HTTP400 tratada não gera erro global: é preciso reportá-la explicitamente quando representar um problema que se deseja monitorar. Chamadas a Bugsnag.notify não são interceptadas pelo Monzori. No frontend cliente, a função reportError centraliza o reporte aos dois SDKs, isolando as falhas de cada destino; durante a carga inicial do chunk, espera no máximo3s e mantém no máximo20 chamadas pendentes. Nenhum reporte é aguardado pela ação da aplicação.
|
|
49
|
+
|
|
23
50
|
`endpoint` é uma opção avançada: para testes locais, informar `http://localhost:3333/v1/web/events`; destinos fora de loopback exigem HTTPS. Um endpoint explícito inválido desabilita o envio, sem fallback silencioso. O endereço padrão não implica que a infraestrutura do piloto já esteja publicada.
|
|
24
51
|
|
|
25
52
|
Compatibilidade: `initMonzori({publicKey, release, endpoint})` continua funcionando como alias legado, usando o mesmo client; preferir `Monzori.start({apiKey, release})` nas novas integrações. Escolher `apiKey` ou `publicKey`, sem combinar os dois. A primeira inicialização fica ativa até `close()`, sem duplicar listeners em chamadas subsequentes.
|
|
@@ -32,7 +59,7 @@ import { MonzoriErrorBoundary } from '@monzori/browser/react';
|
|
|
32
59
|
</MonzoriErrorBoundary>
|
|
33
60
|
```
|
|
34
61
|
|
|
35
|
-
|
|
62
|
+
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
63
|
|
|
37
64
|
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
65
|
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,9 @@ export type Client = {
|
|
|
46
46
|
export declare function start(options: Options): Client;
|
|
47
47
|
/** @deprecated Use Monzori.start(options) or the named start export. */
|
|
48
48
|
export declare const initMonzori: typeof start;
|
|
49
|
+
export declare function notify(error: unknown, context?: unknown): void;
|
|
49
50
|
declare const Monzori: {
|
|
50
51
|
start: typeof start;
|
|
52
|
+
notify: typeof notify;
|
|
51
53
|
};
|
|
52
54
|
export default Monzori;
|
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
|
|
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
|
-
|
|
115
|
-
const timeout = setTimeout(() => controller?.abort(), 3000);
|
|
141
|
+
let timeout;
|
|
116
142
|
try {
|
|
117
|
-
const
|
|
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
|
-
|
|
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;
|
|
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) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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);
|
|
@@ -215,5 +270,13 @@ export function start(options) {
|
|
|
215
270
|
}
|
|
216
271
|
/** @deprecated Use Monzori.start(options) or the named start export. */
|
|
217
272
|
export const initMonzori = start;
|
|
218
|
-
|
|
273
|
+
// Manual reporting uses the same guarded capture/queue as automatic errors.
|
|
274
|
+
// Before start() or after close(), reporting is intentionally a no-op.
|
|
275
|
+
export function notify(error, context) {
|
|
276
|
+
try {
|
|
277
|
+
active?.captureException(error, context);
|
|
278
|
+
}
|
|
279
|
+
catch { /* Telemetry never escapes into the caller. */ }
|
|
280
|
+
}
|
|
281
|
+
const Monzori = { start, notify };
|
|
219
282
|
export default Monzori;
|
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) {
|
|
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
|
}
|