@devkitio/faultlens 0.1.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-6LF3JLVG.js +293 -0
- package/dist/chunk-ZUVQIOFO.js +1282 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +50 -26
- package/dist/express/index.d.ts +1 -1
- package/dist/fastify/index.d.ts +1 -1
- package/dist/index.d.ts +30 -3
- package/dist/index.js +2 -2
- package/dist/node/index.d.ts +61 -2
- package/dist/node/index.js +437 -21
- package/dist/nuxt/runtime/nitro-plugin.js +1 -1
- package/dist/nuxt/runtime/plugin.js +2 -2
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +2 -2
- package/dist/testing/index.d.ts +12 -4
- package/dist/testing/index.js +43 -6
- package/dist/types-hWS-GSV1.d.ts +302 -0
- package/dist/vue/index.d.ts +1 -1
- package/dist/web-vitals/index.d.ts +28 -0
- package/dist/web-vitals/index.js +58 -0
- package/package.json +18 -10
- package/dist/chunk-K63I3OJT.js +0 -154
- package/dist/chunk-LRHXSFK2.js +0 -535
- package/dist/types-DxONWOH8.d.ts +0 -136
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createMonitorRuntime } from './chunk-ZUVQIOFO.js';
|
|
2
|
+
|
|
3
|
+
// src/browser-queue.ts
|
|
4
|
+
var DATABASE_NAME = "faultlens";
|
|
5
|
+
var STORE_NAME = "events";
|
|
6
|
+
var DATABASE_VERSION = 2;
|
|
7
|
+
var MAX_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
var MAX_ITEMS = 50;
|
|
9
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10
|
+
function normalizeItem(item) {
|
|
11
|
+
return {
|
|
12
|
+
...item,
|
|
13
|
+
protocolVersion: typeof item.protocolVersion === "string" && item.protocolVersion ? item.protocolVersion : "1.0",
|
|
14
|
+
attemptCount: typeof item.attemptCount === "number" && Number.isInteger(item.attemptCount) ? Math.max(0, item.attemptCount) : 0,
|
|
15
|
+
nextAttemptAt: typeof item.nextAttemptAt === "number" && Number.isFinite(item.nextAttemptAt) ? item.nextAttemptAt : item.enqueuedAt,
|
|
16
|
+
...typeof item.lastErrorCode === "string" ? { lastErrorCode: item.lastErrorCode } : {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
var IndexedDbEventQueue = class {
|
|
20
|
+
databasePromise;
|
|
21
|
+
static available() {
|
|
22
|
+
return typeof indexedDB !== "undefined";
|
|
23
|
+
}
|
|
24
|
+
database() {
|
|
25
|
+
this.databasePromise ??= new Promise((resolve, reject) => {
|
|
26
|
+
const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
|
|
27
|
+
request.onupgradeneeded = (event) => {
|
|
28
|
+
const database = request.result;
|
|
29
|
+
let store;
|
|
30
|
+
if (!database.objectStoreNames.contains(STORE_NAME)) {
|
|
31
|
+
store = database.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
32
|
+
store.createIndex("enqueuedAt", "enqueuedAt");
|
|
33
|
+
} else {
|
|
34
|
+
store = request.transaction.objectStore(STORE_NAME);
|
|
35
|
+
}
|
|
36
|
+
if (!store.indexNames.contains("nextAttemptAt")) {
|
|
37
|
+
store.createIndex("nextAttemptAt", "nextAttemptAt");
|
|
38
|
+
}
|
|
39
|
+
if (event.oldVersion < 2) {
|
|
40
|
+
const cursorRequest = store.openCursor();
|
|
41
|
+
cursorRequest.onsuccess = () => {
|
|
42
|
+
const cursor = cursorRequest.result;
|
|
43
|
+
if (!cursor) return;
|
|
44
|
+
cursor.update(normalizeItem(cursor.value));
|
|
45
|
+
cursor.continue();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
request.onsuccess = () => resolve(request.result);
|
|
50
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB \u6253\u5F00\u5931\u8D25"));
|
|
51
|
+
});
|
|
52
|
+
return this.databasePromise;
|
|
53
|
+
}
|
|
54
|
+
async all() {
|
|
55
|
+
const database = await this.database();
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const request = database.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).getAll();
|
|
58
|
+
request.onsuccess = () => resolve(
|
|
59
|
+
request.result.map(normalizeItem).sort((a, b) => a.enqueuedAt - b.enqueuedAt)
|
|
60
|
+
);
|
|
61
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB \u8BFB\u53D6\u5931\u8D25"));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async load(now) {
|
|
65
|
+
const items = await this.all();
|
|
66
|
+
const expired = items.filter((item) => now - item.enqueuedAt > MAX_AGE_MS).map((item) => item.id);
|
|
67
|
+
if (expired.length > 0) await this.remove(expired);
|
|
68
|
+
return items.filter((item) => now - item.enqueuedAt <= MAX_AGE_MS).slice(-MAX_ITEMS);
|
|
69
|
+
}
|
|
70
|
+
async put(item) {
|
|
71
|
+
const database = await this.database();
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
const request = database.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(item);
|
|
74
|
+
request.onsuccess = () => resolve();
|
|
75
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB \u5199\u5165\u5931\u8D25"));
|
|
76
|
+
});
|
|
77
|
+
const items = await this.all();
|
|
78
|
+
let bytes = items.reduce((total, current) => total + current.bytes, 0);
|
|
79
|
+
const removeIds = [];
|
|
80
|
+
for (const current of items) {
|
|
81
|
+
if (items.length - removeIds.length <= MAX_ITEMS && bytes <= MAX_BYTES) break;
|
|
82
|
+
removeIds.push(current.id);
|
|
83
|
+
bytes -= current.bytes;
|
|
84
|
+
}
|
|
85
|
+
if (removeIds.length > 0) await this.remove(removeIds);
|
|
86
|
+
}
|
|
87
|
+
async remove(ids) {
|
|
88
|
+
if (ids.length === 0) return;
|
|
89
|
+
const database = await this.database();
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
const transaction = database.transaction(STORE_NAME, "readwrite");
|
|
92
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
93
|
+
for (const id of ids) store.delete(id);
|
|
94
|
+
transaction.oncomplete = () => resolve();
|
|
95
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("IndexedDB \u5220\u9664\u5931\u8D25"));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async clear() {
|
|
99
|
+
const database = await this.database();
|
|
100
|
+
await new Promise((resolve, reject) => {
|
|
101
|
+
const request = database.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).clear();
|
|
102
|
+
request.onsuccess = () => resolve();
|
|
103
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB \u6E05\u7A7A\u5931\u8D25"));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var BrowserSendLease = class {
|
|
108
|
+
owner = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
109
|
+
key = "faultlens:send-lease";
|
|
110
|
+
static available() {
|
|
111
|
+
try {
|
|
112
|
+
return typeof localStorage !== "undefined";
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
acquire(now, durationMs = 1e4) {
|
|
118
|
+
try {
|
|
119
|
+
const current = JSON.parse(localStorage.getItem(this.key) ?? "null");
|
|
120
|
+
if (current && current.owner !== this.owner && current.expiresAt > now) return false;
|
|
121
|
+
localStorage.setItem(this.key, JSON.stringify({ owner: this.owner, expiresAt: now + durationMs }));
|
|
122
|
+
const confirmed = JSON.parse(localStorage.getItem(this.key) ?? "null");
|
|
123
|
+
return confirmed?.owner === this.owner;
|
|
124
|
+
} catch {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
release() {
|
|
129
|
+
try {
|
|
130
|
+
const current = JSON.parse(localStorage.getItem(this.key) ?? "null");
|
|
131
|
+
if (current?.owner === this.owner) localStorage.removeItem(this.key);
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/global-handlers.ts
|
|
138
|
+
function installGlobalHandlers(monitor, transportPath) {
|
|
139
|
+
if (typeof window === "undefined") return () => void 0;
|
|
140
|
+
const onError = (event) => {
|
|
141
|
+
if (event.filename?.includes(transportPath)) return;
|
|
142
|
+
if (event.error) {
|
|
143
|
+
monitor.captureException(event.error, { handled: false, mechanism: "window.error" });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const target = event.target;
|
|
147
|
+
if (target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement) {
|
|
148
|
+
const resource = target instanceof HTMLLinkElement ? target.href : target.src;
|
|
149
|
+
if (!resource.includes(transportPath)) {
|
|
150
|
+
monitor.captureMessage("\u524D\u7AEF\u8D44\u6E90\u52A0\u8F7D\u5931\u8D25", {
|
|
151
|
+
handled: false,
|
|
152
|
+
mechanism: "resource.error",
|
|
153
|
+
context: { tagName: target.tagName, resource }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const onUnhandledRejection = (event) => {
|
|
159
|
+
monitor.captureException(event.reason, {
|
|
160
|
+
handled: false,
|
|
161
|
+
mechanism: "unhandledrejection"
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
window.addEventListener("error", onError, true);
|
|
165
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
166
|
+
return () => {
|
|
167
|
+
window.removeEventListener("error", onError, true);
|
|
168
|
+
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/instrumentation.ts
|
|
173
|
+
function requestUrl(input) {
|
|
174
|
+
const raw = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
175
|
+
try {
|
|
176
|
+
const base = typeof location !== "undefined" ? location.href : void 0;
|
|
177
|
+
return new URL(raw, base);
|
|
178
|
+
} catch {
|
|
179
|
+
return void 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function requestMethod(input, init) {
|
|
183
|
+
const method = init?.method ?? (typeof Request !== "undefined" && input instanceof Request ? input.method : "GET");
|
|
184
|
+
return method.trim().toUpperCase().slice(0, 32) || "GET";
|
|
185
|
+
}
|
|
186
|
+
function defaultShouldPropagate(url) {
|
|
187
|
+
return typeof location !== "undefined" && url.origin === location.origin;
|
|
188
|
+
}
|
|
189
|
+
function installFetchInstrumentation(monitor, options = {}) {
|
|
190
|
+
const target = options.target ?? globalThis;
|
|
191
|
+
const originalFetch = target.fetch;
|
|
192
|
+
if (typeof originalFetch !== "function") return () => void 0;
|
|
193
|
+
const instrumentedFetch = async function instrumentedFetch2(input, init) {
|
|
194
|
+
if (options.shouldTrace && !options.shouldTrace(input, init)) {
|
|
195
|
+
return originalFetch.call(target, input, init);
|
|
196
|
+
}
|
|
197
|
+
const method = requestMethod(input, init);
|
|
198
|
+
const url = requestUrl(input);
|
|
199
|
+
const span = monitor.startSpan(`HTTP ${method}`, {
|
|
200
|
+
attributes: {
|
|
201
|
+
"http.method": method,
|
|
202
|
+
...url ? { "url.path": url.pathname.slice(0, 2048) || "/" } : {}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
const propagate = url && (options.shouldPropagateTraceContext ?? defaultShouldPropagate)(url);
|
|
206
|
+
const headers = new Headers(
|
|
207
|
+
init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0)
|
|
208
|
+
);
|
|
209
|
+
if (propagate) {
|
|
210
|
+
for (const [name, value] of Object.entries(span.toTraceHeaders())) headers.set(name, value);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const response = await originalFetch.call(target, input, { ...init, headers });
|
|
214
|
+
span.setAttribute("http.status_code", response.status);
|
|
215
|
+
span.end(response.status >= 400 ? "error" : "ok");
|
|
216
|
+
return response;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
span.end("error");
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
target.fetch = instrumentedFetch;
|
|
223
|
+
return () => {
|
|
224
|
+
if (target.fetch === instrumentedFetch) target.fetch = originalFetch;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function installXhrInstrumentation(monitor, options = {}) {
|
|
228
|
+
const defaultTarget = globalThis;
|
|
229
|
+
const target = options.target ?? (defaultTarget.XMLHttpRequest ? defaultTarget : void 0);
|
|
230
|
+
if (!target?.XMLHttpRequest) return () => void 0;
|
|
231
|
+
const prototype = target.XMLHttpRequest.prototype;
|
|
232
|
+
const originalOpen = prototype.open;
|
|
233
|
+
const originalSend = prototype.send;
|
|
234
|
+
const originalSetRequestHeader = prototype.setRequestHeader;
|
|
235
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
236
|
+
const instrumentedOpen = function instrumentedOpen2(...args) {
|
|
237
|
+
const method = typeof args[0] === "string" ? args[0].trim().toUpperCase().slice(0, 32) || "GET" : "GET";
|
|
238
|
+
const rawUrl = typeof args[1] === "string" || args[1] instanceof URL ? args[1] : "";
|
|
239
|
+
const url = requestUrl(rawUrl);
|
|
240
|
+
states.set(this, { method, ...url ? { url } : {} });
|
|
241
|
+
return originalOpen.apply(this, args);
|
|
242
|
+
};
|
|
243
|
+
const instrumentedSend = function instrumentedSend2(...args) {
|
|
244
|
+
const state = states.get(this) ?? { method: "GET" };
|
|
245
|
+
if (options.shouldTrace && !options.shouldTrace(state.method, state.url)) {
|
|
246
|
+
return originalSend.apply(this, args);
|
|
247
|
+
}
|
|
248
|
+
const span = monitor.startSpan(`HTTP ${state.method}`, {
|
|
249
|
+
attributes: {
|
|
250
|
+
"http.method": state.method,
|
|
251
|
+
...state.url ? { "url.path": state.url.pathname.slice(0, 2048) || "/" } : {}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
if (state.url && (options.shouldPropagateTraceContext ?? defaultShouldPropagate)(state.url)) {
|
|
255
|
+
for (const [name, value] of Object.entries(span.toTraceHeaders())) {
|
|
256
|
+
originalSetRequestHeader.call(this, name, value);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
let finished = false;
|
|
260
|
+
const finish = () => {
|
|
261
|
+
if (finished) return;
|
|
262
|
+
finished = true;
|
|
263
|
+
if (this.status > 0) span.setAttribute("http.status_code", this.status);
|
|
264
|
+
span.end(this.status === 0 || this.status >= 400 ? "error" : "ok");
|
|
265
|
+
};
|
|
266
|
+
this.addEventListener("loadend", finish, { once: true });
|
|
267
|
+
this.addEventListener("error", finish, { once: true });
|
|
268
|
+
this.addEventListener("abort", finish, { once: true });
|
|
269
|
+
try {
|
|
270
|
+
return originalSend.apply(this, args);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
finish();
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
prototype.open = instrumentedOpen;
|
|
277
|
+
prototype.send = instrumentedSend;
|
|
278
|
+
return () => {
|
|
279
|
+
if (prototype.open === instrumentedOpen) prototype.open = originalOpen;
|
|
280
|
+
if (prototype.send === instrumentedSend) prototype.send = originalSend;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/index.ts
|
|
285
|
+
function createErrorMonitor(options) {
|
|
286
|
+
return createMonitorRuntime(options, {
|
|
287
|
+
...IndexedDbEventQueue.available() ? { persistentQueue: new IndexedDbEventQueue() } : {},
|
|
288
|
+
...BrowserSendLease.available() ? { sendLease: new BrowserSendLease() } : {},
|
|
289
|
+
installGlobalHandlers
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export { createErrorMonitor, installFetchInstrumentation, installXhrInstrumentation };
|