@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 +163 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +86 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +160 -0
- package/dist/index.js.map +1 -0
- package/dist/next/index.cjs +34 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +92 -0
- package/dist/next/index.d.ts +92 -0
- package/dist/next/index.js +32 -0
- package/dist/next/index.js.map +1 -0
- package/dist/react/index.cjs +43 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +97 -0
- package/dist/react/index.d.ts +97 -0
- package/dist/react/index.js +41 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +66 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":"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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"]}
|
|
@@ -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
|
+
}
|