@victor-studio/monitor 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,70 +1,137 @@
1
- interface MonitorEvent {
2
- type: 'heartbeat' | 'request' | 'vital';
3
- data: Record<string, unknown>;
4
- timestamp: string;
1
+ interface HeartbeatData {
2
+ status: 'online' | 'offline';
3
+ latencyMs: number;
5
4
  }
6
- interface TransportConfig {
7
- endpoint: string;
8
- apiKey: string;
5
+ interface RequestData {
6
+ method: string;
7
+ route: string;
8
+ statusCode: number;
9
+ responseTimeMs: number;
10
+ userAgent?: string;
11
+ region?: string;
9
12
  }
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;
13
+ interface VitalData {
14
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
15
+ value: number;
16
+ rating: 'good' | 'needs-improvement' | 'poor';
17
+ page?: string;
18
+ deviceType?: string;
19
+ browser?: string;
20
+ }
21
+ interface ErrorData {
22
+ type: 'unhandled' | 'caught' | 'promise';
23
+ message: string;
24
+ stack?: string;
25
+ groupingKey: string;
26
+ route?: string;
27
+ method?: string;
28
+ statusCode?: number;
29
+ environment?: string;
30
+ commitHash?: string;
31
+ extra?: Record<string, string>;
32
+ }
33
+ interface DeployData {
34
+ commitHash: string;
35
+ branch?: string;
36
+ author?: string;
37
+ status: 'started' | 'succeeded' | 'failed';
38
+ buildDurationMs?: number;
39
+ url?: string;
40
+ environment?: string;
41
+ provider?: string;
21
42
  }
43
+ interface AdapterData {
44
+ adapter: string;
45
+ operation: string;
46
+ durationMs: number;
47
+ success: boolean;
48
+ error?: string;
49
+ meta?: Record<string, string>;
50
+ }
51
+ type MonitorEvent = {
52
+ type: 'heartbeat';
53
+ data: HeartbeatData;
54
+ timestamp: string;
55
+ } | {
56
+ type: 'request';
57
+ data: RequestData;
58
+ timestamp: string;
59
+ } | {
60
+ type: 'vital';
61
+ data: VitalData;
62
+ timestamp: string;
63
+ } | {
64
+ type: 'error';
65
+ data: ErrorData;
66
+ timestamp: string;
67
+ } | {
68
+ type: 'deploy';
69
+ data: DeployData;
70
+ timestamp: string;
71
+ } | {
72
+ type: 'adapter';
73
+ data: AdapterData;
74
+ timestamp: string;
75
+ };
76
+ /** Hook para filtrar/modificar eventos antes do envio */
77
+ type BeforeSendHook = (event: MonitorEvent) => MonitorEvent | null;
22
78
 
23
79
  interface MonitorConfig {
24
80
  /** ID do projeto no Nuvio */
25
81
  projectId: string;
26
82
  /** API key gerada no Nuvio */
27
83
  apiKey: string;
28
- /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
84
+ /** URL do endpoint de ingest */
29
85
  endpoint: string;
30
86
  /** Intervalo do heartbeat em ms (default: 60000) */
31
87
  heartbeatInterval?: number;
32
88
  /** Intervalo do flush do buffer em ms (default: 30000) */
33
89
  flushInterval?: number;
90
+ /** Timeout do fetch em ms (default: 10000) */
91
+ timeout?: number;
92
+ /** Max tentativas de retry (default: 3) */
93
+ maxRetries?: number;
34
94
  /** Desabilitar em desenvolvimento (default: true) */
35
95
  disableInDev?: boolean;
96
+ /** Taxa de amostragem 0.0-1.0 (default: 1.0). Heartbeats nunca são amostrados */
97
+ sampleRate?: number;
98
+ /** Hook chamado antes de enfileirar cada evento. Retorna null para dropar */
99
+ beforeSend?: BeforeSendHook;
100
+ /** Habilitar logs de debug no console (default: false) */
101
+ debug?: boolean;
102
+ /** Capturar erros globais automaticamente — window.onerror (default: false) */
103
+ captureErrors?: boolean;
104
+ /** Capturar unhandled promise rejections automaticamente (default: false) */
105
+ captureUnhandledRejections?: boolean;
36
106
  }
37
107
  declare class MonitorClient {
38
108
  readonly config: MonitorConfig;
39
- readonly collector: Collector;
109
+ private readonly collector;
110
+ private readonly logger;
40
111
  private heartbeatTimer;
112
+ private globalHandlersCleanup;
41
113
  private active;
42
114
  constructor(config: MonitorConfig);
43
115
  /** Inicia o monitoring (heartbeat + collector) */
44
116
  start(): void;
45
117
  /** Para o monitoring */
46
118
  stop(): void;
119
+ /** Força um flush imediato do buffer */
120
+ flush(): void;
121
+ /** Verifica se o monitoring está ativo */
122
+ get isActive(): boolean;
47
123
  /** 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;
124
+ trackRequest(data: RequestData): void;
56
125
  /** 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;
126
+ trackVital(data: VitalData): void;
127
+ /** Registra um evento de adapter (DB, cache, AI, queue, email) */
128
+ trackAdapter(data: AdapterData): void;
129
+ /** Registra um erro capturado */
130
+ captureError(data: ErrorData): void;
131
+ /** Registra um evento de deploy */
132
+ trackDeploy(data: DeployData): void;
65
133
  private startHeartbeat;
66
134
  private sendHeartbeat;
67
- private isDev;
68
135
  }
69
136
 
70
137
  interface MonitorScriptProps {
@@ -76,7 +143,6 @@ interface MonitorScriptProps {
76
143
  *
77
144
  * @example
78
145
  * ```tsx
79
- * // app/layout.tsx
80
146
  * import { MonitorScript } from '@victor-studio/monitor/react'
81
147
  * import { monitor } from '@/lib/monitor'
82
148
  *
@@ -1,70 +1,137 @@
1
- interface MonitorEvent {
2
- type: 'heartbeat' | 'request' | 'vital';
3
- data: Record<string, unknown>;
4
- timestamp: string;
1
+ interface HeartbeatData {
2
+ status: 'online' | 'offline';
3
+ latencyMs: number;
5
4
  }
6
- interface TransportConfig {
7
- endpoint: string;
8
- apiKey: string;
5
+ interface RequestData {
6
+ method: string;
7
+ route: string;
8
+ statusCode: number;
9
+ responseTimeMs: number;
10
+ userAgent?: string;
11
+ region?: string;
9
12
  }
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;
13
+ interface VitalData {
14
+ name: 'LCP' | 'INP' | 'CLS' | 'FCP' | 'TTFB';
15
+ value: number;
16
+ rating: 'good' | 'needs-improvement' | 'poor';
17
+ page?: string;
18
+ deviceType?: string;
19
+ browser?: string;
20
+ }
21
+ interface ErrorData {
22
+ type: 'unhandled' | 'caught' | 'promise';
23
+ message: string;
24
+ stack?: string;
25
+ groupingKey: string;
26
+ route?: string;
27
+ method?: string;
28
+ statusCode?: number;
29
+ environment?: string;
30
+ commitHash?: string;
31
+ extra?: Record<string, string>;
32
+ }
33
+ interface DeployData {
34
+ commitHash: string;
35
+ branch?: string;
36
+ author?: string;
37
+ status: 'started' | 'succeeded' | 'failed';
38
+ buildDurationMs?: number;
39
+ url?: string;
40
+ environment?: string;
41
+ provider?: string;
21
42
  }
43
+ interface AdapterData {
44
+ adapter: string;
45
+ operation: string;
46
+ durationMs: number;
47
+ success: boolean;
48
+ error?: string;
49
+ meta?: Record<string, string>;
50
+ }
51
+ type MonitorEvent = {
52
+ type: 'heartbeat';
53
+ data: HeartbeatData;
54
+ timestamp: string;
55
+ } | {
56
+ type: 'request';
57
+ data: RequestData;
58
+ timestamp: string;
59
+ } | {
60
+ type: 'vital';
61
+ data: VitalData;
62
+ timestamp: string;
63
+ } | {
64
+ type: 'error';
65
+ data: ErrorData;
66
+ timestamp: string;
67
+ } | {
68
+ type: 'deploy';
69
+ data: DeployData;
70
+ timestamp: string;
71
+ } | {
72
+ type: 'adapter';
73
+ data: AdapterData;
74
+ timestamp: string;
75
+ };
76
+ /** Hook para filtrar/modificar eventos antes do envio */
77
+ type BeforeSendHook = (event: MonitorEvent) => MonitorEvent | null;
22
78
 
23
79
  interface MonitorConfig {
24
80
  /** ID do projeto no Nuvio */
25
81
  projectId: string;
26
82
  /** API key gerada no Nuvio */
27
83
  apiKey: string;
28
- /** URL do endpoint de ingest (ex: https://app.nuvio.com/api/monitor/ingest) */
84
+ /** URL do endpoint de ingest */
29
85
  endpoint: string;
30
86
  /** Intervalo do heartbeat em ms (default: 60000) */
31
87
  heartbeatInterval?: number;
32
88
  /** Intervalo do flush do buffer em ms (default: 30000) */
33
89
  flushInterval?: number;
90
+ /** Timeout do fetch em ms (default: 10000) */
91
+ timeout?: number;
92
+ /** Max tentativas de retry (default: 3) */
93
+ maxRetries?: number;
34
94
  /** Desabilitar em desenvolvimento (default: true) */
35
95
  disableInDev?: boolean;
96
+ /** Taxa de amostragem 0.0-1.0 (default: 1.0). Heartbeats nunca são amostrados */
97
+ sampleRate?: number;
98
+ /** Hook chamado antes de enfileirar cada evento. Retorna null para dropar */
99
+ beforeSend?: BeforeSendHook;
100
+ /** Habilitar logs de debug no console (default: false) */
101
+ debug?: boolean;
102
+ /** Capturar erros globais automaticamente — window.onerror (default: false) */
103
+ captureErrors?: boolean;
104
+ /** Capturar unhandled promise rejections automaticamente (default: false) */
105
+ captureUnhandledRejections?: boolean;
36
106
  }
37
107
  declare class MonitorClient {
38
108
  readonly config: MonitorConfig;
39
- readonly collector: Collector;
109
+ private readonly collector;
110
+ private readonly logger;
40
111
  private heartbeatTimer;
112
+ private globalHandlersCleanup;
41
113
  private active;
42
114
  constructor(config: MonitorConfig);
43
115
  /** Inicia o monitoring (heartbeat + collector) */
44
116
  start(): void;
45
117
  /** Para o monitoring */
46
118
  stop(): void;
119
+ /** Força um flush imediato do buffer */
120
+ flush(): void;
121
+ /** Verifica se o monitoring está ativo */
122
+ get isActive(): boolean;
47
123
  /** 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;
124
+ trackRequest(data: RequestData): void;
56
125
  /** 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;
126
+ trackVital(data: VitalData): void;
127
+ /** Registra um evento de adapter (DB, cache, AI, queue, email) */
128
+ trackAdapter(data: AdapterData): void;
129
+ /** Registra um erro capturado */
130
+ captureError(data: ErrorData): void;
131
+ /** Registra um evento de deploy */
132
+ trackDeploy(data: DeployData): void;
65
133
  private startHeartbeat;
66
134
  private sendHeartbeat;
67
- private isDev;
68
135
  }
69
136
 
70
137
  interface MonitorScriptProps {
@@ -76,7 +143,6 @@ interface MonitorScriptProps {
76
143
  *
77
144
  * @example
78
145
  * ```tsx
79
- * // app/layout.tsx
80
146
  * import { MonitorScript } from '@victor-studio/monitor/react'
81
147
  * import { monitor } from '@/lib/monitor'
82
148
  *
@@ -3,6 +3,10 @@ import { useEffect } from 'react';
3
3
  // src/react/monitor-script.tsx
4
4
  function detectDeviceType() {
5
5
  if (typeof window === "undefined") return "unknown";
6
+ const uaData = navigator.userAgentData;
7
+ if (uaData) {
8
+ return uaData.mobile ? "mobile" : "desktop";
9
+ }
6
10
  const ua = navigator.userAgent;
7
11
  if (/Mobile|Android/i.test(ua)) return "mobile";
8
12
  if (/Tablet|iPad/i.test(ua)) return "tablet";
@@ -10,20 +14,32 @@ function detectDeviceType() {
10
14
  }
11
15
  function detectBrowser() {
12
16
  if (typeof window === "undefined") return "unknown";
17
+ const uaData = navigator.userAgentData;
18
+ if (uaData?.brands?.length) {
19
+ const dominated = uaData.brands.find(
20
+ (b) => !b.brand.startsWith("Not") && !b.brand.startsWith("Chromium")
21
+ );
22
+ if (dominated) return dominated.brand;
23
+ }
13
24
  const ua = navigator.userAgent;
14
25
  if (ua.includes("Firefox")) return "Firefox";
15
26
  if (ua.includes("Edg/")) return "Edge";
27
+ if (ua.includes("OPR/") || ua.includes("Opera")) return "Opera";
28
+ if (ua.includes("SamsungBrowser")) return "Samsung Browser";
29
+ if (ua.includes("Brave")) return "Brave";
16
30
  if (ua.includes("Chrome")) return "Chrome";
17
31
  if (ua.includes("Safari")) return "Safari";
18
32
  return "other";
19
33
  }
20
34
  function MonitorScript({ monitor }) {
21
35
  useEffect(() => {
36
+ let cancelled = false;
22
37
  import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {
23
- const page = typeof window !== "undefined" ? window.location.pathname : void 0;
38
+ if (cancelled) return;
24
39
  const deviceType = detectDeviceType();
25
40
  const browser = detectBrowser();
26
41
  const report = (name, value, rating) => {
42
+ const page = typeof window !== "undefined" ? window.location.pathname : void 0;
27
43
  monitor.trackVital({ name, value, rating, page, deviceType, browser });
28
44
  };
29
45
  onLCP((metric) => report("LCP", metric.value, metric.rating));
@@ -32,6 +48,9 @@ function MonitorScript({ monitor }) {
32
48
  onFCP((metric) => report("FCP", metric.value, metric.rating));
33
49
  onTTFB((metric) => report("TTFB", metric.value, metric.rating));
34
50
  });
51
+ return () => {
52
+ cancelled = true;
53
+ };
35
54
  }, [monitor]);
36
55
  return null;
37
56
  }
@@ -1 +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"]}
1
+ {"version":3,"sources":["../../src/react/monitor-script.tsx"],"names":[],"mappings":"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","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\n // Preferir User-Agent Client Hints (API moderna)\n const uaData = (navigator as Navigator & { userAgentData?: { mobile?: boolean } }).userAgentData\n if (uaData) {\n return uaData.mobile ? 'mobile' : 'desktop'\n }\n\n // Fallback para UA string\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\n // Preferir User-Agent Client Hints\n const uaData = (navigator as Navigator & { userAgentData?: { brands?: { brand: string }[] } })\n .userAgentData\n if (uaData?.brands?.length) {\n const dominated = uaData.brands.find(\n (b) => !b.brand.startsWith('Not') && !b.brand.startsWith('Chromium'),\n )\n if (dominated) return dominated.brand\n }\n\n // Fallback para UA string (ordem importa — Chrome inclui Safari no UA)\n const ua = navigator.userAgent\n if (ua.includes('Firefox')) return 'Firefox'\n if (ua.includes('Edg/')) return 'Edge'\n if (ua.includes('OPR/') || ua.includes('Opera')) return 'Opera'\n if (ua.includes('SamsungBrowser')) return 'Samsung Browser'\n if (ua.includes('Brave')) return 'Brave'\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 * 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 let cancelled = false\n\n // Import dinâmico pra não aumentar o bundle\n import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {\n if (cancelled) return\n\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 // Captura page no momento do report, não no mount (fix SPA navigation)\n const page = typeof window !== 'undefined' ? window.location.pathname : undefined\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\n return () => {\n cancelled = true\n }\n }, [monitor])\n\n return null\n}\n"]}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@victor-studio/monitor",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
4
  "description": "Lightweight monitoring SDK for Nuvio-managed projects",
5
5
  "type": "module",
6
+ "sideEffects": false,
6
7
  "main": "./dist/index.cjs",
7
8
  "module": "./dist/index.js",
8
9
  "types": "./dist/index.d.ts",
@@ -21,6 +22,11 @@
21
22
  "types": "./dist/react/index.d.ts",
22
23
  "import": "./dist/react/index.js",
23
24
  "require": "./dist/react/index.cjs"
25
+ },
26
+ "./errors": {
27
+ "types": "./dist/errors/index.d.ts",
28
+ "import": "./dist/errors/index.js",
29
+ "require": "./dist/errors/index.cjs"
24
30
  }
25
31
  },
26
32
  "files": [
@@ -34,7 +40,8 @@
34
40
  },
35
41
  "peerDependencies": {
36
42
  "next": ">=14",
37
- "react": ">=18"
43
+ "react": ">=18",
44
+ "web-vitals": ">=4"
38
45
  },
39
46
  "peerDependenciesMeta": {
40
47
  "next": {
@@ -42,15 +49,16 @@
42
49
  },
43
50
  "react": {
44
51
  "optional": true
52
+ },
53
+ "web-vitals": {
54
+ "optional": true
45
55
  }
46
56
  },
47
57
  "devDependencies": {
48
58
  "@types/node": "^25.6.0",
49
- "@types/react": "^19.0.0",
59
+ "@types/react": ">=18",
50
60
  "tsup": "^8.0.0",
51
- "typescript": "^5.9.0"
52
- },
53
- "dependencies": {
61
+ "typescript": "^5.9.0",
54
62
  "web-vitals": "^4.2.0"
55
63
  },
56
64
  "keywords": [
@@ -58,7 +66,9 @@
58
66
  "sdk",
59
67
  "web-vitals",
60
68
  "uptime",
61
- "performance"
69
+ "performance",
70
+ "heartbeat",
71
+ "apm"
62
72
  ],
63
73
  "author": "Victor Studio",
64
74
  "license": "MIT",