@kenkaiiii/error-mom 0.3.0 → 0.3.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/dist/{chunk-LFQHPPHR.js → chunk-GHOIVBTK.js} +1 -1
- package/dist/index.cjs +375 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.js +1 -1
- package/dist/node.cjs +406 -0
- package/dist/node.d.cts +22 -0
- package/dist/node.js +17 -1
- package/dist/shared-idwux7-h.d.cts +19 -0
- package/package.json +8 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/shared.ts
|
|
2
2
|
var SDK_NAME = "@kenkaiiii/error-mom";
|
|
3
|
-
var SDK_VERSION = "0.3.
|
|
3
|
+
var SDK_VERSION = "0.3.2";
|
|
4
4
|
var MAX_BREADCRUMBS = 50;
|
|
5
5
|
var SECRET_KEY = /authorization|cookie|password|passwd|secret|token|api[-_]?key|session/i;
|
|
6
6
|
var URL_CREDENTIAL = /([?&](?:token|key|secret|password|code)=)[^&\s]*/gi;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
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/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
initErrorMom: () => initErrorMom
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/shared.ts
|
|
28
|
+
var SDK_NAME = "@kenkaiiii/error-mom";
|
|
29
|
+
var SDK_VERSION = "0.3.2";
|
|
30
|
+
var MAX_BREADCRUMBS = 50;
|
|
31
|
+
var SECRET_KEY = /authorization|cookie|password|passwd|secret|token|api[-_]?key|session/i;
|
|
32
|
+
var URL_CREDENTIAL = /([?&](?:token|key|secret|password|code)=)[^&\s]*/gi;
|
|
33
|
+
var EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
34
|
+
function wrapFunction(capture, fn, context) {
|
|
35
|
+
return (...args) => {
|
|
36
|
+
try {
|
|
37
|
+
const result = fn(...args);
|
|
38
|
+
if (result instanceof Promise) {
|
|
39
|
+
return result.catch((error) => {
|
|
40
|
+
capture(error, context);
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
capture(error, context);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function redactString(value) {
|
|
52
|
+
return value.replace(URL_CREDENTIAL, "$1[REDACTED]").replace(EMAIL, "[REDACTED_EMAIL]");
|
|
53
|
+
}
|
|
54
|
+
function sanitize(value, depth = 0) {
|
|
55
|
+
if (depth > 5) return "[TRUNCATED]";
|
|
56
|
+
if (typeof value === "string") return redactString(value).slice(0, 1e4);
|
|
57
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
58
|
+
if (value instanceof Error) {
|
|
59
|
+
return {
|
|
60
|
+
name: value.name,
|
|
61
|
+
message: redactString(value.message),
|
|
62
|
+
stack: value.stack ? redactString(value.stack) : void 0
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(value)) return value.slice(0, 50).map((item) => sanitize(item, depth + 1));
|
|
66
|
+
if (typeof value === "object" && value) {
|
|
67
|
+
return Object.fromEntries(
|
|
68
|
+
Object.entries(value).slice(0, 100).map(([key, item]) => [
|
|
69
|
+
key,
|
|
70
|
+
SECRET_KEY.test(key) ? "[REDACTED]" : sanitize(item, depth + 1)
|
|
71
|
+
])
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return String(value).slice(0, 2e3);
|
|
75
|
+
}
|
|
76
|
+
function printable(value) {
|
|
77
|
+
if (typeof value === "string") return redactString(value);
|
|
78
|
+
try {
|
|
79
|
+
return JSON.stringify(sanitize(value));
|
|
80
|
+
} catch {
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function toError(value) {
|
|
85
|
+
if (value instanceof Error) return value;
|
|
86
|
+
if (typeof value === "string") return new Error(value);
|
|
87
|
+
return new Error(printable(value));
|
|
88
|
+
}
|
|
89
|
+
function platformName() {
|
|
90
|
+
if (typeof navigator !== "undefined") return navigator.platform || "browser";
|
|
91
|
+
return typeof process !== "undefined" ? process.platform : "unknown";
|
|
92
|
+
}
|
|
93
|
+
function createEvent(value, options, breadcrumbs, runtime, captureContext = {}) {
|
|
94
|
+
const error = toError(value);
|
|
95
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
96
|
+
return {
|
|
97
|
+
eventId: crypto.randomUUID(),
|
|
98
|
+
timestamp: now,
|
|
99
|
+
level: captureContext.level ?? "error",
|
|
100
|
+
error: {
|
|
101
|
+
name: redactString(error.name || "Error").slice(0, 500),
|
|
102
|
+
message: redactString(error.message || "Unknown error").slice(0, 1e4),
|
|
103
|
+
...error.stack ? { stack: redactString(error.stack).slice(0, 1e5) } : {}
|
|
104
|
+
},
|
|
105
|
+
environment: options.environment ?? "production",
|
|
106
|
+
...options.release ? { release: options.release } : {},
|
|
107
|
+
platform: platformName(),
|
|
108
|
+
runtime,
|
|
109
|
+
...typeof location !== "undefined" ? { url: redactString(location.href) } : {},
|
|
110
|
+
...captureContext.culprit ? { culprit: redactString(captureContext.culprit) } : {},
|
|
111
|
+
...options.installationId ? { installationId: options.installationId } : {},
|
|
112
|
+
breadcrumbs: breadcrumbs.slice(-MAX_BREADCRUMBS),
|
|
113
|
+
tags: { ...options.tags, ...captureContext.tags },
|
|
114
|
+
context: sanitize(captureContext.context ?? {}) ?? {}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function endpoint(server) {
|
|
118
|
+
return `${server.replace(/\/$/, "")}/api/v1/events`;
|
|
119
|
+
}
|
|
120
|
+
var AI_PROVIDERS = [
|
|
121
|
+
[/(^|\.)api\.anthropic\.com$/, "anthropic"],
|
|
122
|
+
[/(^|\.)api\.openai\.com$/, "openai"],
|
|
123
|
+
[/(^|\.)openai\.azure\.com$/, "azure-openai"],
|
|
124
|
+
[/(^|\.)generativelanguage\.googleapis\.com$/, "google"],
|
|
125
|
+
[/(^|\.)aiplatform\.googleapis\.com$/, "google-vertex"],
|
|
126
|
+
[/(^|\.)bedrock(-runtime)?[.\w-]*\.amazonaws\.com$/, "aws-bedrock"],
|
|
127
|
+
[/(^|\.)api\.mistral\.ai$/, "mistral"],
|
|
128
|
+
[/(^|\.)api\.groq\.com$/, "groq"],
|
|
129
|
+
[/(^|\.)api\.deepseek\.com$/, "deepseek"],
|
|
130
|
+
[/(^|\.)api\.x\.ai$/, "xai"],
|
|
131
|
+
[/(^|\.)openrouter\.ai$/, "openrouter"],
|
|
132
|
+
[/(^|\.)api\.together\.(xyz|ai)$/, "together"],
|
|
133
|
+
[/(^|\.)api\.fireworks\.ai$/, "fireworks"],
|
|
134
|
+
[/(^|\.)api\.cohere\.(ai|com)$/, "cohere"],
|
|
135
|
+
[/(^|\.)api\.perplexity\.ai$/, "perplexity"],
|
|
136
|
+
[/(^|\.)api\.replicate\.com$/, "replicate"],
|
|
137
|
+
[/(^|\.)api-inference\.huggingface\.co$/, "huggingface"],
|
|
138
|
+
[/(^|\.)router\.huggingface\.co$/, "huggingface"],
|
|
139
|
+
[/(^|\.)api\.elevenlabs\.io$/, "elevenlabs"],
|
|
140
|
+
[/(^|\.)api\.moonshot\.(ai|cn)$/, "moonshot"],
|
|
141
|
+
[/(^|\.)api\.kimi\.com$/, "moonshot"],
|
|
142
|
+
[/(^|\.)api\.z\.ai$/, "zai"],
|
|
143
|
+
[/(^|\.)open\.bigmodel\.cn$/, "zai"],
|
|
144
|
+
[/(^|\.)dashscope(-intl)?\.aliyuncs\.com$/, "qwen"],
|
|
145
|
+
[/(^|\.)api\.minimax(i)?\.(chat|com|io)$/, "minimax"],
|
|
146
|
+
[/(^|\.)api\.lingyiwanwu\.com$/, "yi"],
|
|
147
|
+
[/(^|\.)api\.stepfun\.com$/, "stepfun"],
|
|
148
|
+
[/(^|\.)api\.baichuan-ai\.com$/, "baichuan"],
|
|
149
|
+
[/(^|\.)api\.siliconflow\.(cn|com)$/, "siliconflow"],
|
|
150
|
+
[/(^|\.)api\.cerebras\.ai$/, "cerebras"],
|
|
151
|
+
[/(^|\.)api\.sambanova\.ai$/, "sambanova"],
|
|
152
|
+
[/(^|\.)api\.deepinfra\.com$/, "deepinfra"],
|
|
153
|
+
[/(^|\.)api\.novita\.ai$/, "novita"],
|
|
154
|
+
[/(^|\.)api\.hyperbolic\.xyz$/, "hyperbolic"],
|
|
155
|
+
[/(^|\.)api\.studio\.nebius\.(ai|com)$/, "nebius"],
|
|
156
|
+
[/(^|\.)models\.inference\.ai\.azure\.com$/, "github-models"],
|
|
157
|
+
[/(^|\.)models\.github\.ai$/, "github-models"],
|
|
158
|
+
[/(^|\.)ai-gateway\.vercel\.sh$/, "vercel-ai-gateway"],
|
|
159
|
+
[/(^|\.)gateway\.ai\.cloudflare\.com$/, "cloudflare-ai-gateway"],
|
|
160
|
+
[/(^|\.)api\.voyageai\.com$/, "voyage"],
|
|
161
|
+
[/(^|\.)api\.jina\.ai$/, "jina"],
|
|
162
|
+
[/(^|\.)api\.stability\.ai$/, "stability"],
|
|
163
|
+
[/(^|\.)(queue\.)?fal\.run$/, "fal"],
|
|
164
|
+
[/(^|\.)api\.assemblyai\.com$/, "assemblyai"],
|
|
165
|
+
[/(^|\.)api\.deepgram\.com$/, "deepgram"],
|
|
166
|
+
[/(^|\.)api\.lumalabs\.ai$/, "luma"],
|
|
167
|
+
[/(^|\.)api\.dev\.runwayml\.com$/, "runway"]
|
|
168
|
+
];
|
|
169
|
+
function providerForUrl(url) {
|
|
170
|
+
try {
|
|
171
|
+
const hostname = new URL(url).hostname;
|
|
172
|
+
for (const [pattern, provider] of AI_PROVIDERS) {
|
|
173
|
+
if (pattern.test(hostname)) return provider;
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
function describeFailedRequest(method, url, status) {
|
|
180
|
+
const provider = providerForUrl(url);
|
|
181
|
+
const relevant = status >= 500 || provider !== void 0 && status >= 400;
|
|
182
|
+
if (!relevant) return void 0;
|
|
183
|
+
const cleanUrl = redactString(url);
|
|
184
|
+
const error = new Error(`${method} ${cleanUrl} returned ${status}`);
|
|
185
|
+
if (provider) {
|
|
186
|
+
const label = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
187
|
+
error.name = `${label.replace(/-(\w)/g, (_, c) => c.toUpperCase())}ApiError`;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
error,
|
|
191
|
+
context: {
|
|
192
|
+
culprit: "fetch",
|
|
193
|
+
tags: {
|
|
194
|
+
statusCode: String(status),
|
|
195
|
+
method,
|
|
196
|
+
...provider ? { provider } : {}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/index.ts
|
|
203
|
+
var clients = /* @__PURE__ */ new Map();
|
|
204
|
+
var BrowserClient = class {
|
|
205
|
+
options;
|
|
206
|
+
breadcrumbs = [];
|
|
207
|
+
queue = [];
|
|
208
|
+
flushPromise;
|
|
209
|
+
restore = [];
|
|
210
|
+
interval;
|
|
211
|
+
storageKey;
|
|
212
|
+
nativeFetch;
|
|
213
|
+
constructor(options) {
|
|
214
|
+
this.options = {
|
|
215
|
+
...options,
|
|
216
|
+
captureConsoleErrors: options.captureConsoleErrors ?? true,
|
|
217
|
+
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
218
|
+
flushIntervalMs: options.flushIntervalMs ?? 5e3,
|
|
219
|
+
maxQueueSize: options.maxQueueSize ?? 100
|
|
220
|
+
};
|
|
221
|
+
this.storageKey = `error-mom:${options.projectKey.slice(-12)}`;
|
|
222
|
+
this.restoreQueue();
|
|
223
|
+
this.installGlobalHandlers();
|
|
224
|
+
this.interval = setInterval(() => void this.flush(), this.options.flushIntervalMs);
|
|
225
|
+
void this.flush();
|
|
226
|
+
}
|
|
227
|
+
captureError(error, context = {}) {
|
|
228
|
+
const event = createEvent(error, this.options, this.breadcrumbs, "browser", context);
|
|
229
|
+
this.queue.push(event);
|
|
230
|
+
this.queue = this.queue.slice(-this.options.maxQueueSize);
|
|
231
|
+
this.persistQueue();
|
|
232
|
+
void this.flush();
|
|
233
|
+
return event.eventId;
|
|
234
|
+
}
|
|
235
|
+
wrap(fn, context) {
|
|
236
|
+
return wrapFunction((error, ctx) => this.captureError(error, ctx), fn, context);
|
|
237
|
+
}
|
|
238
|
+
addBreadcrumb(input) {
|
|
239
|
+
this.breadcrumbs.push({
|
|
240
|
+
...input,
|
|
241
|
+
timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
242
|
+
message: redactString(input.message).slice(0, 2e3)
|
|
243
|
+
});
|
|
244
|
+
if (this.breadcrumbs.length > MAX_BREADCRUMBS) this.breadcrumbs.shift();
|
|
245
|
+
}
|
|
246
|
+
flush() {
|
|
247
|
+
if (this.flushPromise) return this.flushPromise;
|
|
248
|
+
if (this.queue.length === 0) return Promise.resolve();
|
|
249
|
+
this.flushPromise = this.sendBatch().finally(() => {
|
|
250
|
+
this.flushPromise = void 0;
|
|
251
|
+
});
|
|
252
|
+
return this.flushPromise;
|
|
253
|
+
}
|
|
254
|
+
async sendBatch() {
|
|
255
|
+
const batch = this.queue.slice(0, 20);
|
|
256
|
+
try {
|
|
257
|
+
const transport = this.nativeFetch ?? fetch;
|
|
258
|
+
const response = await transport(endpoint(this.options.server), {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: {
|
|
261
|
+
"content-type": "application/json",
|
|
262
|
+
"x-error-mom-key": this.options.projectKey
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify({ events: batch, sdk: { name: SDK_NAME, version: SDK_VERSION } }),
|
|
265
|
+
keepalive: true
|
|
266
|
+
});
|
|
267
|
+
if (!response.ok) return;
|
|
268
|
+
const accepted = new Set(batch.map((event) => event.eventId));
|
|
269
|
+
this.queue = this.queue.filter((event) => !accepted.has(event.eventId));
|
|
270
|
+
this.persistQueue();
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
dispose() {
|
|
275
|
+
if (this.interval) clearInterval(this.interval);
|
|
276
|
+
for (const callback of this.restore.reverse()) callback();
|
|
277
|
+
clients.delete(this.options.projectKey);
|
|
278
|
+
}
|
|
279
|
+
installGlobalHandlers() {
|
|
280
|
+
if (typeof window === "undefined") return;
|
|
281
|
+
const onError = (event) => {
|
|
282
|
+
this.captureError(event.error ?? new Error(event.message), {
|
|
283
|
+
level: "fatal",
|
|
284
|
+
...event.filename ? { culprit: `${event.filename}:${event.lineno}:${event.colno}` } : {}
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
const onRejection = (event) => {
|
|
288
|
+
this.captureError(event.reason, { culprit: "unhandledrejection" });
|
|
289
|
+
};
|
|
290
|
+
const onVisibility = () => {
|
|
291
|
+
if (document.visibilityState === "hidden") void this.flush();
|
|
292
|
+
};
|
|
293
|
+
window.addEventListener("error", onError);
|
|
294
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
295
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
296
|
+
this.restore.push(() => window.removeEventListener("error", onError));
|
|
297
|
+
this.restore.push(() => window.removeEventListener("unhandledrejection", onRejection));
|
|
298
|
+
this.restore.push(() => document.removeEventListener("visibilitychange", onVisibility));
|
|
299
|
+
this.captureConsole();
|
|
300
|
+
this.captureFetchFailures();
|
|
301
|
+
}
|
|
302
|
+
captureConsole() {
|
|
303
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
304
|
+
for (const level of levels) {
|
|
305
|
+
const original = console[level].bind(console);
|
|
306
|
+
console[level] = (...args) => {
|
|
307
|
+
original(...args);
|
|
308
|
+
const message = args.map(printable).join(" ").slice(0, 2e3);
|
|
309
|
+
this.addBreadcrumb({
|
|
310
|
+
category: "console",
|
|
311
|
+
level: level === "warn" ? "warning" : level,
|
|
312
|
+
message
|
|
313
|
+
});
|
|
314
|
+
if (level === "error" && this.options.captureConsoleErrors) {
|
|
315
|
+
this.captureError(args[0] instanceof Error ? args[0] : new Error(message), {
|
|
316
|
+
culprit: "console.error"
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
this.restore.push(() => {
|
|
321
|
+
console[level] = original;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
captureFetchFailures() {
|
|
326
|
+
if (!this.options.captureFailedRequests || typeof fetch === "undefined") return;
|
|
327
|
+
const original = fetch.bind(globalThis);
|
|
328
|
+
this.nativeFetch = original;
|
|
329
|
+
globalThis.fetch = async (input, init) => {
|
|
330
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
331
|
+
if (url.startsWith(this.options.server)) return original(input, init);
|
|
332
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
333
|
+
try {
|
|
334
|
+
const response = await original(input, init);
|
|
335
|
+
const failure = describeFailedRequest(method, url, response.status);
|
|
336
|
+
if (failure) this.captureError(failure.error, failure.context);
|
|
337
|
+
return response;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
this.captureError(error, { culprit: "fetch", tags: { method, url: redactString(url) } });
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
this.restore.push(() => {
|
|
344
|
+
globalThis.fetch = original;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
restoreQueue() {
|
|
348
|
+
try {
|
|
349
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
350
|
+
if (stored) this.queue = JSON.parse(stored);
|
|
351
|
+
} catch {
|
|
352
|
+
this.queue = [];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
persistQueue() {
|
|
356
|
+
try {
|
|
357
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.queue));
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
function initErrorMom(options) {
|
|
363
|
+
if (!options.server || !options.projectKey) {
|
|
364
|
+
throw new Error("Error Mom requires both server and projectKey");
|
|
365
|
+
}
|
|
366
|
+
const existing = clients.get(options.projectKey);
|
|
367
|
+
if (existing) return existing;
|
|
368
|
+
const client = new BrowserClient(options);
|
|
369
|
+
clients.set(options.projectKey, client);
|
|
370
|
+
return client;
|
|
371
|
+
}
|
|
372
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
373
|
+
0 && (module.exports = {
|
|
374
|
+
initErrorMom
|
|
375
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Breadcrumb } from '@kenkaiiii/error-mom-protocol';
|
|
2
|
+
export { Breadcrumb, ErrorEvent } from '@kenkaiiii/error-mom-protocol';
|
|
3
|
+
import { C as CommonOptions, a as CaptureContext } from './shared-idwux7-h.cjs';
|
|
4
|
+
|
|
5
|
+
interface BrowserOptions extends CommonOptions {
|
|
6
|
+
captureFailedRequests?: boolean;
|
|
7
|
+
flushIntervalMs?: number;
|
|
8
|
+
maxQueueSize?: number;
|
|
9
|
+
}
|
|
10
|
+
interface ErrorMomBrowser {
|
|
11
|
+
captureError(error: unknown, context?: CaptureContext): string;
|
|
12
|
+
wrap<A extends unknown[], R>(fn: (...args: A) => R, context?: CaptureContext): (...args: A) => R;
|
|
13
|
+
addBreadcrumb(breadcrumb: Omit<Breadcrumb, "timestamp"> & {
|
|
14
|
+
timestamp?: string;
|
|
15
|
+
}): void;
|
|
16
|
+
flush(): Promise<void>;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
}
|
|
19
|
+
declare function initErrorMom(options: BrowserOptions): ErrorMomBrowser;
|
|
20
|
+
|
|
21
|
+
export { type BrowserOptions, CaptureContext, type ErrorMomBrowser, initErrorMom };
|
package/dist/index.js
CHANGED
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
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/node.ts
|
|
21
|
+
var node_exports = {};
|
|
22
|
+
__export(node_exports, {
|
|
23
|
+
initErrorMom: () => initErrorMom
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(node_exports);
|
|
26
|
+
var import_node_crypto = require("crypto");
|
|
27
|
+
var import_node_fs = require("fs");
|
|
28
|
+
var import_promises = require("fs/promises");
|
|
29
|
+
var import_node_os = require("os");
|
|
30
|
+
var import_node_path = require("path");
|
|
31
|
+
|
|
32
|
+
// src/shared.ts
|
|
33
|
+
var SDK_NAME = "@kenkaiiii/error-mom";
|
|
34
|
+
var SDK_VERSION = "0.3.2";
|
|
35
|
+
var MAX_BREADCRUMBS = 50;
|
|
36
|
+
var SECRET_KEY = /authorization|cookie|password|passwd|secret|token|api[-_]?key|session/i;
|
|
37
|
+
var URL_CREDENTIAL = /([?&](?:token|key|secret|password|code)=)[^&\s]*/gi;
|
|
38
|
+
var EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
39
|
+
function wrapFunction(capture, fn, context) {
|
|
40
|
+
return (...args) => {
|
|
41
|
+
try {
|
|
42
|
+
const result = fn(...args);
|
|
43
|
+
if (result instanceof Promise) {
|
|
44
|
+
return result.catch((error) => {
|
|
45
|
+
capture(error, context);
|
|
46
|
+
throw error;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
capture(error, context);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function redactString(value) {
|
|
57
|
+
return value.replace(URL_CREDENTIAL, "$1[REDACTED]").replace(EMAIL, "[REDACTED_EMAIL]");
|
|
58
|
+
}
|
|
59
|
+
function sanitize(value, depth = 0) {
|
|
60
|
+
if (depth > 5) return "[TRUNCATED]";
|
|
61
|
+
if (typeof value === "string") return redactString(value).slice(0, 1e4);
|
|
62
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
63
|
+
if (value instanceof Error) {
|
|
64
|
+
return {
|
|
65
|
+
name: value.name,
|
|
66
|
+
message: redactString(value.message),
|
|
67
|
+
stack: value.stack ? redactString(value.stack) : void 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) return value.slice(0, 50).map((item) => sanitize(item, depth + 1));
|
|
71
|
+
if (typeof value === "object" && value) {
|
|
72
|
+
return Object.fromEntries(
|
|
73
|
+
Object.entries(value).slice(0, 100).map(([key, item]) => [
|
|
74
|
+
key,
|
|
75
|
+
SECRET_KEY.test(key) ? "[REDACTED]" : sanitize(item, depth + 1)
|
|
76
|
+
])
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return String(value).slice(0, 2e3);
|
|
80
|
+
}
|
|
81
|
+
function printable(value) {
|
|
82
|
+
if (typeof value === "string") return redactString(value);
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(sanitize(value));
|
|
85
|
+
} catch {
|
|
86
|
+
return String(value);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function toError(value) {
|
|
90
|
+
if (value instanceof Error) return value;
|
|
91
|
+
if (typeof value === "string") return new Error(value);
|
|
92
|
+
return new Error(printable(value));
|
|
93
|
+
}
|
|
94
|
+
function platformName() {
|
|
95
|
+
if (typeof navigator !== "undefined") return navigator.platform || "browser";
|
|
96
|
+
return typeof process !== "undefined" ? process.platform : "unknown";
|
|
97
|
+
}
|
|
98
|
+
function createEvent(value, options, breadcrumbs, runtime, captureContext = {}) {
|
|
99
|
+
const error = toError(value);
|
|
100
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
101
|
+
return {
|
|
102
|
+
eventId: crypto.randomUUID(),
|
|
103
|
+
timestamp: now,
|
|
104
|
+
level: captureContext.level ?? "error",
|
|
105
|
+
error: {
|
|
106
|
+
name: redactString(error.name || "Error").slice(0, 500),
|
|
107
|
+
message: redactString(error.message || "Unknown error").slice(0, 1e4),
|
|
108
|
+
...error.stack ? { stack: redactString(error.stack).slice(0, 1e5) } : {}
|
|
109
|
+
},
|
|
110
|
+
environment: options.environment ?? "production",
|
|
111
|
+
...options.release ? { release: options.release } : {},
|
|
112
|
+
platform: platformName(),
|
|
113
|
+
runtime,
|
|
114
|
+
...typeof location !== "undefined" ? { url: redactString(location.href) } : {},
|
|
115
|
+
...captureContext.culprit ? { culprit: redactString(captureContext.culprit) } : {},
|
|
116
|
+
...options.installationId ? { installationId: options.installationId } : {},
|
|
117
|
+
breadcrumbs: breadcrumbs.slice(-MAX_BREADCRUMBS),
|
|
118
|
+
tags: { ...options.tags, ...captureContext.tags },
|
|
119
|
+
context: sanitize(captureContext.context ?? {}) ?? {}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function endpoint(server) {
|
|
123
|
+
return `${server.replace(/\/$/, "")}/api/v1/events`;
|
|
124
|
+
}
|
|
125
|
+
var AI_PROVIDERS = [
|
|
126
|
+
[/(^|\.)api\.anthropic\.com$/, "anthropic"],
|
|
127
|
+
[/(^|\.)api\.openai\.com$/, "openai"],
|
|
128
|
+
[/(^|\.)openai\.azure\.com$/, "azure-openai"],
|
|
129
|
+
[/(^|\.)generativelanguage\.googleapis\.com$/, "google"],
|
|
130
|
+
[/(^|\.)aiplatform\.googleapis\.com$/, "google-vertex"],
|
|
131
|
+
[/(^|\.)bedrock(-runtime)?[.\w-]*\.amazonaws\.com$/, "aws-bedrock"],
|
|
132
|
+
[/(^|\.)api\.mistral\.ai$/, "mistral"],
|
|
133
|
+
[/(^|\.)api\.groq\.com$/, "groq"],
|
|
134
|
+
[/(^|\.)api\.deepseek\.com$/, "deepseek"],
|
|
135
|
+
[/(^|\.)api\.x\.ai$/, "xai"],
|
|
136
|
+
[/(^|\.)openrouter\.ai$/, "openrouter"],
|
|
137
|
+
[/(^|\.)api\.together\.(xyz|ai)$/, "together"],
|
|
138
|
+
[/(^|\.)api\.fireworks\.ai$/, "fireworks"],
|
|
139
|
+
[/(^|\.)api\.cohere\.(ai|com)$/, "cohere"],
|
|
140
|
+
[/(^|\.)api\.perplexity\.ai$/, "perplexity"],
|
|
141
|
+
[/(^|\.)api\.replicate\.com$/, "replicate"],
|
|
142
|
+
[/(^|\.)api-inference\.huggingface\.co$/, "huggingface"],
|
|
143
|
+
[/(^|\.)router\.huggingface\.co$/, "huggingface"],
|
|
144
|
+
[/(^|\.)api\.elevenlabs\.io$/, "elevenlabs"],
|
|
145
|
+
[/(^|\.)api\.moonshot\.(ai|cn)$/, "moonshot"],
|
|
146
|
+
[/(^|\.)api\.kimi\.com$/, "moonshot"],
|
|
147
|
+
[/(^|\.)api\.z\.ai$/, "zai"],
|
|
148
|
+
[/(^|\.)open\.bigmodel\.cn$/, "zai"],
|
|
149
|
+
[/(^|\.)dashscope(-intl)?\.aliyuncs\.com$/, "qwen"],
|
|
150
|
+
[/(^|\.)api\.minimax(i)?\.(chat|com|io)$/, "minimax"],
|
|
151
|
+
[/(^|\.)api\.lingyiwanwu\.com$/, "yi"],
|
|
152
|
+
[/(^|\.)api\.stepfun\.com$/, "stepfun"],
|
|
153
|
+
[/(^|\.)api\.baichuan-ai\.com$/, "baichuan"],
|
|
154
|
+
[/(^|\.)api\.siliconflow\.(cn|com)$/, "siliconflow"],
|
|
155
|
+
[/(^|\.)api\.cerebras\.ai$/, "cerebras"],
|
|
156
|
+
[/(^|\.)api\.sambanova\.ai$/, "sambanova"],
|
|
157
|
+
[/(^|\.)api\.deepinfra\.com$/, "deepinfra"],
|
|
158
|
+
[/(^|\.)api\.novita\.ai$/, "novita"],
|
|
159
|
+
[/(^|\.)api\.hyperbolic\.xyz$/, "hyperbolic"],
|
|
160
|
+
[/(^|\.)api\.studio\.nebius\.(ai|com)$/, "nebius"],
|
|
161
|
+
[/(^|\.)models\.inference\.ai\.azure\.com$/, "github-models"],
|
|
162
|
+
[/(^|\.)models\.github\.ai$/, "github-models"],
|
|
163
|
+
[/(^|\.)ai-gateway\.vercel\.sh$/, "vercel-ai-gateway"],
|
|
164
|
+
[/(^|\.)gateway\.ai\.cloudflare\.com$/, "cloudflare-ai-gateway"],
|
|
165
|
+
[/(^|\.)api\.voyageai\.com$/, "voyage"],
|
|
166
|
+
[/(^|\.)api\.jina\.ai$/, "jina"],
|
|
167
|
+
[/(^|\.)api\.stability\.ai$/, "stability"],
|
|
168
|
+
[/(^|\.)(queue\.)?fal\.run$/, "fal"],
|
|
169
|
+
[/(^|\.)api\.assemblyai\.com$/, "assemblyai"],
|
|
170
|
+
[/(^|\.)api\.deepgram\.com$/, "deepgram"],
|
|
171
|
+
[/(^|\.)api\.lumalabs\.ai$/, "luma"],
|
|
172
|
+
[/(^|\.)api\.dev\.runwayml\.com$/, "runway"]
|
|
173
|
+
];
|
|
174
|
+
function providerForUrl(url) {
|
|
175
|
+
try {
|
|
176
|
+
const hostname = new URL(url).hostname;
|
|
177
|
+
for (const [pattern, provider] of AI_PROVIDERS) {
|
|
178
|
+
if (pattern.test(hostname)) return provider;
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
function describeFailedRequest(method, url, status) {
|
|
185
|
+
const provider = providerForUrl(url);
|
|
186
|
+
const relevant = status >= 500 || provider !== void 0 && status >= 400;
|
|
187
|
+
if (!relevant) return void 0;
|
|
188
|
+
const cleanUrl = redactString(url);
|
|
189
|
+
const error = new Error(`${method} ${cleanUrl} returned ${status}`);
|
|
190
|
+
if (provider) {
|
|
191
|
+
const label = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
192
|
+
error.name = `${label.replace(/-(\w)/g, (_, c) => c.toUpperCase())}ApiError`;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
error,
|
|
196
|
+
context: {
|
|
197
|
+
culprit: "fetch",
|
|
198
|
+
tags: {
|
|
199
|
+
statusCode: String(status),
|
|
200
|
+
method,
|
|
201
|
+
...provider ? { provider } : {}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/node.ts
|
|
208
|
+
var NodeClient = class {
|
|
209
|
+
options;
|
|
210
|
+
breadcrumbs = [];
|
|
211
|
+
queue = [];
|
|
212
|
+
operation = Promise.resolve();
|
|
213
|
+
interval;
|
|
214
|
+
spoolFile;
|
|
215
|
+
handlers = [];
|
|
216
|
+
flushPromise;
|
|
217
|
+
nativeFetch;
|
|
218
|
+
constructor(options) {
|
|
219
|
+
this.options = {
|
|
220
|
+
...options,
|
|
221
|
+
captureConsoleErrors: options.captureConsoleErrors ?? true,
|
|
222
|
+
flushIntervalMs: options.flushIntervalMs ?? 5e3,
|
|
223
|
+
maxQueueSize: options.maxQueueSize ?? 1e3,
|
|
224
|
+
spoolDirectory: options.spoolDirectory ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".error-mom", "spool")
|
|
225
|
+
};
|
|
226
|
+
this.spoolFile = (0, import_node_path.join)(this.options.spoolDirectory, `${safeFilePart(options.projectKey)}.jsonl`);
|
|
227
|
+
this.operation = this.restoreQueue();
|
|
228
|
+
this.installProcessHandlers();
|
|
229
|
+
this.captureConsole();
|
|
230
|
+
this.captureFetchFailures();
|
|
231
|
+
this.interval = setInterval(() => void this.flush(), this.options.flushIntervalMs);
|
|
232
|
+
void this.flush();
|
|
233
|
+
}
|
|
234
|
+
captureError(error, context = {}) {
|
|
235
|
+
const event = createEvent(
|
|
236
|
+
error,
|
|
237
|
+
this.options,
|
|
238
|
+
this.breadcrumbs,
|
|
239
|
+
`node ${process.version}`,
|
|
240
|
+
context
|
|
241
|
+
);
|
|
242
|
+
this.queue.push(event);
|
|
243
|
+
this.queue = this.queue.slice(-this.options.maxQueueSize);
|
|
244
|
+
this.operation = this.operation.then(async () => {
|
|
245
|
+
await (0, import_promises.mkdir)(this.options.spoolDirectory, { recursive: true });
|
|
246
|
+
await (0, import_promises.appendFile)(this.spoolFile, `${JSON.stringify(event)}
|
|
247
|
+
`, { mode: 384 });
|
|
248
|
+
}).catch(() => void 0);
|
|
249
|
+
void this.flush();
|
|
250
|
+
return event.eventId;
|
|
251
|
+
}
|
|
252
|
+
wrap(fn, context) {
|
|
253
|
+
return wrapFunction((error, ctx) => this.captureError(error, ctx), fn, context);
|
|
254
|
+
}
|
|
255
|
+
addBreadcrumb(input) {
|
|
256
|
+
this.breadcrumbs.push({ ...input, timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString() });
|
|
257
|
+
if (this.breadcrumbs.length > MAX_BREADCRUMBS) this.breadcrumbs.shift();
|
|
258
|
+
}
|
|
259
|
+
flush() {
|
|
260
|
+
if (this.flushPromise) return this.flushPromise;
|
|
261
|
+
this.flushPromise = this.sendBatch().finally(() => {
|
|
262
|
+
this.flushPromise = void 0;
|
|
263
|
+
});
|
|
264
|
+
return this.flushPromise;
|
|
265
|
+
}
|
|
266
|
+
async sendBatch() {
|
|
267
|
+
await this.operation;
|
|
268
|
+
if (this.queue.length === 0) return;
|
|
269
|
+
const batch = this.queue.slice(0, 50);
|
|
270
|
+
try {
|
|
271
|
+
const transport = this.nativeFetch ?? fetch;
|
|
272
|
+
const response = await transport(endpoint(this.options.server), {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: {
|
|
275
|
+
"content-type": "application/json",
|
|
276
|
+
"x-error-mom-key": this.options.projectKey
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify({ events: batch, sdk: { name: SDK_NAME, version: SDK_VERSION } })
|
|
279
|
+
});
|
|
280
|
+
if (!response.ok) return;
|
|
281
|
+
const accepted = new Set(batch.map((event) => event.eventId));
|
|
282
|
+
this.queue = this.queue.filter((event) => !accepted.has(event.eventId));
|
|
283
|
+
this.operation = this.operation.then(() => this.rewriteSpool());
|
|
284
|
+
await this.operation;
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async dispose() {
|
|
289
|
+
if (this.interval) clearInterval(this.interval);
|
|
290
|
+
for (const remove of this.handlers.reverse()) remove();
|
|
291
|
+
await this.flush();
|
|
292
|
+
if (singleton === this) singleton = void 0;
|
|
293
|
+
}
|
|
294
|
+
installProcessHandlers() {
|
|
295
|
+
const onUncaught = (error) => {
|
|
296
|
+
this.captureError(error, { level: "fatal", culprit: "uncaughtException" });
|
|
297
|
+
};
|
|
298
|
+
const onRejection = (reason) => {
|
|
299
|
+
this.captureError(reason, { culprit: "unhandledRejection" });
|
|
300
|
+
};
|
|
301
|
+
const onExit = () => {
|
|
302
|
+
this.persistQueueSync();
|
|
303
|
+
};
|
|
304
|
+
process.on("uncaughtExceptionMonitor", onUncaught);
|
|
305
|
+
process.on("unhandledRejection", onRejection);
|
|
306
|
+
process.on("exit", onExit);
|
|
307
|
+
this.handlers.push(() => process.off("uncaughtExceptionMonitor", onUncaught));
|
|
308
|
+
this.handlers.push(() => process.off("unhandledRejection", onRejection));
|
|
309
|
+
this.handlers.push(() => process.off("exit", onExit));
|
|
310
|
+
}
|
|
311
|
+
persistQueueSync() {
|
|
312
|
+
if (this.queue.length === 0) return;
|
|
313
|
+
try {
|
|
314
|
+
(0, import_node_fs.mkdirSync)(this.options.spoolDirectory, { recursive: true });
|
|
315
|
+
const contents = this.queue.map((event) => JSON.stringify(event)).join("\n");
|
|
316
|
+
(0, import_node_fs.writeFileSync)(this.spoolFile, `${contents}
|
|
317
|
+
`, { mode: 384 });
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
captureFetchFailures() {
|
|
322
|
+
if (this.options.captureFailedRequests === false || typeof fetch === "undefined") return;
|
|
323
|
+
const original = fetch.bind(globalThis);
|
|
324
|
+
this.nativeFetch = original;
|
|
325
|
+
globalThis.fetch = (async (input, init) => {
|
|
326
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
327
|
+
if (url.startsWith(this.options.server)) return original(input, init);
|
|
328
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
329
|
+
try {
|
|
330
|
+
const response = await original(input, init);
|
|
331
|
+
const failure = describeFailedRequest(method, url, response.status);
|
|
332
|
+
if (failure) this.captureError(failure.error, failure.context);
|
|
333
|
+
return response;
|
|
334
|
+
} catch (error) {
|
|
335
|
+
this.captureError(error, { culprit: "fetch", tags: { method, url: redactString(url) } });
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
this.handlers.push(() => {
|
|
340
|
+
globalThis.fetch = original;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
captureConsole() {
|
|
344
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
345
|
+
for (const level of levels) {
|
|
346
|
+
const original = console[level].bind(console);
|
|
347
|
+
console[level] = (...args) => {
|
|
348
|
+
original(...args);
|
|
349
|
+
const message = args.map(printable).join(" ").slice(0, 2e3);
|
|
350
|
+
this.addBreadcrumb({
|
|
351
|
+
category: "console",
|
|
352
|
+
level: level === "warn" ? "warning" : level,
|
|
353
|
+
message
|
|
354
|
+
});
|
|
355
|
+
if (level === "error" && this.options.captureConsoleErrors) {
|
|
356
|
+
this.captureError(args[0] instanceof Error ? args[0] : new Error(message), {
|
|
357
|
+
culprit: "console.error"
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
this.handlers.push(() => {
|
|
362
|
+
console[level] = original;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async restoreQueue() {
|
|
367
|
+
try {
|
|
368
|
+
const contents = await (0, import_promises.readFile)(this.spoolFile, "utf8");
|
|
369
|
+
const restored = contents.split("\n").filter(Boolean).flatMap((line) => {
|
|
370
|
+
try {
|
|
371
|
+
return [JSON.parse(line)];
|
|
372
|
+
} catch {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
const merged = new Map(
|
|
377
|
+
[...restored, ...this.queue].map((event) => [event.eventId, event])
|
|
378
|
+
);
|
|
379
|
+
this.queue = [...merged.values()].slice(-this.options.maxQueueSize);
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async rewriteSpool() {
|
|
384
|
+
await (0, import_promises.mkdir)(this.options.spoolDirectory, { recursive: true });
|
|
385
|
+
const temporary = `${this.spoolFile}.${process.pid}.tmp`;
|
|
386
|
+
const contents = this.queue.map((event) => JSON.stringify(event)).join("\n");
|
|
387
|
+
await (0, import_promises.writeFile)(temporary, contents ? `${contents}
|
|
388
|
+
` : "", { mode: 384 });
|
|
389
|
+
await (0, import_promises.rename)(temporary, this.spoolFile);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
function safeFilePart(projectKey) {
|
|
393
|
+
return (0, import_node_crypto.createHash)("sha256").update(projectKey).digest("hex").slice(0, 32);
|
|
394
|
+
}
|
|
395
|
+
var singleton;
|
|
396
|
+
function initErrorMom(options) {
|
|
397
|
+
if (!options.server || !options.projectKey) {
|
|
398
|
+
throw new Error("Error Mom requires both server and projectKey");
|
|
399
|
+
}
|
|
400
|
+
singleton ??= new NodeClient(options);
|
|
401
|
+
return singleton;
|
|
402
|
+
}
|
|
403
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
404
|
+
0 && (module.exports = {
|
|
405
|
+
initErrorMom
|
|
406
|
+
});
|
package/dist/node.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Breadcrumb } from '@kenkaiiii/error-mom-protocol';
|
|
2
|
+
export { Breadcrumb, ErrorEvent } from '@kenkaiiii/error-mom-protocol';
|
|
3
|
+
import { a as CaptureContext, C as CommonOptions } from './shared-idwux7-h.cjs';
|
|
4
|
+
|
|
5
|
+
interface NodeOptions extends CommonOptions {
|
|
6
|
+
captureFailedRequests?: boolean;
|
|
7
|
+
spoolDirectory?: string;
|
|
8
|
+
flushIntervalMs?: number;
|
|
9
|
+
maxQueueSize?: number;
|
|
10
|
+
}
|
|
11
|
+
interface ErrorMomNode {
|
|
12
|
+
captureError(error: unknown, context?: CaptureContext): string;
|
|
13
|
+
wrap<A extends unknown[], R>(fn: (...args: A) => R, context?: CaptureContext): (...args: A) => R;
|
|
14
|
+
addBreadcrumb(breadcrumb: Omit<Breadcrumb, "timestamp"> & {
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
}): void;
|
|
17
|
+
flush(): Promise<void>;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
declare function initErrorMom(options: NodeOptions): ErrorMomNode;
|
|
21
|
+
|
|
22
|
+
export { CaptureContext, type ErrorMomNode, type NodeOptions, initErrorMom };
|
package/dist/node.js
CHANGED
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
printable,
|
|
9
9
|
redactString,
|
|
10
10
|
wrapFunction
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GHOIVBTK.js";
|
|
12
12
|
|
|
13
13
|
// src/node.ts
|
|
14
14
|
import { createHash } from "crypto";
|
|
15
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
15
16
|
import { mkdir, readFile, rename, writeFile, appendFile } from "fs/promises";
|
|
16
17
|
import { homedir } from "os";
|
|
17
18
|
import { join } from "path";
|
|
@@ -108,10 +109,25 @@ var NodeClient = class {
|
|
|
108
109
|
const onRejection = (reason) => {
|
|
109
110
|
this.captureError(reason, { culprit: "unhandledRejection" });
|
|
110
111
|
};
|
|
112
|
+
const onExit = () => {
|
|
113
|
+
this.persistQueueSync();
|
|
114
|
+
};
|
|
111
115
|
process.on("uncaughtExceptionMonitor", onUncaught);
|
|
112
116
|
process.on("unhandledRejection", onRejection);
|
|
117
|
+
process.on("exit", onExit);
|
|
113
118
|
this.handlers.push(() => process.off("uncaughtExceptionMonitor", onUncaught));
|
|
114
119
|
this.handlers.push(() => process.off("unhandledRejection", onRejection));
|
|
120
|
+
this.handlers.push(() => process.off("exit", onExit));
|
|
121
|
+
}
|
|
122
|
+
persistQueueSync() {
|
|
123
|
+
if (this.queue.length === 0) return;
|
|
124
|
+
try {
|
|
125
|
+
mkdirSync(this.options.spoolDirectory, { recursive: true });
|
|
126
|
+
const contents = this.queue.map((event) => JSON.stringify(event)).join("\n");
|
|
127
|
+
writeFileSync(this.spoolFile, `${contents}
|
|
128
|
+
`, { mode: 384 });
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
115
131
|
}
|
|
116
132
|
captureFetchFailures() {
|
|
117
133
|
if (this.options.captureFailedRequests === false || typeof fetch === "undefined") return;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ErrorEvent } from '@kenkaiiii/error-mom-protocol';
|
|
2
|
+
|
|
3
|
+
interface CommonOptions {
|
|
4
|
+
server: string;
|
|
5
|
+
projectKey: string;
|
|
6
|
+
environment?: string;
|
|
7
|
+
release?: string;
|
|
8
|
+
installationId?: string;
|
|
9
|
+
tags?: Record<string, string>;
|
|
10
|
+
captureConsoleErrors?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface CaptureContext {
|
|
13
|
+
level?: ErrorEvent["level"];
|
|
14
|
+
culprit?: string;
|
|
15
|
+
tags?: Record<string, string>;
|
|
16
|
+
context?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type { CommonOptions as C, CaptureContext as a };
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenkaiiii/error-mom",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Automatic browser and Node.js error capture for self-hosted Error Mom",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js"
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
12
13
|
},
|
|
13
14
|
"./node": {
|
|
14
15
|
"types": "./dist/node.d.ts",
|
|
15
|
-
"import": "./dist/node.js"
|
|
16
|
+
"import": "./dist/node.js",
|
|
17
|
+
"require": "./dist/node.cjs"
|
|
16
18
|
}
|
|
17
19
|
},
|
|
18
20
|
"files": [
|
|
@@ -30,8 +32,9 @@
|
|
|
30
32
|
"access": "public"
|
|
31
33
|
},
|
|
32
34
|
"license": "MIT",
|
|
35
|
+
"module": "./dist/index.js",
|
|
33
36
|
"scripts": {
|
|
34
|
-
"build": "tsup src/index.ts src/node.ts --format esm --dts --clean",
|
|
37
|
+
"build": "tsup src/index.ts src/node.ts --format esm,cjs --dts --clean",
|
|
35
38
|
"check": "tsc --noEmit",
|
|
36
39
|
"test": "vitest run"
|
|
37
40
|
}
|