@victor-studio/monitor 0.1.0 → 0.4.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/dist/index.js CHANGED
@@ -1,21 +1,71 @@
1
1
  // src/core/transport.ts
2
- async function sendBatch(config, events) {
2
+ var DEFAULT_TIMEOUT = 1e4;
3
+ var DEFAULT_MAX_RETRIES = 3;
4
+ var MAX_PAYLOAD_BYTES = 6e4;
5
+ async function sendBatch(config, events, logger, options) {
3
6
  if (events.length === 0) return true;
4
- try {
5
- const response = await fetch(config.endpoint, {
6
- method: "POST",
7
- headers: { "Content-Type": "application/json" },
8
- body: JSON.stringify({
9
- apiKey: config.apiKey,
10
- events
11
- }),
12
- // Usar keepalive pra garantir envio mesmo quando a página fecha
13
- keepalive: true
14
- });
15
- return response.ok;
16
- } catch {
17
- return false;
7
+ const payload = JSON.stringify({ events });
8
+ if (options?.useBeacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
9
+ const beaconPayload = JSON.stringify({ apiKey: config.apiKey, events });
10
+ const beaconBlob = new Blob([beaconPayload], { type: "application/json" });
11
+ const sent = navigator.sendBeacon(config.endpoint, beaconBlob);
12
+ logger.debug("sendBeacon", sent ? "queued" : "failed", `${events.length} events`);
13
+ return sent;
18
14
  }
15
+ const payloadSize = new Blob([payload]).size;
16
+ if (payloadSize > MAX_PAYLOAD_BYTES && events.length > 1) {
17
+ const mid = Math.floor(events.length / 2);
18
+ const [first, second] = await Promise.all([
19
+ sendBatch(config, events.slice(0, mid), logger),
20
+ sendBatch(config, events.slice(mid), logger)
21
+ ]);
22
+ return first && second;
23
+ }
24
+ return sendWithRetry(config, payload, logger);
25
+ }
26
+ async function sendWithRetry(config, payload, logger) {
27
+ const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
28
+ const timeout = config.timeout ?? DEFAULT_TIMEOUT;
29
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
30
+ try {
31
+ const controller = new AbortController();
32
+ const timer = setTimeout(() => controller.abort(), timeout);
33
+ const response = await fetch(config.endpoint, {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ "Authorization": `Bearer ${config.apiKey}`
38
+ },
39
+ body: payload,
40
+ signal: controller.signal,
41
+ keepalive: attempt === 0
42
+ // keepalive só na primeira tentativa
43
+ });
44
+ clearTimeout(timer);
45
+ if (response.ok) {
46
+ logger.debug("sent", `attempt ${attempt + 1}`, response.status);
47
+ return true;
48
+ }
49
+ if (response.status >= 400 && response.status < 500) {
50
+ logger.warn("client error", response.status, "\u2014 no retry");
51
+ return false;
52
+ }
53
+ logger.warn("server error", response.status, `attempt ${attempt + 1}/${maxRetries + 1}`);
54
+ } catch (err) {
55
+ const message = err instanceof Error ? err.message : "unknown error";
56
+ logger.warn("network error", message, `attempt ${attempt + 1}/${maxRetries + 1}`);
57
+ }
58
+ if (attempt < maxRetries) {
59
+ const delay = Math.min(1e3 * Math.pow(2, attempt), 16e3);
60
+ const jitter = Math.random() * 1e3;
61
+ await sleep(delay + jitter);
62
+ }
63
+ }
64
+ logger.error("all retries exhausted, dropping batch");
65
+ return false;
66
+ }
67
+ function sleep(ms) {
68
+ return new Promise((resolve) => setTimeout(resolve, ms));
19
69
  }
20
70
 
21
71
  // src/core/collector.ts
