@devkitio/faultlens 0.1.5 → 1.0.1

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.
@@ -0,0 +1,1282 @@
1
+ // ../security/dist/redaction.js
2
+ var REDACTED = "[\u5DF2\u8131\u654F]";
3
+ var MAX_STRING_LENGTH = 32768;
4
+ var SENSITIVE_KEY = /^(?:pass(?:word|wd)?|pwd|cookie|set[-_]?cookie|authorization|proxy[-_]?authorization|(?:access|refresh|id)?[-_]?token|api[-_]?key|secret|client[-_]?secret|private[-_]?key|card(?:number)?|pan|cvv|cvc|payment(?:data)?|oauth[-_]?(?:state|nonce)|state|nonce|registration[-_]?draft)$/i;
5
+ var DANGEROUS_OBJECT_KEY = /^(?:__proto__|constructor|prototype)$/;
6
+ var AUTH_VALUE = /^(?:Bearer|Basic|Digest)\s+\S+/i;
7
+ var PRIVATE_KEY_VALUE = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/;
8
+ var JWT_VALUE = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
9
+ var ABSOLUTE_HTTP_URL = /https?:\/\/[^\s<>"'`]+/gi;
10
+ var SENSITIVE_PATH = /(\/(?:token|secret|password|key|reset|invite|download|verify|verification|activate|activation|credential)s?\/)([^/?#\s]+)/gi;
11
+ var EMBEDDED_AUTH_VALUE = /\b((?:authorization|proxy[-_]?authorization)\s*[:=]\s*)?(?:Bearer|Basic|Digest)\s+[^\s,;"']+/gi;
12
+ var EMBEDDED_SENSITIVE_VALUE = /\b((?:(?:access|refresh|id)[-_]?token|token|api[-_]?key|secret|client[-_]?secret|password|passwd|pwd)\s*[:=]\s*)[^\s,;"']+/gi;
13
+ var EMBEDDED_JWT_VALUE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
14
+ function looksLikePaymentCard(value) {
15
+ const digits = value.replace(/[\s-]/g, "");
16
+ if (!/^\d{13,19}$/.test(digits))
17
+ return false;
18
+ let sum = 0;
19
+ let alternate = false;
20
+ for (let index = digits.length - 1; index >= 0; index -= 1) {
21
+ let digit = Number(digits[index]);
22
+ if (alternate) {
23
+ digit *= 2;
24
+ if (digit > 9)
25
+ digit -= 9;
26
+ }
27
+ sum += digit;
28
+ alternate = !alternate;
29
+ }
30
+ return sum % 10 === 0;
31
+ }
32
+ function redactHttpUrl(value) {
33
+ try {
34
+ const url = new URL(value);
35
+ const segments = url.pathname.split("/");
36
+ for (let index = 0; index < segments.length - 1; index += 1) {
37
+ if (/^(?:token|secret|password|key|reset|invite|download|verify|verification|activate|activation|credential)s?$/i.test(segments[index] ?? "")) {
38
+ segments[index + 1] = REDACTED;
39
+ }
40
+ }
41
+ return `${url.origin}${segments.join("/")}`;
42
+ } catch {
43
+ return REDACTED;
44
+ }
45
+ }
46
+ function redactSensitiveString(value) {
47
+ if (AUTH_VALUE.test(value) || PRIVATE_KEY_VALUE.test(value) || JWT_VALUE.test(value) || looksLikePaymentCard(value)) {
48
+ return REDACTED;
49
+ }
50
+ return value.slice(0, MAX_STRING_LENGTH).replace(ABSOLUTE_HTTP_URL, redactHttpUrl).replace(SENSITIVE_PATH, `$1${REDACTED}`).replace(EMBEDDED_AUTH_VALUE, (_matched, prefix) => `${prefix ?? ""}${REDACTED}`).replace(EMBEDDED_SENSITIVE_VALUE, `$1${REDACTED}`).replace(EMBEDDED_JWT_VALUE, REDACTED);
51
+ }
52
+ function shouldRedactObjectKey(key) {
53
+ return SENSITIVE_KEY.test(key) || DANGEROUS_OBJECT_KEY.test(key);
54
+ }
55
+
56
+ // src/sanitize.ts
57
+ var REDACTED2 = "[\u5DF2\u8131\u654F]";
58
+ function clientRedactString(value) {
59
+ return redactSensitiveString(value);
60
+ }
61
+ function clientRedact(value) {
62
+ const seen = /* @__PURE__ */ new WeakSet();
63
+ let keys = 0;
64
+ function visit(current, depth) {
65
+ if (typeof current === "string") {
66
+ return clientRedactString(current);
67
+ }
68
+ if (current === null || typeof current !== "object") return current;
69
+ if (depth > 10 || seen.has(current)) return "[\u5DF2\u622A\u65AD]";
70
+ seen.add(current);
71
+ if (Array.isArray(current)) return current.slice(0, 100).map((item) => visit(item, depth + 1));
72
+ const result = {};
73
+ for (const [key, item] of Object.entries(current)) {
74
+ keys += 1;
75
+ if (keys > 400) {
76
+ result.__truncated__ = "[\u5DF2\u622A\u65AD]";
77
+ break;
78
+ }
79
+ Object.defineProperty(result, key, {
80
+ value: shouldRedactObjectKey(key) ? REDACTED2 : visit(item, depth + 1),
81
+ enumerable: true,
82
+ configurable: true,
83
+ writable: true
84
+ });
85
+ }
86
+ return result;
87
+ }
88
+ return visit(value, 0);
89
+ }
90
+
91
+ // src/transport.ts
92
+ function parseDirectDsn(dsn) {
93
+ const url = new URL(dsn);
94
+ const localHostname = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
95
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && localHostname)) {
96
+ throw new Error("Direct DSN \u5FC5\u987B\u4F7F\u7528 HTTPS");
97
+ }
98
+ if (!url.username || url.password || url.pathname !== "/v1/direct/envelope" || url.search || url.hash) {
99
+ throw new Error("Direct DSN \u683C\u5F0F\u65E0\u6548");
100
+ }
101
+ const publicDsn = decodeURIComponent(url.username);
102
+ url.username = "";
103
+ url.password = "";
104
+ return { endpoint: url.toString(), publicDsn };
105
+ }
106
+ function parseRemoteSdkConfig(input) {
107
+ if (typeof input !== "object" || input === null) return void 0;
108
+ const value = input;
109
+ const telemetry = typeof value.telemetry === "object" && value.telemetry !== null ? {
110
+ ...typeof value.telemetry.enabled === "boolean" ? { enabled: value.telemetry.enabled } : {},
111
+ ...typeof value.telemetry.sampleRate === "number" ? { sampleRate: value.telemetry.sampleRate } : {},
112
+ ...typeof value.telemetry.dailyQuota === "number" ? { dailyQuota: value.telemetry.dailyQuota } : {},
113
+ ...typeof value.telemetry.retentionDays === "number" ? { retentionDays: value.telemetry.retentionDays } : {}
114
+ } : void 0;
115
+ return {
116
+ ...typeof value.enabled === "boolean" ? { enabled: value.enabled } : {},
117
+ ...typeof value.sampleRate === "number" ? { sampleRate: value.sampleRate } : {},
118
+ ...Array.isArray(value.errorTypeAllowlist) ? { errorTypeAllowlist: value.errorTypeAllowlist.filter((item) => typeof item === "string") } : {},
119
+ ...typeof value.queueLimit === "number" ? { queueLimit: value.queueLimit } : {},
120
+ ...typeof value.retryBaseMs === "number" ? { retryBaseMs: value.retryBaseMs } : {},
121
+ ...telemetry ? { telemetry } : {}
122
+ };
123
+ }
124
+ function retryAfterMs(response, body, now) {
125
+ const value = response.headers.get("retry-after");
126
+ if (value && /^\d+$/.test(value)) return Number(value) * 1e3;
127
+ if (value) {
128
+ const retryAt = Date.parse(value);
129
+ if (Number.isFinite(retryAt)) return Math.max(0, retryAt - now);
130
+ }
131
+ if (typeof body === "object" && body !== null && "retryAfterMs" in body && typeof body.retryAfterMs === "number") {
132
+ return Math.max(0, body.retryAfterMs);
133
+ }
134
+ return 0;
135
+ }
136
+ function responseCode(response, body) {
137
+ return typeof body === "object" && body !== null && "code" in body && typeof body.code === "string" ? body.code : `http_${response.status}`;
138
+ }
139
+ function parseBatchResults(envelope, body) {
140
+ if (typeof body !== "object" || body === null || !("ingestId" in body) || typeof body.ingestId !== "string" || !("results" in body) || !Array.isArray(body.results)) {
141
+ return void 0;
142
+ }
143
+ const expectedIds = new Set(envelope.events.map((event) => event.eventId));
144
+ const seenIds = /* @__PURE__ */ new Set();
145
+ const results = [];
146
+ for (const value of body.results) {
147
+ if (typeof value !== "object" || value === null) return void 0;
148
+ const eventId2 = value.eventId;
149
+ const status = value.status;
150
+ const code = value.code;
151
+ if (typeof eventId2 !== "string" || !expectedIds.has(eventId2) || seenIds.has(eventId2) || !["accepted", "duplicate", "rejected", "retryable"].includes(String(status)) || code !== void 0 && typeof code !== "string") {
152
+ return void 0;
153
+ }
154
+ seenIds.add(eventId2);
155
+ results.push({
156
+ eventId: eventId2,
157
+ status,
158
+ ...typeof code === "string" ? { errorCode: code } : {}
159
+ });
160
+ }
161
+ if (seenIds.size !== expectedIds.size) return void 0;
162
+ return {
163
+ ingestId: body.ingestId,
164
+ results
165
+ };
166
+ }
167
+ async function parseTransportResponse(response, envelope, now = Date.now()) {
168
+ let body;
169
+ try {
170
+ body = await response.json();
171
+ } catch {
172
+ body = void 0;
173
+ }
174
+ if (response.status === 202) {
175
+ const batch = parseBatchResults(envelope, body);
176
+ if (!batch) {
177
+ return {
178
+ kind: "retryable",
179
+ retryAfterMs: retryAfterMs(response, body, now),
180
+ code: "invalid_ingest_response"
181
+ };
182
+ }
183
+ return {
184
+ kind: "batch",
185
+ ...batch,
186
+ retryAfterMs: retryAfterMs(response, body, now)
187
+ };
188
+ }
189
+ if (response.status === 429 || response.status >= 500) {
190
+ return {
191
+ kind: "retryable",
192
+ retryAfterMs: retryAfterMs(response, body, now),
193
+ code: responseCode(response, body)
194
+ };
195
+ }
196
+ return { kind: "permanent_error", code: responseCode(response, body) };
197
+ }
198
+ var FetchTransport = class {
199
+ constructor(ingest, fetcher = fetch, now = Date.now) {
200
+ this.ingest = ingest;
201
+ this.fetcher = fetcher;
202
+ this.now = now;
203
+ }
204
+ async send(envelope) {
205
+ try {
206
+ const direct = this.ingest.mode === "direct" ? parseDirectDsn(this.ingest.dsn) : void 0;
207
+ const endpoint = direct?.endpoint ?? this.ingest.endpoint;
208
+ const response = await this.fetcher(endpoint, {
209
+ method: "POST",
210
+ headers: {
211
+ "content-type": "application/json",
212
+ ...direct ? { "x-faultlens-dsn": direct.publicDsn } : {}
213
+ },
214
+ body: JSON.stringify(envelope),
215
+ credentials: "omit",
216
+ cache: "no-store",
217
+ redirect: "error",
218
+ keepalive: true
219
+ });
220
+ return await parseTransportResponse(response, envelope, this.now());
221
+ } catch {
222
+ return { kind: "network_error", code: "network_error" };
223
+ }
224
+ }
225
+ async loadRemoteConfig() {
226
+ const result = await this.loadRemoteConfigResult();
227
+ return result.kind === "configured" ? result.config : void 0;
228
+ }
229
+ async loadRemoteConfigResult() {
230
+ try {
231
+ const direct = this.ingest.mode === "direct" ? parseDirectDsn(this.ingest.dsn) : void 0;
232
+ const base = direct?.endpoint ?? this.ingest.endpoint;
233
+ const endpoint = `${base.replace(/\/$/, "")}/config`;
234
+ const response = await this.fetcher(endpoint, {
235
+ method: "GET",
236
+ headers: direct ? { "x-faultlens-dsn": direct.publicDsn } : {},
237
+ credentials: "omit",
238
+ cache: "no-store",
239
+ redirect: "error"
240
+ });
241
+ if (response.status === 404) return { kind: "unsupported", code: "config_not_found" };
242
+ if (response.status === 422) return { kind: "unsupported", code: "unsupported_protocol" };
243
+ if (!response.ok) return { kind: "retryable_error", code: `http_${response.status}` };
244
+ let body;
245
+ try {
246
+ body = await response.json();
247
+ } catch {
248
+ return { kind: "retryable_error", code: "invalid_remote_config" };
249
+ }
250
+ const config = parseRemoteSdkConfig(body);
251
+ return config ? { kind: "configured", config } : { kind: "retryable_error", code: "invalid_remote_config" };
252
+ } catch {
253
+ return { kind: "retryable_error", code: "network_error" };
254
+ }
255
+ }
256
+ };
257
+
258
+ // src/telemetry-transport.ts
259
+ function responseCode2(response, body) {
260
+ return typeof body === "object" && body !== null && "code" in body && typeof body.code === "string" ? body.code : `http_${response.status}`;
261
+ }
262
+ function retryAfterMs2(response, body, now) {
263
+ const value = response.headers.get("retry-after");
264
+ if (value && /^\d+$/.test(value)) return Number(value) * 1e3;
265
+ if (value) {
266
+ const retryAt = Date.parse(value);
267
+ if (Number.isFinite(retryAt)) return Math.max(0, retryAt - now);
268
+ }
269
+ if (typeof body === "object" && body !== null && "retryAfterMs" in body && typeof body.retryAfterMs === "number") {
270
+ return Math.max(0, body.retryAfterMs);
271
+ }
272
+ return 0;
273
+ }
274
+ function parseBatchResults2(envelope, body) {
275
+ if (typeof body !== "object" || body === null || typeof body.ingestId !== "string" || !Array.isArray(body.results)) {
276
+ return void 0;
277
+ }
278
+ const expectedIds = /* @__PURE__ */ new Set([
279
+ ...envelope.traces.map((item) => item.telemetryId),
280
+ ...envelope.spans.map((item) => item.telemetryId),
281
+ ...envelope.webVitals.map((item) => item.telemetryId)
282
+ ]);
283
+ const seenIds = /* @__PURE__ */ new Set();
284
+ const results = [];
285
+ for (const value of body.results) {
286
+ if (typeof value !== "object" || value === null) return void 0;
287
+ const telemetryId = value.telemetryId;
288
+ const status = value.status;
289
+ const code = value.code;
290
+ if (typeof telemetryId !== "string" || !expectedIds.has(telemetryId) || seenIds.has(telemetryId) || !["accepted", "duplicate", "rejected", "retryable"].includes(String(status)) || code !== void 0 && typeof code !== "string") {
291
+ return void 0;
292
+ }
293
+ seenIds.add(telemetryId);
294
+ results.push({
295
+ telemetryId,
296
+ status,
297
+ ...typeof code === "string" ? { errorCode: code } : {}
298
+ });
299
+ }
300
+ if (seenIds.size !== expectedIds.size) return void 0;
301
+ return { ingestId: body.ingestId, results };
302
+ }
303
+ function relayTelemetryEndpoint(endpoint) {
304
+ if (!/\/envelope\/?$/.test(endpoint)) {
305
+ throw new Error("Relay Endpoint \u5FC5\u987B\u4EE5 /envelope \u7ED3\u5C3E");
306
+ }
307
+ return endpoint.replace(/\/envelope\/?$/, "/telemetry");
308
+ }
309
+ var FetchTelemetryTransport = class {
310
+ constructor(ingest, fetcher = fetch, now = Date.now) {
311
+ this.ingest = ingest;
312
+ this.fetcher = fetcher;
313
+ this.now = now;
314
+ }
315
+ async send(envelope) {
316
+ try {
317
+ const direct = this.ingest.mode === "direct" ? parseDirectDsn(this.ingest.dsn) : void 0;
318
+ const endpoint = direct ? direct.endpoint.replace(/\/v1\/direct\/envelope$/, "/v1/direct/telemetry") : relayTelemetryEndpoint(this.ingest.endpoint);
319
+ const response = await this.fetcher(endpoint, {
320
+ method: "POST",
321
+ headers: {
322
+ "content-type": "application/json",
323
+ ...direct ? { "x-faultlens-dsn": direct.publicDsn } : {}
324
+ },
325
+ body: JSON.stringify(envelope),
326
+ credentials: "omit",
327
+ cache: "no-store",
328
+ redirect: "error",
329
+ keepalive: true
330
+ });
331
+ return await parseTelemetryTransportResponse(response, envelope, this.now());
332
+ } catch {
333
+ return { kind: "network_error", code: "network_error" };
334
+ }
335
+ }
336
+ };
337
+ async function parseTelemetryTransportResponse(response, envelope, now = Date.now()) {
338
+ let body;
339
+ try {
340
+ body = await response.json();
341
+ } catch {
342
+ body = void 0;
343
+ }
344
+ if (response.status === 404 || response.status === 422) {
345
+ return { kind: "unsupported", code: responseCode2(response, body) };
346
+ }
347
+ if (response.status === 202) {
348
+ const batch = parseBatchResults2(envelope, body);
349
+ if (!batch) {
350
+ return {
351
+ kind: "retryable",
352
+ retryAfterMs: retryAfterMs2(response, body, now),
353
+ code: "invalid_telemetry_response"
354
+ };
355
+ }
356
+ return {
357
+ kind: "batch",
358
+ ...batch,
359
+ retryAfterMs: retryAfterMs2(response, body, now)
360
+ };
361
+ }
362
+ if (response.status === 429 || response.status >= 500) {
363
+ return {
364
+ kind: "retryable",
365
+ retryAfterMs: retryAfterMs2(response, body, now),
366
+ code: responseCode2(response, body)
367
+ };
368
+ }
369
+ return { kind: "permanent_error", code: responseCode2(response, body) };
370
+ }
371
+
372
+ // src/trace-context.ts
373
+ var TRACE_PARENT_PATTERN = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(?:-(.+))?$/;
374
+ function validTraceId(value) {
375
+ return /^[0-9a-f]{32}$/.test(value) && value !== "0".repeat(32);
376
+ }
377
+ function validSpanId(value) {
378
+ return /^[0-9a-f]{16}$/.test(value) && value !== "0".repeat(16);
379
+ }
380
+ function validTraceState(value) {
381
+ return value !== void 0 && value.length > 0 && value.length <= 512 && !/[\r\n]/.test(value);
382
+ }
383
+ function parseTraceParent(traceParent, traceState) {
384
+ if (!traceParent) return void 0;
385
+ const match = traceParent.match(TRACE_PARENT_PATTERN);
386
+ if (!match) return void 0;
387
+ const [, version, traceId, spanId, traceFlags, extension] = match;
388
+ if (!version || version === "ff" || !traceId || !spanId || !traceFlags || !validTraceId(traceId) || !validSpanId(spanId) || version === "00" && extension !== void 0) {
389
+ return void 0;
390
+ }
391
+ return {
392
+ traceId,
393
+ spanId,
394
+ traceFlags,
395
+ ...validTraceState(traceState ?? void 0) ? { traceState } : {},
396
+ ...version === "00" ? {} : { traceVersion: version }
397
+ };
398
+ }
399
+ function formatTraceParent(context) {
400
+ const version = context.traceVersion ?? "00";
401
+ if (!/^[0-9a-f]{2}$/.test(version) || version === "ff" || !validTraceId(context.traceId) || !validSpanId(context.spanId) || !/^[0-9a-f]{2}$/.test(context.traceFlags)) {
402
+ throw new Error("Trace Context \u65E0\u6548");
403
+ }
404
+ return `${version}-${context.traceId}-${context.spanId}-${context.traceFlags}`;
405
+ }
406
+ function traceContextHeaders(context) {
407
+ return {
408
+ traceparent: formatTraceParent(context),
409
+ ...validTraceState(context.traceState) ? { tracestate: context.traceState } : {}
410
+ };
411
+ }
412
+
413
+ // src/monitor.ts
414
+ var SDK_VERSION = "1.0.1";
415
+ var PROTOCOL_VERSION = "1.0";
416
+ var REVOKED_CREDENTIAL_CODES = /* @__PURE__ */ new Set(["invalid_dsn", "relay_key_revoked", "credential_revoked"]);
417
+ function eventId() {
418
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
419
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`;
420
+ }
421
+ function randomHexId(byteLength) {
422
+ let value = "";
423
+ do {
424
+ if (typeof crypto !== "undefined" && "getRandomValues" in crypto) {
425
+ const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
426
+ value = Array.from(bytes, (item) => item.toString(16).padStart(2, "0")).join("");
427
+ } else {
428
+ value = Array.from(
429
+ { length: byteLength * 2 },
430
+ () => Math.floor(Math.random() * 16).toString(16)
431
+ ).join("");
432
+ }
433
+ } while (/^0+$/.test(value));
434
+ return value;
435
+ }
436
+ function validTraceId2(value) {
437
+ return !!value && /^[0-9a-f]{32}$/.test(value) && value !== "0".repeat(32);
438
+ }
439
+ function validSpanId2(value) {
440
+ return !!value && /^[0-9a-f]{16}$/.test(value) && value !== "0".repeat(16);
441
+ }
442
+ function normalizedFilename(filename) {
443
+ try {
444
+ const url = new URL(filename);
445
+ url.search = "";
446
+ url.hash = "";
447
+ return url.toString();
448
+ } catch {
449
+ return filename.split(/[?#]/, 1)[0] ?? filename;
450
+ }
451
+ }
452
+ function rawStackFrames(stack) {
453
+ return stack.split("\n").slice(1, 101).map((line) => {
454
+ const trimmed = line.trim();
455
+ const match = trimmed.match(/(?:at\s+(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
456
+ return {
457
+ filename: match?.[2] ?? trimmed.slice(0, 2048),
458
+ ...match?.[1] ? { function: match[1].slice(0, 1024) } : {},
459
+ ...match?.[3] ? { line: Number(match[3]) } : {},
460
+ ...match?.[4] ? { column: Number(match[4]) } : {},
461
+ inApp: match?.[2] ? !/node_modules|<anonymous>/.test(match[2]) : false
462
+ };
463
+ });
464
+ }
465
+ function runtimeDebugIds() {
466
+ const value = globalThis.__FAULTLENS_DEBUG_IDS__;
467
+ const result = /* @__PURE__ */ new Map();
468
+ if (!value || typeof value !== "object") return result;
469
+ for (const [stack, debugId] of Object.entries(value)) {
470
+ if (typeof debugId !== "string") continue;
471
+ const frame = rawStackFrames(stack).find((item) => item.filename);
472
+ if (frame) result.set(normalizedFilename(frame.filename), debugId);
473
+ }
474
+ return result;
475
+ }
476
+ function parseErrorStack(error) {
477
+ let stack = "";
478
+ try {
479
+ stack = typeof error.stack === "string" ? error.stack : "";
480
+ } catch {
481
+ return [];
482
+ }
483
+ const debugIds = runtimeDebugIds();
484
+ return stack ? rawStackFrames(stack).map((frame) => {
485
+ const debugId = debugIds.get(normalizedFilename(frame.filename));
486
+ return debugId ? { ...frame, debugId } : frame;
487
+ }) : [];
488
+ }
489
+ function errorShape(error) {
490
+ let isError = false;
491
+ try {
492
+ isError = error instanceof Error;
493
+ } catch {
494
+ isError = false;
495
+ }
496
+ if (isError) {
497
+ let name = "Error";
498
+ let message = "\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u5F02\u5E38\u5185\u5BB9";
499
+ try {
500
+ if (typeof error.name === "string" && error.name) {
501
+ name = error.name;
502
+ }
503
+ } catch {
504
+ name = "Error";
505
+ }
506
+ try {
507
+ if (typeof error.message === "string") {
508
+ message = clientRedactString(error.message);
509
+ }
510
+ } catch {
511
+ message = "\u65E0\u6CD5\u5B89\u5168\u8BFB\u53D6\u5F02\u5E38\u5185\u5BB9";
512
+ }
513
+ return {
514
+ type: name,
515
+ message,
516
+ stack: parseErrorStack(error)
517
+ };
518
+ }
519
+ if (typeof error === "string") return { type: "Error", message: clientRedactString(error), stack: [] };
520
+ return {
521
+ type: "NonErrorException",
522
+ message: "\u6355\u83B7\u5230\u975E Error \u7C7B\u578B\u5F02\u5E38",
523
+ stack: []
524
+ };
525
+ }
526
+ function shallowFingerprint(event) {
527
+ const frame = event.stack.find((item) => item.inApp) ?? event.stack[0];
528
+ return `${event.type}${event.message}${frame?.filename ?? ""}${frame?.line ?? ""}`;
529
+ }
530
+ function createMonitorRuntime(options, overrides = {}) {
531
+ const dependencies = {
532
+ transport: overrides.transport ?? new FetchTransport(options.ingest),
533
+ telemetryTransport: overrides.telemetryTransport ?? new FetchTelemetryTransport(options.ingest),
534
+ now: overrides.now ?? (() => /* @__PURE__ */ new Date()),
535
+ random: overrides.random ?? Math.random,
536
+ createEventId: overrides.createEventId ?? eventId,
537
+ createTraceId: overrides.createTraceId ?? (() => randomHexId(16)),
538
+ createSpanId: overrides.createSpanId ?? (() => randomHexId(8))
539
+ };
540
+ let queue = [];
541
+ const pending = /* @__PURE__ */ new Set();
542
+ const pendingEventIds = /* @__PURE__ */ new Set();
543
+ const breadcrumbs = [];
544
+ const namedContext = /* @__PURE__ */ new Map();
545
+ const seenErrors = /* @__PURE__ */ new WeakSet();
546
+ const recentFingerprints = /* @__PURE__ */ new Map();
547
+ const droppedReasons = {};
548
+ const localQueueLimit = Math.min(50, Math.max(1, options.queueLimit ?? 50));
549
+ const localSampleRate = Math.min(1, Math.max(0, options.sampleRate ?? 1));
550
+ const localTelemetrySampleRate = Math.min(1, Math.max(0, options.telemetry?.sampleRate ?? 1));
551
+ const remoteConfigRefreshIntervalMs = Math.min(
552
+ 60 * 60 * 1e3,
553
+ Math.max(3e4, options.remoteConfigRefreshIntervalMs ?? 5 * 60 * 1e3)
554
+ );
555
+ let queueLimit = localQueueLimit;
556
+ let sampleRate = localSampleRate;
557
+ let errorTypeAllowlist;
558
+ let user = null;
559
+ let closed = false;
560
+ let sendTimer;
561
+ let remoteConfigTimer;
562
+ let sendTimerDueAt;
563
+ let activeFlush;
564
+ let recentSendStatus = "idle";
565
+ let lastRateLimitedAt = null;
566
+ let remotelyDisabled = false;
567
+ let retryBaseMs = 250;
568
+ let telemetryEnabled = false;
569
+ let telemetryUnsupported = false;
570
+ let remoteConfigLastRefreshAt = null;
571
+ let remoteConfigLastSuccessAt = null;
572
+ let remoteConfigFailureCode = null;
573
+ let remoteConfigRefreshFailures = 0;
574
+ let telemetrySampleRate = localTelemetrySampleRate;
575
+ const telemetryRequested = options.telemetry?.enabled !== false;
576
+ let telemetryQueue = [];
577
+ const telemetrySamplingDecisions = /* @__PURE__ */ new Map();
578
+ let activeSpan;
579
+ const spanContextManager = overrides.spanContextManager ?? {
580
+ active: () => activeSpan,
581
+ run(span, callback) {
582
+ const previous = activeSpan;
583
+ activeSpan = span;
584
+ try {
585
+ return callback();
586
+ } finally {
587
+ activeSpan = previous;
588
+ }
589
+ }
590
+ };
591
+ const persistentQueue = overrides.persistentQueue;
592
+ const sendLease = overrides.sendLease;
593
+ const restorePromise = persistentQueue?.load(dependencies.now().getTime()).then((items) => {
594
+ for (const item of items) {
595
+ if (!queue.some((queued) => queued.event.eventId === item.id)) {
596
+ queue.push(item);
597
+ }
598
+ }
599
+ scheduleNextQueuedFlush();
600
+ }).catch(() => drop("persistent_queue_unavailable"));
601
+ const legacyRemoteConfigLoader = dependencies.transport.loadRemoteConfig?.bind(
602
+ dependencies.transport
603
+ );
604
+ const resultRemoteConfigLoader = dependencies.transport.loadRemoteConfigResult?.bind(
605
+ dependencies.transport
606
+ );
607
+ const loadRemoteConfigResult = resultRemoteConfigLoader ? resultRemoteConfigLoader : legacyRemoteConfigLoader ? async () => {
608
+ try {
609
+ const config = await legacyRemoteConfigLoader();
610
+ return config ? { kind: "configured", config } : { kind: "retryable_error", code: "remote_config_unavailable" };
611
+ } catch {
612
+ return { kind: "retryable_error", code: "network_error" };
613
+ }
614
+ } : void 0;
615
+ function applyRemoteConfig(config) {
616
+ remotelyDisabled = config.enabled === false;
617
+ sampleRate = typeof config.sampleRate === "number" ? Math.min(localSampleRate, Math.max(0, Math.min(1, config.sampleRate))) : localSampleRate;
618
+ queueLimit = typeof config.queueLimit === "number" ? Math.min(localQueueLimit, Math.max(1, config.queueLimit)) : localQueueLimit;
619
+ retryBaseMs = typeof config.retryBaseMs === "number" ? Math.min(3e4, Math.max(1, config.retryBaseMs)) : 250;
620
+ errorTypeAllowlist = config.errorTypeAllowlist ? new Set(config.errorTypeAllowlist) : void 0;
621
+ telemetryEnabled = telemetryRequested && config.telemetry?.enabled === true;
622
+ telemetryUnsupported = false;
623
+ telemetrySampleRate = typeof config.telemetry?.sampleRate === "number" ? Math.min(localTelemetrySampleRate, Math.max(0, Math.min(1, config.telemetry.sampleRate))) : localTelemetrySampleRate;
624
+ }
625
+ function scheduleRemoteConfigRefresh() {
626
+ if (!loadRemoteConfigResult || closed) return;
627
+ remoteConfigTimer = setTimeout(() => {
628
+ remoteConfigTimer = void 0;
629
+ void refreshRemoteConfig();
630
+ }, remoteConfigRefreshIntervalMs);
631
+ remoteConfigTimer.unref?.();
632
+ }
633
+ async function refreshRemoteConfig() {
634
+ if (!loadRemoteConfigResult || closed) return;
635
+ try {
636
+ const result = await loadRemoteConfigResult();
637
+ if (closed) return;
638
+ remoteConfigLastRefreshAt = dependencies.now().toISOString();
639
+ if (result.kind === "configured") {
640
+ applyRemoteConfig(result.config);
641
+ remoteConfigLastSuccessAt = remoteConfigLastRefreshAt;
642
+ remoteConfigFailureCode = null;
643
+ return;
644
+ }
645
+ remoteConfigFailureCode = result.code;
646
+ remoteConfigRefreshFailures += 1;
647
+ if (result.kind === "unsupported") {
648
+ telemetryEnabled = false;
649
+ telemetryUnsupported = true;
650
+ drop(`remote_config_${result.code}`);
651
+ }
652
+ } catch {
653
+ if (!closed) {
654
+ remoteConfigLastRefreshAt = dependencies.now().toISOString();
655
+ remoteConfigFailureCode = "network_error";
656
+ remoteConfigRefreshFailures += 1;
657
+ }
658
+ } finally {
659
+ scheduleRemoteConfigRefresh();
660
+ }
661
+ }
662
+ const remoteConfigPromise = refreshRemoteConfig();
663
+ function drop(reason) {
664
+ droppedReasons[reason] = (droppedReasons[reason] ?? 0) + 1;
665
+ }
666
+ function scheduleFlush(delayMs = 100) {
667
+ if (closed) return;
668
+ const normalizedDelay = Math.max(0, delayMs);
669
+ const dueAt = dependencies.now().getTime() + normalizedDelay;
670
+ if (sendTimer && sendTimerDueAt !== void 0 && sendTimerDueAt <= dueAt) return;
671
+ if (sendTimer) clearTimeout(sendTimer);
672
+ sendTimerDueAt = dueAt;
673
+ sendTimer = setTimeout(() => {
674
+ sendTimer = void 0;
675
+ sendTimerDueAt = void 0;
676
+ void flushInternal();
677
+ }, normalizedDelay);
678
+ }
679
+ function scheduleNextQueuedFlush() {
680
+ if (queue.length === 0 || closed) return;
681
+ const now = dependencies.now().getTime();
682
+ const nextAttemptAt = Math.min(...queue.map((item) => item.nextAttemptAt));
683
+ scheduleFlush(Math.max(0, nextAttemptAt - now));
684
+ }
685
+ function retryDelay(item, serverDelayMs = 0) {
686
+ const exponent = Math.min(16, item.attemptCount);
687
+ const jitteredBackoff = retryBaseMs * 2 ** exponent * (0.5 + dependencies.random() * 0.5);
688
+ return Math.min(
689
+ 24 * 60 * 60 * 1e3,
690
+ Math.max(serverDelayMs, Math.min(3e4, Math.round(jitteredBackoff)))
691
+ );
692
+ }
693
+ function retryItem(item, retryAfterMs3, errorCode) {
694
+ const attemptCount = item.attemptCount + 1;
695
+ const next = {
696
+ ...item,
697
+ attemptCount,
698
+ nextAttemptAt: dependencies.now().getTime() + retryDelay({ ...item, attemptCount }, retryAfterMs3),
699
+ lastErrorCode: errorCode
700
+ };
701
+ return next;
702
+ }
703
+ async function persistRetriedItems(items) {
704
+ if (!persistentQueue) return;
705
+ await Promise.all(
706
+ items.map(
707
+ (item) => persistentQueue.put(item).catch(() => drop("persistent_queue_write_failed"))
708
+ )
709
+ );
710
+ }
711
+ async function prepare(event) {
712
+ try {
713
+ const beforeSendResult = options.beforeSend ? await options.beforeSend(event) : event;
714
+ if (!beforeSendResult) {
715
+ drop("before_send");
716
+ return;
717
+ }
718
+ const sanitized = clientRedact(beforeSendResult);
719
+ const size = new TextEncoder().encode(JSON.stringify(sanitized)).byteLength;
720
+ if (size > 128 * 1024) {
721
+ drop("payload_too_large");
722
+ return;
723
+ }
724
+ while (queue.length >= queueLimit) {
725
+ const removed = queue.shift();
726
+ if (removed) await persistentQueue?.remove([removed.id]).catch(() => void 0);
727
+ drop("queue_limit");
728
+ }
729
+ const enqueuedAt = dependencies.now().getTime();
730
+ const item = {
731
+ id: sanitized.eventId,
732
+ event: sanitized,
733
+ enqueuedAt,
734
+ bytes: size,
735
+ protocolVersion: PROTOCOL_VERSION,
736
+ attemptCount: 0,
737
+ nextAttemptAt: enqueuedAt
738
+ };
739
+ queue.push(item);
740
+ await persistentQueue?.put(item).catch(() => drop("persistent_queue_write_failed"));
741
+ scheduleFlush();
742
+ } catch {
743
+ drop("before_send_error");
744
+ }
745
+ }
746
+ function enqueue(event) {
747
+ pendingEventIds.add(event.eventId);
748
+ const promise = prepare(event).finally(() => {
749
+ pending.delete(promise);
750
+ pendingEventIds.delete(event.eventId);
751
+ });
752
+ pending.add(promise);
753
+ return event.eventId;
754
+ }
755
+ function baseEvent(shape, capture = {}) {
756
+ const context = {
757
+ ...user ? { user } : {},
758
+ contexts: Object.fromEntries(namedContext),
759
+ breadcrumbs: breadcrumbs.slice(-50),
760
+ ...capture.context === void 0 ? {} : { captured: capture.context },
761
+ contextMode: options.contextMode ?? "minimal"
762
+ };
763
+ const activeTrace = spanContextManager.active()?.getTraceContext();
764
+ const traceId = validTraceId2(capture.traceId) ? capture.traceId : activeTrace?.traceId;
765
+ const spanId = validSpanId2(capture.spanId) ? capture.spanId : activeTrace?.spanId;
766
+ return {
767
+ eventId: dependencies.createEventId(),
768
+ occurredAt: dependencies.now().toISOString(),
769
+ type: shape.type,
770
+ message: shape.message,
771
+ handled: capture.handled ?? false,
772
+ mechanism: capture.mechanism ?? "manual",
773
+ level: capture.level ?? "error",
774
+ stack: shape.stack,
775
+ tags: clientRedact(capture.tags ?? {}),
776
+ context: clientRedact(context),
777
+ ...traceId ? { traceId } : {},
778
+ ...spanId ? { spanId } : {}
779
+ };
780
+ }
781
+ function normalizedAttributeValue(value) {
782
+ if (typeof value === "number" && !Number.isFinite(value)) return void 0;
783
+ return typeof value === "string" ? clientRedactString(value).slice(0, 4096) : value;
784
+ }
785
+ function queueFinishedSpan(span) {
786
+ if (closed || !telemetryRequested) return;
787
+ const occurredAt = span.endedAt.toISOString();
788
+ const base = {
789
+ occurredAt,
790
+ traceId: span.traceId,
791
+ name: span.name,
792
+ startedAt: span.startedAt.toISOString(),
793
+ endedAt: occurredAt,
794
+ status: span.status,
795
+ attributes: clientRedact(span.attributes)
796
+ };
797
+ const now = dependencies.now().getTime();
798
+ if (!span.parentSpanId) {
799
+ telemetryQueue.push({
800
+ kind: "trace",
801
+ item: { telemetryId: dependencies.createEventId(), ...base },
802
+ attemptCount: 0,
803
+ nextAttemptAt: now
804
+ });
805
+ }
806
+ telemetryQueue.push({
807
+ kind: "span",
808
+ item: {
809
+ telemetryId: dependencies.createEventId(),
810
+ ...base,
811
+ spanId: span.spanId,
812
+ ...span.parentSpanId ? { parentSpanId: span.parentSpanId } : {}
813
+ },
814
+ attemptCount: 0,
815
+ nextAttemptAt: now
816
+ });
817
+ scheduleFlush();
818
+ }
819
+ function startSpan(name, spanOptions = {}) {
820
+ const parent = spanOptions.parent ?? spanContextManager.active()?.getTraceContext();
821
+ const traceId = validTraceId2(parent?.traceId) ? parent.traceId : dependencies.createTraceId();
822
+ const spanId = dependencies.createSpanId();
823
+ const parentSpanId = validSpanId2(parent?.spanId) ? parent.spanId : void 0;
824
+ const traceFlags = /^[0-9a-f]{2}$/.test(parent?.traceFlags ?? "") ? parent.traceFlags : telemetrySampleRate > 0 ? "01" : "00";
825
+ const startedAt = spanOptions.startTime && !Number.isNaN(spanOptions.startTime.getTime()) ? new Date(spanOptions.startTime) : dependencies.now();
826
+ const attributes = {};
827
+ for (const [key, value] of Object.entries(spanOptions.attributes ?? {})) {
828
+ const normalized = normalizedAttributeValue(value);
829
+ if (normalized !== void 0) attributes[key.slice(0, 256)] = normalized;
830
+ }
831
+ let status = "unset";
832
+ let ended = false;
833
+ const normalizedName = name.trim().slice(0, 1024) || "\u672A\u547D\u540D Span";
834
+ const traceContext = () => ({
835
+ traceId,
836
+ spanId,
837
+ traceFlags,
838
+ ...parent?.traceState ? { traceState: parent.traceState } : {}
839
+ });
840
+ return {
841
+ traceId,
842
+ spanId,
843
+ ...parentSpanId ? { parentSpanId } : {},
844
+ name: normalizedName,
845
+ setAttribute(key, value) {
846
+ if (ended) return;
847
+ const normalized = normalizedAttributeValue(value);
848
+ if (normalized !== void 0) attributes[key.slice(0, 256)] = normalized;
849
+ },
850
+ setStatus(value) {
851
+ if (!ended) status = value;
852
+ },
853
+ getTraceContext() {
854
+ return traceContext();
855
+ },
856
+ toTraceHeaders() {
857
+ return traceContextHeaders(traceContext());
858
+ },
859
+ end(value) {
860
+ if (ended) return;
861
+ if (value) status = value;
862
+ ended = true;
863
+ queueFinishedSpan({
864
+ traceId,
865
+ spanId,
866
+ ...parentSpanId ? { parentSpanId } : {},
867
+ name: normalizedName,
868
+ startedAt,
869
+ endedAt: dependencies.now(),
870
+ status,
871
+ attributes: { ...attributes }
872
+ });
873
+ }
874
+ };
875
+ }
876
+ function captureWebVital(vital) {
877
+ const telemetryId = dependencies.createEventId();
878
+ const names = /* @__PURE__ */ new Set(["CLS", "FCP", "FID", "INP", "LCP", "TTFB"]);
879
+ const ratings = /* @__PURE__ */ new Set(["good", "needs-improvement", "poor"]);
880
+ const occurredAt = vital.occurredAt instanceof Date ? new Date(vital.occurredAt) : new Date(vital.occurredAt ?? dependencies.now());
881
+ let pageUrl;
882
+ try {
883
+ pageUrl = new URL(vital.pageUrl);
884
+ if (pageUrl.protocol !== "http:" && pageUrl.protocol !== "https:") pageUrl = void 0;
885
+ } catch {
886
+ pageUrl = void 0;
887
+ }
888
+ if (closed || !telemetryRequested || !names.has(vital.name) || !ratings.has(vital.rating) || !Number.isFinite(vital.value) || vital.value < 0 || Number.isNaN(occurredAt.getTime()) || !pageUrl) {
889
+ drop(closed ? "closed" : "invalid_web_vital");
890
+ return telemetryId;
891
+ }
892
+ pageUrl.search = "";
893
+ pageUrl.hash = "";
894
+ const attributes = {};
895
+ for (const [key, value] of Object.entries(vital.attributes ?? {})) {
896
+ const normalized = normalizedAttributeValue(value);
897
+ if (normalized !== void 0) attributes[key.slice(0, 256)] = normalized;
898
+ }
899
+ telemetryQueue.push({
900
+ kind: "web_vital",
901
+ item: {
902
+ telemetryId,
903
+ occurredAt: occurredAt.toISOString(),
904
+ name: vital.name,
905
+ value: vital.value,
906
+ rating: vital.rating,
907
+ pageUrl: pageUrl.toString(),
908
+ ...vital.navigationType ? { navigationType: clientRedactString(vital.navigationType).slice(0, 128) } : {},
909
+ attributes: clientRedact(attributes)
910
+ },
911
+ attemptCount: 0,
912
+ nextAttemptAt: dependencies.now().getTime()
913
+ });
914
+ scheduleFlush();
915
+ return telemetryId;
916
+ }
917
+ function shouldIgnore(error, event) {
918
+ if (closed || remotelyDisabled) {
919
+ drop(closed ? "closed" : "remote_disabled");
920
+ return true;
921
+ }
922
+ if (options.ignoreError?.(error)) {
923
+ drop("ignored");
924
+ return true;
925
+ }
926
+ if (errorTypeAllowlist && !errorTypeAllowlist.has(event.type)) {
927
+ drop("remote_type_filter");
928
+ return true;
929
+ }
930
+ if (typeof error === "object" && error !== null) {
931
+ if (seenErrors.has(error)) {
932
+ drop("duplicate");
933
+ return true;
934
+ }
935
+ seenErrors.add(error);
936
+ }
937
+ const fingerprint = shallowFingerprint(event);
938
+ const seenAt = recentFingerprints.get(fingerprint);
939
+ const now = dependencies.now().getTime();
940
+ if (seenAt !== void 0 && now - seenAt < 2e3) {
941
+ drop("duplicate");
942
+ return true;
943
+ }
944
+ recentFingerprints.set(fingerprint, now);
945
+ if (dependencies.random() >= sampleRate) {
946
+ drop("sampled");
947
+ return true;
948
+ }
949
+ return false;
950
+ }
951
+ function envelope(events) {
952
+ return {
953
+ protocolVersion: PROTOCOL_VERSION,
954
+ sdkName: "@devkitio/faultlens",
955
+ sdkVersion: SDK_VERSION,
956
+ clientType: options.ingest.mode === "relay" ? "relay" : "browser",
957
+ expectedEnvironment: options.expectedEnvironment,
958
+ release: options.release,
959
+ dist: options.dist ?? "default",
960
+ buildId: options.buildId ?? options.release,
961
+ clientBatchId: dependencies.createEventId(),
962
+ events
963
+ };
964
+ }
965
+ async function performEventFlush() {
966
+ await restorePromise;
967
+ await Promise.all([...pending]);
968
+ const now = dependencies.now().getTime();
969
+ const expiredBefore = now - 24 * 60 * 60 * 1e3;
970
+ const expiredIds = queue.filter((item) => item.enqueuedAt < expiredBefore).map((item) => item.id);
971
+ if (expiredIds.length > 0) {
972
+ const expired = new Set(expiredIds);
973
+ queue = queue.filter((item) => !expired.has(item.id));
974
+ await persistentQueue?.remove(expiredIds).catch(() => void 0);
975
+ drop("queue_expired");
976
+ }
977
+ if (queue.length === 0) return true;
978
+ const batch = queue.filter((item) => item.nextAttemptAt <= now).slice(0, 20);
979
+ if (batch.length === 0) {
980
+ scheduleNextQueuedFlush();
981
+ return false;
982
+ }
983
+ if (sendLease && !sendLease.acquire(now)) {
984
+ scheduleFlush(250);
985
+ return false;
986
+ }
987
+ recentSendStatus = "sending";
988
+ let outcome;
989
+ try {
990
+ outcome = await dependencies.transport.send(envelope(batch.map((item) => item.event)));
991
+ } catch {
992
+ outcome = { kind: "network_error", code: "network_error" };
993
+ } finally {
994
+ sendLease?.release();
995
+ }
996
+ if (outcome.kind === "batch") {
997
+ const expectedIds = new Set(batch.map((item) => item.id));
998
+ const seenIds = /* @__PURE__ */ new Set();
999
+ const validResults = outcome.results.every((result) => {
1000
+ if (!expectedIds.has(result.eventId) || seenIds.has(result.eventId)) return false;
1001
+ seenIds.add(result.eventId);
1002
+ return true;
1003
+ }) && seenIds.size === expectedIds.size;
1004
+ if (!validResults) {
1005
+ queue = queue.map(
1006
+ (item) => expectedIds.has(item.id) ? retryItem(item, outcome.retryAfterMs, "invalid_ingest_response") : item
1007
+ );
1008
+ await persistRetriedItems(queue.filter((item) => expectedIds.has(item.id)));
1009
+ recentSendStatus = "retryable";
1010
+ scheduleNextQueuedFlush();
1011
+ return false;
1012
+ }
1013
+ const byId = new Map(outcome.results.map((result) => [result.eventId, result]));
1014
+ const removedIds = [];
1015
+ let hasRetryable = false;
1016
+ let hasRejected = false;
1017
+ queue = queue.flatMap((item) => {
1018
+ if (!expectedIds.has(item.id)) return [item];
1019
+ const result = byId.get(item.id);
1020
+ if (result.status === "retryable") {
1021
+ hasRetryable = true;
1022
+ return [retryItem(item, outcome.retryAfterMs, result.errorCode ?? "retryable")];
1023
+ }
1024
+ removedIds.push(item.id);
1025
+ if (result.status === "rejected") {
1026
+ hasRejected = true;
1027
+ drop(result.errorCode ?? "rejected");
1028
+ }
1029
+ return [];
1030
+ });
1031
+ await persistRetriedItems(
1032
+ queue.filter((item) => expectedIds.has(item.id) && byId.get(item.id)?.status === "retryable")
1033
+ );
1034
+ await persistentQueue?.remove(removedIds).catch(() => void 0);
1035
+ recentSendStatus = hasRetryable ? "retryable" : hasRejected ? "permanent_error" : "accepted";
1036
+ if (hasRetryable) lastRateLimitedAt = dependencies.now().toISOString();
1037
+ scheduleNextQueuedFlush();
1038
+ return queue.length === 0;
1039
+ }
1040
+ if (outcome.kind === "permanent_error") {
1041
+ if (REVOKED_CREDENTIAL_CODES.has(outcome.code)) {
1042
+ remotelyDisabled = true;
1043
+ queue = [];
1044
+ await persistentQueue?.clear().catch(() => void 0);
1045
+ } else {
1046
+ const removedIds = new Set(batch.map((item) => item.id));
1047
+ queue = queue.filter((item) => !removedIds.has(item.id));
1048
+ await persistentQueue?.remove([...removedIds]).catch(() => void 0);
1049
+ }
1050
+ recentSendStatus = "permanent_error";
1051
+ drop(outcome.code);
1052
+ scheduleNextQueuedFlush();
1053
+ return queue.length === 0;
1054
+ }
1055
+ const retryIds = new Set(batch.map((item) => item.id));
1056
+ const retryAfterMs3 = outcome.kind === "retryable" ? outcome.retryAfterMs : 0;
1057
+ queue = queue.map(
1058
+ (item) => retryIds.has(item.id) ? retryItem(item, retryAfterMs3, outcome.code) : item
1059
+ );
1060
+ await persistRetriedItems(queue.filter((item) => retryIds.has(item.id)));
1061
+ recentSendStatus = "retryable";
1062
+ if (outcome.kind === "retryable") lastRateLimitedAt = dependencies.now().toISOString();
1063
+ scheduleNextQueuedFlush();
1064
+ return false;
1065
+ }
1066
+ function telemetryEnvelope(items) {
1067
+ return {
1068
+ protocolVersion: PROTOCOL_VERSION,
1069
+ sdkName: "@devkitio/faultlens",
1070
+ sdkVersion: SDK_VERSION,
1071
+ clientType: options.ingest.mode === "relay" ? "relay" : "browser",
1072
+ expectedEnvironment: options.expectedEnvironment,
1073
+ release: options.release,
1074
+ dist: options.dist ?? "default",
1075
+ buildId: options.buildId ?? options.release,
1076
+ clientBatchId: dependencies.createEventId(),
1077
+ traces: items.flatMap((item) => item.kind === "trace" ? [item.item] : []),
1078
+ spans: items.flatMap((item) => item.kind === "span" ? [item.item] : []),
1079
+ webVitals: items.flatMap((item) => item.kind === "web_vital" ? [item.item] : [])
1080
+ };
1081
+ }
1082
+ function telemetryRetryDelay(item, serverDelayMs) {
1083
+ const jittered = 500 * 2 ** Math.min(10, item.attemptCount) * (0.5 + dependencies.random() * 0.5);
1084
+ return Math.min(3e4, Math.max(serverDelayMs, Math.round(jittered)));
1085
+ }
1086
+ async function performTelemetryFlush() {
1087
+ await remoteConfigPromise;
1088
+ if (telemetryQueue.length === 0) return true;
1089
+ if (!telemetryEnabled || telemetryUnsupported) {
1090
+ telemetryQueue = [];
1091
+ drop(telemetryUnsupported ? "telemetry_unsupported" : "telemetry_disabled");
1092
+ return true;
1093
+ }
1094
+ telemetryQueue = telemetryQueue.filter((queued) => {
1095
+ const samplingKey = queued.kind === "web_vital" ? queued.item.telemetryId : queued.item.traceId;
1096
+ let sampled = telemetrySamplingDecisions.get(samplingKey);
1097
+ if (sampled === void 0) {
1098
+ sampled = dependencies.random() < telemetrySampleRate;
1099
+ telemetrySamplingDecisions.set(samplingKey, sampled);
1100
+ }
1101
+ if (!sampled) drop("telemetry_sampled");
1102
+ return sampled;
1103
+ });
1104
+ if (telemetryQueue.length === 0) return true;
1105
+ const now = dependencies.now().getTime();
1106
+ const batch = telemetryQueue.filter((item) => item.nextAttemptAt <= now).slice(0, 100);
1107
+ if (batch.length === 0) {
1108
+ const nextAttemptAt = Math.min(...telemetryQueue.map((item) => item.nextAttemptAt));
1109
+ scheduleFlush(Math.max(0, nextAttemptAt - now));
1110
+ return false;
1111
+ }
1112
+ const ids = new Set(batch.map((item) => item.item.telemetryId));
1113
+ let outcome;
1114
+ try {
1115
+ outcome = await dependencies.telemetryTransport.send(telemetryEnvelope(batch));
1116
+ } catch {
1117
+ outcome = { kind: "network_error", code: "network_error" };
1118
+ }
1119
+ if (outcome.kind === "unsupported") {
1120
+ telemetryUnsupported = true;
1121
+ telemetryEnabled = false;
1122
+ telemetryQueue = [];
1123
+ drop("telemetry_unsupported");
1124
+ return true;
1125
+ }
1126
+ if (outcome.kind === "batch") {
1127
+ const byId = new Map(outcome.results.map((result) => [result.telemetryId, result]));
1128
+ const complete = outcome.results.length === ids.size && [...ids].every((id) => byId.has(id));
1129
+ telemetryQueue = telemetryQueue.flatMap((item) => {
1130
+ if (!ids.has(item.item.telemetryId)) return [item];
1131
+ const result = complete ? byId.get(item.item.telemetryId) : void 0;
1132
+ if (!result || result.status === "retryable") {
1133
+ const attemptCount = item.attemptCount + 1;
1134
+ return [{
1135
+ ...item,
1136
+ attemptCount,
1137
+ nextAttemptAt: dependencies.now().getTime() + telemetryRetryDelay({ ...item, attemptCount }, outcome.retryAfterMs)
1138
+ }];
1139
+ }
1140
+ if (result.status === "rejected") drop(result.errorCode ?? "telemetry_rejected");
1141
+ return [];
1142
+ });
1143
+ } else if (outcome.kind === "permanent_error") {
1144
+ telemetryQueue = telemetryQueue.filter((item) => !ids.has(item.item.telemetryId));
1145
+ drop(outcome.code);
1146
+ } else {
1147
+ const serverDelayMs = outcome.kind === "retryable" ? outcome.retryAfterMs : 0;
1148
+ telemetryQueue = telemetryQueue.map((item) => {
1149
+ if (!ids.has(item.item.telemetryId)) return item;
1150
+ const attemptCount = item.attemptCount + 1;
1151
+ return {
1152
+ ...item,
1153
+ attemptCount,
1154
+ nextAttemptAt: dependencies.now().getTime() + telemetryRetryDelay({ ...item, attemptCount }, serverDelayMs)
1155
+ };
1156
+ });
1157
+ }
1158
+ if (telemetryQueue.length > 0) {
1159
+ const nextAttemptAt = Math.min(...telemetryQueue.map((item) => item.nextAttemptAt));
1160
+ scheduleFlush(Math.max(0, nextAttemptAt - dependencies.now().getTime()));
1161
+ }
1162
+ return telemetryQueue.length === 0;
1163
+ }
1164
+ async function performFlush() {
1165
+ const [eventResult] = await Promise.all([performEventFlush(), performTelemetryFlush()]);
1166
+ return eventResult;
1167
+ }
1168
+ function flushInternal() {
1169
+ activeFlush ??= performFlush().finally(() => {
1170
+ activeFlush = void 0;
1171
+ });
1172
+ return activeFlush;
1173
+ }
1174
+ async function flush(timeout) {
1175
+ if (sendTimer) {
1176
+ clearTimeout(sendTimer);
1177
+ sendTimer = void 0;
1178
+ sendTimerDueAt = void 0;
1179
+ }
1180
+ let timer;
1181
+ try {
1182
+ return await Promise.race([
1183
+ flushInternal(),
1184
+ new Promise((resolve) => {
1185
+ timer = setTimeout(() => resolve(false), Math.max(0, timeout));
1186
+ })
1187
+ ]);
1188
+ } finally {
1189
+ if (timer) clearTimeout(timer);
1190
+ }
1191
+ }
1192
+ const monitor = {
1193
+ captureException(error, capture = {}) {
1194
+ const event = baseEvent(errorShape(error), capture);
1195
+ if (shouldIgnore(error, event)) return event.eventId;
1196
+ return enqueue(event);
1197
+ },
1198
+ captureMessage(message, capture = {}) {
1199
+ const event = baseEvent({ type: "Message", message: message.slice(0, 32768), stack: [] }, capture);
1200
+ if (shouldIgnore(message, event)) return event.eventId;
1201
+ return enqueue(event);
1202
+ },
1203
+ addBreadcrumb(breadcrumb) {
1204
+ breadcrumbs.push(
1205
+ clientRedact({
1206
+ ...breadcrumb,
1207
+ timestamp: breadcrumb.timestamp ?? dependencies.now().toISOString()
1208
+ })
1209
+ );
1210
+ if (breadcrumbs.length > 50) breadcrumbs.shift();
1211
+ },
1212
+ setUser(value) {
1213
+ user = value ? clientRedact(value) : null;
1214
+ },
1215
+ setContext(name, value) {
1216
+ namedContext.set(name.slice(0, 128), clientRedact(value));
1217
+ },
1218
+ startSpan,
1219
+ withSpan(name, callback, spanOptions = {}) {
1220
+ const span = startSpan(name, spanOptions);
1221
+ return spanContextManager.run(span, () => {
1222
+ try {
1223
+ const result = callback(span);
1224
+ if (result && typeof result.then === "function") {
1225
+ return Promise.resolve(result).then(
1226
+ (value) => {
1227
+ span.end("ok");
1228
+ return value;
1229
+ },
1230
+ (error) => {
1231
+ span.end("error");
1232
+ throw error;
1233
+ }
1234
+ );
1235
+ }
1236
+ span.end("ok");
1237
+ return result;
1238
+ } catch (error) {
1239
+ span.end("error");
1240
+ throw error;
1241
+ }
1242
+ });
1243
+ },
1244
+ captureWebVital,
1245
+ getStatus() {
1246
+ const queuedEventIds = new Set(queue.map((item) => item.event.eventId));
1247
+ return {
1248
+ queueLength: (/* @__PURE__ */ new Set([...queuedEventIds, ...pendingEventIds])).size,
1249
+ recentSendStatus,
1250
+ droppedReasons: { ...droppedReasons },
1251
+ protocolVersion: PROTOCOL_VERSION,
1252
+ sdkVersion: SDK_VERSION,
1253
+ lastRateLimitedAt,
1254
+ remotelyDisabled,
1255
+ telemetryEnabled,
1256
+ telemetryQueueLength: telemetryQueue.length,
1257
+ remoteConfigLastRefreshAt,
1258
+ remoteConfigLastSuccessAt,
1259
+ remoteConfigFailureCode,
1260
+ remoteConfigRefreshFailures
1261
+ };
1262
+ },
1263
+ flush,
1264
+ async close() {
1265
+ closed = true;
1266
+ if (sendTimer) clearTimeout(sendTimer);
1267
+ if (remoteConfigTimer) clearTimeout(remoteConfigTimer);
1268
+ sendTimerDueAt = void 0;
1269
+ return flush(2e3);
1270
+ }
1271
+ };
1272
+ const transportPath = options.ingest.mode === "relay" ? options.ingest.endpoint : new URL(options.ingest.dsn).pathname;
1273
+ const removeGlobalHandlers = overrides.installGlobalHandlers?.(monitor, transportPath) ?? (() => void 0);
1274
+ const originalClose = monitor.close;
1275
+ monitor.close = async () => {
1276
+ removeGlobalHandlers();
1277
+ return originalClose();
1278
+ };
1279
+ return monitor;
1280
+ }
1281
+
1282
+ export { FetchTelemetryTransport, SDK_VERSION, clientRedact, createMonitorRuntime, formatTraceParent, parseErrorStack, parseRemoteSdkConfig, parseTelemetryTransportResponse, parseTraceParent, parseTransportResponse, traceContextHeaders };