@monzori/browser 0.0.0-stage → 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
@@ -1,3 +1,58 @@
1
- # Temporary Holding Version
1
+ # Monzori Browser SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Captura erros globais, promises rejeitadas, reporte manual e console error/warn. Demais níveis de console são opt-in. O adaptador React exporta MonzoriErrorBoundary; React é peer opcional e fica fora do núcleo.
4
+
5
+ Instale com npm install @monzori/browser. Versão inicial para piloto privado.
6
+
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.
23
+
24
+ ```ts
25
+ import Monzori from '@monzori/browser';
26
+
27
+ const monzori = Monzori.start({
28
+ apiKey: 'IDENTIFICADOR_PUBLICO_GERADO_NO_PAINEL',
29
+ release: 'web-2026.11.10',
30
+ console: { log: false, info: false, debug: false }
31
+ });
32
+
33
+ monzori.captureException(new Error('Evento explícito de teste'));
34
+ ```
35
+
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`.
37
+
38
+ `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.
39
+
40
+ 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.
41
+
42
+ ```tsx
43
+ import { MonzoriErrorBoundary } from '@monzori/browser/react';
44
+
45
+ <MonzoriErrorBoundary client={monzori} fallback={<p>Não foi possível exibir este conteúdo.</p>}>
46
+ <App />
47
+ </MonzoriErrorBoundary>
48
+ ```
49
+
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.
51
+
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.
53
+
54
+ URL, mensagens, stacks e atributos são sanitizados antes do envio; antes do commit há uma segunda sanitização no servidor. beforeSend permite remover eventos ou ajustar conteúdo. Nenhuma heurística remove todo segredo ou dado pessoal de texto livre: evite emiti-los. Contexto usa apenas propriedades próprias de objetos simples, sem avaliar getters; a stack de um Error real é lida explicitamente para suportar engines com stack lazy.
55
+
56
+ monzori.stats() mostra pendências, bytes, descartes, reenvios e confirmações HTTP locais. Confirmado não significa já pesquisável. monzori.flush() tenta enviar os pendentes, respeitando backoff. monzori.close() encerra captura/timers e restaura os métodos de console se eles ainda forem os wrappers instalados pelo SDK.
57
+
58
+ Source maps v3 planos Vite/Rollup e Webpack são enviados de forma privada pelo painel/API autenticada, fora do SDK. Não publicar .map no bucket/CDN público. O backend só consulta mapas cadastrados para organização/aplicação/release/URL do artefato exatos; nunca busca sourceMappingURL na Internet. Sem mapa, o painel preserva a stack recebida.
@@ -0,0 +1,52 @@
1
+ export type Kind = 'js_error' | 'promise_rejection' | 'react_error' | 'console' | 'manual';
2
+ export type Severity = 'error' | 'warn' | 'info' | 'debug';
3
+ type BaseOptions = {
4
+ endpoint?: string;
5
+ release: string;
6
+ console?: false | {
7
+ log?: boolean;
8
+ info?: boolean;
9
+ debug?: boolean;
10
+ };
11
+ beforeSend?: (event: WebEvent) => WebEvent | null;
12
+ };
13
+ export type Options = BaseOptions & ({
14
+ apiKey: string;
15
+ publicKey?: never;
16
+ } | {
17
+ publicKey: string;
18
+ apiKey?: never;
19
+ });
20
+ export declare const DEFAULT_ENDPOINT = "https://ingest.monzori.com.br/v1/web/events";
21
+ export type WebEvent = {
22
+ event_id: string;
23
+ occurred_at: string;
24
+ kind: Kind;
25
+ severity: Severity;
26
+ message: string;
27
+ stack: string;
28
+ url: string;
29
+ release: string;
30
+ attributes: unknown;
31
+ };
32
+ export type Client = {
33
+ captureException: (error: unknown, context?: unknown, kind?: Kind) => void;
34
+ captureMessage: (message: string, severity?: Severity, context?: unknown) => void;
35
+ flush: () => Promise<void>;
36
+ close: () => void;
37
+ stats: () => {
38
+ pending: number;
39
+ bytes: number;
40
+ dropped: number;
41
+ retries: number;
42
+ confirmed: number;
43
+ disabled: boolean;
44
+ };
45
+ };
46
+ export declare function start(options: Options): Client;
47
+ /** @deprecated Use Monzori.start(options) or the named start export. */
48
+ export declare const initMonzori: typeof start;
49
+ declare const Monzori: {
50
+ start: typeof start;
51
+ };
52
+ export default Monzori;
package/dist/index.js ADDED
@@ -0,0 +1,274 @@
1
+ import { cleanText, cleanUrl, safeValue } from './sanitize.js';
2
+ export const DEFAULT_ENDPOINT = 'https://ingest.monzori.com.br/v1/web/events';
3
+ let active;
4
+ export function start(options) {
5
+ if (active)
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();
26
+ let endpoint = '', apiKey = '', disabled = false;
27
+ try {
28
+ apiKey = options.apiKey ?? options.publicKey ?? '';
29
+ const parsed = new URL(options.endpoint === undefined ? DEFAULT_ENDPOINT : options.endpoint);
30
+ if (parsed.protocol !== 'https:' && !(['localhost', '127.0.0.1', '[::1]'].includes(parsed.hostname) && parsed.protocol === 'http:'))
31
+ throw Error();
32
+ if (parsed.username || parsed.password || parsed.search || parsed.hash)
33
+ throw Error();
34
+ endpoint = parsed.href;
35
+ if ((options.apiKey !== undefined && options.publicKey !== undefined) || !/^[A-Za-z0-9_-]{43}$/.test(apiKey) || !/^[A-Za-z0-9_.\/-]{1,128}$/.test(options.release))
36
+ throw Error();
37
+ }
38
+ catch {
39
+ disabled = true;
40
+ }
41
+ if (typeof window === 'undefined' || typeof crypto === 'undefined' || typeof fetch === 'undefined' || typeof AbortController === 'undefined')
42
+ disabled = true;
43
+ let queue = [], bytes = 0, dropped = 0, retries = 0, confirmed = 0, closed = false, attempt = 0, nextAttempt = 0;
44
+ let pending;
45
+ let controller;
46
+ const protectedIds = new Set();
47
+ const disable = () => { disabled = true; queue = []; bytes = 0; protectedIds.clear(); };
48
+ const expired = () => { const keep = queue.filter(entry => { if (Date.now() - Date.parse(entry.event.occurred_at) > 86400000) {
49
+ bytes -= entry.size;
50
+ dropped++;
51
+ return false;
52
+ } return true; }); queue = keep; };
53
+ function capture(message, stack, kind, severity, context) {
54
+ if (disabled || closed)
55
+ return;
56
+ try {
57
+ let event = { event_id: crypto.randomUUID(), occurred_at: new Date().toISOString(), kind, severity, message: cleanText(message, 1200) || 'Evento sem mensagem', stack: cleanText(stack, 4000), url: cleanUrl(window.location.href), release: options.release, attributes: safeValue(context ?? {}) };
58
+ if (options.beforeSend) {
59
+ const candidate = options.beforeSend(event);
60
+ if (!candidate) {
61
+ dropped++;
62
+ return;
63
+ }
64
+ event = { ...candidate, event_id: event.event_id, occurred_at: event.occurred_at, kind, severity, release: options.release, message: cleanText(String(candidate.message), 1200), stack: cleanText(String(candidate.stack), 4000), url: cleanUrl(String(candidate.url)), attributes: safeValue(candidate.attributes) };
65
+ }
66
+ // Reserve room for the server's tenant/application context inside the 8 KiB canonical ceiling.
67
+ const size = encode.encode(JSON.stringify(event)).length;
68
+ if (size > 6144) {
69
+ dropped++;
70
+ return;
71
+ }
72
+ expired();
73
+ while (queue.length >= 100 || bytes + size > 256 * 1024) {
74
+ const index = queue.findIndex(e => !protectedIds.has(e.event.event_id));
75
+ if (index < 0) {
76
+ dropped++;
77
+ return;
78
+ }
79
+ bytes -= queue[index].size;
80
+ queue.splice(index, 1);
81
+ dropped++;
82
+ }
83
+ queue.push({ event, size });
84
+ bytes += size;
85
+ if (queue.length >= 20)
86
+ void flush();
87
+ }
88
+ catch {
89
+ dropped++;
90
+ }
91
+ }
92
+ function exception(error, context, kind = 'manual') {
93
+ try {
94
+ const object = error && typeof error === 'object' ? error : {};
95
+ const message = Object.getOwnPropertyDescriptor(object, 'message')?.value;
96
+ let stack = Object.getOwnPropertyDescriptor(object, 'stack')?.value;
97
+ // Modern engines expose Error.stack lazily. Read that explicit Error field, never arbitrary context getters.
98
+ if (typeof stack !== 'string' && error instanceof Error) {
99
+ try {
100
+ stack = error.stack;
101
+ }
102
+ catch {
103
+ stack = '';
104
+ }
105
+ }
106
+ capture(typeof message === 'string' ? message : typeof error === 'string' ? error : 'Erro sem mensagem', typeof stack === 'string' ? stack : '', kind, 'error', context);
107
+ }
108
+ catch {
109
+ dropped++;
110
+ }
111
+ }
112
+ function flush() {
113
+ try {
114
+ return send();
115
+ }
116
+ catch {
117
+ disable();
118
+ return Promise.resolve();
119
+ }
120
+ }
121
+ function send() {
122
+ if (pending)
123
+ return pending;
124
+ if (disabled || closed || Date.now() < nextAttempt)
125
+ return Promise.resolve();
126
+ expired();
127
+ if (!queue.length)
128
+ return Promise.resolve();
129
+ const batch = [];
130
+ let size = 20;
131
+ for (const entry of queue) {
132
+ if (batch.length >= 50 || size + entry.size + 1 > 32 * 1024)
133
+ break;
134
+ batch.push(entry);
135
+ size += entry.size + 1;
136
+ protectedIds.add(entry.event.event_id);
137
+ }
138
+ if (!batch.length)
139
+ return Promise.resolve();
140
+ pending = Promise.resolve().then(async () => {
141
+ let timeout;
142
+ try {
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]);
151
+ if (response.status === 202 || [400, 409, 413, 415, 422].includes(response.status)) {
152
+ const ids = new Set(batch.map(e => e.event.event_id));
153
+ queue = queue.filter(e => { if (ids.has(e.event.event_id)) {
154
+ bytes -= e.size;
155
+ return false;
156
+ } return true; });
157
+ if (response.status === 202)
158
+ confirmed += batch.length;
159
+ else
160
+ dropped += batch.length;
161
+ attempt = 0;
162
+ nextAttempt = 0;
163
+ }
164
+ else if (response.status === 401 || response.status === 403) {
165
+ disabled = true;
166
+ dropped += queue.length;
167
+ queue = [];
168
+ bytes = 0;
169
+ }
170
+ else
171
+ throw Error('retry');
172
+ }
173
+ catch {
174
+ if (!closed) {
175
+ retries++;
176
+ const delay = Math.min(60000, 1000 * 2 ** Math.min(attempt++, 6));
177
+ nextAttempt = Date.now() + Math.max(1000, delay * (.5 + Math.random() * .5));
178
+ }
179
+ }
180
+ finally {
181
+ try {
182
+ clearTimeout(timeout);
183
+ }
184
+ catch { }
185
+ protectedIds.clear();
186
+ pending = undefined;
187
+ controller = undefined;
188
+ }
189
+ }).catch(() => { disable(); pending = undefined; });
190
+ return pending;
191
+ }
192
+ const client = { captureException: exception, captureMessage: (message, severity = 'info', context) => capture(message, '', 'manual', severity, context), flush,
193
+ stats: () => ({ pending: queue.length, bytes, dropped, retries, confirmed, disabled }),
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)
203
+ active = undefined; }
204
+ };
205
+ if (!disabled) {
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));
223
+ window.addEventListener('error', onError);
224
+ restorers.push(() => window.removeEventListener('unhandledrejection', onRejection));
225
+ window.addEventListener('unhandledrejection', onRejection);
226
+ restorers.push(() => document.removeEventListener('visibilitychange', onVisibility));
227
+ document.addEventListener('visibilitychange', onVisibility);
228
+ if (options.console !== false) {
229
+ let minute = 0, count = 0;
230
+ const levels = ['error', 'warn', ...(options.console?.log ? ['log'] : []), ...(options.console?.info ? ['info'] : []), ...(options.console?.debug ? ['debug'] : [])];
231
+ for (const level of levels) {
232
+ const original = console[level];
233
+ const wrapped = (...args) => {
234
+ const result = Reflect.apply(original, console, args);
235
+ try {
236
+ const now = Math.floor(Date.now() / 60000);
237
+ if (now !== minute) {
238
+ minute = now;
239
+ count = 0;
240
+ }
241
+ if (count++ >= 60)
242
+ dropped++;
243
+ else {
244
+ const values = args.slice(0, 20), error = values.find(value => value instanceof Error);
245
+ let stack = '';
246
+ if (error instanceof Error) {
247
+ try {
248
+ stack = error.stack ?? '';
249
+ }
250
+ catch { /* Optional console stack. */ }
251
+ }
252
+ capture(values.map(a => typeof a === 'string' ? cleanText(a, 1000) : a instanceof Error ? String(Object.getOwnPropertyDescriptor(a, 'message')?.value ?? '[Error]') : JSON.stringify(safeValue(a))).join(' '), stack, 'console', level === 'log' ? 'info' : level);
253
+ }
254
+ }
255
+ catch {
256
+ dropped++;
257
+ }
258
+ return result;
259
+ };
260
+ restorers.push(() => { if (console[level] === wrapped)
261
+ console[level] = original; });
262
+ console[level] = wrapped;
263
+ }
264
+ }
265
+ const timer = setInterval(() => void flush(), 5000);
266
+ restorers.push(() => clearInterval(timer));
267
+ }
268
+ active = client;
269
+ return client;
270
+ }
271
+ /** @deprecated Use Monzori.start(options) or the named start export. */
272
+ export const initMonzori = start;
273
+ const Monzori = { start };
274
+ export default Monzori;
@@ -0,0 +1,18 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ import type { Client } from './index.js';
3
+ export declare class MonzoriErrorBoundary extends Component<{
4
+ client: Client;
5
+ children: ReactNode;
6
+ fallback?: ReactNode;
7
+ }, {
8
+ failed: boolean;
9
+ }> {
10
+ state: {
11
+ failed: boolean;
12
+ };
13
+ static getDerivedStateFromError(): {
14
+ failed: boolean;
15
+ };
16
+ componentDidCatch(error: unknown, info: ErrorInfo): void;
17
+ render(): ReactNode;
18
+ }
package/dist/react.js ADDED
@@ -0,0 +1,13 @@
1
+ import { Component, createElement } from 'react';
2
+ export class MonzoriErrorBoundary extends Component {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.state = { failed: false };
6
+ }
7
+ static getDerivedStateFromError() { return { failed: true }; }
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. */ } }
12
+ render() { return this.state.failed ? this.props.fallback ?? createElement('p', null, 'Não foi possível exibir este conteúdo.') : this.props.children; }
13
+ }
@@ -0,0 +1,6 @@
1
+ export declare function cleanUrl(value: string): string;
2
+ export declare function cleanText(text: string, max?: number): string;
3
+ export declare function safeValue(value: unknown, depth?: number, budget?: {
4
+ nodes: number;
5
+ characters: number;
6
+ }): unknown;
@@ -0,0 +1,57 @@
1
+ const sensitive = /password|passwd|secret|authorization|cookie|token|api[_-]?key|localstorage|sessionstorage/i;
2
+ export function cleanUrl(value) { try {
3
+ const u = new URL(value);
4
+ return ['http:', 'https:'].includes(u.protocol) ? `${u.origin}${u.pathname}` : '';
5
+ }
6
+ catch {
7
+ return '';
8
+ } }
9
+ export function cleanText(text, max = 4000) { return text.slice(0, 16000).replace(/https?:\/\/[^\s'"<>\)]+/g, raw => { const suffix = raw.match(/(:\d+:\d+)$/)?.[1] ?? ''; return cleanUrl(suffix ? raw.slice(0, -suffix.length) : raw) + suffix; }).replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]').replace(/\b(password|passwd|secret|token|api[_-]?key|authorization|cookie)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1=[redacted]').slice(0, max); }
10
+ export function safeValue(value, depth = 0, budget = { nodes: 128, characters: 2000 }) {
11
+ try {
12
+ if (budget.nodes-- <= 0)
13
+ return '[value limit]';
14
+ if (depth > 3)
15
+ return '[depth limit]';
16
+ if (typeof value === 'string') {
17
+ if (budget.characters <= 0)
18
+ return '[string limit]';
19
+ const text = cleanText(value.slice(0, Math.min(1000, budget.characters)), 1000);
20
+ budget.characters -= text.length;
21
+ return text;
22
+ }
23
+ if (typeof value === 'number')
24
+ return Number.isFinite(value) ? value : String(value);
25
+ if (typeof value === 'boolean' || value === null)
26
+ return value;
27
+ if (typeof value === 'function')
28
+ return '[function]';
29
+ if (typeof value !== 'object')
30
+ return cleanText(String(value), 100);
31
+ const prototype = Object.getPrototypeOf(value);
32
+ if (prototype !== Object.prototype && prototype !== null && !Array.isArray(value) && !(value instanceof Error))
33
+ return '[non-plain object]';
34
+ const keys = [];
35
+ for (const key in value) {
36
+ if (Object.prototype.hasOwnProperty.call(value, key))
37
+ keys.push(key);
38
+ if (keys.length >= 20)
39
+ break;
40
+ }
41
+ const out = {};
42
+ for (const key of keys.sort()) {
43
+ if (budget.nodes <= 0) {
44
+ out['...'] = '[value limit]';
45
+ break;
46
+ }
47
+ const item = Object.getOwnPropertyDescriptor(value, key);
48
+ if (!item)
49
+ continue;
50
+ out[key.slice(0, 80)] = sensitive.test(key) ? '[redacted]' : 'value' in item ? safeValue(item.value, depth + 1, budget) : '[accessor omitted]';
51
+ }
52
+ return out;
53
+ }
54
+ catch {
55
+ return '[unavailable]';
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
- "name": "@monzori/browser",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@monzori/browser",
3
+ "version": "0.1.2",
4
+ "description": "Browser error and console collection SDK for Monzori",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react.d.ts",
17
+ "default": "./dist/react.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist/index.js",
22
+ "dist/index.d.ts",
23
+ "dist/react.js",
24
+ "dist/react.d.ts",
25
+ "dist/sanitize.js",
26
+ "dist/sanitize.d.ts"
27
+ ],
28
+ "sideEffects": false,
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "typecheck": "tsc --noEmit -p tsconfig.json",
32
+ "test": "vitest run"
33
+ },
34
+ "peerDependencies": {
35
+ "react": ">=18 <20"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "react": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "devDependencies": {
43
+ "@types/react": "^19.2.2",
44
+ "vitest": "^5.1.0"
45
+ }
46
+ }