@@ -26,9 +76,16 @@ var Collector = class {
26
76
  timer = null;
27
77
  config;
28
78
  flushInterval;
29
- constructor(config, flushInterval = DEFAULT_FLUSH_INTERVAL) {
79
+ logger;
80
+ beforeSend;
81
+ sampleRate;
82
+ visibilityHandler = null;
83
+ constructor(config, logger, options) {
30
84
  this.config = config;
31
- this.flushInterval = flushInterval;
85
+ this.logger = logger;
86
+ this.flushInterval = options?.flushInterval ?? DEFAULT_FLUSH_INTERVAL;
87
+ this.beforeSend = options?.beforeSend;
88
+ this.sampleRate = options?.sampleRate ?? 1;
32
89
  }
33
90
  start() {
34
91
  if (this.timer) return;
@@ -36,11 +93,12 @@ var Collector = class {
36
93
  this.flush();
37
94
  }, this.flushInterval);
38
95
  if (typeof window !== "undefined") {
39
- window.addEventListener("visibilitychange", () => {
96
+ this.visibilityHandler = () => {
40
97
  if (document.visibilityState === "hidden") {
41
- this.flush();
98
+ this.flush(true);
42
99
  }
43
- });
100
+ };
101
+ window.addEventListener("visibilitychange", this.visibilityHandler);
44
102
  }
45
103
  }
46
104
  stop() {
@@ -48,55 +106,226 @@ var Collector = class {
48
106
  clearInterval(this.timer);
49
107
  this.timer = null;
50
108
  }
109
+ if (this.visibilityHandler && typeof window !== "undefined") {
110
+ window.removeEventListener("visibilitychange", this.visibilityHandler);
111
+ this.visibilityHandler = null;
112
+ }
51
113
  this.flush();
52
114
  }
53
- push(event) {
54
- this.buffer.push({
55
- ...event,
115
+ push(input) {
116
+ if (input.type !== "heartbeat" && this.sampleRate < 1) {
117
+ if (Math.random() >= this.sampleRate) {
118
+ this.logger.debug("sampled out", input.type);
119
+ return;
120
+ }
121
+ }
122
+ const event = {
123
+ ...input,
56
124
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
57
- });
125
+ };
126
+ if (this.beforeSend) {
127
+ const result = this.beforeSend(event);
128
+ if (result === null) {
129
+ this.logger.debug("dropped by beforeSend", event.type);
130
+ return;
131
+ }
132
+ this.buffer.push(result);
133
+ } else {
134
+ this.buffer.push(event);
135
+ }
136
+ this.logger.debug("buffered", event.type, `(${this.buffer.length}/${MAX_BUFFER_SIZE})`);
58
137
  if (this.buffer.length >= MAX_BUFFER_SIZE) {
59
138
  this.flush();
60
139
  }
61
140
  }
62
- flush() {
141
+ /** Flush manual — exposto via MonitorClient.flush() */
142
+ flush(useBeacon = false) {
63
143
  if (this.buffer.length === 0) return;
64
144
  const events = [...this.buffer];
65
145
  this.buffer = [];
66
- sendBatch(this.config, events).catch(() => {
146
+ this.logger.debug("flushing", events.length, "events", useBeacon ? "(beacon)" : "");
147
+ sendBatch(this.config, events, this.logger, { useBeacon }).catch(() => {
67
148
  });
68
149
  }
150
+ /** Número de eventos no buffer (para debug/testes) */
151
+ get pending() {
152
+ return this.buffer.length;
153
+ }
69
154
  };
70
155
 
156
+ // src/core/env.ts
157
+ var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "[::1]"]);
158
+ function isDev() {
159
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") return true;
160
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "test") return true;
161
+ if (typeof window !== "undefined" && LOCAL_HOSTNAMES.has(window.location?.hostname)) return true;
162
+ return false;
163
+ }
164
+
165
+ // src/errors/normalize.ts
166
+ function generateGroupingKey(error) {
167
+ const name = error.name || "Error";
168
+ const message = stripDynamicValues(error.message);
169
+ const frames = extractTopFrames(error.stack, 3);
170
+ const raw = `${name}:${message}:${frames.join("|")}`;
171
+ return simpleHash(raw);
172
+ }
173
+ function normalizeStack(stack) {
174
+ if (!stack) return void 0;
175
+ return stack.split("\n").map((line) => {
176
+ let normalized = line.replace(/\(?\/?(?:[\w.-]+\/)+/g, "");
177
+ normalized = normalized.replace(/\?[^):\s]+/g, "");
178
+ return normalized;
179
+ }).join("\n");
180
+ }
181
+ function extractTopFrames(stack, count) {
182
+ if (!stack) return [];
183
+ return stack.split("\n").filter((line) => line.includes("at ") || line.includes("@")).slice(0, count).map((line) => line.trim());
184
+ }
185
+ function stripDynamicValues(message) {
186
+ return message.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<uuid>").replace(/\b\d{4,}\b/g, "<n>").replace(/["'][^"']{32,}["']/g, "<token>");
187
+ }
188
+ function simpleHash(str) {
189
+ let hash = 0;
190
+ for (let i = 0; i < str.length; i++) {
191
+ const char = str.charCodeAt(i);
192
+ hash = (hash << 5) - hash + char | 0;
193
+ }
194
+ return (hash >>> 0).toString(16).padStart(8, "0");
195
+ }
196
+
197
+ // src/errors/handler.ts
198
+ function captureError(monitor, error, context) {
199
+ const normalized = toError(error);
200
+ const data = {
201
+ type: "caught",
202
+ message: normalized.message,
203
+ stack: normalizeStack(normalized.stack),
204
+ groupingKey: generateGroupingKey(normalized),
205
+ route: context?.route,
206
+ method: context?.method,
207
+ statusCode: context?.statusCode,
208
+ extra: context?.extra
209
+ };
210
+ monitor.captureError(data);
211
+ }
212
+ function setupGlobalHandlers(monitor) {
213
+ if (typeof window === "undefined") return () => {
214
+ };
215
+ const onError = (event) => {
216
+ const error = event.error ?? new Error(event.message);
217
+ const normalized = toError(error);
218
+ monitor.captureError({
219
+ type: "unhandled",
220
+ message: normalized.message,
221
+ stack: normalizeStack(normalized.stack),
222
+ groupingKey: generateGroupingKey(normalized),
223
+ route: window.location.pathname
224
+ });
225
+ };
226
+ const onRejection = (event) => {
227
+ const error = event.reason ?? new Error("Unhandled promise rejection");
228
+ const normalized = toError(error);
229
+ monitor.captureError({
230
+ type: "promise",
231
+ message: normalized.message,
232
+ stack: normalizeStack(normalized.stack),
233
+ groupingKey: generateGroupingKey(normalized),
234
+ route: window.location.pathname
235
+ });
236
+ };
237
+ window.addEventListener("error", onError);
238
+ window.addEventListener("unhandledrejection", onRejection);
239
+ return () => {
240
+ window.removeEventListener("error", onError);
241
+ window.removeEventListener("unhandledrejection", onRejection);
242
+ };
243
+ }
244
+ function toError(value) {
245
+ if (value instanceof Error) return value;
246
+ if (typeof value === "string") return new Error(value);
247
+ return new Error(String(value));
248
+ }
249
+
250
+ // src/core/logger.ts
251
+ var PREFIX = "[monitor]";
252
+ function createLogger(debug) {
253
+ if (!debug) {
254
+ return { debug: noop, warn: noop, error: noop };
255
+ }
256
+ return {
257
+ debug: (...args) => console.debug(PREFIX, ...args),
258
+ warn: (...args) => console.warn(PREFIX, ...args),
259
+ error: (...args) => console.error(PREFIX, ...args)
260
+ };
261
+ }
262
+ function noop() {
263
+ }
264
+
71
265
  // src/core/client.ts
72
266
  var MonitorClient = class {
73
267
  config;
74
268
  collector;
269
+ logger;
75
270
  heartbeatTimer = null;
271
+ globalHandlersCleanup = null;
76
272
  active = false;
77
273
  constructor(config) {
78
274
  this.config = config;
275
+ this.logger = createLogger(config.debug ?? false);
79
276
  this.collector = new Collector(
80
- { endpoint: config.endpoint, apiKey: config.apiKey },
81
- config.flushInterval
277
+ {
278
+ endpoint: config.endpoint,
279
+ apiKey: config.apiKey,
280
+ timeout: config.timeout,
281
+ maxRetries: config.maxRetries
282
+ },
283
+ this.logger,
284
+ {
285
+ flushInterval: config.flushInterval,
286
+ beforeSend: config.beforeSend,
287
+ sampleRate: config.sampleRate
288
+ }
82
289
  );
83
290
  }
84
291
  /** Inicia o monitoring (heartbeat + collector) */
85
292
  start() {
86
- if (this.config.disableInDev !== false && this.isDev()) return;
293
+ if (this.config.disableInDev !== false && isDev()) {
294
+ this.logger.debug("disabled in dev \u2014 call start() with disableInDev: false to override");
295
+ return;
296
+ }
87
297
  if (this.active) return;
88
298
  this.active = true;
89
299
  this.collector.start();
90
300
  this.startHeartbeat();
301
+ if (this.config.captureErrors || this.config.captureUnhandledRejections) {
302
+ this.globalHandlersCleanup = setupGlobalHandlers(this);
303
+ this.logger.debug("global error handlers registered");
304
+ }
305
+ this.logger.debug("started", `endpoint=${this.config.endpoint}`);
91
306
  }
92
307
  /** Para o monitoring */
93
308
  stop() {
309
+ if (!this.active) return;
94
310
  this.active = false;
95
311
  this.collector.stop();
96
312
  if (this.heartbeatTimer) {
97
313
  clearInterval(this.heartbeatTimer);
98
314
  this.heartbeatTimer = null;
99
315
  }
316
+ if (this.globalHandlersCleanup) {
317
+ this.globalHandlersCleanup();
318
+ this.globalHandlersCleanup = null;
319
+ }
320
+ this.logger.debug("stopped");
321
+ }
322
+ /** Força um flush imediato do buffer */
323
+ flush() {
324
+ this.collector.flush();
325
+ }
326
+ /** Verifica se o monitoring está ativo */
327
+ get isActive() {
328
+ return this.active;
100
329
  }
101
330
  /** Registra um evento de request HTTP (usado pelo middleware) */
102
331
  trackRequest(data) {
@@ -108,6 +337,22 @@ var MonitorClient = class {
108
337
  if (!this.active) return;
109
338
  this.collector.push({ type: "vital", data });
110
339
  }
340
+ /** Registra um evento de adapter (DB, cache, AI, queue, email) */
341
+ trackAdapter(data) {
342
+ if (!this.active) return;
343
+ this.collector.push({ type: "adapter", data });
344
+ }
345
+ /** Registra um erro capturado */
346
+ captureError(data) {
347
+ if (!this.active) return;
348
+ this.collector.push({ type: "error", data });
349
+ }
350
+ /** Registra um evento de deploy */
351
+ trackDeploy(data) {
352
+ if (!this.active) return;
353
+ this.collector.push({ type: "deploy", data });
354
+ }
355
+ // ─── Private ────────────────────────────────────────
111
356
  startHeartbeat() {
112
357
  const interval = this.config.heartbeatInterval ?? 6e4;
113
358
  this.sendHeartbeat();
@@ -134,18 +379,12 @@ var MonitorClient = class {
134
379
  this.collector.push({
135
380
  type: "heartbeat",
136
381
  data: {
137
- status: "online",
138
- // O app está rodando, mesmo que o endpoint falhe
382
+ status: "offline",
139
383
  latencyMs: Math.round(performance.now() - start)
140
384
  }
141
385
  });
142
386
  }
143
387
  }
144
- isDev() {
145
- if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") return true;
146
- if (typeof window !== "undefined" && window.location?.hostname === "localhost") return true;
147
- return false;
148
- }
149
388
  };
150
389
 
151
390
  // src/index.ts
@@ -155,6 +394,6 @@ function createMonitor(config) {
155
394
  return client;
156
395
  }
157
396
 
158
- export { MonitorClient, createMonitor };
397
+ export { MonitorClient, captureError, createMonitor, setupGlobalHandlers };
159
398
  //# sourceMappingURL=index.js.map
160
399
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/transport.ts","../src/core/collector.ts","../src/core/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Transport — envia batch de eventos pro backend Nuvio\n\nexport interface MonitorEvent {\n type: 'heartbeat' | 'request' | 'vital'\n data: Record<string, unknown>\n timestamp: string\n}\n\nexport interface TransportConfig {\n endpoint: string\n apiKey: string\n}\n\nexport async function sendBatch(\n config: TransportConfig,\n events: MonitorEvent[],\n): Promise<boolean> {\n if (events.length === 0) return true\n\n try {\n const response = await fetch(config.endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n apiKey: config.apiKey,\n events,\n }),\n // Usar keepalive pra garantir envio mesmo quando a página fecha\n keepalive: true,\n })\n\n return response.ok\n } catch {\n // Falha silenciosa — monitoring não pode quebrar o app do cliente\n return false\n }\n}\n","// Collector — acumula eventos em buffer e envia em batch\n\nimport { sendBatch, type MonitorEvent, type TransportConfig } from './transport'\n\nconst DEFAULT_FLUSH_INTERVAL = 30_000 // 30 segundos\nconst MAX_BUFFER_SIZE = 50\n\nexport class Collector {\n private buffer: MonitorEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private config: TransportConfig\n private flushInterval: number\n\n constructor(config: TransportConfig, flushInterval = DEFAULT_FLUSH_INTERVAL) {\n this.config = config\n this.flushInterval = flushInterval\n }\n\n start() {\n if (this.timer) return\n\n this.timer = setInterval(() => {\n this.flush()\n }, this.flushInterval)\n\n // Flush quando a página for fechada (browser)\n if (typeof window !== 'undefined') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flush()\n }\n })\n }\n }\n\n stop() {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n // Flush final\n this.flush()\n }\n\n push(event: Omit<MonitorEvent, 'timestamp'>) {\n this.buffer.push({\n ...event,\n timestamp: new Date().toISOString(),\n })\n\n // Se buffer cheio, flush imediato\n if (this.buffer.length >= MAX_BUFFER_SIZE) {\n this.flush()\n }\n }\n\n private flush() {\n if (this.buffer.length === 0) return\n\n const events = [...this.buffer]\n this.buffer = []\n\n // Fire-and-forget — não bloqueia o app\n sendBatch(this.config, events).catch(() => {\n // Silencioso — eventos perdidos são aceitáveis\n })\n }\n}\n","// MonitorClient — instância principal do SDK\n\nimport { Collector } from './collector'\n\nexport interface MonitorConfig {\n /** ID do projeto no Nuvio */\n projectId: string\n /** API key gerada no Nuvio */\n apiKey: string\n /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */\n endpoint: string\n /** Intervalo do heartbeat em ms (default: 60000) */\n heartbeatInterval?: number\n /** Intervalo do flush do buffer em ms (default: 30000) */\n flushInterval?: number\n /** Desabilitar em desenvolvimento (default: true) */\n disableInDev?: boolean\n}\n\nexport class MonitorClient {\n readonly config: MonitorConfig\n readonly collector: Collector\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private active = false\n\n constructor(config: MonitorConfig) {\n this.config = config\n this.collector = new Collector(\n { endpoint: config.endpoint, apiKey: config.apiKey },\n config.flushInterval,\n )\n }\n\n /** Inicia o monitoring (heartbeat + collector) */\n start() {\n // Não roda em dev por padrão\n if (this.config.disableInDev !== false && this.isDev()) return\n if (this.active) return\n\n this.active = true\n this.collector.start()\n this.startHeartbeat()\n }\n\n /** Para o monitoring */\n stop() {\n this.active = false\n this.collector.stop()\n\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /** Registra um evento de request HTTP (usado pelo middleware) */\n trackRequest(data: {\n method: string\n route: string\n statusCode: number\n responseTimeMs: number\n userAgent?: string\n region?: string\n }) {\n if (!this.active) return\n this.collector.push({ type: 'request', data })\n }\n\n /** Registra um Web Vital (usado pelo MonitorScript) */\n trackVital(data: {\n name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB'\n value: number\n rating: 'good' | 'needs-improvement' | 'poor'\n page?: string\n deviceType?: string\n browser?: string\n }) {\n if (!this.active) return\n this.collector.push({ type: 'vital', data })\n }\n\n private startHeartbeat() {\n const interval = this.config.heartbeatInterval ?? 60_000\n\n // Primeiro heartbeat imediato\n this.sendHeartbeat()\n\n this.heartbeatTimer = setInterval(() => {\n this.sendHeartbeat()\n }, interval)\n }\n\n private async sendHeartbeat() {\n const start = performance.now()\n\n try {\n // Ping pro endpoint pra medir latência\n const response = await fetch(this.config.endpoint, {\n method: 'HEAD',\n cache: 'no-store',\n })\n\n const latencyMs = Math.round(performance.now() - start)\n\n this.collector.push({\n type: 'heartbeat',\n data: {\n status: response.ok ? 'online' : 'offline',\n latencyMs,\n },\n })\n } catch {\n this.collector.push({\n type: 'heartbeat',\n data: {\n status: 'online', // O app está rodando, mesmo que o endpoint falhe\n latencyMs: Math.round(performance.now() - start),\n },\n })\n }\n }\n\n private isDev(): boolean {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') return true\n if (typeof window !== 'undefined' && window.location?.hostname === 'localhost') return true\n return false\n }\n}\n","// @victor-studio/monitor — SDK de monitoramento\n\nimport { MonitorClient, type MonitorConfig } from './core/client'\n\nexport { MonitorClient, type MonitorConfig } from './core/client'\n\n/**\n * Cria e inicia uma instância do monitor.\n *\n * @example\n * ```ts\n * import { createMonitor } from '@victor-studio/monitor'\n *\n * export const monitor = createMonitor({\n * projectId: 'uuid-do-projeto',\n * apiKey: 'nuvio_mon_xxxxxxxx',\n * endpoint: 'https://app.nuvio.com/api/monitor/ingest',\n * })\n * ```\n */\nexport function createMonitor(config: MonitorConfig): MonitorClient {\n const client = new MonitorClient(config)\n client.start()\n return client\n}\n"]}
1
+ {"version":3,"sources":["../src/core/transport.ts","../src/core/collector.ts","../src/core/env.ts","../src/errors/normalize.ts","../src/errors/handler.ts","../src/core/logger.ts","../src/core/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Transport — envia batch de eventos pro backend Nuvio\n\nimport type { MonitorEvent } from '../types'\nimport type { Logger } from './logger'\n\nexport interface TransportConfig {\n endpoint: string\n apiKey: string\n /** Timeout do fetch em ms (default: 10000) */\n timeout?: number\n /** Max tentativas de retry (default: 3) */\n maxRetries?: number\n}\n\ninterface SendOptions {\n /** Usar sendBeacon em vez de fetch (para page unload) */\n useBeacon?: boolean\n}\n\nconst DEFAULT_TIMEOUT = 10_000\nconst DEFAULT_MAX_RETRIES = 3\nconst MAX_PAYLOAD_BYTES = 60_000 // 60KB — abaixo do limite de 64KB do keepalive\n\n/**\n * Envia um batch de eventos para o endpoint.\n * Usa Authorization header, retry com backoff, e sendBeacon como fallback.\n */\nexport async function sendBatch(\n config: TransportConfig,\n events: MonitorEvent[],\n logger: Logger,\n options?: SendOptions,\n): Promise<boolean> {\n if (events.length === 0) return true\n\n const payload = JSON.stringify({ events })\n\n // Se é unload, usa sendBeacon (mais confiável que fetch no page close)\n if (options?.useBeacon && typeof navigator !== 'undefined' && navigator.sendBeacon) {\n // sendBeacon não suporta headers customizados — inclui apiKey no body\n const beaconPayload = JSON.stringify({ apiKey: config.apiKey, events })\n const beaconBlob = new Blob([beaconPayload], { type: 'application/json' })\n const sent = navigator.sendBeacon(config.endpoint, beaconBlob)\n logger.debug('sendBeacon', sent ? 'queued' : 'failed', `${events.length} events`)\n return sent\n }\n\n // Dividir em batches menores se payload é muito grande\n const payloadSize = new Blob([payload]).size\n if (payloadSize > MAX_PAYLOAD_BYTES && events.length > 1) {\n const mid = Math.floor(events.length / 2)\n const [first, second] = await Promise.all([\n sendBatch(config, events.slice(0, mid), logger),\n sendBatch(config, events.slice(mid), logger),\n ])\n return first && second\n }\n\n return sendWithRetry(config, payload, logger)\n}\n\nasync function sendWithRetry(\n config: TransportConfig,\n payload: string,\n logger: Logger,\n): Promise<boolean> {\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(config.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: payload,\n signal: controller.signal,\n keepalive: attempt === 0, // keepalive só na primeira tentativa\n })\n\n clearTimeout(timer)\n\n if (response.ok) {\n logger.debug('sent', `attempt ${attempt + 1}`, response.status)\n return true\n }\n\n // 4xx — erro do cliente, não faz retry (apiKey inválida, payload mal formado, etc.)\n if (response.status >= 400 && response.status < 500) {\n logger.warn('client error', response.status, '— no retry')\n return false\n }\n\n // 5xx — erro do servidor, faz retry\n logger.warn('server error', response.status, `attempt ${attempt + 1}/${maxRetries + 1}`)\n } catch (err) {\n // Network error ou timeout — faz retry\n const message = err instanceof Error ? err.message : 'unknown error'\n logger.warn('network error', message, `attempt ${attempt + 1}/${maxRetries + 1}`)\n }\n\n // Esperar antes do retry (exponential backoff com jitter)\n if (attempt < maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, attempt), 16_000)\n const jitter = Math.random() * 1000\n await sleep(delay + jitter)\n }\n }\n\n logger.error('all retries exhausted, dropping batch')\n return false\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n","// Collector — acumula eventos em buffer e envia em batch\n\nimport type { MonitorEvent, MonitorEventInput, BeforeSendHook } from '../types'\nimport { sendBatch, type TransportConfig } from './transport'\nimport type { Logger } from './logger'\n\nconst DEFAULT_FLUSH_INTERVAL = 30_000 // 30 segundos\nconst MAX_BUFFER_SIZE = 50\n\nexport class Collector {\n private buffer: MonitorEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private config: TransportConfig\n private flushInterval: number\n private logger: Logger\n private beforeSend: BeforeSendHook | undefined\n private sampleRate: number\n private visibilityHandler: (() => void) | null = null\n\n constructor(\n config: TransportConfig,\n logger: Logger,\n options?: {\n flushInterval?: number\n beforeSend?: BeforeSendHook\n sampleRate?: number\n },\n ) {\n this.config = config\n this.logger = logger\n this.flushInterval = options?.flushInterval ?? DEFAULT_FLUSH_INTERVAL\n this.beforeSend = options?.beforeSend\n this.sampleRate = options?.sampleRate ?? 1.0\n }\n\n start() {\n if (this.timer) return\n\n this.timer = setInterval(() => {\n this.flush()\n }, this.flushInterval)\n\n // Flush quando a página for fechada (browser) — usa sendBeacon\n if (typeof window !== 'undefined') {\n this.visibilityHandler = () => {\n if (document.visibilityState === 'hidden') {\n this.flush(true)\n }\n }\n window.addEventListener('visibilitychange', this.visibilityHandler)\n }\n }\n\n stop() {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n\n // Remover listener para evitar memory leak\n if (this.visibilityHandler && typeof window !== 'undefined') {\n window.removeEventListener('visibilitychange', this.visibilityHandler)\n this.visibilityHandler = null\n }\n\n // Flush final\n this.flush()\n }\n\n push(input: MonitorEventInput) {\n // Sampling — heartbeats nunca são amostrados\n if (input.type !== 'heartbeat' && this.sampleRate < 1.0) {\n if (Math.random() >= this.sampleRate) {\n this.logger.debug('sampled out', input.type)\n return\n }\n }\n\n const event: MonitorEvent = {\n ...input,\n timestamp: new Date().toISOString(),\n } as MonitorEvent\n\n // beforeSend hook — permite filtrar ou modificar eventos\n if (this.beforeSend) {\n const result = this.beforeSend(event)\n if (result === null) {\n this.logger.debug('dropped by beforeSend', event.type)\n return\n }\n // Usa o evento possivelmente modificado\n this.buffer.push(result)\n } else {\n this.buffer.push(event)\n }\n\n this.logger.debug('buffered', event.type, `(${this.buffer.length}/${MAX_BUFFER_SIZE})`)\n\n // Se buffer cheio, flush imediato\n if (this.buffer.length >= MAX_BUFFER_SIZE) {\n this.flush()\n }\n }\n\n /** Flush manual — exposto via MonitorClient.flush() */\n flush(useBeacon = false) {\n if (this.buffer.length === 0) return\n\n const events = [...this.buffer]\n this.buffer = []\n\n this.logger.debug('flushing', events.length, 'events', useBeacon ? '(beacon)' : '')\n\n // Fire-and-forget — não bloqueia o app\n sendBatch(this.config, events, this.logger, { useBeacon }).catch(() => {\n // Silencioso — transport já loga erros via logger\n })\n }\n\n /** Número de eventos no buffer (para debug/testes) */\n get pending(): number {\n return this.buffer.length\n }\n}\n","// Detecção de ambiente — centraliza lógica de dev/server\n\nconst LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '0.0.0.0', '[::1]'])\n\n/** Detecta se estamos em ambiente de desenvolvimento */\nexport function isDev(): boolean {\n // Node.js / bundler env\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') return true\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') return true\n\n // Browser\n if (typeof window !== 'undefined' && LOCAL_HOSTNAMES.has(window.location?.hostname)) return true\n\n return false\n}\n\n/** Detecta se estamos rodando no servidor (Node.js) */\nexport function isServer(): boolean {\n return typeof window === 'undefined'\n}\n\n/** Detecta se estamos no browser */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined'\n}\n","// Normalização de erros — stack cleanup e grouping key\n\n/**\n * Gera uma chave de agrupamento a partir do erro.\n * Erros com o mesmo groupingKey são agrupados no dashboard.\n * Usa: nome do erro + mensagem + 3 primeiros frames do stack.\n */\nexport function generateGroupingKey(error: Error): string {\n const name = error.name || 'Error'\n const message = stripDynamicValues(error.message)\n const frames = extractTopFrames(error.stack, 3)\n\n const raw = `${name}:${message}:${frames.join('|')}`\n return simpleHash(raw)\n}\n\n/**\n * Normaliza stack trace — remove caminhos absolutos e queries dinâmicas.\n */\nexport function normalizeStack(stack: string | undefined): string | undefined {\n if (!stack) return undefined\n\n return stack\n .split('\\n')\n .map((line) => {\n // Remover caminhos absolutos do filesystem\n let normalized = line.replace(/\\(?\\/?(?:[\\w.-]+\\/)+/g, '')\n // Remover query strings e hashes de URLs\n normalized = normalized.replace(/\\?[^):\\s]+/g, '')\n return normalized\n })\n .join('\\n')\n}\n\n// ─── Helpers internos ───────────────────────────────\n\n/** Extrai as N primeiras linhas significativas do stack */\nfunction extractTopFrames(stack: string | undefined, count: number): string[] {\n if (!stack) return []\n\n return stack\n .split('\\n')\n .filter((line) => line.includes('at ') || line.includes('@'))\n .slice(0, count)\n .map((line) => line.trim())\n}\n\n/** Remove valores dinâmicos da mensagem (UUIDs, números, tokens) */\nfunction stripDynamicValues(message: string): string {\n return message\n .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<uuid>')\n .replace(/\\b\\d{4,}\\b/g, '<n>')\n .replace(/[\"'][^\"']{32,}[\"']/g, '<token>')\n}\n\n/** Hash simples para gerar groupingKey determinístico */\nfunction simpleHash(str: string): string {\n let hash = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hash = ((hash << 5) - hash + char) | 0\n }\n // Converter para hex positivo\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n","// Error handler — captura de erros manual e automática\n\nimport type { MonitorClient } from '../core/client'\nimport type { ErrorData } from '../types'\nimport { generateGroupingKey, normalizeStack } from './normalize'\n\ninterface CaptureContext {\n route?: string\n method?: string\n statusCode?: number\n extra?: Record<string, string>\n}\n\n/**\n * Captura um erro e envia pro monitor.\n *\n * @example\n * ```ts\n * try {\n * await riskyOperation()\n * } catch (err) {\n * captureError(monitor, err, { route: '/api/users' })\n * }\n * ```\n */\nexport function captureError(\n monitor: MonitorClient,\n error: unknown,\n context?: CaptureContext,\n): void {\n const normalized = toError(error)\n const data: ErrorData = {\n type: 'caught',\n message: normalized.message,\n stack: normalizeStack(normalized.stack),\n groupingKey: generateGroupingKey(normalized),\n route: context?.route,\n method: context?.method,\n statusCode: context?.statusCode,\n extra: context?.extra,\n }\n\n monitor.captureError(data)\n}\n\n/**\n * Registra handlers globais para erros não capturados.\n * Só funciona no browser — no servidor, NÃO interceptamos uncaughtException\n * (perigoso para um SDK de monitoring).\n *\n * @returns Função de cleanup que remove os handlers\n *\n * @example\n * ```ts\n * const cleanup = setupGlobalHandlers(monitor)\n * // Quando quiser parar:\n * cleanup()\n * ```\n */\nexport function setupGlobalHandlers(monitor: MonitorClient): () => void {\n if (typeof window === 'undefined') return () => {}\n\n const onError = (event: ErrorEvent) => {\n const error = event.error ?? new Error(event.message)\n const normalized = toError(error)\n\n monitor.captureError({\n type: 'unhandled',\n message: normalized.message,\n stack: normalizeStack(normalized.stack),\n groupingKey: generateGroupingKey(normalized),\n route: window.location.pathname,\n })\n }\n\n const onRejection = (event: PromiseRejectionEvent) => {\n const error = event.reason ?? new Error('Unhandled promise rejection')\n const normalized = toError(error)\n\n monitor.captureError({\n type: 'promise',\n message: normalized.message,\n stack: normalizeStack(normalized.stack),\n groupingKey: generateGroupingKey(normalized),\n route: window.location.pathname,\n })\n }\n\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n\n return () => {\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n }\n}\n\n/** Converte qualquer valor em Error */\nfunction toError(value: unknown): Error {\n if (value instanceof Error) return value\n if (typeof value === 'string') return new Error(value)\n return new Error(String(value))\n}\n","// Logger — debug mode com prefixo [monitor]\n\nconst PREFIX = '[monitor]'\n\nexport interface Logger {\n debug(...args: unknown[]): void\n warn(...args: unknown[]): void\n error(...args: unknown[]): void\n}\n\n/** Cria logger que loga no console quando debug=true, noop caso contrário */\nexport function createLogger(debug: boolean): Logger {\n if (!debug) {\n return { debug: noop, warn: noop, error: noop }\n }\n\n return {\n debug: (...args: unknown[]) => console.debug(PREFIX, ...args),\n warn: (...args: unknown[]) => console.warn(PREFIX, ...args),\n error: (...args: unknown[]) => console.error(PREFIX, ...args),\n }\n}\n\nfunction noop() {\n // silencioso\n}\n","// MonitorClient — instância principal do SDK\n\nimport type {\n HeartbeatData,\n RequestData,\n VitalData,\n AdapterData,\n ErrorData,\n DeployData,\n BeforeSendHook,\n} from '../types'\nimport { Collector } from './collector'\nimport { isDev } from './env'\nimport { setupGlobalHandlers } from '../errors/handler'\nimport { createLogger, type Logger } from './logger'\n\nexport interface MonitorConfig {\n /** ID do projeto no Nuvio */\n projectId: string\n /** API key gerada no Nuvio */\n apiKey: string\n /** URL do endpoint de ingest */\n endpoint: string\n /** Intervalo do heartbeat em ms (default: 60000) */\n heartbeatInterval?: number\n /** Intervalo do flush do buffer em ms (default: 30000) */\n flushInterval?: number\n /** Timeout do fetch em ms (default: 10000) */\n timeout?: number\n /** Max tentativas de retry (default: 3) */\n maxRetries?: number\n /** Desabilitar em desenvolvimento (default: true) */\n disableInDev?: boolean\n /** Taxa de amostragem 0.0-1.0 (default: 1.0). Heartbeats nunca são amostrados */\n sampleRate?: number\n /** Hook chamado antes de enfileirar cada evento. Retorna null para dropar */\n beforeSend?: BeforeSendHook\n /** Habilitar logs de debug no console (default: false) */\n debug?: boolean\n /** Capturar erros globais automaticamente — window.onerror (default: false) */\n captureErrors?: boolean\n /** Capturar unhandled promise rejections automaticamente (default: false) */\n captureUnhandledRejections?: boolean\n}\n\nexport class MonitorClient {\n readonly config: MonitorConfig\n private readonly collector: Collector\n private readonly logger: Logger\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private globalHandlersCleanup: (() => void) | null = null\n private active = false\n\n constructor(config: MonitorConfig) {\n this.config = config\n this.logger = createLogger(config.debug ?? false)\n this.collector = new Collector(\n {\n endpoint: config.endpoint,\n apiKey: config.apiKey,\n timeout: config.timeout,\n maxRetries: config.maxRetries,\n },\n this.logger,\n {\n flushInterval: config.flushInterval,\n beforeSend: config.beforeSend,\n sampleRate: config.sampleRate,\n },\n )\n }\n\n /** Inicia o monitoring (heartbeat + collector) */\n start() {\n // Não roda em dev por padrão\n if (this.config.disableInDev !== false && isDev()) {\n this.logger.debug('disabled in dev — call start() with disableInDev: false to override')\n return\n }\n\n if (this.active) return\n\n this.active = true\n this.collector.start()\n this.startHeartbeat()\n\n // Error tracking automático (browser only)\n if (this.config.captureErrors || this.config.captureUnhandledRejections) {\n this.globalHandlersCleanup = setupGlobalHandlers(this)\n this.logger.debug('global error handlers registered')\n }\n\n this.logger.debug('started', `endpoint=${this.config.endpoint}`)\n }\n\n /** Para o monitoring */\n stop() {\n if (!this.active) return\n\n this.active = false\n this.collector.stop()\n\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n\n if (this.globalHandlersCleanup) {\n this.globalHandlersCleanup()\n this.globalHandlersCleanup = null\n }\n\n this.logger.debug('stopped')\n }\n\n /** Força um flush imediato do buffer */\n flush() {\n this.collector.flush()\n }\n\n /** Verifica se o monitoring está ativo */\n get isActive(): boolean {\n return this.active\n }\n\n /** Registra um evento de request HTTP (usado pelo middleware) */\n trackRequest(data: RequestData) {\n if (!this.active) return\n this.collector.push({ type: 'request', data })\n }\n\n /** Registra um Web Vital (usado pelo MonitorScript) */\n trackVital(data: VitalData) {\n if (!this.active) return\n this.collector.push({ type: 'vital', data })\n }\n\n /** Registra um evento de adapter (DB, cache, AI, queue, email) */\n trackAdapter(data: AdapterData) {\n if (!this.active) return\n this.collector.push({ type: 'adapter', data })\n }\n\n /** Registra um erro capturado */\n captureError(data: ErrorData) {\n if (!this.active) return\n this.collector.push({ type: 'error', data })\n }\n\n /** Registra um evento de deploy */\n trackDeploy(data: DeployData) {\n if (!this.active) return\n this.collector.push({ type: 'deploy', data })\n }\n\n // ─── Private ────────────────────────────────────────\n\n private startHeartbeat() {\n const interval = this.config.heartbeatInterval ?? 60_000\n\n // Primeiro heartbeat imediato\n this.sendHeartbeat()\n\n this.heartbeatTimer = setInterval(() => {\n this.sendHeartbeat()\n }, interval)\n }\n\n private async sendHeartbeat() {\n const start = performance.now()\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: 'HEAD',\n cache: 'no-store',\n })\n\n const latencyMs = Math.round(performance.now() - start)\n\n this.collector.push({\n type: 'heartbeat',\n data: {\n status: response.ok ? 'online' : 'offline',\n latencyMs,\n } satisfies HeartbeatData,\n })\n } catch {\n // Network error — o endpoint não respondeu, reporta offline\n this.collector.push({\n type: 'heartbeat',\n data: {\n status: 'offline',\n latencyMs: Math.round(performance.now() - start),\n } satisfies HeartbeatData,\n })\n }\n }\n}\n","// @victor-studio/monitor — SDK de monitoramento\n\nimport { MonitorClient, type MonitorConfig } from './core/client'\n\nexport { MonitorClient, type MonitorConfig } from './core/client'\nexport type {\n MonitorEvent,\n MonitorEventInput,\n MonitorEventType,\n BeforeSendHook,\n HeartbeatData,\n RequestData,\n VitalData,\n ErrorData,\n DeployData,\n AdapterData,\n} from './types'\n\nexport { captureError, setupGlobalHandlers } from './errors'\n\n/**\n * Cria e inicia uma instância do monitor.\n *\n * @example\n * ```ts\n * import { createMonitor } from '@victor-studio/monitor'\n *\n * export const monitor = createMonitor({\n * projectId: 'uuid-do-projeto',\n * apiKey: 'nuvio_mon_xxxxxxxx',\n * endpoint: 'https://nuvio.app/api/monitor/ingest',\n * })\n * ```\n */\nexport function createMonitor(config: MonitorConfig): MonitorClient {\n const client = new MonitorClient(config)\n client.start()\n return client\n}\n"]}
@@ -1,28 +1,47 @@
1
1
  'use strict';
2
2
 
3
3
  // src/next/middleware.ts
4
- function withMonitor(monitor, middleware) {
4
+ var STATIC_ASSET_PATTERN = /^\/(_next\/|favicon|.*\.(ico|png|jpg|jpeg|gif|svg|css|woff2?|ttf|eot)$)/;
5
+ var REGION_HEADERS = [
6
+ "x-vercel-ip-country",
7
+ // Vercel
8
+ "x-railway-region",
9
+ // Railway
10
+ "cf-ipcountry",
11
+ // Cloudflare
12
+ "x-country-code"
13
+ // Generic CDN
14
+ ];
15
+ function withMonitor(monitor, middleware, options) {
5
16
  return async (request) => {
17
+ const { pathname } = new URL(request.url);
18
+ if (!options?.includeStaticAssets && STATIC_ASSET_PATTERN.test(pathname)) {
19
+ return middleware ? middleware(request) : void 0;
20
+ }
21
+ if (options?.excludeRoutes?.some((pattern) => pattern.test(pathname))) {
22
+ return middleware ? middleware(request) : void 0;
23
+ }
6
24
  const start = performance.now();
7
25
  let response;
8
26
  if (middleware) {
9
27
  response = await middleware(request);
10
28
  }
11
29
  const responseTimeMs = Math.round(performance.now() - start);
12
- const method = request.method;
13
- const route = new URL(request.url).pathname;
14
- const statusCode = response instanceof Response ? response.status : 200;
15
- const userAgent = request.headers.get("user-agent") ?? void 0;
16
- const region = request.headers.get("x-vercel-ip-country") ?? void 0;
17
- if (route.startsWith("/_next/") || route.startsWith("/favicon") || route.endsWith(".ico") || route.endsWith(".png") || route.endsWith(".jpg") || route.endsWith(".svg") || route.endsWith(".css") || route.endsWith(".js")) {
18
- return response;
30
+ let region;
31
+ for (const header of REGION_HEADERS) {
32
+ const value = request.headers.get(header);
33
+ if (value) {
34
+ region = value;
35
+ break;
36
+ }
19
37
  }
20
38
  monitor.trackRequest({
21
- method,
22
- route,
23
- statusCode,
39
+ method: request.method,
40
+ route: pathname,
41
+ // Não assumir 200 quando middleware retorna undefined (pass-through)
42
+ statusCode: response instanceof Response ? response.status : 0,
24
43
  responseTimeMs,
25
- userAgent,
44
+ userAgent: request.headers.get("user-agent") ?? void 0,
26
45
  region
27
46
  });
28
47
  return response;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/middleware.ts"],"names":[],"mappings":";;;AA0BO,SAAS,WAAA,CAAY,SAAwB,UAAA,EAA6B;AAC/E,EAAA,OAAO,OAAO,OAAA,KAAuE;AACnF,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAA,EAAI;AAG9B,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,QAAA,GAAW,MAAM,WAAW,OAAO,CAAA;AAAA,IACrC;AAEA,IAAA,MAAM,iBAAiB,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,GAAA,KAAQ,KAAK,CAAA;AAG3D,IAAA,MAAM,SAAS,OAAA,CAAQ,MAAA;AACvB,IAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA,CAAE,QAAA;AACnC,IAAA,MAAM,UAAA,GAAa,QAAA,YAAoB,QAAA,GAAW,QAAA,CAAS,MAAA,GAAS,GAAA;AACpE,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,IAAK,MAAA;AACvD,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,qBAAqB,CAAA,IAAK,MAAA;AAG7D,IAAA,IACE,KAAA,CAAM,UAAA,CAAW,SAAS,CAAA,IAC1B,KAAA,CAAM,UAAA,CAAW,UAAU,CAAA,IAC3B,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IACrB,MAAM,QAAA,CAAS,MAAM,CAAA,IACrB,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IACrB,KAAA,CAAM,SAAS,MAAM,CAAA,IACrB,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,IACrB,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,EACpB;AACA,MAAA,OAAO,QAAA;AAAA,IACT;AAEA,IAAA,OAAA,CAAQ,YAAA,CAAa;AAAA,MACnB,MAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MACA,cAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF","file":"index.cjs","sourcesContent":["// withMonitor — wrapper de middleware Next.js que captura métricas HTTP\n\nimport type { MonitorClient } from '../core/client'\nimport type { NextRequest, NextResponse } from 'next/server'\n\ntype NextMiddleware = (\n request: NextRequest,\n) => NextResponse | Response | Promise<NextResponse | Response> | undefined | Promise<undefined>\n\n/**\n * Envolve o middleware do Next.js para capturar métricas de request.\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { withMonitor } from '@victor-studio/monitor/next'\n * import { monitor } from '@/lib/monitor'\n *\n * function middleware(request: NextRequest) {\n * // sua lógica de middleware\n * return NextResponse.next()\n * }\n *\n * export default withMonitor(monitor, middleware)\n * ```\n */\nexport function withMonitor(monitor: MonitorClient, middleware?: NextMiddleware) {\n return async (request: NextRequest): Promise<NextResponse | Response | undefined> => {\n const start = performance.now()\n\n // Executar middleware original\n let response: NextResponse | Response | undefined\n if (middleware) {\n response = await middleware(request)\n }\n\n const responseTimeMs = Math.round(performance.now() - start)\n\n // Extrair dados do request\n const method = request.method\n const route = new URL(request.url).pathname\n const statusCode = response instanceof Response ? response.status : 200\n const userAgent = request.headers.get('user-agent') ?? undefined\n const region = request.headers.get('x-vercel-ip-country') ?? undefined\n\n // Ignorar requests de assets estáticos\n if (\n route.startsWith('/_next/') ||\n route.startsWith('/favicon') ||\n route.endsWith('.ico') ||\n route.endsWith('.png') ||\n route.endsWith('.jpg') ||\n route.endsWith('.svg') ||\n route.endsWith('.css') ||\n route.endsWith('.js')\n ) {\n return response\n }\n\n monitor.trackRequest({\n method,\n route,\n statusCode,\n responseTimeMs,\n userAgent,\n region,\n })\n\n return response\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/next/middleware.ts"],"names":[],"mappings":";;;AAkBA,IAAM,oBAAA,GACJ,yEAAA;AAGF,IAAM,cAAA,GAAiB;AAAA,EACrB,qBAAA;AAAA;AAAA,EACA,kBAAA;AAAA;AAAA,EACA,cAAA;AAAA;AAAA,EACA;AAAA;AACF,CAAA;AAeO,SAAS,WAAA,CACd,OAAA,EACA,UAAA,EACA,OAAA,EACA;AACA,EAAA,OAAO,OAAO,OAAA,KAAuE;AACnF,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,IAAI,GAAA,CAAI,QAAQ,GAAG,CAAA;AAGxC,IAAA,IAAI,CAAC,OAAA,EAAS,mBAAA,IAAuB,oBAAA,CAAqB,IAAA,CAAK,QAAQ,CAAA,EAAG;AACxE,MAAA,OAAO,UAAA,GAAa,UAAA,CAAW,OAAO,CAAA,GAAI,MAAA;AAAA,IAC5C;AAGA,IAAA,IAAI,OAAA,EAAS,eAAe,IAAA,CAAK,CAAC,YAAY,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAC,CAAA,EAAG;AACrE,MAAA,OAAO,UAAA,GAAa,UAAA,CAAW,OAAO,CAAA,GAAI,MAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAA,EAAI;AAG9B,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,QAAA,GAAW,MAAM,WAAW,OAAO,CAAA;AAAA,IACrC;AAEA,IAAA,MAAM,iBAAiB,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,GAAA,KAAQ,KAAK,CAAA;AAG3D,IAAA,IAAI,MAAA;AACJ,IAAA,KAAA,MAAW,UAAU,cAAA,EAAgB;AACnC,MAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AACxC,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,MAAA,GAAS,KAAA;AACT,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,YAAA,CAAa;AAAA,MACnB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,KAAA,EAAO,QAAA;AAAA;AAAA,MAEP,UAAA,EAAY,QAAA,YAAoB,QAAA,GAAW,QAAA,CAAS,MAAA,GAAS,CAAA;AAAA,MAC7D,cAAA;AAAA,MACA,SAAA,EAAW,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,IAAK,MAAA;AAAA,MAChD;AAAA,KACD,CAAA;AAED,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF","file":"index.cjs","sourcesContent":["// withMonitor — wrapper de middleware Next.js para tracking de requests\n\nimport type { MonitorClient } from '../core/client'\nimport type { NextRequest, NextResponse } from 'next/server'\n\ntype NextMiddleware = (\n request: NextRequest,\n) => NextResponse | Response | Promise<NextResponse | Response> | undefined | Promise<undefined>\n\n/** Opções do middleware de monitoring */\nexport interface WithMonitorOptions {\n /** Regex patterns para rotas a excluir (além dos defaults) */\n excludeRoutes?: RegExp[]\n /** Incluir assets estáticos no tracking (default: false) */\n includeStaticAssets?: boolean\n}\n\n// Padrão default para assets estáticos\nconst STATIC_ASSET_PATTERN =\n /^\\/(_next\\/|favicon|.*\\.(ico|png|jpg|jpeg|gif|svg|css|woff2?|ttf|eot)$)/\n\n// Headers de região por provider (testados em ordem)\nconst REGION_HEADERS = [\n 'x-vercel-ip-country', // Vercel\n 'x-railway-region', // Railway\n 'cf-ipcountry', // Cloudflare\n 'x-country-code', // Generic CDN\n] as const\n\n/**\n * Envolve o middleware do Next.js para capturar métricas de request.\n *\n * @example\n * ```ts\n * import { withMonitor } from '@victor-studio/monitor/next'\n * import { monitor } from '@/lib/monitor'\n *\n * export default withMonitor(monitor)\n * // ou com middleware existente:\n * export default withMonitor(monitor, myMiddleware, { excludeRoutes: [/^\\/api\\/health/] })\n * ```\n */\nexport function withMonitor(\n monitor: MonitorClient,\n middleware?: NextMiddleware,\n options?: WithMonitorOptions,\n) {\n return async (request: NextRequest): Promise<NextResponse | Response | undefined> => {\n const { pathname } = new URL(request.url)\n\n // Filtrar assets estáticos (a menos que explicitamente incluídos)\n if (!options?.includeStaticAssets && STATIC_ASSET_PATTERN.test(pathname)) {\n return middleware ? middleware(request) : undefined\n }\n\n // Filtrar rotas customizadas\n if (options?.excludeRoutes?.some((pattern) => pattern.test(pathname))) {\n return middleware ? middleware(request) : undefined\n }\n\n const start = performance.now()\n\n // Executar middleware original (se existir)\n let response: NextResponse | Response | undefined\n if (middleware) {\n response = await middleware(request)\n }\n\n const responseTimeMs = Math.round(performance.now() - start)\n\n // Detectar região — chain de headers por provider\n let region: string | undefined\n for (const header of REGION_HEADERS) {\n const value = request.headers.get(header)\n if (value) {\n region = value\n break\n }\n }\n\n monitor.trackRequest({\n method: request.method,\n route: pathname,\n // Não assumir 200 quando middleware retorna undefined (pass-through)\n statusCode: response instanceof Response ? response.status : 0,\n responseTimeMs,\n userAgent: request.headers.get('user-agent') ?? undefined,\n region,\n })\n\n return response\n }\n}\n"]}