@iaportafolio/nextjs 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -9
- package/dist/chunk-LFDO56A5.js +27 -0
- package/dist/chunk-YWAGEOOH.js +39 -0
- package/dist/client.cjs +61 -0
- package/dist/client.d.cts +59 -0
- package/dist/client.d.ts +59 -0
- package/dist/client.js +28 -0
- package/dist/index.cjs +101 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +36 -0
- package/dist/server.cjs +63 -0
- package/dist/server.d.cts +42 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +10 -0
- package/package.json +46 -10
- package/src/client.ts +68 -131
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# @iaportafolio/nextjs
|
|
2
2
|
|
|
3
|
-
SDK para Next.js
|
|
3
|
+
SDK para Next.js — App Router y Pages Router. Cubre ambos lados:
|
|
4
|
+
|
|
5
|
+
- **Server**: captura errores de Route Handlers, Server Actions y SSR. Vive sobre [`@iaportafolio/node`](../node/).
|
|
6
|
+
- **Client (browser)**: RUM completo — Web Vitals (LCP/CLS/INP/FCP/TTFB), `window.error`, `unhandledrejection`, clicks/navegaciones como breadcrumbs, React `<ErrorBoundary>` y flush garantizado al cerrar el tab. Vive sobre [`@iaportafolio/browser`](../browser/).
|
|
4
7
|
|
|
5
8
|
```bash
|
|
6
|
-
npm install @iaportafolio/nextjs @iaportafolio/node
|
|
9
|
+
npm install @iaportafolio/nextjs @iaportafolio/node @iaportafolio/browser
|
|
7
10
|
```
|
|
8
11
|
|
|
9
12
|
## Server-side
|
|
@@ -43,22 +46,34 @@ export async function POST(req: Request) {
|
|
|
43
46
|
}
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
## Client-side (
|
|
49
|
+
## Client-side (RUM completo)
|
|
47
50
|
|
|
48
51
|
```tsx
|
|
49
52
|
// app/faro-client.tsx
|
|
50
53
|
'use client';
|
|
51
54
|
import { useEffect } from 'react';
|
|
52
|
-
import {
|
|
55
|
+
import { usePathname, useSearchParams } from 'next/navigation';
|
|
56
|
+
import { initFaroClient, addBreadcrumb, setUser } from '@iaportafolio/nextjs/client';
|
|
53
57
|
|
|
54
58
|
export function FaroClient() {
|
|
59
|
+
const pathname = usePathname();
|
|
60
|
+
const search = useSearchParams();
|
|
61
|
+
|
|
55
62
|
useEffect(() => {
|
|
56
63
|
initFaroClient({
|
|
57
64
|
endpoint: process.env.NEXT_PUBLIC_FARO_ENDPOINT!,
|
|
58
65
|
token: process.env.NEXT_PUBLIC_FARO_TOKEN!,
|
|
59
66
|
service: 'mi-next-app-web',
|
|
67
|
+
// release se autodetecta desde NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA si no la pasas
|
|
60
68
|
});
|
|
61
69
|
}, []);
|
|
70
|
+
|
|
71
|
+
// Breadcrumb explícito por ruta — el SDK ya rastrea pushState/popstate,
|
|
72
|
+
// pero esto da una entrada limpia con el pathname de Next.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
addBreadcrumb({ category: 'navigation', message: pathname, data: { pathname } });
|
|
75
|
+
}, [pathname, search]);
|
|
76
|
+
|
|
62
77
|
return null;
|
|
63
78
|
}
|
|
64
79
|
|
|
@@ -76,10 +91,58 @@ export default function RootLayout({ children }) {
|
|
|
76
91
|
}
|
|
77
92
|
```
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
### Identificar al usuario
|
|
95
|
+
|
|
96
|
+
Tras hacer login:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { setUser } from '@iaportafolio/nextjs/client';
|
|
100
|
+
setUser({ id: user.id, email: user.email });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Todos los eventos siguientes incluyen `user.id`, `user.email`.
|
|
104
|
+
|
|
105
|
+
### React Error Boundary
|
|
106
|
+
|
|
107
|
+
Envuelve secciones críticas para capturar errores de render sin reventar la app entera:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
'use client';
|
|
111
|
+
import { FaroErrorBoundary } from '@iaportafolio/nextjs/client';
|
|
112
|
+
|
|
113
|
+
export default function CheckoutPage() {
|
|
114
|
+
return (
|
|
115
|
+
<FaroErrorBoundary
|
|
116
|
+
tags={{ module: 'checkout' }}
|
|
117
|
+
fallback={({ error, reset }) => (
|
|
118
|
+
<div>
|
|
119
|
+
<h1>Algo se rompió en el checkout</h1>
|
|
120
|
+
<pre>{error.message}</pre>
|
|
121
|
+
<button onClick={reset}>Reintentar</button>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
<Checkout />
|
|
126
|
+
</FaroErrorBoundary>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Qué captura automáticamente
|
|
132
|
+
|
|
133
|
+
| Cosa | Cómo |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| Errores no atrapados | `window.onerror` y `unhandledrejection` |
|
|
136
|
+
| Errores de React | `<FaroErrorBoundary>` (manual) |
|
|
137
|
+
| **Web Vitals** | LCP, CLS, INP, FCP, TTFB enviados como logs con `metric.name`/`metric.value` |
|
|
138
|
+
| Clicks | Breadcrumb con tag + id + texto del elemento |
|
|
139
|
+
| Navegaciones | Breadcrumb en cada `history.pushState`/`popstate` |
|
|
140
|
+
| Contexto | `browser.url`, `browser.userAgent`, `user.*` (si llamas `setUser`) |
|
|
141
|
+
| Flush al cerrar tab | `navigator.sendBeacon` en `pagehide`/`visibilitychange=hidden` |
|
|
142
|
+
|
|
143
|
+
### Apagar comportamientos
|
|
144
|
+
|
|
145
|
+
`initFaroClient({ captureWebVitals: false, captureClicks: false, ... })` — todos los flags están en [`@iaportafolio/browser`](../browser/).
|
|
83
146
|
|
|
84
147
|
## Variables de entorno
|
|
85
148
|
|
|
@@ -88,4 +151,9 @@ El cliente:
|
|
|
88
151
|
| `FARO_ENDPOINT` | solo servidor | URL base |
|
|
89
152
|
| `FARO_TOKEN` | solo servidor | Token de proyecto (privado) |
|
|
90
153
|
| `NEXT_PUBLIC_FARO_ENDPOINT` | cliente + servidor | URL base para el navegador |
|
|
91
|
-
| `NEXT_PUBLIC_FARO_TOKEN` | cliente + servidor | **Mismo token de proyecto.**
|
|
154
|
+
| `NEXT_PUBLIC_FARO_TOKEN` | cliente + servidor | **Mismo token de proyecto.** Queda expuesto en el bundle — es deliberado, igual que en Sentry: el token solo permite ingerir, no leer datos del dashboard. |
|
|
155
|
+
|
|
156
|
+
## Changelog
|
|
157
|
+
|
|
158
|
+
- **v0.2.0**: RUM completo (Web Vitals, breadcrumbs, ErrorBoundary, setUser, navigation tracking). El cliente ahora se apoya en `@iaportafolio/browser`.
|
|
159
|
+
- **v0.1.x**: captura básica de errores en el cliente.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import * as faro from "@iaportafolio/node";
|
|
3
|
+
var installed = false;
|
|
4
|
+
function registerFaro(opts) {
|
|
5
|
+
if (installed) return;
|
|
6
|
+
if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== "nodejs") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
faro.init(opts);
|
|
10
|
+
installed = true;
|
|
11
|
+
}
|
|
12
|
+
function captureRequestError(err, request) {
|
|
13
|
+
if (!installed) return;
|
|
14
|
+
faro.captureException(err, {
|
|
15
|
+
tags: {
|
|
16
|
+
"http.path": request.path ?? "",
|
|
17
|
+
"http.method": request.method ?? "",
|
|
18
|
+
"next.router": request.routerKind ?? ""
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
faro,
|
|
25
|
+
registerFaro,
|
|
26
|
+
captureRequestError
|
|
27
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import {
|
|
3
|
+
init as initBrowser
|
|
4
|
+
} from "@iaportafolio/browser";
|
|
5
|
+
import {
|
|
6
|
+
log,
|
|
7
|
+
info,
|
|
8
|
+
warn,
|
|
9
|
+
error,
|
|
10
|
+
captureException,
|
|
11
|
+
setUser,
|
|
12
|
+
addBreadcrumb,
|
|
13
|
+
flush,
|
|
14
|
+
close,
|
|
15
|
+
getClient
|
|
16
|
+
} from "@iaportafolio/browser";
|
|
17
|
+
import { FaroErrorBoundary } from "@iaportafolio/browser/react";
|
|
18
|
+
function initFaroClient(opts) {
|
|
19
|
+
let release = opts.release;
|
|
20
|
+
if (!release && typeof process !== "undefined" && process.env) {
|
|
21
|
+
release = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERSION || void 0;
|
|
22
|
+
}
|
|
23
|
+
return initBrowser({ ...opts, release });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
initFaroClient,
|
|
28
|
+
log,
|
|
29
|
+
info,
|
|
30
|
+
warn,
|
|
31
|
+
error,
|
|
32
|
+
captureException,
|
|
33
|
+
setUser,
|
|
34
|
+
addBreadcrumb,
|
|
35
|
+
flush,
|
|
36
|
+
close,
|
|
37
|
+
getClient,
|
|
38
|
+
FaroErrorBoundary
|
|
39
|
+
};
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/client.ts
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
|
+
FaroErrorBoundary: () => import_react.FaroErrorBoundary,
|
|
24
|
+
addBreadcrumb: () => import_browser2.addBreadcrumb,
|
|
25
|
+
captureException: () => import_browser2.captureException,
|
|
26
|
+
close: () => import_browser2.close,
|
|
27
|
+
error: () => import_browser2.error,
|
|
28
|
+
flush: () => import_browser2.flush,
|
|
29
|
+
getClient: () => import_browser2.getClient,
|
|
30
|
+
info: () => import_browser2.info,
|
|
31
|
+
initFaroClient: () => initFaroClient,
|
|
32
|
+
log: () => import_browser2.log,
|
|
33
|
+
setUser: () => import_browser2.setUser,
|
|
34
|
+
warn: () => import_browser2.warn
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(client_exports);
|
|
37
|
+
var import_browser = require("@iaportafolio/browser");
|
|
38
|
+
var import_browser2 = require("@iaportafolio/browser");
|
|
39
|
+
var import_react = require("@iaportafolio/browser/react");
|
|
40
|
+
function initFaroClient(opts) {
|
|
41
|
+
let release = opts.release;
|
|
42
|
+
if (!release && typeof process !== "undefined" && process.env) {
|
|
43
|
+
release = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERSION || void 0;
|
|
44
|
+
}
|
|
45
|
+
return (0, import_browser.init)({ ...opts, release });
|
|
46
|
+
}
|
|
47
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
48
|
+
0 && (module.exports = {
|
|
49
|
+
FaroErrorBoundary,
|
|
50
|
+
addBreadcrumb,
|
|
51
|
+
captureException,
|
|
52
|
+
close,
|
|
53
|
+
error,
|
|
54
|
+
flush,
|
|
55
|
+
getClient,
|
|
56
|
+
info,
|
|
57
|
+
initFaroClient,
|
|
58
|
+
log,
|
|
59
|
+
setUser,
|
|
60
|
+
warn
|
|
61
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { FaroBrowserOptions, FaroBrowser } from '@iaportafolio/browser';
|
|
2
|
+
export { Breadcrumb, FaroBrowserOptions, LogEntry, Severity, UserContext, WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, log, setUser, warn } from '@iaportafolio/browser';
|
|
3
|
+
export { FaroErrorBoundary, FaroErrorBoundaryProps } from '@iaportafolio/browser/react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Faro para Next.js — lado cliente (corre en el navegador).
|
|
7
|
+
*
|
|
8
|
+
* Es un wrapper fino sobre @iaportafolio/browser. El core (captura de
|
|
9
|
+
* window.error, Web Vitals, breadcrumbs, batching, sendBeacon en pagehide,
|
|
10
|
+
* ErrorBoundary React) vive en el paquete browser. Aquí sólo añadimos:
|
|
11
|
+
* - auto-detección de la release desde env vars típicas de Vercel/Next
|
|
12
|
+
* - re-exports para que sea ergonómico (`import {...} from '@iaportafolio/nextjs/client'`)
|
|
13
|
+
*
|
|
14
|
+
* Uso típico (App Router):
|
|
15
|
+
*
|
|
16
|
+
* // app/faro-client.tsx
|
|
17
|
+
* 'use client';
|
|
18
|
+
* import { useEffect } from 'react';
|
|
19
|
+
* import { usePathname, useSearchParams } from 'next/navigation';
|
|
20
|
+
* import { initFaroClient, addBreadcrumb } from '@iaportafolio/nextjs/client';
|
|
21
|
+
*
|
|
22
|
+
* export function FaroClient() {
|
|
23
|
+
* const pathname = usePathname();
|
|
24
|
+
* const search = useSearchParams();
|
|
25
|
+
*
|
|
26
|
+
* useEffect(() => {
|
|
27
|
+
* initFaroClient({
|
|
28
|
+
* endpoint: process.env.NEXT_PUBLIC_FARO_ENDPOINT!,
|
|
29
|
+
* token: process.env.NEXT_PUBLIC_FARO_TOKEN!,
|
|
30
|
+
* service: 'mi-next-app-web',
|
|
31
|
+
* });
|
|
32
|
+
* }, []);
|
|
33
|
+
*
|
|
34
|
+
* // (opcional) breadcrumb explícito en cada route change con el pathname limpio.
|
|
35
|
+
* // El SDK ya captura pushState, esto sólo es más legible en el dashboard.
|
|
36
|
+
* useEffect(() => {
|
|
37
|
+
* addBreadcrumb({ category: 'navigation', message: pathname, data: { pathname } });
|
|
38
|
+
* }, [pathname, search]);
|
|
39
|
+
*
|
|
40
|
+
* return null;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // app/layout.tsx
|
|
44
|
+
* import { FaroClient } from './faro-client';
|
|
45
|
+
* <body><FaroClient />{children}</body>
|
|
46
|
+
*
|
|
47
|
+
* Y opcionalmente envuelve secciones críticas con ErrorBoundary:
|
|
48
|
+
*
|
|
49
|
+
* import { FaroErrorBoundary } from '@iaportafolio/nextjs/client';
|
|
50
|
+
* <FaroErrorBoundary fallback={...}><Checkout /></FaroErrorBoundary>
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Inicializa el SDK browser. Seguro de llamar en SSR — si `typeof window === 'undefined'`
|
|
55
|
+
* el SDK subyacente no hace nada. Llámalo desde `useEffect` en un componente 'use client'.
|
|
56
|
+
*/
|
|
57
|
+
declare function initFaroClient(opts: FaroBrowserOptions): FaroBrowser;
|
|
58
|
+
|
|
59
|
+
export { initFaroClient };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { FaroBrowserOptions, FaroBrowser } from '@iaportafolio/browser';
|
|
2
|
+
export { Breadcrumb, FaroBrowserOptions, LogEntry, Severity, UserContext, WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, log, setUser, warn } from '@iaportafolio/browser';
|
|
3
|
+
export { FaroErrorBoundary, FaroErrorBoundaryProps } from '@iaportafolio/browser/react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Faro para Next.js — lado cliente (corre en el navegador).
|
|
7
|
+
*
|
|
8
|
+
* Es un wrapper fino sobre @iaportafolio/browser. El core (captura de
|
|
9
|
+
* window.error, Web Vitals, breadcrumbs, batching, sendBeacon en pagehide,
|
|
10
|
+
* ErrorBoundary React) vive en el paquete browser. Aquí sólo añadimos:
|
|
11
|
+
* - auto-detección de la release desde env vars típicas de Vercel/Next
|
|
12
|
+
* - re-exports para que sea ergonómico (`import {...} from '@iaportafolio/nextjs/client'`)
|
|
13
|
+
*
|
|
14
|
+
* Uso típico (App Router):
|
|
15
|
+
*
|
|
16
|
+
* // app/faro-client.tsx
|
|
17
|
+
* 'use client';
|
|
18
|
+
* import { useEffect } from 'react';
|
|
19
|
+
* import { usePathname, useSearchParams } from 'next/navigation';
|
|
20
|
+
* import { initFaroClient, addBreadcrumb } from '@iaportafolio/nextjs/client';
|
|
21
|
+
*
|
|
22
|
+
* export function FaroClient() {
|
|
23
|
+
* const pathname = usePathname();
|
|
24
|
+
* const search = useSearchParams();
|
|
25
|
+
*
|
|
26
|
+
* useEffect(() => {
|
|
27
|
+
* initFaroClient({
|
|
28
|
+
* endpoint: process.env.NEXT_PUBLIC_FARO_ENDPOINT!,
|
|
29
|
+
* token: process.env.NEXT_PUBLIC_FARO_TOKEN!,
|
|
30
|
+
* service: 'mi-next-app-web',
|
|
31
|
+
* });
|
|
32
|
+
* }, []);
|
|
33
|
+
*
|
|
34
|
+
* // (opcional) breadcrumb explícito en cada route change con el pathname limpio.
|
|
35
|
+
* // El SDK ya captura pushState, esto sólo es más legible en el dashboard.
|
|
36
|
+
* useEffect(() => {
|
|
37
|
+
* addBreadcrumb({ category: 'navigation', message: pathname, data: { pathname } });
|
|
38
|
+
* }, [pathname, search]);
|
|
39
|
+
*
|
|
40
|
+
* return null;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // app/layout.tsx
|
|
44
|
+
* import { FaroClient } from './faro-client';
|
|
45
|
+
* <body><FaroClient />{children}</body>
|
|
46
|
+
*
|
|
47
|
+
* Y opcionalmente envuelve secciones críticas con ErrorBoundary:
|
|
48
|
+
*
|
|
49
|
+
* import { FaroErrorBoundary } from '@iaportafolio/nextjs/client';
|
|
50
|
+
* <FaroErrorBoundary fallback={...}><Checkout /></FaroErrorBoundary>
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Inicializa el SDK browser. Seguro de llamar en SSR — si `typeof window === 'undefined'`
|
|
55
|
+
* el SDK subyacente no hace nada. Llámalo desde `useEffect` en un componente 'use client'.
|
|
56
|
+
*/
|
|
57
|
+
declare function initFaroClient(opts: FaroBrowserOptions): FaroBrowser;
|
|
58
|
+
|
|
59
|
+
export { initFaroClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FaroErrorBoundary,
|
|
3
|
+
addBreadcrumb,
|
|
4
|
+
captureException,
|
|
5
|
+
close,
|
|
6
|
+
error,
|
|
7
|
+
flush,
|
|
8
|
+
getClient,
|
|
9
|
+
info,
|
|
10
|
+
initFaroClient,
|
|
11
|
+
log,
|
|
12
|
+
setUser,
|
|
13
|
+
warn
|
|
14
|
+
} from "./chunk-YWAGEOOH.js";
|
|
15
|
+
export {
|
|
16
|
+
FaroErrorBoundary,
|
|
17
|
+
addBreadcrumb,
|
|
18
|
+
captureException,
|
|
19
|
+
close,
|
|
20
|
+
error,
|
|
21
|
+
flush,
|
|
22
|
+
getClient,
|
|
23
|
+
info,
|
|
24
|
+
initFaroClient,
|
|
25
|
+
log,
|
|
26
|
+
setUser,
|
|
27
|
+
warn
|
|
28
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
FaroErrorBoundary: () => import_react.FaroErrorBoundary,
|
|
34
|
+
addBreadcrumb: () => import_browser2.addBreadcrumb,
|
|
35
|
+
captureException: () => import_browser2.captureException,
|
|
36
|
+
captureRequestError: () => captureRequestError,
|
|
37
|
+
close: () => import_browser2.close,
|
|
38
|
+
error: () => import_browser2.error,
|
|
39
|
+
faro: () => faro,
|
|
40
|
+
flush: () => import_browser2.flush,
|
|
41
|
+
getClient: () => import_browser2.getClient,
|
|
42
|
+
info: () => import_browser2.info,
|
|
43
|
+
initFaroClient: () => initFaroClient,
|
|
44
|
+
log: () => import_browser2.log,
|
|
45
|
+
registerFaro: () => registerFaro,
|
|
46
|
+
setUser: () => import_browser2.setUser,
|
|
47
|
+
warn: () => import_browser2.warn
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
|
|
51
|
+
// src/server.ts
|
|
52
|
+
var faro = __toESM(require("@iaportafolio/node"), 1);
|
|
53
|
+
var installed = false;
|
|
54
|
+
function registerFaro(opts) {
|
|
55
|
+
if (installed) return;
|
|
56
|
+
if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== "nodejs") {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
faro.init(opts);
|
|
60
|
+
installed = true;
|
|
61
|
+
}
|
|
62
|
+
function captureRequestError(err, request) {
|
|
63
|
+
if (!installed) return;
|
|
64
|
+
faro.captureException(err, {
|
|
65
|
+
tags: {
|
|
66
|
+
"http.path": request.path ?? "",
|
|
67
|
+
"http.method": request.method ?? "",
|
|
68
|
+
"next.router": request.routerKind ?? ""
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/client.ts
|
|
74
|
+
var import_browser = require("@iaportafolio/browser");
|
|
75
|
+
var import_browser2 = require("@iaportafolio/browser");
|
|
76
|
+
var import_react = require("@iaportafolio/browser/react");
|
|
77
|
+
function initFaroClient(opts) {
|
|
78
|
+
let release = opts.release;
|
|
79
|
+
if (!release && typeof process !== "undefined" && process.env) {
|
|
80
|
+
release = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERSION || void 0;
|
|
81
|
+
}
|
|
82
|
+
return (0, import_browser.init)({ ...opts, release });
|
|
83
|
+
}
|
|
84
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
85
|
+
0 && (module.exports = {
|
|
86
|
+
FaroErrorBoundary,
|
|
87
|
+
addBreadcrumb,
|
|
88
|
+
captureException,
|
|
89
|
+
captureRequestError,
|
|
90
|
+
close,
|
|
91
|
+
error,
|
|
92
|
+
faro,
|
|
93
|
+
flush,
|
|
94
|
+
getClient,
|
|
95
|
+
info,
|
|
96
|
+
initFaroClient,
|
|
97
|
+
log,
|
|
98
|
+
registerFaro,
|
|
99
|
+
setUser,
|
|
100
|
+
warn
|
|
101
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ServerOptions, captureRequestError, registerFaro } from './server.cjs';
|
|
2
|
+
export { initFaroClient } from './client.cjs';
|
|
3
|
+
export { Breadcrumb, FaroBrowserOptions, LogEntry, Severity, UserContext, WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, log, setUser, warn } from '@iaportafolio/browser';
|
|
4
|
+
export { FaroErrorBoundary, FaroErrorBoundaryProps } from '@iaportafolio/browser/react';
|
|
5
|
+
import * as faro from '@iaportafolio/node';
|
|
6
|
+
export { faro };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ServerOptions, captureRequestError, registerFaro } from './server.js';
|
|
2
|
+
export { initFaroClient } from './client.js';
|
|
3
|
+
export { Breadcrumb, FaroBrowserOptions, LogEntry, Severity, UserContext, WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, log, setUser, warn } from '@iaportafolio/browser';
|
|
4
|
+
export { FaroErrorBoundary, FaroErrorBoundaryProps } from '@iaportafolio/browser/react';
|
|
5
|
+
import * as faro from '@iaportafolio/node';
|
|
6
|
+
export { faro };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FaroErrorBoundary,
|
|
3
|
+
addBreadcrumb,
|
|
4
|
+
captureException,
|
|
5
|
+
close,
|
|
6
|
+
error,
|
|
7
|
+
flush,
|
|
8
|
+
getClient,
|
|
9
|
+
info,
|
|
10
|
+
initFaroClient,
|
|
11
|
+
log,
|
|
12
|
+
setUser,
|
|
13
|
+
warn
|
|
14
|
+
} from "./chunk-YWAGEOOH.js";
|
|
15
|
+
import {
|
|
16
|
+
captureRequestError,
|
|
17
|
+
faro,
|
|
18
|
+
registerFaro
|
|
19
|
+
} from "./chunk-LFDO56A5.js";
|
|
20
|
+
export {
|
|
21
|
+
FaroErrorBoundary,
|
|
22
|
+
addBreadcrumb,
|
|
23
|
+
captureException,
|
|
24
|
+
captureRequestError,
|
|
25
|
+
close,
|
|
26
|
+
error,
|
|
27
|
+
faro,
|
|
28
|
+
flush,
|
|
29
|
+
getClient,
|
|
30
|
+
info,
|
|
31
|
+
initFaroClient,
|
|
32
|
+
log,
|
|
33
|
+
registerFaro,
|
|
34
|
+
setUser,
|
|
35
|
+
warn
|
|
36
|
+
};
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/server.ts
|
|
31
|
+
var server_exports = {};
|
|
32
|
+
__export(server_exports, {
|
|
33
|
+
captureRequestError: () => captureRequestError,
|
|
34
|
+
faro: () => faro,
|
|
35
|
+
registerFaro: () => registerFaro
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(server_exports);
|
|
38
|
+
var faro = __toESM(require("@iaportafolio/node"), 1);
|
|
39
|
+
var installed = false;
|
|
40
|
+
function registerFaro(opts) {
|
|
41
|
+
if (installed) return;
|
|
42
|
+
if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== "nodejs") {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
faro.init(opts);
|
|
46
|
+
installed = true;
|
|
47
|
+
}
|
|
48
|
+
function captureRequestError(err, request) {
|
|
49
|
+
if (!installed) return;
|
|
50
|
+
faro.captureException(err, {
|
|
51
|
+
tags: {
|
|
52
|
+
"http.path": request.path ?? "",
|
|
53
|
+
"http.method": request.method ?? "",
|
|
54
|
+
"next.router": request.routerKind ?? ""
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
59
|
+
0 && (module.exports = {
|
|
60
|
+
captureRequestError,
|
|
61
|
+
faro,
|
|
62
|
+
registerFaro
|
|
63
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as faro from '@iaportafolio/node';
|
|
2
|
+
export { faro };
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Faro para Next.js — lado servidor (instrumentation.ts).
|
|
6
|
+
*
|
|
7
|
+
* Uso (Next.js 13.4+ App Router):
|
|
8
|
+
*
|
|
9
|
+
* // next.config.mjs
|
|
10
|
+
* export default { experimental: { instrumentationHook: true } }; // Next 14- only
|
|
11
|
+
*
|
|
12
|
+
* // instrumentation.ts
|
|
13
|
+
* export async function register() {
|
|
14
|
+
* const { registerFaro } = await import('@iaportafolio/nextjs/server');
|
|
15
|
+
* registerFaro({
|
|
16
|
+
* endpoint: process.env.FARO_ENDPOINT!,
|
|
17
|
+
* token: process.env.FARO_TOKEN!,
|
|
18
|
+
* service: 'mi-next-app',
|
|
19
|
+
* environment: process.env.NODE_ENV,
|
|
20
|
+
* release: process.env.VERCEL_GIT_COMMIT_SHA,
|
|
21
|
+
* });
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* export async function onRequestError(err: unknown, request: { path: string; method: string }) {
|
|
25
|
+
* const { captureRequestError } = await import('@iaportafolio/nextjs/server');
|
|
26
|
+
* captureRequestError(err, request);
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
type ServerOptions = faro.FaroOptions;
|
|
31
|
+
declare function registerFaro(opts: ServerOptions): void;
|
|
32
|
+
/**
|
|
33
|
+
* Engánchalo al hook de instrumentación `onRequestError` de Next.js (Next 15+).
|
|
34
|
+
* Reporta el error con el contexto de la request adjunto como tags.
|
|
35
|
+
*/
|
|
36
|
+
declare function captureRequestError(err: unknown, request: {
|
|
37
|
+
path?: string;
|
|
38
|
+
method?: string;
|
|
39
|
+
routerKind?: string;
|
|
40
|
+
}): void;
|
|
41
|
+
|
|
42
|
+
export { type ServerOptions, captureRequestError, registerFaro };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as faro from '@iaportafolio/node';
|
|
2
|
+
export { faro };
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Faro para Next.js — lado servidor (instrumentation.ts).
|
|
6
|
+
*
|
|
7
|
+
* Uso (Next.js 13.4+ App Router):
|
|
8
|
+
*
|
|
9
|
+
* // next.config.mjs
|
|
10
|
+
* export default { experimental: { instrumentationHook: true } }; // Next 14- only
|
|
11
|
+
*
|
|
12
|
+
* // instrumentation.ts
|
|
13
|
+
* export async function register() {
|
|
14
|
+
* const { registerFaro } = await import('@iaportafolio/nextjs/server');
|
|
15
|
+
* registerFaro({
|
|
16
|
+
* endpoint: process.env.FARO_ENDPOINT!,
|
|
17
|
+
* token: process.env.FARO_TOKEN!,
|
|
18
|
+
* service: 'mi-next-app',
|
|
19
|
+
* environment: process.env.NODE_ENV,
|
|
20
|
+
* release: process.env.VERCEL_GIT_COMMIT_SHA,
|
|
21
|
+
* });
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* export async function onRequestError(err: unknown, request: { path: string; method: string }) {
|
|
25
|
+
* const { captureRequestError } = await import('@iaportafolio/nextjs/server');
|
|
26
|
+
* captureRequestError(err, request);
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
type ServerOptions = faro.FaroOptions;
|
|
31
|
+
declare function registerFaro(opts: ServerOptions): void;
|
|
32
|
+
/**
|
|
33
|
+
* Engánchalo al hook de instrumentación `onRequestError` de Next.js (Next 15+).
|
|
34
|
+
* Reporta el error con el contexto de la request adjunto como tags.
|
|
35
|
+
*/
|
|
36
|
+
declare function captureRequestError(err: unknown, request: {
|
|
37
|
+
path?: string;
|
|
38
|
+
method?: string;
|
|
39
|
+
routerKind?: string;
|
|
40
|
+
}): void;
|
|
41
|
+
|
|
42
|
+
export { type ServerOptions, captureRequestError, registerFaro };
|
package/dist/server.js
ADDED
package/package.json
CHANGED
|
@@ -1,24 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iaportafolio/nextjs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Faro SDK for Next.js (App Router + Pages Router)",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Faro SDK for Next.js (App Router + Pages Router): RUM, Web Vitals, error capture, ErrorBoundary",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
8
8
|
"module": "./dist/index.js",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./client": {
|
|
17
|
+
"types": "./dist/client.d.ts",
|
|
18
|
+
"import": "./dist/client.js",
|
|
19
|
+
"require": "./dist/client.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./server": {
|
|
22
|
+
"types": "./dist/server.d.ts",
|
|
23
|
+
"import": "./dist/server.js",
|
|
24
|
+
"require": "./dist/server.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/IA-Portafolio/faro.git",
|
|
35
|
+
"directory": "sdks/nextjs"
|
|
14
36
|
},
|
|
15
|
-
"files": ["dist", "src", "README.md"],
|
|
16
|
-
"repository": { "type": "git", "url": "git+https://github.com/IA-Portafolio/faro.git", "directory": "sdks/nextjs" },
|
|
17
37
|
"homepage": "https://github.com/IA-Portafolio/faro/tree/main/sdks/nextjs#readme",
|
|
18
38
|
"bugs": "https://github.com/IA-Portafolio/faro/issues",
|
|
19
39
|
"keywords": ["faro", "nextjs", "observability", "logging", "telemetry"],
|
|
20
40
|
"publishConfig": { "access": "public" },
|
|
21
|
-
"scripts": { "build": "tsup src/index.ts src/client.ts src/server.ts --format cjs,esm --dts --clean" },
|
|
22
|
-
"peerDependencies": {
|
|
23
|
-
|
|
41
|
+
"scripts": { "build": "tsup src/index.ts src/client.ts src/server.ts --format cjs,esm --dts --clean --external react --external next --external @iaportafolio/node --external @iaportafolio/browser" },
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@iaportafolio/node": "^0.1.0",
|
|
44
|
+
"@iaportafolio/browser": "^0.1.0",
|
|
45
|
+
"next": ">=13",
|
|
46
|
+
"react": ">=16.8.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": { "optional": true }
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"tsup": "^8.0.0",
|
|
53
|
+
"typescript": "^5.4.0",
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"@types/react": "^18.0.0",
|
|
56
|
+
"react": "^18.0.0",
|
|
57
|
+
"@iaportafolio/node": "^0.1.0",
|
|
58
|
+
"@iaportafolio/browser": "^0.1.0"
|
|
59
|
+
}
|
|
24
60
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Faro para Next.js — lado cliente (corre en el navegador).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Es un wrapper fino sobre @iaportafolio/browser. El core (captura de
|
|
5
|
+
* window.error, Web Vitals, breadcrumbs, batching, sendBeacon en pagehide,
|
|
6
|
+
* ErrorBoundary React) vive en el paquete browser. Aquí sólo añadimos:
|
|
7
|
+
* - auto-detección de la release desde env vars típicas de Vercel/Next
|
|
8
|
+
* - re-exports para que sea ergonómico (`import {...} from '@iaportafolio/nextjs/client'`)
|
|
9
|
+
*
|
|
10
|
+
* Uso típico (App Router):
|
|
5
11
|
*
|
|
6
12
|
* // app/faro-client.tsx
|
|
7
13
|
* 'use client';
|
|
8
14
|
* import { useEffect } from 'react';
|
|
9
|
-
* import {
|
|
15
|
+
* import { usePathname, useSearchParams } from 'next/navigation';
|
|
16
|
+
* import { initFaroClient, addBreadcrumb } from '@iaportafolio/nextjs/client';
|
|
10
17
|
*
|
|
11
18
|
* export function FaroClient() {
|
|
19
|
+
* const pathname = usePathname();
|
|
20
|
+
* const search = useSearchParams();
|
|
21
|
+
*
|
|
12
22
|
* useEffect(() => {
|
|
13
23
|
* initFaroClient({
|
|
14
24
|
* endpoint: process.env.NEXT_PUBLIC_FARO_ENDPOINT!,
|
|
@@ -16,143 +26,70 @@
|
|
|
16
26
|
* service: 'mi-next-app-web',
|
|
17
27
|
* });
|
|
18
28
|
* }, []);
|
|
29
|
+
*
|
|
30
|
+
* // (opcional) breadcrumb explícito en cada route change con el pathname limpio.
|
|
31
|
+
* // El SDK ya captura pushState, esto sólo es más legible en el dashboard.
|
|
32
|
+
* useEffect(() => {
|
|
33
|
+
* addBreadcrumb({ category: 'navigation', message: pathname, data: { pathname } });
|
|
34
|
+
* }, [pathname, search]);
|
|
35
|
+
*
|
|
19
36
|
* return null;
|
|
20
37
|
* }
|
|
21
38
|
*
|
|
22
|
-
* //
|
|
39
|
+
* // app/layout.tsx
|
|
40
|
+
* import { FaroClient } from './faro-client';
|
|
41
|
+
* <body><FaroClient />{children}</body>
|
|
42
|
+
*
|
|
43
|
+
* Y opcionalmente envuelve secciones críticas con ErrorBoundary:
|
|
44
|
+
*
|
|
45
|
+
* import { FaroErrorBoundary } from '@iaportafolio/nextjs/client';
|
|
46
|
+
* <FaroErrorBoundary fallback={...}><Checkout /></FaroErrorBoundary>
|
|
23
47
|
*/
|
|
24
48
|
|
|
25
|
-
import
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
log(entry: { level?: string; message: string; attributes?: Record<string, unknown> }): void;
|
|
31
|
-
info(msg: string, attrs?: Record<string, unknown>): void;
|
|
32
|
-
warn(msg: string, attrs?: Record<string, unknown>): void;
|
|
33
|
-
error(msg: string, attrs?: Record<string, unknown>): void;
|
|
34
|
-
captureException(err: unknown, ctx?: { tags?: Record<string, string>; message?: string }): void;
|
|
35
|
-
flush(): Promise<void>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function newClient(opts: FaroOptions): BrowserClient {
|
|
39
|
-
const endpoint = opts.endpoint.replace(/\/$/, '');
|
|
40
|
-
const queue: unknown[] = [];
|
|
41
|
-
const maxBatch = opts.maxBatchSize ?? 100;
|
|
42
|
-
let pendingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
49
|
+
import {
|
|
50
|
+
init as initBrowser,
|
|
51
|
+
type FaroBrowser,
|
|
52
|
+
type FaroBrowserOptions,
|
|
53
|
+
} from '@iaportafolio/browser';
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
async function flush(): Promise<void> {
|
|
53
|
-
if (queue.length === 0) return;
|
|
54
|
-
const batch = queue.splice(0, maxBatch);
|
|
55
|
-
try {
|
|
56
|
-
// sendBeacon maneja `pagehide` mejor que fetch.
|
|
57
|
-
const body = JSON.stringify({ service: opts.service, logs: batch });
|
|
58
|
-
const ok =
|
|
59
|
-
typeof navigator !== 'undefined' &&
|
|
60
|
-
typeof navigator.sendBeacon === 'function' &&
|
|
61
|
-
document.visibilityState === 'hidden'
|
|
62
|
-
? navigator.sendBeacon(
|
|
63
|
-
`${endpoint}/api/v1/ingest/logs?_token=${encodeURIComponent(opts.token)}`,
|
|
64
|
-
new Blob([body], { type: 'application/json' }),
|
|
65
|
-
)
|
|
66
|
-
: false;
|
|
67
|
-
if (ok) return;
|
|
68
|
-
const res = await fetch(`${endpoint}/api/v1/ingest/logs`, {
|
|
69
|
-
method: 'POST',
|
|
70
|
-
keepalive: true,
|
|
71
|
-
headers: {
|
|
72
|
-
'Authorization': `Bearer ${opts.token}`,
|
|
73
|
-
'Content-Type': 'application/json',
|
|
74
|
-
},
|
|
75
|
-
body,
|
|
76
|
-
});
|
|
77
|
-
if (!res.ok) {
|
|
78
|
-
// Re-encola en best-effort si no es un 4xx (ahí la request es irrecuperable).
|
|
79
|
-
if (res.status >= 500) queue.unshift(...batch);
|
|
80
|
-
}
|
|
81
|
-
} catch (_e) {
|
|
82
|
-
queue.unshift(...batch);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
55
|
+
export type {
|
|
56
|
+
FaroBrowserOptions,
|
|
57
|
+
UserContext,
|
|
58
|
+
Breadcrumb,
|
|
59
|
+
LogEntry,
|
|
60
|
+
Severity,
|
|
61
|
+
WireEvent,
|
|
62
|
+
} from '@iaportafolio/browser';
|
|
85
63
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
for (const [k, v] of Object.entries(attributes)) {
|
|
99
|
-
attrs[k] = typeof v === 'string' ? v : JSON.stringify(v);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
queue.push({
|
|
103
|
-
level,
|
|
104
|
-
message,
|
|
105
|
-
timestamp: new Date().toISOString(),
|
|
106
|
-
attributes: attrs,
|
|
107
|
-
});
|
|
108
|
-
if (queue.length >= maxBatch) void flush();
|
|
109
|
-
else scheduleFlush();
|
|
110
|
-
}
|
|
64
|
+
export {
|
|
65
|
+
log,
|
|
66
|
+
info,
|
|
67
|
+
warn,
|
|
68
|
+
error,
|
|
69
|
+
captureException,
|
|
70
|
+
setUser,
|
|
71
|
+
addBreadcrumb,
|
|
72
|
+
flush,
|
|
73
|
+
close,
|
|
74
|
+
getClient,
|
|
75
|
+
} from '@iaportafolio/browser';
|
|
111
76
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
info: (m, a) => enqueue('INFO', m, a),
|
|
115
|
-
warn: (m, a) => enqueue('WARN', m, a),
|
|
116
|
-
error: (m, a) => enqueue('ERROR', m, a),
|
|
117
|
-
captureException: (err, ctx) => {
|
|
118
|
-
const e = err instanceof Error ? err : new Error(typeof err === 'string' ? err : JSON.stringify(err));
|
|
119
|
-
enqueue('ERROR', ctx?.message ?? `${e.name}: ${e.message}`, {
|
|
120
|
-
'exception.type': e.name,
|
|
121
|
-
'exception.message': e.message,
|
|
122
|
-
'exception.stacktrace': e.stack ?? '',
|
|
123
|
-
...(ctx?.tags ?? {}),
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
flush,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
77
|
+
export { FaroErrorBoundary } from '@iaportafolio/browser/react';
|
|
78
|
+
export type { FaroErrorBoundaryProps } from '@iaportafolio/browser/react';
|
|
129
79
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Inicializa el SDK browser. Seguro de llamar en SSR — si `typeof window === 'undefined'`
|
|
82
|
+
* el SDK subyacente no hace nada. Llámalo desde `useEffect` en un componente 'use client'.
|
|
83
|
+
*/
|
|
84
|
+
export function initFaroClient(opts: FaroBrowserOptions): FaroBrowser {
|
|
85
|
+
// Auto-detect release desde env vars típicas de Vercel/Next si no se pasó explícita.
|
|
86
|
+
let release = opts.release;
|
|
87
|
+
if (!release && typeof process !== 'undefined' && process.env) {
|
|
88
|
+
release =
|
|
89
|
+
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||
|
|
90
|
+
process.env.NEXT_PUBLIC_GIT_COMMIT_SHA ||
|
|
91
|
+
process.env.NEXT_PUBLIC_VERSION ||
|
|
92
|
+
undefined;
|
|
136
93
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Captura errores no manejados en el navegador.
|
|
140
|
-
window.addEventListener('error', (ev) => {
|
|
141
|
-
client?.captureException(ev.error ?? ev.message, { tags: { origin: 'window.error' } });
|
|
142
|
-
});
|
|
143
|
-
window.addEventListener('unhandledrejection', (ev) => {
|
|
144
|
-
client?.captureException(ev.reason, { tags: { origin: 'unhandledrejection' } });
|
|
145
|
-
});
|
|
146
|
-
// Hace flush cuando la pestaña se oculta.
|
|
147
|
-
document.addEventListener('visibilitychange', () => {
|
|
148
|
-
if (document.visibilityState === 'hidden') void client?.flush();
|
|
149
|
-
});
|
|
150
|
-
window.addEventListener('pagehide', () => void client?.flush());
|
|
151
|
-
|
|
152
|
-
return client;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function faroClient(): BrowserClient {
|
|
156
|
-
if (!client) throw new Error('Hay que llamar a initFaroClient() antes de usarlo');
|
|
157
|
-
return client;
|
|
94
|
+
return initBrowser({ ...opts, release });
|
|
158
95
|
}
|