@iaportafolio/nextjs 0.1.1 → 0.3.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/README.md +86 -8
- package/dist/chunk-I3FDJF4L.js +369 -0
- package/dist/client.cjs +365 -93
- package/dist/client.d.cts +191 -0
- package/dist/client.d.ts +191 -0
- package/dist/client.js +25 -5
- package/dist/index.cjs +354 -94
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25 -5
- package/dist/server.cjs +1 -0
- package/dist/server.d.cts +42 -0
- package/dist/server.d.ts +42 -0
- package/package.json +61 -62
- package/src/browser-core.ts +394 -0
- package/src/browser-react.tsx +53 -0
- package/src/client.ts +63 -131
- package/dist/chunk-TYH3TMKC.js +0 -120
package/dist/client.cjs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
1
3
|
var __defProp = Object.defineProperty;
|
|
2
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
8
|
var __export = (target, all) => {
|
|
6
9
|
for (var name in all)
|
|
@@ -14,131 +17,400 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
14
17
|
}
|
|
15
18
|
return to;
|
|
16
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
|
+
));
|
|
17
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
29
|
|
|
19
30
|
// src/client.ts
|
|
20
31
|
var client_exports = {};
|
|
21
32
|
__export(client_exports, {
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
FaroErrorBoundary: () => FaroErrorBoundary,
|
|
34
|
+
addBreadcrumb: () => addBreadcrumb,
|
|
35
|
+
captureException: () => captureException,
|
|
36
|
+
close: () => close,
|
|
37
|
+
error: () => error,
|
|
38
|
+
flush: () => flush,
|
|
39
|
+
getClient: () => getClient,
|
|
40
|
+
info: () => info,
|
|
41
|
+
initFaroClient: () => initFaroClient,
|
|
42
|
+
log: () => log,
|
|
43
|
+
setUser: () => setUser,
|
|
44
|
+
warn: () => warn
|
|
24
45
|
});
|
|
25
46
|
module.exports = __toCommonJS(client_exports);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
// src/browser-core.ts
|
|
49
|
+
var FaroBrowser = class {
|
|
50
|
+
constructor(opts) {
|
|
51
|
+
this.queue = [];
|
|
52
|
+
this.breadcrumbs = [];
|
|
53
|
+
this.user = null;
|
|
54
|
+
this.timer = null;
|
|
55
|
+
this.cleanup = [];
|
|
56
|
+
this.closed = false;
|
|
57
|
+
this.opts = {
|
|
58
|
+
endpoint: opts.endpoint.replace(/\/$/, ""),
|
|
59
|
+
token: opts.token,
|
|
60
|
+
service: opts.service,
|
|
61
|
+
environment: opts.environment,
|
|
62
|
+
release: opts.release,
|
|
63
|
+
attributes: opts.attributes,
|
|
64
|
+
flushIntervalMs: opts.flushIntervalMs ?? 2e3,
|
|
65
|
+
maxBatchSize: opts.maxBatchSize ?? 100,
|
|
66
|
+
maxQueueSize: opts.maxQueueSize ?? 2e3,
|
|
67
|
+
maxBreadcrumbs: opts.maxBreadcrumbs ?? 30,
|
|
68
|
+
captureUnhandled: opts.captureUnhandled ?? true,
|
|
69
|
+
captureConsole: opts.captureConsole ?? false,
|
|
70
|
+
captureWebVitals: opts.captureWebVitals ?? true,
|
|
71
|
+
captureClicks: opts.captureClicks ?? true,
|
|
72
|
+
captureNavigation: opts.captureNavigation ?? true,
|
|
73
|
+
beforeSend: opts.beforeSend
|
|
74
|
+
};
|
|
75
|
+
if (typeof window === "undefined") {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.timer = setInterval(() => void this.flush(), this.opts.flushIntervalMs);
|
|
79
|
+
if (this.opts.captureUnhandled) this.installErrorHandlers();
|
|
80
|
+
if (this.opts.captureConsole) this.installConsoleCapture();
|
|
81
|
+
if (this.opts.captureWebVitals) this.installWebVitals();
|
|
82
|
+
if (this.opts.captureClicks) this.installClickTracking();
|
|
83
|
+
if (this.opts.captureNavigation) this.installNavigationTracking();
|
|
84
|
+
this.installLifecycleHooks();
|
|
85
|
+
}
|
|
86
|
+
setUser(user) {
|
|
87
|
+
this.user = user;
|
|
88
|
+
}
|
|
89
|
+
addBreadcrumb(crumb) {
|
|
90
|
+
if (this.breadcrumbs.length >= this.opts.maxBreadcrumbs) {
|
|
91
|
+
this.breadcrumbs.shift();
|
|
92
|
+
}
|
|
93
|
+
this.breadcrumbs.push({ ...crumb, timestamp: Date.now() });
|
|
94
|
+
}
|
|
95
|
+
log(entry) {
|
|
96
|
+
if (this.closed) return;
|
|
97
|
+
const attrs = this.composeAttributes(entry.attributes);
|
|
98
|
+
const evt = {
|
|
99
|
+
level: entry.level ?? "INFO",
|
|
100
|
+
message: entry.message,
|
|
101
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
102
|
+
attributes: attrs,
|
|
103
|
+
trace_id: entry.trace_id,
|
|
104
|
+
span_id: entry.span_id
|
|
105
|
+
};
|
|
106
|
+
this.enqueue(evt);
|
|
107
|
+
}
|
|
108
|
+
info(message, attrs) {
|
|
109
|
+
this.log({ level: "INFO", message, attributes: attrs });
|
|
110
|
+
}
|
|
111
|
+
warn(message, attrs) {
|
|
112
|
+
this.log({ level: "WARN", message, attributes: attrs });
|
|
113
|
+
}
|
|
114
|
+
error(message, attrs) {
|
|
115
|
+
this.log({ level: "ERROR", message, attributes: attrs });
|
|
116
|
+
}
|
|
117
|
+
captureException(err, ctx) {
|
|
118
|
+
const e = toError(err);
|
|
119
|
+
this.log({
|
|
120
|
+
level: "ERROR",
|
|
121
|
+
message: ctx?.message ?? `${e.name}: ${e.message}`,
|
|
122
|
+
attributes: {
|
|
123
|
+
"exception.type": e.name,
|
|
124
|
+
"exception.message": e.message,
|
|
125
|
+
"exception.stacktrace": e.stack ?? "",
|
|
126
|
+
...ctx?.tags ?? {}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async flush(useBeacon = false) {
|
|
131
|
+
if (this.queue.length === 0) return;
|
|
132
|
+
const batch = this.queue.splice(0, this.opts.maxBatchSize);
|
|
133
|
+
const body = JSON.stringify({ service: this.opts.service, logs: batch });
|
|
134
|
+
const url = `${this.opts.endpoint}/api/v1/ingest/logs`;
|
|
135
|
+
if (useBeacon && typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
136
|
+
const beaconUrl = `${url}?_token=${encodeURIComponent(this.opts.token)}`;
|
|
137
|
+
const ok = navigator.sendBeacon(beaconUrl, new Blob([body], { type: "application/json" }));
|
|
48
138
|
if (ok) return;
|
|
49
|
-
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch(url, {
|
|
50
142
|
method: "POST",
|
|
51
143
|
keepalive: true,
|
|
52
144
|
headers: {
|
|
53
|
-
"Authorization": `Bearer ${opts.token}`,
|
|
145
|
+
"Authorization": `Bearer ${this.opts.token}`,
|
|
54
146
|
"Content-Type": "application/json"
|
|
55
147
|
},
|
|
56
148
|
body
|
|
57
149
|
});
|
|
58
|
-
if (!res.ok) {
|
|
59
|
-
|
|
150
|
+
if (!res.ok && res.status >= 500) {
|
|
151
|
+
this.queue.unshift(...batch);
|
|
60
152
|
}
|
|
61
|
-
} catch
|
|
62
|
-
queue.unshift(...batch);
|
|
153
|
+
} catch {
|
|
154
|
+
this.queue.unshift(...batch);
|
|
63
155
|
}
|
|
64
156
|
}
|
|
65
|
-
|
|
157
|
+
close() {
|
|
158
|
+
if (this.closed) return;
|
|
159
|
+
this.closed = true;
|
|
160
|
+
if (this.timer) clearInterval(this.timer);
|
|
161
|
+
this.timer = null;
|
|
162
|
+
for (const fn of this.cleanup) fn();
|
|
163
|
+
this.cleanup = [];
|
|
164
|
+
void this.flush(true);
|
|
165
|
+
}
|
|
166
|
+
enqueue(evt) {
|
|
167
|
+
const processed = this.opts.beforeSend ? this.opts.beforeSend(evt) : evt;
|
|
168
|
+
if (!processed) return;
|
|
169
|
+
if (this.queue.length >= this.opts.maxQueueSize) return;
|
|
170
|
+
this.queue.push(processed);
|
|
171
|
+
}
|
|
172
|
+
composeAttributes(extra) {
|
|
66
173
|
const attrs = {};
|
|
67
|
-
if (opts.attributes) {
|
|
68
|
-
for (const [k, v] of Object.entries(opts.attributes)) attrs[k] = String(v);
|
|
174
|
+
if (this.opts.attributes) {
|
|
175
|
+
for (const [k, v] of Object.entries(this.opts.attributes)) attrs[k] = String(v);
|
|
69
176
|
}
|
|
70
|
-
if (opts.environment) attrs["deployment.environment"] = opts.environment;
|
|
71
|
-
if (opts.release) attrs["service.version"] = opts.release;
|
|
177
|
+
if (this.opts.environment) attrs["deployment.environment"] = this.opts.environment;
|
|
178
|
+
if (this.opts.release) attrs["service.version"] = this.opts.release;
|
|
72
179
|
if (typeof window !== "undefined") {
|
|
73
180
|
attrs["browser.url"] = window.location.href;
|
|
74
181
|
attrs["browser.userAgent"] = navigator.userAgent;
|
|
75
182
|
}
|
|
76
|
-
if (
|
|
77
|
-
|
|
183
|
+
if (this.user) {
|
|
184
|
+
if (this.user.id) attrs["user.id"] = this.user.id;
|
|
185
|
+
if (this.user.email) attrs["user.email"] = this.user.email;
|
|
186
|
+
if (this.user.username) attrs["user.name"] = this.user.username;
|
|
187
|
+
}
|
|
188
|
+
if (this.breadcrumbs.length > 0) {
|
|
189
|
+
attrs["breadcrumbs"] = JSON.stringify(this.breadcrumbs.slice(-this.opts.maxBreadcrumbs));
|
|
190
|
+
}
|
|
191
|
+
if (extra) {
|
|
192
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
78
193
|
attrs[k] = typeof v === "string" ? v : JSON.stringify(v);
|
|
79
194
|
}
|
|
80
195
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (queue.length >= maxBatch) void flush();
|
|
88
|
-
else scheduleFlush();
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
log: (e) => enqueue(e.level ?? "INFO", e.message, e.attributes),
|
|
92
|
-
info: (m, a) => enqueue("INFO", m, a),
|
|
93
|
-
warn: (m, a) => enqueue("WARN", m, a),
|
|
94
|
-
error: (m, a) => enqueue("ERROR", m, a),
|
|
95
|
-
captureException: (err, ctx) => {
|
|
96
|
-
const e = err instanceof Error ? err : new Error(typeof err === "string" ? err : JSON.stringify(err));
|
|
97
|
-
enqueue("ERROR", (ctx == null ? void 0 : ctx.message) ?? `${e.name}: ${e.message}`, {
|
|
98
|
-
"exception.type": e.name,
|
|
99
|
-
"exception.message": e.message,
|
|
100
|
-
"exception.stacktrace": e.stack ?? "",
|
|
101
|
-
...(ctx == null ? void 0 : ctx.tags) ?? {}
|
|
196
|
+
return attrs;
|
|
197
|
+
}
|
|
198
|
+
installErrorHandlers() {
|
|
199
|
+
const onError = (ev) => {
|
|
200
|
+
this.captureException(ev.error ?? ev.message, {
|
|
201
|
+
tags: { origin: "window.error", "source.file": ev.filename ?? "", "source.line": String(ev.lineno ?? 0) }
|
|
102
202
|
});
|
|
103
|
-
},
|
|
104
|
-
flush
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function initFaroClient(opts) {
|
|
108
|
-
if (typeof window === "undefined") {
|
|
109
|
-
return {
|
|
110
|
-
log() {
|
|
111
|
-
},
|
|
112
|
-
info() {
|
|
113
|
-
},
|
|
114
|
-
warn() {
|
|
115
|
-
},
|
|
116
|
-
error() {
|
|
117
|
-
},
|
|
118
|
-
captureException() {
|
|
119
|
-
},
|
|
120
|
-
flush: async () => void 0
|
|
121
203
|
};
|
|
204
|
+
const onRejection = (ev) => {
|
|
205
|
+
this.captureException(ev.reason, { tags: { origin: "unhandledrejection" } });
|
|
206
|
+
};
|
|
207
|
+
window.addEventListener("error", onError);
|
|
208
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
209
|
+
this.cleanup.push(() => window.removeEventListener("error", onError));
|
|
210
|
+
this.cleanup.push(() => window.removeEventListener("unhandledrejection", onRejection));
|
|
122
211
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
212
|
+
installConsoleCapture() {
|
|
213
|
+
const orig = { error: console.error, warn: console.warn };
|
|
214
|
+
console.error = (...args) => {
|
|
215
|
+
this.addBreadcrumb({ category: "console", message: String(args[0] ?? ""), data: { level: "error" } });
|
|
216
|
+
this.log({ level: "ERROR", message: stringifyArgs(args), attributes: { "console.method": "error" } });
|
|
217
|
+
orig.error.apply(console, args);
|
|
218
|
+
};
|
|
219
|
+
console.warn = (...args) => {
|
|
220
|
+
this.addBreadcrumb({ category: "console", message: String(args[0] ?? ""), data: { level: "warn" } });
|
|
221
|
+
orig.warn.apply(console, args);
|
|
222
|
+
};
|
|
223
|
+
this.cleanup.push(() => {
|
|
224
|
+
console.error = orig.error;
|
|
225
|
+
console.warn = orig.warn;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
installWebVitals() {
|
|
229
|
+
void import("web-vitals").then(({ onLCP, onCLS, onINP, onFCP, onTTFB }) => {
|
|
230
|
+
const report = (name) => (m) => {
|
|
231
|
+
this.log({
|
|
232
|
+
level: "INFO",
|
|
233
|
+
message: `web-vital ${name}`,
|
|
234
|
+
attributes: {
|
|
235
|
+
"metric.name": name,
|
|
236
|
+
"metric.value": m.value,
|
|
237
|
+
"metric.rating": m.rating,
|
|
238
|
+
"metric.id": m.id
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
onLCP(report("LCP"));
|
|
243
|
+
onCLS(report("CLS"));
|
|
244
|
+
onINP(report("INP"));
|
|
245
|
+
onFCP(report("FCP"));
|
|
246
|
+
onTTFB(report("TTFB"));
|
|
247
|
+
}).catch(() => {
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
installClickTracking() {
|
|
251
|
+
const onClick = (ev) => {
|
|
252
|
+
const target = ev.target;
|
|
253
|
+
if (!target) return;
|
|
254
|
+
const tag = target.tagName?.toLowerCase() ?? "";
|
|
255
|
+
const id = target.id;
|
|
256
|
+
const text = (target.textContent ?? "").trim().slice(0, 60);
|
|
257
|
+
const data = { tag };
|
|
258
|
+
if (id) data.id = id;
|
|
259
|
+
if (text) data.text = text;
|
|
260
|
+
this.addBreadcrumb({ category: "click", message: `${tag}${id ? "#" + id : ""}`, data });
|
|
261
|
+
};
|
|
262
|
+
window.addEventListener("click", onClick, { capture: true, passive: true });
|
|
263
|
+
this.cleanup.push(() => window.removeEventListener("click", onClick, { capture: true }));
|
|
264
|
+
}
|
|
265
|
+
installNavigationTracking() {
|
|
266
|
+
const log2 = (from, to, method) => {
|
|
267
|
+
if (from === to) return;
|
|
268
|
+
this.addBreadcrumb({ category: "navigation", message: `${from} \u2192 ${to}`, data: { method, to } });
|
|
269
|
+
};
|
|
270
|
+
const origPush = history.pushState;
|
|
271
|
+
const origReplace = history.replaceState;
|
|
272
|
+
history.pushState = function(...args) {
|
|
273
|
+
const from = location.href;
|
|
274
|
+
const ret = origPush.apply(this, args);
|
|
275
|
+
log2(from, location.href, "pushState");
|
|
276
|
+
return ret;
|
|
277
|
+
};
|
|
278
|
+
history.replaceState = function(...args) {
|
|
279
|
+
const from = location.href;
|
|
280
|
+
const ret = origReplace.apply(this, args);
|
|
281
|
+
log2(from, location.href, "replaceState");
|
|
282
|
+
return ret;
|
|
283
|
+
};
|
|
284
|
+
const onPop = () => log2("", location.href, "popstate");
|
|
285
|
+
window.addEventListener("popstate", onPop);
|
|
286
|
+
this.cleanup.push(() => {
|
|
287
|
+
history.pushState = origPush;
|
|
288
|
+
history.replaceState = origReplace;
|
|
289
|
+
window.removeEventListener("popstate", onPop);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
installLifecycleHooks() {
|
|
293
|
+
const onHide = () => {
|
|
294
|
+
if (document.visibilityState === "hidden") void this.flush(true);
|
|
295
|
+
};
|
|
296
|
+
const onPageHide = () => void this.flush(true);
|
|
297
|
+
document.addEventListener("visibilitychange", onHide);
|
|
298
|
+
window.addEventListener("pagehide", onPageHide);
|
|
299
|
+
this.cleanup.push(() => document.removeEventListener("visibilitychange", onHide));
|
|
300
|
+
this.cleanup.push(() => window.removeEventListener("pagehide", onPageHide));
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
function toError(err) {
|
|
304
|
+
if (err instanceof Error) return err;
|
|
305
|
+
if (typeof err === "string") return new Error(err);
|
|
306
|
+
try {
|
|
307
|
+
return new Error(JSON.stringify(err));
|
|
308
|
+
} catch {
|
|
309
|
+
return new Error(String(err));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function stringifyArgs(args) {
|
|
313
|
+
return args.map((a) => typeof a === "string" ? a : a instanceof Error ? a.stack ?? a.message : safeJson(a)).join(" ");
|
|
314
|
+
}
|
|
315
|
+
function safeJson(v) {
|
|
316
|
+
try {
|
|
317
|
+
return JSON.stringify(v);
|
|
318
|
+
} catch {
|
|
319
|
+
return String(v);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
var singleton = null;
|
|
323
|
+
function init(opts) {
|
|
324
|
+
if (singleton) singleton.close();
|
|
325
|
+
singleton = new FaroBrowser(opts);
|
|
326
|
+
return singleton;
|
|
327
|
+
}
|
|
328
|
+
function getClient() {
|
|
329
|
+
if (!singleton) throw new Error("faro: init() must be called before use");
|
|
330
|
+
return singleton;
|
|
331
|
+
}
|
|
332
|
+
function log(entry) {
|
|
333
|
+
getClient().log(entry);
|
|
334
|
+
}
|
|
335
|
+
function info(msg, attrs) {
|
|
336
|
+
getClient().info(msg, attrs);
|
|
135
337
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
338
|
+
function warn(msg, attrs) {
|
|
339
|
+
getClient().warn(msg, attrs);
|
|
340
|
+
}
|
|
341
|
+
function error(msg, attrs) {
|
|
342
|
+
getClient().error(msg, attrs);
|
|
343
|
+
}
|
|
344
|
+
function captureException(err, ctx) {
|
|
345
|
+
getClient().captureException(err, ctx);
|
|
346
|
+
}
|
|
347
|
+
function setUser(user) {
|
|
348
|
+
getClient().setUser(user);
|
|
349
|
+
}
|
|
350
|
+
function addBreadcrumb(crumb) {
|
|
351
|
+
getClient().addBreadcrumb(crumb);
|
|
352
|
+
}
|
|
353
|
+
function flush() {
|
|
354
|
+
return getClient().flush();
|
|
355
|
+
}
|
|
356
|
+
function close() {
|
|
357
|
+
getClient().close();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/browser-react.tsx
|
|
361
|
+
var React = __toESM(require("react"), 1);
|
|
362
|
+
var FaroErrorBoundary = class extends React.Component {
|
|
363
|
+
constructor() {
|
|
364
|
+
super(...arguments);
|
|
365
|
+
this.state = { error: null };
|
|
366
|
+
this.reset = () => {
|
|
367
|
+
this.setState({ error: null });
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
static getDerivedStateFromError(error2) {
|
|
371
|
+
return { error: error2 };
|
|
372
|
+
}
|
|
373
|
+
componentDidCatch(error2, info2) {
|
|
374
|
+
captureException(error2, {
|
|
375
|
+
tags: {
|
|
376
|
+
origin: "react.error-boundary",
|
|
377
|
+
...this.props.tags ?? {}
|
|
378
|
+
},
|
|
379
|
+
message: error2.message
|
|
380
|
+
});
|
|
381
|
+
this.props.onError?.(error2, info2);
|
|
382
|
+
}
|
|
383
|
+
render() {
|
|
384
|
+
if (this.state.error) {
|
|
385
|
+
const fb = this.props.fallback;
|
|
386
|
+
if (typeof fb === "function") return fb({ error: this.state.error, reset: this.reset });
|
|
387
|
+
if (fb !== void 0) return fb;
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return this.props.children;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/client.ts
|
|
395
|
+
function initFaroClient(opts) {
|
|
396
|
+
let release = opts.release;
|
|
397
|
+
if (!release && typeof process !== "undefined" && process.env) {
|
|
398
|
+
release = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERSION || void 0;
|
|
399
|
+
}
|
|
400
|
+
return init({ ...opts, release });
|
|
139
401
|
}
|
|
140
402
|
// Annotate the CommonJS export names for ESM import in node:
|
|
141
403
|
0 && (module.exports = {
|
|
142
|
-
|
|
143
|
-
|
|
404
|
+
FaroErrorBoundary,
|
|
405
|
+
addBreadcrumb,
|
|
406
|
+
captureException,
|
|
407
|
+
close,
|
|
408
|
+
error,
|
|
409
|
+
flush,
|
|
410
|
+
getClient,
|
|
411
|
+
info,
|
|
412
|
+
initFaroClient,
|
|
413
|
+
log,
|
|
414
|
+
setUser,
|
|
415
|
+
warn
|
|
144
416
|
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core RUM para navegador (interno de @iaportafolio/nextjs).
|
|
5
|
+
*
|
|
6
|
+
* Captura errores no manejados, Web Vitals, navegaciones y clicks como
|
|
7
|
+
* breadcrumbs, y envía todo en lotes a la API de ingesta usando
|
|
8
|
+
* sendBeacon cuando el tab se cierra (sin perder eventos).
|
|
9
|
+
*
|
|
10
|
+
* Este archivo no se exporta directamente al usuario; el entrypoint
|
|
11
|
+
* público es `@iaportafolio/nextjs/client`.
|
|
12
|
+
*/
|
|
13
|
+
type Severity = 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
|
|
14
|
+
interface FaroBrowserOptions {
|
|
15
|
+
endpoint: string;
|
|
16
|
+
token: string;
|
|
17
|
+
service: string;
|
|
18
|
+
environment?: string;
|
|
19
|
+
release?: string;
|
|
20
|
+
/** Atributos por defecto adjuntados a cada evento */
|
|
21
|
+
attributes?: Record<string, string | number | boolean>;
|
|
22
|
+
/** Cadencia de flush en ms (default 2000) */
|
|
23
|
+
flushIntervalMs?: number;
|
|
24
|
+
/** Tamaño máximo de batch por POST (default 100) */
|
|
25
|
+
maxBatchSize?: number;
|
|
26
|
+
/** Cola en memoria máxima (default 2000) */
|
|
27
|
+
maxQueueSize?: number;
|
|
28
|
+
/** Tamaño del ring buffer de breadcrumbs (default 30) */
|
|
29
|
+
maxBreadcrumbs?: number;
|
|
30
|
+
/** Capturar window.onerror + unhandledrejection (default true) */
|
|
31
|
+
captureUnhandled?: boolean;
|
|
32
|
+
/** Capturar console.error y console.warn (default false — puede meter ruido) */
|
|
33
|
+
captureConsole?: boolean;
|
|
34
|
+
/** Capturar Web Vitals LCP/CLS/INP/FID/TTFB (default true) */
|
|
35
|
+
captureWebVitals?: boolean;
|
|
36
|
+
/** Capturar clicks como breadcrumbs (default true) */
|
|
37
|
+
captureClicks?: boolean;
|
|
38
|
+
/** Capturar navegaciones SPA (history.pushState/popstate) (default true) */
|
|
39
|
+
captureNavigation?: boolean;
|
|
40
|
+
/** Hook para muestrear o redactar eventos antes de enviar; devolver null descarta */
|
|
41
|
+
beforeSend?: (event: WireEvent) => WireEvent | null;
|
|
42
|
+
}
|
|
43
|
+
interface UserContext {
|
|
44
|
+
id?: string;
|
|
45
|
+
email?: string;
|
|
46
|
+
username?: string;
|
|
47
|
+
[key: string]: string | undefined;
|
|
48
|
+
}
|
|
49
|
+
interface Breadcrumb {
|
|
50
|
+
category: 'click' | 'navigation' | 'console' | 'fetch' | 'custom';
|
|
51
|
+
message: string;
|
|
52
|
+
timestamp: number;
|
|
53
|
+
data?: Record<string, string | number | boolean | undefined>;
|
|
54
|
+
}
|
|
55
|
+
interface LogEntry {
|
|
56
|
+
level?: Severity;
|
|
57
|
+
message: string;
|
|
58
|
+
attributes?: Record<string, unknown>;
|
|
59
|
+
trace_id?: string;
|
|
60
|
+
span_id?: string;
|
|
61
|
+
}
|
|
62
|
+
interface WireEvent {
|
|
63
|
+
level: Severity;
|
|
64
|
+
message: string;
|
|
65
|
+
timestamp: string;
|
|
66
|
+
attributes: Record<string, string>;
|
|
67
|
+
trace_id?: string;
|
|
68
|
+
span_id?: string;
|
|
69
|
+
}
|
|
70
|
+
declare class FaroBrowser {
|
|
71
|
+
private opts;
|
|
72
|
+
private queue;
|
|
73
|
+
private breadcrumbs;
|
|
74
|
+
private user;
|
|
75
|
+
private timer;
|
|
76
|
+
private cleanup;
|
|
77
|
+
private closed;
|
|
78
|
+
constructor(opts: FaroBrowserOptions);
|
|
79
|
+
setUser(user: UserContext | null): void;
|
|
80
|
+
addBreadcrumb(crumb: Omit<Breadcrumb, 'timestamp'>): void;
|
|
81
|
+
log(entry: LogEntry): void;
|
|
82
|
+
info(message: string, attrs?: Record<string, unknown>): void;
|
|
83
|
+
warn(message: string, attrs?: Record<string, unknown>): void;
|
|
84
|
+
error(message: string, attrs?: Record<string, unknown>): void;
|
|
85
|
+
captureException(err: unknown, ctx?: {
|
|
86
|
+
tags?: Record<string, string>;
|
|
87
|
+
message?: string;
|
|
88
|
+
}): void;
|
|
89
|
+
flush(useBeacon?: boolean): Promise<void>;
|
|
90
|
+
close(): void;
|
|
91
|
+
private enqueue;
|
|
92
|
+
private composeAttributes;
|
|
93
|
+
private installErrorHandlers;
|
|
94
|
+
private installConsoleCapture;
|
|
95
|
+
private installWebVitals;
|
|
96
|
+
private installClickTracking;
|
|
97
|
+
private installNavigationTracking;
|
|
98
|
+
private installLifecycleHooks;
|
|
99
|
+
}
|
|
100
|
+
declare function getClient(): FaroBrowser;
|
|
101
|
+
declare function log(entry: LogEntry): void;
|
|
102
|
+
declare function info(msg: string, attrs?: Record<string, unknown>): void;
|
|
103
|
+
declare function warn(msg: string, attrs?: Record<string, unknown>): void;
|
|
104
|
+
declare function error(msg: string, attrs?: Record<string, unknown>): void;
|
|
105
|
+
declare function captureException(err: unknown, ctx?: {
|
|
106
|
+
tags?: Record<string, string>;
|
|
107
|
+
message?: string;
|
|
108
|
+
}): void;
|
|
109
|
+
declare function setUser(user: UserContext | null): void;
|
|
110
|
+
declare function addBreadcrumb(crumb: Omit<Breadcrumb, 'timestamp'>): void;
|
|
111
|
+
declare function flush(): Promise<void>;
|
|
112
|
+
declare function close(): void;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* React ErrorBoundary que reporta automáticamente a Faro.
|
|
116
|
+
* Importar desde `@iaportafolio/nextjs/client`.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
interface FaroErrorBoundaryProps {
|
|
120
|
+
children: React.ReactNode;
|
|
121
|
+
/** Fallback UI cuando un hijo lanza. Recibe el error y un `reset` para reintentar. */
|
|
122
|
+
fallback?: React.ReactNode | ((args: {
|
|
123
|
+
error: Error;
|
|
124
|
+
reset: () => void;
|
|
125
|
+
}) => React.ReactNode);
|
|
126
|
+
/** Tags adicionales para el evento (ej. nombre del módulo) */
|
|
127
|
+
tags?: Record<string, string>;
|
|
128
|
+
/** Hook opcional cuando se captura un error (para tracking adicional) */
|
|
129
|
+
onError?: (error: Error, info: React.ErrorInfo) => void;
|
|
130
|
+
}
|
|
131
|
+
interface State {
|
|
132
|
+
error: Error | null;
|
|
133
|
+
}
|
|
134
|
+
declare class FaroErrorBoundary extends React.Component<FaroErrorBoundaryProps, State> {
|
|
135
|
+
state: State;
|
|
136
|
+
static getDerivedStateFromError(error: Error): State;
|
|
137
|
+
componentDidCatch(error: Error, info: React.ErrorInfo): void;
|
|
138
|
+
reset: () => void;
|
|
139
|
+
render(): React.ReactNode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Faro para Next.js — lado cliente (corre en el navegador).
|
|
144
|
+
*
|
|
145
|
+
* Punto de entrada público para el RUM en Next.js:
|
|
146
|
+
* - captura de window.error / unhandledrejection
|
|
147
|
+
* - Web Vitals (LCP/CLS/INP/FCP/TTFB)
|
|
148
|
+
* - breadcrumbs de clicks y navegaciones (history.pushState/popstate)
|
|
149
|
+
* - sendBeacon en pagehide / visibilitychange=hidden (no se pierden eventos)
|
|
150
|
+
* - ErrorBoundary React (`<FaroErrorBoundary>`)
|
|
151
|
+
* - auto-detección de release desde env vars típicas de Vercel/Next
|
|
152
|
+
*
|
|
153
|
+
* Uso típico (App Router):
|
|
154
|
+
*
|
|
155
|
+
* // app/faro-client.tsx
|
|
156
|
+
* 'use client';
|
|
157
|
+
* import { useEffect } from 'react';
|
|
158
|
+
* import { usePathname, useSearchParams } from 'next/navigation';
|
|
159
|
+
* import { initFaroClient, addBreadcrumb } from '@iaportafolio/nextjs/client';
|
|
160
|
+
*
|
|
161
|
+
* export function FaroClient() {
|
|
162
|
+
* const pathname = usePathname();
|
|
163
|
+
* const search = useSearchParams();
|
|
164
|
+
*
|
|
165
|
+
* useEffect(() => {
|
|
166
|
+
* initFaroClient({
|
|
167
|
+
* endpoint: process.env.NEXT_PUBLIC_FARO_ENDPOINT!,
|
|
168
|
+
* token: process.env.NEXT_PUBLIC_FARO_TOKEN!,
|
|
169
|
+
* service: 'mi-next-app-web',
|
|
170
|
+
* });
|
|
171
|
+
* }, []);
|
|
172
|
+
*
|
|
173
|
+
* useEffect(() => {
|
|
174
|
+
* addBreadcrumb({ category: 'navigation', message: pathname, data: { pathname } });
|
|
175
|
+
* }, [pathname, search]);
|
|
176
|
+
*
|
|
177
|
+
* return null;
|
|
178
|
+
* }
|
|
179
|
+
*
|
|
180
|
+
* // app/layout.tsx
|
|
181
|
+
* import { FaroClient } from './faro-client';
|
|
182
|
+
* <body><FaroClient />{children}</body>
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Inicializa el RUM en el navegador. Seguro de llamar en SSR — si `typeof window === 'undefined'`
|
|
187
|
+
* el core no hace nada. Llámalo desde `useEffect` en un componente 'use client'.
|
|
188
|
+
*/
|
|
189
|
+
declare function initFaroClient(opts: FaroBrowserOptions): FaroBrowser;
|
|
190
|
+
|
|
191
|
+
export { type Breadcrumb, FaroBrowser, type FaroBrowserOptions, FaroErrorBoundary, type FaroErrorBoundaryProps, type LogEntry, type Severity, type UserContext, type WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, initFaroClient, log, setUser, warn };
|