@victor-studio/monitor 0.1.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.cjs ADDED
@@ -0,0 +1,163 @@
1
+ 'use strict';
2
+
3
+ // src/core/transport.ts
4
+ async function sendBatch(config, events) {
5
+ if (events.length === 0) return true;
6
+ try {
7
+ const response = await fetch(config.endpoint, {
8
+ method: "POST",
9
+ headers: { "Content-Type": "application/json" },
10
+ body: JSON.stringify({
11
+ apiKey: config.apiKey,
12
+ events
13
+ }),
14
+ // Usar keepalive pra garantir envio mesmo quando a página fecha
15
+ keepalive: true
16
+ });
17
+ return response.ok;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ // src/core/collector.ts
24
+ var DEFAULT_FLUSH_INTERVAL = 3e4;
25
+ var MAX_BUFFER_SIZE = 50;
26
+ var Collector = class {
27
+ buffer = [];
28
+ timer = null;
29
+ config;
30
+ flushInterval;
31
+ constructor(config, flushInterval = DEFAULT_FLUSH_INTERVAL) {
32
+ this.config = config;
33
+ this.flushInterval = flushInterval;
34
+ }
35
+ start() {
36
+ if (this.timer) return;
37
+ this.timer = setInterval(() => {
38
+ this.flush();
39
+ }, this.flushInterval);
40
+ if (typeof window !== "undefined") {
41
+ window.addEventListener("visibilitychange", () => {
42
+ if (document.visibilityState === "hidden") {
43
+ this.flush();
44
+ }
45
+ });
46
+ }
47
+ }
48
+ stop() {
49
+ if (this.timer) {
50
+ clearInterval(this.timer);
51
+ this.timer = null;
52
+ }
53
+ this.flush();
54
+ }
55
+ push(event) {
56
+ this.buffer.push({
57
+ ...event,
58
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
59
+ });
60
+ if (this.buffer.length >= MAX_BUFFER_SIZE) {
61
+ this.flush();
62
+ }
63
+ }
64
+ flush() {
65
+ if (this.buffer.length === 0) return;
66
+ const events = [...this.buffer];
67
+ this.buffer = [];
68
+ sendBatch(this.config, events).catch(() => {
69
+ });
70
+ }
71
+ };
72
+
73
+ // src/core/client.ts
74
+ var MonitorClient = class {
75
+ config;
76
+ collector;
77
+ heartbeatTimer = null;
78
+ active = false;
79
+ constructor(config) {
80
+ this.config = config;
81
+ this.collector = new Collector(
82
+ { endpoint: config.endpoint, apiKey: config.apiKey },
83
+ config.flushInterval
84
+ );
85
+ }
86
+ /** Inicia o monitoring (heartbeat + collector) */
87
+ start() {
88
+ if (this.config.disableInDev !== false && this.isDev()) return;
89
+ if (this.active) return;
90
+ this.active = true;
91
+ this.collector.start();
92
+ this.startHeartbeat();
93
+ }
94
+ /** Para o monitoring */
95
+ stop() {
96
+ this.active = false;
97
+ this.collector.stop();
98
+ if (this.heartbeatTimer) {
99
+ clearInterval(this.heartbeatTimer);
100
+ this.heartbeatTimer = null;
101
+ }
102
+ }
103
+ /** Registra um evento de request HTTP (usado pelo middleware) */
104
+ trackRequest(data) {
105
+ if (!this.active) return;
106
+ this.collector.push({ type: "request", data });
107
+ }
108
+ /** Registra um Web Vital (usado pelo MonitorScript) */
109
+ trackVital(data) {
110
+ if (!this.active) return;
111
+ this.collector.push({ type: "vital", data });
112
+ }
113
+ startHeartbeat() {
114
+ const interval = this.config.heartbeatInterval ?? 6e4;
115
+ this.sendHeartbeat();
116
+ this.heartbeatTimer = setInterval(() => {
117
+ this.sendHeartbeat();
118
+ }, interval);
119
+ }
120
+ async sendHeartbeat() {
121
+ const start = performance.now();
122
+ try {
123
+ const response = await fetch(this.config.endpoint, {
124
+ method: "HEAD",
125
+ cache: "no-store"
126
+ });
127
+ const latencyMs = Math.round(performance.now() - start);
128
+ this.collector.push({
129
+ type: "heartbeat",
130
+ data: {
131
+ status: response.ok ? "online" : "offline",
132
+ latencyMs
133
+ }
134
+ });
135
+ } catch {
136
+ this.collector.push({
137
+ type: "heartbeat",
138
+ data: {
139
+ status: "online",
140
+ // O app está rodando, mesmo que o endpoint falhe
141
+ latencyMs: Math.round(performance.now() - start)
142
+ }
143
+ });
144
+ }
145
+ }
146
+ isDev() {
147
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") return true;
148
+ if (typeof window !== "undefined" && window.location?.hostname === "localhost") return true;
149
+ return false;
150
+ }
151
+ };
152
+
153
+ // src/index.ts
154
+ function createMonitor(config) {
155
+ const client = new MonitorClient(config);
156
+ client.start();
157
+ return client;
158
+ }
159
+
160
+ exports.MonitorClient = MonitorClient;
161
+ exports.createMonitor = createMonitor;
162
+ //# sourceMappingURL=index.cjs.map
163
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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.cjs","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"]}
@@ -0,0 +1,86 @@
1
+ interface MonitorEvent {
2
+ type: 'heartbeat' | 'request' | 'vital';
3
+ data: Record<string, unknown>;
4
+ timestamp: string;
5
+ }
6
+ interface TransportConfig {
7
+ endpoint: string;
8
+ apiKey: string;
9
+ }
10
+
11
+ declare class Collector {
12
+ private buffer;
13
+ private timer;
14
+ private config;
15
+ private flushInterval;
16
+ constructor(config: TransportConfig, flushInterval?: number);
17
+ start(): void;
18
+ stop(): void;
19
+ push(event: Omit<MonitorEvent, 'timestamp'>): void;
20
+ private flush;
21
+ }
22
+
23
+ interface MonitorConfig {
24
+ /** ID do projeto no Nuvio */
25
+ projectId: string;
26
+ /** API key gerada no Nuvio */
27
+ apiKey: string;
28
+ /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
29
+ endpoint: string;
30
+ /** Intervalo do heartbeat em ms (default: 60000) */
31
+ heartbeatInterval?: number;
32
+ /** Intervalo do flush do buffer em ms (default: 30000) */
33
+ flushInterval?: number;
34
+ /** Desabilitar em desenvolvimento (default: true) */
35
+ disableInDev?: boolean;
36
+ }
37
+ declare class MonitorClient {
38
+ readonly config: MonitorConfig;
39
+ readonly collector: Collector;
40
+ private heartbeatTimer;
41
+ private active;
42
+ constructor(config: MonitorConfig);
43
+ /** Inicia o monitoring (heartbeat + collector) */
44
+ start(): void;
45
+ /** Para o monitoring */
46
+ stop(): void;
47
+ /** Registra um evento de request HTTP (usado pelo middleware) */
48
+ trackRequest(data: {
49
+ method: string;
50
+ route: string;
51
+ statusCode: number;
52
+ responseTimeMs: number;
53
+ userAgent?: string;
54
+ region?: string;
55
+ }): void;
56
+ /** Registra um Web Vital (usado pelo MonitorScript) */
57
+ trackVital(data: {
58
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
59
+ value: number;
60
+ rating: 'good' | 'needs-improvement' | 'poor';
61
+ page?: string;
62
+ deviceType?: string;
63
+ browser?: string;
64
+ }): void;
65
+ private startHeartbeat;
66
+ private sendHeartbeat;
67
+ private isDev;
68
+ }
69
+
70
+ /**
71
+ * Cria e inicia uma instância do monitor.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { createMonitor } from '@victor-studio/monitor'
76
+ *
77
+ * export const monitor = createMonitor({
78
+ * projectId: 'uuid-do-projeto',
79
+ * apiKey: 'nuvio_mon_xxxxxxxx',
80
+ * endpoint: 'https://app.nuvio.com/api/monitor/ingest',
81
+ * })
82
+ * ```
83
+ */
84
+ declare function createMonitor(config: MonitorConfig): MonitorClient;
85
+
86
+ export { MonitorClient, type MonitorConfig, createMonitor };
@@ -0,0 +1,86 @@
1
+ interface MonitorEvent {
2
+ type: 'heartbeat' | 'request' | 'vital';
3
+ data: Record<string, unknown>;
4
+ timestamp: string;
5
+ }
6
+ interface TransportConfig {
7
+ endpoint: string;
8
+ apiKey: string;
9
+ }
10
+
11
+ declare class Collector {
12
+ private buffer;
13
+ private timer;
14
+ private config;
15
+ private flushInterval;
16
+ constructor(config: TransportConfig, flushInterval?: number);
17
+ start(): void;
18
+ stop(): void;
19
+ push(event: Omit<MonitorEvent, 'timestamp'>): void;
20
+ private flush;
21
+ }
22
+
23
+ interface MonitorConfig {
24
+ /** ID do projeto no Nuvio */
25
+ projectId: string;
26
+ /** API key gerada no Nuvio */
27
+ apiKey: string;
28
+ /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
29
+ endpoint: string;
30
+ /** Intervalo do heartbeat em ms (default: 60000) */
31
+ heartbeatInterval?: number;
32
+ /** Intervalo do flush do buffer em ms (default: 30000) */
33
+ flushInterval?: number;
34
+ /** Desabilitar em desenvolvimento (default: true) */
35
+ disableInDev?: boolean;
36
+ }
37
+ declare class MonitorClient {
38
+ readonly config: MonitorConfig;
39
+ readonly collector: Collector;
40
+ private heartbeatTimer;
41
+ private active;
42
+ constructor(config: MonitorConfig);
43
+ /** Inicia o monitoring (heartbeat + collector) */
44
+ start(): void;
45
+ /** Para o monitoring */
46
+ stop(): void;
47
+ /** Registra um evento de request HTTP (usado pelo middleware) */
48
+ trackRequest(data: {
49
+ method: string;
50
+ route: string;
51
+ statusCode: number;
52
+ responseTimeMs: number;
53
+ userAgent?: string;
54
+ region?: string;
55
+ }): void;
56
+ /** Registra um Web Vital (usado pelo MonitorScript) */
57
+ trackVital(data: {
58
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
59
+ value: number;
60
+ rating: 'good' | 'needs-improvement' | 'poor';
61
+ page?: string;
62
+ deviceType?: string;
63
+ browser?: string;
64
+ }): void;
65
+ private startHeartbeat;
66
+ private sendHeartbeat;
67
+ private isDev;
68
+ }
69
+
70
+ /**
71
+ * Cria e inicia uma instância do monitor.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { createMonitor } from '@victor-studio/monitor'
76
+ *
77
+ * export const monitor = createMonitor({
78
+ * projectId: 'uuid-do-projeto',
79
+ * apiKey: 'nuvio_mon_xxxxxxxx',
80
+ * endpoint: 'https://app.nuvio.com/api/monitor/ingest',
81
+ * })
82
+ * ```
83
+ */
84
+ declare function createMonitor(config: MonitorConfig): MonitorClient;
85
+
86
+ export { MonitorClient, type MonitorConfig, createMonitor };
package/dist/index.js ADDED
@@ -0,0 +1,160 @@
1
+ // src/core/transport.ts
2
+ async function sendBatch(config, events) {
3
+ 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;
18
+ }
19
+ }
20
+
21
+ // src/core/collector.ts
22
+ var DEFAULT_FLUSH_INTERVAL = 3e4;
23
+ var MAX_BUFFER_SIZE = 50;
24
+ var Collector = class {
25
+ buffer = [];
26
+ timer = null;
27
+ config;
28
+ flushInterval;
29
+ constructor(config, flushInterval = DEFAULT_FLUSH_INTERVAL) {
30
+ this.config = config;
31
+ this.flushInterval = flushInterval;
32
+ }
33
+ start() {
34
+ if (this.timer) return;
35
+ this.timer = setInterval(() => {
36
+ this.flush();
37
+ }, this.flushInterval);
38
+ if (typeof window !== "undefined") {
39
+ window.addEventListener("visibilitychange", () => {
40
+ if (document.visibilityState === "hidden") {
41
+ this.flush();
42
+ }
43
+ });
44
+ }
45
+ }
46
+ stop() {
47
+ if (this.timer) {
48
+ clearInterval(this.timer);
49
+ this.timer = null;
50
+ }
51
+ this.flush();
52
+ }
53
+ push(event) {
54
+ this.buffer.push({
55
+ ...event,
56
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
57
+ });
58
+ if (this.buffer.length >= MAX_BUFFER_SIZE) {
59
+ this.flush();
60
+ }
61
+ }
62
+ flush() {
63
+ if (this.buffer.length === 0) return;
64
+ const events = [...this.buffer];
65
+ this.buffer = [];
66
+ sendBatch(this.config, events).catch(() => {
67
+ });
68
+ }
69
+ };
70
+
71
+ // src/core/client.ts
72
+ var MonitorClient = class {
73
+ config;
74
+ collector;
75
+ heartbeatTimer = null;
76
+ active = false;
77
+ constructor(config) {
78
+ this.config = config;
79
+ this.collector = new Collector(
80
+ { endpoint: config.endpoint, apiKey: config.apiKey },
81
+ config.flushInterval
82
+ );
83
+ }
84
+ /** Inicia o monitoring (heartbeat + collector) */
85
+ start() {
86
+ if (this.config.disableInDev !== false && this.isDev()) return;
87
+ if (this.active) return;
88
+ this.active = true;
89
+ this.collector.start();
90
+ this.startHeartbeat();
91
+ }
92
+ /** Para o monitoring */
93
+ stop() {
94
+ this.active = false;
95
+ this.collector.stop();
96
+ if (this.heartbeatTimer) {
97
+ clearInterval(this.heartbeatTimer);
98
+ this.heartbeatTimer = null;
99
+ }
100
+ }
101
+ /** Registra um evento de request HTTP (usado pelo middleware) */
102
+ trackRequest(data) {
103
+ if (!this.active) return;
104
+ this.collector.push({ type: "request", data });
105
+ }
106
+ /** Registra um Web Vital (usado pelo MonitorScript) */
107
+ trackVital(data) {
108
+ if (!this.active) return;
109
+ this.collector.push({ type: "vital", data });
110
+ }
111
+ startHeartbeat() {
112
+ const interval = this.config.heartbeatInterval ?? 6e4;
113
+ this.sendHeartbeat();
114
+ this.heartbeatTimer = setInterval(() => {
115
+ this.sendHeartbeat();
116
+ }, interval);
117
+ }
118
+ async sendHeartbeat() {
119
+ const start = performance.now();
120
+ try {
121
+ const response = await fetch(this.config.endpoint, {
122
+ method: "HEAD",
123
+ cache: "no-store"
124
+ });
125
+ const latencyMs = Math.round(performance.now() - start);
126
+ this.collector.push({
127
+ type: "heartbeat",
128
+ data: {
129
+ status: response.ok ? "online" : "offline",
130
+ latencyMs
131
+ }
132
+ });
133
+ } catch {
134
+ this.collector.push({
135
+ type: "heartbeat",
136
+ data: {
137
+ status: "online",
138
+ // O app está rodando, mesmo que o endpoint falhe
139
+ latencyMs: Math.round(performance.now() - start)
140
+ }
141
+ });
142
+ }
143
+ }
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
+ };
150
+
151
+ // src/index.ts
152
+ function createMonitor(config) {
153
+ const client = new MonitorClient(config);
154
+ client.start();
155
+ return client;
156
+ }
157
+
158
+ export { MonitorClient, createMonitor };
159
+ //# sourceMappingURL=index.js.map
160
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/transport.ts","../src/core/collector.ts","../src/core/client.ts","../src/index.ts"],"names":[],"mappings":";AAaA,eAAsB,SAAA,CACpB,QACA,MAAA,EACkB;AAClB,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAEhC,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,MAAA,CAAO,QAAA,EAAU;AAAA,MAC5C,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACnB,QAAQ,MAAA,CAAO,MAAA;AAAA,QACf;AAAA,OACD,CAAA;AAAA;AAAA,MAED,SAAA,EAAW;AAAA,KACZ,CAAA;AAED,IAAA,OAAO,QAAA,CAAS,EAAA;AAAA,EAClB,CAAA,CAAA,MAAQ;AAEN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;;;AChCA,IAAM,sBAAA,GAAyB,GAAA;AAC/B,IAAM,eAAA,GAAkB,EAAA;AAEjB,IAAM,YAAN,MAAgB;AAAA,EACb,SAAyB,EAAC;AAAA,EAC1B,KAAA,GAA+C,IAAA;AAAA,EAC/C,MAAA;AAAA,EACA,aAAA;AAAA,EAER,WAAA,CAAY,MAAA,EAAyB,aAAA,GAAgB,sBAAA,EAAwB;AAC3E,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AAAA,EACvB;AAAA,EAEA,KAAA,GAAQ;AACN,IAAA,IAAI,KAAK,KAAA,EAAO;AAEhB,IAAA,IAAA,CAAK,KAAA,GAAQ,YAAY,MAAM;AAC7B,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,IACb,CAAA,EAAG,KAAK,aAAa,CAAA;AAGrB,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,gBAAA,CAAiB,oBAAoB,MAAM;AAChD,QAAA,IAAI,QAAA,CAAS,oBAAoB,QAAA,EAAU;AACzC,UAAA,IAAA,CAAK,KAAA,EAAM;AAAA,QACb;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,IAAA,GAAO;AACL,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,aAAA,CAAc,KAAK,KAAK,CAAA;AACxB,MAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AAAA,IACf;AAEA,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb;AAAA,EAEA,KAAK,KAAA,EAAwC;AAC3C,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK;AAAA,MACf,GAAG,KAAA;AAAA,MACH,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACnC,CAAA;AAGD,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,MAAA,IAAU,eAAA,EAAiB;AACzC,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEQ,KAAA,GAAQ;AACd,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAE9B,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,IAAA,CAAK,MAAM,CAAA;AAC9B,IAAA,IAAA,CAAK,SAAS,EAAC;AAGf,IAAA,SAAA,CAAU,IAAA,CAAK,MAAA,EAAQ,MAAM,CAAA,CAAE,MAAM,MAAM;AAAA,IAE3C,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AChDO,IAAM,gBAAN,MAAoB;AAAA,EAChB,MAAA;AAAA,EACA,SAAA;AAAA,EACD,cAAA,GAAwD,IAAA;AAAA,EACxD,MAAA,GAAS,KAAA;AAAA,EAEjB,YAAY,MAAA,EAAuB;AACjC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,YAAY,IAAI,SAAA;AAAA,MACnB,EAAE,QAAA,EAAU,MAAA,CAAO,QAAA,EAAU,MAAA,EAAQ,OAAO,MAAA,EAAO;AAAA,MACnD,MAAA,CAAO;AAAA,KACT;AAAA,EACF;AAAA;AAAA,EAGA,KAAA,GAAQ;AAEN,IAAA,IAAI,KAAK,MAAA,CAAO,YAAA,KAAiB,KAAA,IAAS,IAAA,CAAK,OAAM,EAAG;AACxD,IAAA,IAAI,KAAK,MAAA,EAAQ;AAEjB,IAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AACd,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AACrB,IAAA,IAAA,CAAK,cAAA,EAAe;AAAA,EACtB;AAAA;AAAA,EAGA,IAAA,GAAO;AACL,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,UAAU,IAAA,EAAK;AAEpB,IAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,MAAA,aAAA,CAAc,KAAK,cAAc,CAAA;AACjC,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,aAAa,IAAA,EAOV;AACD,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAClB,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,EAAW,MAAM,CAAA;AAAA,EAC/C;AAAA;AAAA,EAGA,WAAW,IAAA,EAOR;AACD,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAClB,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAAA,EAC7C;AAAA,EAEQ,cAAA,GAAiB;AACvB,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,iBAAA,IAAqB,GAAA;AAGlD,IAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,IAAA,IAAA,CAAK,cAAA,GAAiB,YAAY,MAAM;AACtC,MAAA,IAAA,CAAK,aAAA,EAAc;AAAA,IACrB,GAAG,QAAQ,CAAA;AAAA,EACb;AAAA,EAEA,MAAc,aAAA,GAAgB;AAC5B,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAA,EAAI;AAE9B,IAAA,IAAI;AAEF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,IAAA,CAAK,OAAO,QAAA,EAAU;AAAA,QACjD,MAAA,EAAQ,MAAA;AAAA,QACR,KAAA,EAAO;AAAA,OACR,CAAA;AAED,MAAA,MAAM,YAAY,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,GAAA,KAAQ,KAAK,CAAA;AAEtD,MAAA,IAAA,CAAK,UAAU,IAAA,CAAK;AAAA,QAClB,IAAA,EAAM,WAAA;AAAA,QACN,IAAA,EAAM;AAAA,UACJ,MAAA,EAAQ,QAAA,CAAS,EAAA,GAAK,QAAA,GAAW,SAAA;AAAA,UACjC;AAAA;AACF,OACD,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AACN,MAAA,IAAA,CAAK,UAAU,IAAA,CAAK;AAAA,QAClB,IAAA,EAAM,WAAA;AAAA,QACN,IAAA,EAAM;AAAA,UACJ,MAAA,EAAQ,QAAA;AAAA;AAAA,UACR,WAAW,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,GAAA,KAAQ,KAAK;AAAA;AACjD,OACD,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,KAAA,GAAiB;AACvB,IAAA,IAAI,OAAO,OAAA,KAAY,WAAA,IAAe,QAAQ,GAAA,EAAK,QAAA,KAAa,eAAe,OAAO,IAAA;AACtF,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,QAAA,EAAU,QAAA,KAAa,aAAa,OAAO,IAAA;AACvF,IAAA,OAAO,KAAA;AAAA,EACT;AACF;;;AC3GO,SAAS,cAAc,MAAA,EAAsC;AAClE,EAAA,MAAM,MAAA,GAAS,IAAI,aAAA,CAAc,MAAM,CAAA;AACvC,EAAA,MAAA,CAAO,KAAA,EAAM;AACb,EAAA,OAAO,MAAA;AACT","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"]}
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ // src/next/middleware.ts
4
+ function withMonitor(monitor, middleware) {
5
+ return async (request) => {
6
+ const start = performance.now();
7
+ let response;
8
+ if (middleware) {
9
+ response = await middleware(request);
10
+ }
11
+ 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;
19
+ }
20
+ monitor.trackRequest({
21
+ method,
22
+ route,
23
+ statusCode,
24
+ responseTimeMs,
25
+ userAgent,
26
+ region
27
+ });
28
+ return response;
29
+ };
30
+ }
31
+
32
+ exports.withMonitor = withMonitor;
33
+ //# sourceMappingURL=index.cjs.map
34
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,92 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ interface MonitorEvent {
4
+ type: 'heartbeat' | 'request' | 'vital';
5
+ data: Record<string, unknown>;
6
+ timestamp: string;
7
+ }
8
+ interface TransportConfig {
9
+ endpoint: string;
10
+ apiKey: string;
11
+ }
12
+
13
+ declare class Collector {
14
+ private buffer;
15
+ private timer;
16
+ private config;
17
+ private flushInterval;
18
+ constructor(config: TransportConfig, flushInterval?: number);
19
+ start(): void;
20
+ stop(): void;
21
+ push(event: Omit<MonitorEvent, 'timestamp'>): void;
22
+ private flush;
23
+ }
24
+
25
+ interface MonitorConfig {
26
+ /** ID do projeto no Nuvio */
27
+ projectId: string;
28
+ /** API key gerada no Nuvio */
29
+ apiKey: string;
30
+ /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
31
+ endpoint: string;
32
+ /** Intervalo do heartbeat em ms (default: 60000) */
33
+ heartbeatInterval?: number;
34
+ /** Intervalo do flush do buffer em ms (default: 30000) */
35
+ flushInterval?: number;
36
+ /** Desabilitar em desenvolvimento (default: true) */
37
+ disableInDev?: boolean;
38
+ }
39
+ declare class MonitorClient {
40
+ readonly config: MonitorConfig;
41
+ readonly collector: Collector;
42
+ private heartbeatTimer;
43
+ private active;
44
+ constructor(config: MonitorConfig);
45
+ /** Inicia o monitoring (heartbeat + collector) */
46
+ start(): void;
47
+ /** Para o monitoring */
48
+ stop(): void;
49
+ /** Registra um evento de request HTTP (usado pelo middleware) */
50
+ trackRequest(data: {
51
+ method: string;
52
+ route: string;
53
+ statusCode: number;
54
+ responseTimeMs: number;
55
+ userAgent?: string;
56
+ region?: string;
57
+ }): void;
58
+ /** Registra um Web Vital (usado pelo MonitorScript) */
59
+ trackVital(data: {
60
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
61
+ value: number;
62
+ rating: 'good' | 'needs-improvement' | 'poor';
63
+ page?: string;
64
+ deviceType?: string;
65
+ browser?: string;
66
+ }): void;
67
+ private startHeartbeat;
68
+ private sendHeartbeat;
69
+ private isDev;
70
+ }
71
+
72
+ type NextMiddleware = (request: NextRequest) => NextResponse | Response | Promise<NextResponse | Response> | undefined | Promise<undefined>;
73
+ /**
74
+ * Envolve o middleware do Next.js para capturar métricas de request.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * // middleware.ts
79
+ * import { withMonitor } from '@victor-studio/monitor/next'
80
+ * import { monitor } from '@/lib/monitor'
81
+ *
82
+ * function middleware(request: NextRequest) {
83
+ * // sua lógica de middleware
84
+ * return NextResponse.next()
85
+ * }
86
+ *
87
+ * export default withMonitor(monitor, middleware)
88
+ * ```
89
+ */
90
+ declare function withMonitor(monitor: MonitorClient, middleware?: NextMiddleware): (request: NextRequest) => Promise<NextResponse | Response | undefined>;
91
+
92
+ export { withMonitor };
@@ -0,0 +1,92 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ interface MonitorEvent {
4
+ type: 'heartbeat' | 'request' | 'vital';
5
+ data: Record<string, unknown>;
6
+ timestamp: string;
7
+ }
8
+ interface TransportConfig {
9
+ endpoint: string;
10
+ apiKey: string;
11
+ }
12
+
13
+ declare class Collector {
14
+ private buffer;
15
+ private timer;
16
+ private config;
17
+ private flushInterval;
18
+ constructor(config: TransportConfig, flushInterval?: number);
19
+ start(): void;
20
+ stop(): void;
21
+ push(event: Omit<MonitorEvent, 'timestamp'>): void;
22
+ private flush;
23
+ }
24
+
25
+ interface MonitorConfig {
26
+ /** ID do projeto no Nuvio */
27
+ projectId: string;
28
+ /** API key gerada no Nuvio */
29
+ apiKey: string;
30
+ /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
31
+ endpoint: string;
32
+ /** Intervalo do heartbeat em ms (default: 60000) */
33
+ heartbeatInterval?: number;
34
+ /** Intervalo do flush do buffer em ms (default: 30000) */
35
+ flushInterval?: number;
36
+ /** Desabilitar em desenvolvimento (default: true) */
37
+ disableInDev?: boolean;
38
+ }
39
+ declare class MonitorClient {
40
+ readonly config: MonitorConfig;
41
+ readonly collector: Collector;
42
+ private heartbeatTimer;
43
+ private active;
44
+ constructor(config: MonitorConfig);
45
+ /** Inicia o monitoring (heartbeat + collector) */
46
+ start(): void;
47
+ /** Para o monitoring */
48
+ stop(): void;
49
+ /** Registra um evento de request HTTP (usado pelo middleware) */
50
+ trackRequest(data: {
51
+ method: string;
52
+ route: string;
53
+ statusCode: number;
54
+ responseTimeMs: number;
55
+ userAgent?: string;
56
+ region?: string;
57
+ }): void;
58
+ /** Registra um Web Vital (usado pelo MonitorScript) */
59
+ trackVital(data: {
60
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
61
+ value: number;
62
+ rating: 'good' | 'needs-improvement' | 'poor';
63
+ page?: string;
64
+ deviceType?: string;
65
+ browser?: string;
66
+ }): void;
67
+ private startHeartbeat;
68
+ private sendHeartbeat;
69
+ private isDev;
70
+ }
71
+
72
+ type NextMiddleware = (request: NextRequest) => NextResponse | Response | Promise<NextResponse | Response> | undefined | Promise<undefined>;
73
+ /**
74
+ * Envolve o middleware do Next.js para capturar métricas de request.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * // middleware.ts
79
+ * import { withMonitor } from '@victor-studio/monitor/next'
80
+ * import { monitor } from '@/lib/monitor'
81
+ *
82
+ * function middleware(request: NextRequest) {
83
+ * // sua lógica de middleware
84
+ * return NextResponse.next()
85
+ * }
86
+ *
87
+ * export default withMonitor(monitor, middleware)
88
+ * ```
89
+ */
90
+ declare function withMonitor(monitor: MonitorClient, middleware?: NextMiddleware): (request: NextRequest) => Promise<NextResponse | Response | undefined>;
91
+
92
+ export { withMonitor };
@@ -0,0 +1,32 @@
1
+ // src/next/middleware.ts
2
+ function withMonitor(monitor, middleware) {
3
+ return async (request) => {
4
+ const start = performance.now();
5
+ let response;
6
+ if (middleware) {
7
+ response = await middleware(request);
8
+ }
9
+ const responseTimeMs = Math.round(performance.now() - start);
10
+ const method = request.method;
11
+ const route = new URL(request.url).pathname;
12
+ const statusCode = response instanceof Response ? response.status : 200;
13
+ const userAgent = request.headers.get("user-agent") ?? void 0;
14
+ const region = request.headers.get("x-vercel-ip-country") ?? void 0;
15
+ 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")) {
16
+ return response;
17
+ }
18
+ monitor.trackRequest({
19
+ method,
20
+ route,
21
+ statusCode,
22
+ responseTimeMs,
23
+ userAgent,
24
+ region
25
+ });
26
+ return response;
27
+ };
28
+ }
29
+
30
+ export { withMonitor };
31
+ //# sourceMappingURL=index.js.map
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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.js","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"]}
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // src/react/monitor-script.tsx
6
+ function detectDeviceType() {
7
+ if (typeof window === "undefined") return "unknown";
8
+ const ua = navigator.userAgent;
9
+ if (/Mobile|Android/i.test(ua)) return "mobile";
10
+ if (/Tablet|iPad/i.test(ua)) return "tablet";
11
+ return "desktop";
12
+ }
13
+ function detectBrowser() {
14
+ if (typeof window === "undefined") return "unknown";
15
+ const ua = navigator.userAgent;
16
+ if (ua.includes("Firefox")) return "Firefox";
17
+ if (ua.includes("Edg/")) return "Edge";
18
+ if (ua.includes("Chrome")) return "Chrome";
19
+ if (ua.includes("Safari")) return "Safari";
20
+ return "other";
21
+ }
22
+ function MonitorScript({ monitor }) {
23
+ react.useEffect(() => {
24
+ import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {
25
+ const page = typeof window !== "undefined" ? window.location.pathname : void 0;
26
+ const deviceType = detectDeviceType();
27
+ const browser = detectBrowser();
28
+ const report = (name, value, rating) => {
29
+ monitor.trackVital({ name, value, rating, page, deviceType, browser });
30
+ };
31
+ onLCP((metric) => report("LCP", metric.value, metric.rating));
32
+ onINP((metric) => report("INP", metric.value, metric.rating));
33
+ onCLS((metric) => report("CLS", metric.value, metric.rating));
34
+ onFCP((metric) => report("FCP", metric.value, metric.rating));
35
+ onTTFB((metric) => report("TTFB", metric.value, metric.rating));
36
+ });
37
+ }, [monitor]);
38
+ return null;
39
+ }
40
+
41
+ exports.MonitorScript = MonitorScript;
42
+ //# sourceMappingURL=index.cjs.map
43
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/monitor-script.tsx"],"names":["useEffect"],"mappings":";;;;;AAWA,SAAS,gBAAA,GAA2B;AAClC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,SAAA;AAC1C,EAAA,MAAM,KAAK,SAAA,CAAU,SAAA;AACrB,EAAA,IAAI,iBAAA,CAAkB,IAAA,CAAK,EAAE,CAAA,EAAG,OAAO,QAAA;AACvC,EAAA,IAAI,cAAA,CAAe,IAAA,CAAK,EAAE,CAAA,EAAG,OAAO,QAAA;AACpC,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,aAAA,GAAwB;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,SAAA;AAC1C,EAAA,MAAM,KAAK,SAAA,CAAU,SAAA;AACrB,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,SAAA;AACnC,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAChC,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AAClC,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AAClC,EAAA,OAAO,OAAA;AACT;AAwBO,SAAS,aAAA,CAAc,EAAE,OAAA,EAAQ,EAAuB;AAC7D,EAAAA,eAAA,CAAU,MAAM;AAEd,IAAA,OAAO,YAAY,CAAA,CAAE,IAAA,CAAK,CAAC,EAAE,OAAO,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,MAAA,EAAO,KAAM;AACpE,MAAA,MAAM,OAAO,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,MAAA;AACxE,MAAA,MAAM,aAAa,gBAAA,EAAiB;AACpC,MAAA,MAAM,UAAU,aAAA,EAAc;AAE9B,MAAA,MAAM,MAAA,GAAS,CACb,IAAA,EACA,KAAA,EACA,MAAA,KACG;AACH,QAAA,OAAA,CAAQ,UAAA,CAAW,EAAE,IAAA,EAAM,KAAA,EAAO,QAAQ,IAAA,EAAM,UAAA,EAAY,SAAS,CAAA;AAAA,MACvE,CAAA;AAEA,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,MAAA,CAAO,CAAC,WAAW,MAAA,CAAO,MAAA,EAAQ,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,IAChE,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,IAAA;AACT","file":"index.cjs","sourcesContent":["// MonitorScript — componente React que coleta Web Vitals\n\n'use client'\n\nimport { useEffect } from 'react'\nimport type { MonitorClient } from '../core/client'\n\ninterface MonitorScriptProps {\n monitor: MonitorClient\n}\n\nfunction detectDeviceType(): string {\n if (typeof window === 'undefined') return 'unknown'\n const ua = navigator.userAgent\n if (/Mobile|Android/i.test(ua)) return 'mobile'\n if (/Tablet|iPad/i.test(ua)) return 'tablet'\n return 'desktop'\n}\n\nfunction detectBrowser(): string {\n if (typeof window === 'undefined') return 'unknown'\n const ua = navigator.userAgent\n if (ua.includes('Firefox')) return 'Firefox'\n if (ua.includes('Edg/')) return 'Edge'\n if (ua.includes('Chrome')) return 'Chrome'\n if (ua.includes('Safari')) return 'Safari'\n return 'other'\n}\n\n/**\n * Componente React que coleta Web Vitals automaticamente.\n * Coloque no root layout da aplicação.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { MonitorScript } from '@victor-studio/monitor/react'\n * import { monitor } from '@/lib/monitor'\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * {children}\n * <MonitorScript monitor={monitor} />\n * </body>\n * </html>\n * )\n * }\n * ```\n */\nexport function MonitorScript({ monitor }: MonitorScriptProps) {\n useEffect(() => {\n // Import dinâmico pra não aumentar o bundle se não usar\n import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {\n const page = typeof window !== 'undefined' ? window.location.pathname : undefined\n const deviceType = detectDeviceType()\n const browser = detectBrowser()\n\n const report = (\n name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB',\n value: number,\n rating: 'good' | 'needs-improvement' | 'poor',\n ) => {\n monitor.trackVital({ name, value, rating, page, deviceType, browser })\n }\n\n onLCP((metric) => report('LCP', metric.value, metric.rating))\n onINP((metric) => report('INP', metric.value, metric.rating))\n onCLS((metric) => report('CLS', metric.value, metric.rating))\n onFCP((metric) => report('FCP', metric.value, metric.rating))\n onTTFB((metric) => report('TTFB', metric.value, metric.rating))\n })\n }, [monitor])\n\n return null\n}\n"]}
@@ -0,0 +1,97 @@
1
+ interface MonitorEvent {
2
+ type: 'heartbeat' | 'request' | 'vital';
3
+ data: Record<string, unknown>;
4
+ timestamp: string;
5
+ }
6
+ interface TransportConfig {
7
+ endpoint: string;
8
+ apiKey: string;
9
+ }
10
+
11
+ declare class Collector {
12
+ private buffer;
13
+ private timer;
14
+ private config;
15
+ private flushInterval;
16
+ constructor(config: TransportConfig, flushInterval?: number);
17
+ start(): void;
18
+ stop(): void;
19
+ push(event: Omit<MonitorEvent, 'timestamp'>): void;
20
+ private flush;
21
+ }
22
+
23
+ interface MonitorConfig {
24
+ /** ID do projeto no Nuvio */
25
+ projectId: string;
26
+ /** API key gerada no Nuvio */
27
+ apiKey: string;
28
+ /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
29
+ endpoint: string;
30
+ /** Intervalo do heartbeat em ms (default: 60000) */
31
+ heartbeatInterval?: number;
32
+ /** Intervalo do flush do buffer em ms (default: 30000) */
33
+ flushInterval?: number;
34
+ /** Desabilitar em desenvolvimento (default: true) */
35
+ disableInDev?: boolean;
36
+ }
37
+ declare class MonitorClient {
38
+ readonly config: MonitorConfig;
39
+ readonly collector: Collector;
40
+ private heartbeatTimer;
41
+ private active;
42
+ constructor(config: MonitorConfig);
43
+ /** Inicia o monitoring (heartbeat + collector) */
44
+ start(): void;
45
+ /** Para o monitoring */
46
+ stop(): void;
47
+ /** Registra um evento de request HTTP (usado pelo middleware) */
48
+ trackRequest(data: {
49
+ method: string;
50
+ route: string;
51
+ statusCode: number;
52
+ responseTimeMs: number;
53
+ userAgent?: string;
54
+ region?: string;
55
+ }): void;
56
+ /** Registra um Web Vital (usado pelo MonitorScript) */
57
+ trackVital(data: {
58
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
59
+ value: number;
60
+ rating: 'good' | 'needs-improvement' | 'poor';
61
+ page?: string;
62
+ deviceType?: string;
63
+ browser?: string;
64
+ }): void;
65
+ private startHeartbeat;
66
+ private sendHeartbeat;
67
+ private isDev;
68
+ }
69
+
70
+ interface MonitorScriptProps {
71
+ monitor: MonitorClient;
72
+ }
73
+ /**
74
+ * Componente React que coleta Web Vitals automaticamente.
75
+ * Coloque no root layout da aplicação.
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * // app/layout.tsx
80
+ * import { MonitorScript } from '@victor-studio/monitor/react'
81
+ * import { monitor } from '@/lib/monitor'
82
+ *
83
+ * export default function RootLayout({ children }) {
84
+ * return (
85
+ * <html>
86
+ * <body>
87
+ * {children}
88
+ * <MonitorScript monitor={monitor} />
89
+ * </body>
90
+ * </html>
91
+ * )
92
+ * }
93
+ * ```
94
+ */
95
+ declare function MonitorScript({ monitor }: MonitorScriptProps): null;
96
+
97
+ export { MonitorScript };
@@ -0,0 +1,97 @@
1
+ interface MonitorEvent {
2
+ type: 'heartbeat' | 'request' | 'vital';
3
+ data: Record<string, unknown>;
4
+ timestamp: string;
5
+ }
6
+ interface TransportConfig {
7
+ endpoint: string;
8
+ apiKey: string;
9
+ }
10
+
11
+ declare class Collector {
12
+ private buffer;
13
+ private timer;
14
+ private config;
15
+ private flushInterval;
16
+ constructor(config: TransportConfig, flushInterval?: number);
17
+ start(): void;
18
+ stop(): void;
19
+ push(event: Omit<MonitorEvent, 'timestamp'>): void;
20
+ private flush;
21
+ }
22
+
23
+ interface MonitorConfig {
24
+ /** ID do projeto no Nuvio */
25
+ projectId: string;
26
+ /** API key gerada no Nuvio */
27
+ apiKey: string;
28
+ /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
29
+ endpoint: string;
30
+ /** Intervalo do heartbeat em ms (default: 60000) */
31
+ heartbeatInterval?: number;
32
+ /** Intervalo do flush do buffer em ms (default: 30000) */
33
+ flushInterval?: number;
34
+ /** Desabilitar em desenvolvimento (default: true) */
35
+ disableInDev?: boolean;
36
+ }
37
+ declare class MonitorClient {
38
+ readonly config: MonitorConfig;
39
+ readonly collector: Collector;
40
+ private heartbeatTimer;
41
+ private active;
42
+ constructor(config: MonitorConfig);
43
+ /** Inicia o monitoring (heartbeat + collector) */
44
+ start(): void;
45
+ /** Para o monitoring */
46
+ stop(): void;
47
+ /** Registra um evento de request HTTP (usado pelo middleware) */
48
+ trackRequest(data: {
49
+ method: string;
50
+ route: string;
51
+ statusCode: number;
52
+ responseTimeMs: number;
53
+ userAgent?: string;
54
+ region?: string;
55
+ }): void;
56
+ /** Registra um Web Vital (usado pelo MonitorScript) */
57
+ trackVital(data: {
58
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
59
+ value: number;
60
+ rating: 'good' | 'needs-improvement' | 'poor';
61
+ page?: string;
62
+ deviceType?: string;
63
+ browser?: string;
64
+ }): void;
65
+ private startHeartbeat;
66
+ private sendHeartbeat;
67
+ private isDev;
68
+ }
69
+
70
+ interface MonitorScriptProps {
71
+ monitor: MonitorClient;
72
+ }
73
+ /**
74
+ * Componente React que coleta Web Vitals automaticamente.
75
+ * Coloque no root layout da aplicação.
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * // app/layout.tsx
80
+ * import { MonitorScript } from '@victor-studio/monitor/react'
81
+ * import { monitor } from '@/lib/monitor'
82
+ *
83
+ * export default function RootLayout({ children }) {
84
+ * return (
85
+ * <html>
86
+ * <body>
87
+ * {children}
88
+ * <MonitorScript monitor={monitor} />
89
+ * </body>
90
+ * </html>
91
+ * )
92
+ * }
93
+ * ```
94
+ */
95
+ declare function MonitorScript({ monitor }: MonitorScriptProps): null;
96
+
97
+ export { MonitorScript };
@@ -0,0 +1,41 @@
1
+ import { useEffect } from 'react';
2
+
3
+ // src/react/monitor-script.tsx
4
+ function detectDeviceType() {
5
+ if (typeof window === "undefined") return "unknown";
6
+ const ua = navigator.userAgent;
7
+ if (/Mobile|Android/i.test(ua)) return "mobile";
8
+ if (/Tablet|iPad/i.test(ua)) return "tablet";
9
+ return "desktop";
10
+ }
11
+ function detectBrowser() {
12
+ if (typeof window === "undefined") return "unknown";
13
+ const ua = navigator.userAgent;
14
+ if (ua.includes("Firefox")) return "Firefox";
15
+ if (ua.includes("Edg/")) return "Edge";
16
+ if (ua.includes("Chrome")) return "Chrome";
17
+ if (ua.includes("Safari")) return "Safari";
18
+ return "other";
19
+ }
20
+ function MonitorScript({ monitor }) {
21
+ useEffect(() => {
22
+ import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {
23
+ const page = typeof window !== "undefined" ? window.location.pathname : void 0;
24
+ const deviceType = detectDeviceType();
25
+ const browser = detectBrowser();
26
+ const report = (name, value, rating) => {
27
+ monitor.trackVital({ name, value, rating, page, deviceType, browser });
28
+ };
29
+ onLCP((metric) => report("LCP", metric.value, metric.rating));
30
+ onINP((metric) => report("INP", metric.value, metric.rating));
31
+ onCLS((metric) => report("CLS", metric.value, metric.rating));
32
+ onFCP((metric) => report("FCP", metric.value, metric.rating));
33
+ onTTFB((metric) => report("TTFB", metric.value, metric.rating));
34
+ });
35
+ }, [monitor]);
36
+ return null;
37
+ }
38
+
39
+ export { MonitorScript };
40
+ //# sourceMappingURL=index.js.map
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/monitor-script.tsx"],"names":[],"mappings":";;;AAWA,SAAS,gBAAA,GAA2B;AAClC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,SAAA;AAC1C,EAAA,MAAM,KAAK,SAAA,CAAU,SAAA;AACrB,EAAA,IAAI,iBAAA,CAAkB,IAAA,CAAK,EAAE,CAAA,EAAG,OAAO,QAAA;AACvC,EAAA,IAAI,cAAA,CAAe,IAAA,CAAK,EAAE,CAAA,EAAG,OAAO,QAAA;AACpC,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,aAAA,GAAwB;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,SAAA;AAC1C,EAAA,MAAM,KAAK,SAAA,CAAU,SAAA;AACrB,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,SAAA;AACnC,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAChC,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AAClC,EAAA,IAAI,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,OAAO,QAAA;AAClC,EAAA,OAAO,OAAA;AACT;AAwBO,SAAS,aAAA,CAAc,EAAE,OAAA,EAAQ,EAAuB;AAC7D,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,OAAO,YAAY,CAAA,CAAE,IAAA,CAAK,CAAC,EAAE,OAAO,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,MAAA,EAAO,KAAM;AACpE,MAAA,MAAM,OAAO,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,MAAA;AACxE,MAAA,MAAM,aAAa,gBAAA,EAAiB;AACpC,MAAA,MAAM,UAAU,aAAA,EAAc;AAE9B,MAAA,MAAM,MAAA,GAAS,CACb,IAAA,EACA,KAAA,EACA,MAAA,KACG;AACH,QAAA,OAAA,CAAQ,UAAA,CAAW,EAAE,IAAA,EAAM,KAAA,EAAO,QAAQ,IAAA,EAAM,UAAA,EAAY,SAAS,CAAA;AAAA,MACvE,CAAA;AAEA,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,KAAA,CAAM,CAAC,WAAW,MAAA,CAAO,KAAA,EAAO,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5D,MAAA,MAAA,CAAO,CAAC,WAAW,MAAA,CAAO,MAAA,EAAQ,OAAO,KAAA,EAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,IAChE,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,IAAA;AACT","file":"index.js","sourcesContent":["// MonitorScript — componente React que coleta Web Vitals\n\n'use client'\n\nimport { useEffect } from 'react'\nimport type { MonitorClient } from '../core/client'\n\ninterface MonitorScriptProps {\n monitor: MonitorClient\n}\n\nfunction detectDeviceType(): string {\n if (typeof window === 'undefined') return 'unknown'\n const ua = navigator.userAgent\n if (/Mobile|Android/i.test(ua)) return 'mobile'\n if (/Tablet|iPad/i.test(ua)) return 'tablet'\n return 'desktop'\n}\n\nfunction detectBrowser(): string {\n if (typeof window === 'undefined') return 'unknown'\n const ua = navigator.userAgent\n if (ua.includes('Firefox')) return 'Firefox'\n if (ua.includes('Edg/')) return 'Edge'\n if (ua.includes('Chrome')) return 'Chrome'\n if (ua.includes('Safari')) return 'Safari'\n return 'other'\n}\n\n/**\n * Componente React que coleta Web Vitals automaticamente.\n * Coloque no root layout da aplicação.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { MonitorScript } from '@victor-studio/monitor/react'\n * import { monitor } from '@/lib/monitor'\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * {children}\n * <MonitorScript monitor={monitor} />\n * </body>\n * </html>\n * )\n * }\n * ```\n */\nexport function MonitorScript({ monitor }: MonitorScriptProps) {\n useEffect(() => {\n // Import dinâmico pra não aumentar o bundle se não usar\n import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {\n const page = typeof window !== 'undefined' ? window.location.pathname : undefined\n const deviceType = detectDeviceType()\n const browser = detectBrowser()\n\n const report = (\n name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB',\n value: number,\n rating: 'good' | 'needs-improvement' | 'poor',\n ) => {\n monitor.trackVital({ name, value, rating, page, deviceType, browser })\n }\n\n onLCP((metric) => report('LCP', metric.value, metric.rating))\n onINP((metric) => report('INP', metric.value, metric.rating))\n onCLS((metric) => report('CLS', metric.value, metric.rating))\n onFCP((metric) => report('FCP', metric.value, metric.rating))\n onTTFB((metric) => report('TTFB', metric.value, metric.rating))\n })\n }, [monitor])\n\n return null\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@victor-studio/monitor",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight monitoring SDK for Nuvio-managed projects",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./next": {
16
+ "types": "./dist/next/index.d.ts",
17
+ "import": "./dist/next/index.js",
18
+ "require": "./dist/next/index.cjs"
19
+ },
20
+ "./react": {
21
+ "types": "./dist/react/index.d.ts",
22
+ "import": "./dist/react/index.js",
23
+ "require": "./dist/react/index.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "dev": "tsup --watch",
32
+ "typecheck": "tsc --noEmit",
33
+ "prepublishOnly": "pnpm build"
34
+ },
35
+ "peerDependencies": {
36
+ "next": ">=14",
37
+ "react": ">=18"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "next": {
41
+ "optional": true
42
+ },
43
+ "react": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^25.6.0",
49
+ "@types/react": "^19.0.0",
50
+ "tsup": "^8.0.0",
51
+ "typescript": "^5.9.0"
52
+ },
53
+ "dependencies": {
54
+ "web-vitals": "^4.2.0"
55
+ },
56
+ "keywords": [
57
+ "monitoring",
58
+ "sdk",
59
+ "web-vitals",
60
+ "uptime",
61
+ "performance"
62
+ ],
63
+ "author": "Victor Studio",
64
+ "license": "MIT",
65
+ "packageManager": "pnpm@10.33.0"
66
+ }