@debugkit/sdk 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -3
- package/dist/chunk-5PCBQL5S.js +655 -0
- package/dist/chunk-5PCBQL5S.js.map +1 -0
- package/dist/{chunk-KUJ4EW3D.cjs → chunk-EH2FC54I.cjs} +10 -10
- package/dist/chunk-EH2FC54I.cjs.map +1 -0
- package/dist/{chunk-NSWUGBA2.cjs → chunk-JWCGEOCU.cjs} +20 -12
- package/dist/chunk-JWCGEOCU.cjs.map +1 -0
- package/dist/chunk-Q33W6RAG.cjs +667 -0
- package/dist/chunk-Q33W6RAG.cjs.map +1 -0
- package/dist/{chunk-BD2FNVCP.js → chunk-T56RQGKR.js} +15 -7
- package/dist/chunk-T56RQGKR.js.map +1 -0
- package/dist/{chunk-3UN5VMQU.js → chunk-TRUHD4EU.js} +5 -5
- package/dist/chunk-TRUHD4EU.js.map +1 -0
- package/dist/client-VakEOlcU.d.cts +222 -0
- package/dist/client-VakEOlcU.d.ts +222 -0
- package/dist/expo-router.cjs +4 -4
- package/dist/expo-router.d.cts +1 -1
- package/dist/expo-router.d.ts +1 -1
- package/dist/expo-router.js +2 -2
- package/dist/expo.cjs +13 -13
- package/dist/expo.d.cts +1 -1
- package/dist/expo.d.ts +1 -1
- package/dist/expo.js +5 -5
- package/dist/index.cjs +12 -8
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/node.cjs +69 -14
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +4 -7
- package/dist/node.d.ts +4 -7
- package/dist/node.js +55 -5
- package/dist/node.js.map +1 -1
- package/dist/react-native.cjs +10 -10
- package/dist/react-native.d.cts +1 -1
- package/dist/react-native.d.ts +1 -1
- package/dist/react-native.js +3 -3
- package/dist/react.cjs +6 -6
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-3UN5VMQU.js.map +0 -1
- package/dist/chunk-BD2FNVCP.js.map +0 -1
- package/dist/chunk-KUJ4EW3D.cjs.map +0 -1
- package/dist/chunk-NSWUGBA2.cjs.map +0 -1
- package/dist/chunk-VIJ5USKQ.js +0 -300
- package/dist/chunk-VIJ5USKQ.js.map +0 -1
- package/dist/chunk-XEFWRCN4.cjs +0 -311
- package/dist/chunk-XEFWRCN4.cjs.map +0 -1
- package/dist/client-drmb0n15.d.cts +0 -103
- package/dist/client-drmb0n15.d.ts +0 -103
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
|
|
7
|
+
// ../schema/src/core.ts
|
|
8
|
+
var V8 = /^\s*at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?\s*$/;
|
|
9
|
+
var GECKO = /^\s*(.*?)@(.+?):(\d+):(\d+)\s*$/;
|
|
10
|
+
var NOT_IN_APP = [/node_modules/, /\bnative\b/, /\[native code\]/, /InternalBytecode/];
|
|
11
|
+
var RN_BUNDLE = /\.(?:js)?bundle\b/;
|
|
12
|
+
function cleanFile(file) {
|
|
13
|
+
return file.replace(/^address at /, "");
|
|
14
|
+
}
|
|
15
|
+
function parseStack(stack) {
|
|
16
|
+
var _a;
|
|
17
|
+
if (!stack) return [];
|
|
18
|
+
const frames = [];
|
|
19
|
+
for (const raw of stack.split("\n")) {
|
|
20
|
+
const m = (_a = V8.exec(raw)) != null ? _a : GECKO.exec(raw);
|
|
21
|
+
if (!m) continue;
|
|
22
|
+
const [, fn, rawFile, line, col] = m;
|
|
23
|
+
const file = cleanFile(rawFile != null ? rawFile : "");
|
|
24
|
+
frames.push({
|
|
25
|
+
fn: fn || void 0,
|
|
26
|
+
file,
|
|
27
|
+
line: Number(line),
|
|
28
|
+
col: Number(col),
|
|
29
|
+
inApp: RN_BUNDLE.test(file) ? !/^(?:anonymous|eval|global code)$/.test(fn != null ? fn : "") : !NOT_IN_APP.some((re) => re.test(file))
|
|
30
|
+
});
|
|
31
|
+
if (frames.length >= 100) break;
|
|
32
|
+
}
|
|
33
|
+
return frames;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/context.ts
|
|
37
|
+
function mergeContext(a, b) {
|
|
38
|
+
const out = { ...a };
|
|
39
|
+
for (const [k, v] of Object.entries(b)) {
|
|
40
|
+
const prev = out[k];
|
|
41
|
+
out[k] = v && typeof v === "object" && prev && typeof prev === "object" ? { ...prev, ...v } : v != null ? v : prev;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function safe(fn) {
|
|
46
|
+
try {
|
|
47
|
+
return fn();
|
|
48
|
+
} catch {
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function timezone() {
|
|
53
|
+
return safe(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
54
|
+
}
|
|
55
|
+
function locale() {
|
|
56
|
+
return safe(() => Intl.DateTimeFormat().resolvedOptions().locale);
|
|
57
|
+
}
|
|
58
|
+
function browserContext() {
|
|
59
|
+
var _a, _b, _c;
|
|
60
|
+
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
61
|
+
const ua2 = typeof navigator !== "undefined" ? navigator.userAgent : void 0;
|
|
62
|
+
const node = typeof process !== "undefined" ? (_a = process.versions) == null ? void 0 : _a.node : void 0;
|
|
63
|
+
return {
|
|
64
|
+
runtime: { name: ua2 === "Cloudflare-Workers" ? "cloudflare-workers" : node ? "node" : "edge", version: node },
|
|
65
|
+
os: { name: "server" },
|
|
66
|
+
timezone: timezone()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const ua = navigator.userAgent;
|
|
70
|
+
const android = (_b = /Android ([\d.]+)/.exec(ua)) == null ? void 0 : _b[1];
|
|
71
|
+
const ios = (_c = /(?:iPhone|iPad|iPod).*? OS (\d+[_\d]*)/.exec(ua)) == null ? void 0 : _c[1];
|
|
72
|
+
const os = android ? { name: "android", version: android } : ios ? { name: "ios", version: ios.replace(/_/g, ".") } : /Mac OS X/.test(ua) ? { name: "macos" } : /Windows/.test(ua) ? { name: "windows" } : /Linux/.test(ua) ? { name: "linux" } : { name: "web" };
|
|
73
|
+
const conn = navigator.connection;
|
|
74
|
+
const browser = /Edg\/([\d.]+)/.exec(ua) ? { name: "edge", v: /Edg\/([\d.]+)/.exec(ua)[1] } : /Chrome\/([\d.]+)/.exec(ua) ? { name: "chrome", v: /Chrome\/([\d.]+)/.exec(ua)[1] } : /Version\/([\d.]+).*Safari/.exec(ua) ? { name: "safari", v: /Version\/([\d.]+)/.exec(ua)[1] } : /Firefox\/([\d.]+)/.exec(ua) ? { name: "firefox", v: /Firefox\/([\d.]+)/.exec(ua)[1] } : void 0;
|
|
75
|
+
return {
|
|
76
|
+
os,
|
|
77
|
+
runtime: { name: "browser", version: browser ? `${browser.name} ${browser.v}` : void 0 },
|
|
78
|
+
device: {
|
|
79
|
+
type: /iPad|Tablet/.test(ua) ? "tablet" : /Mobi/.test(ua) ? "phone" : "desktop",
|
|
80
|
+
screen: typeof screen !== "undefined" ? { width: screen.width, height: screen.height, scale: window.devicePixelRatio } : void 0
|
|
81
|
+
},
|
|
82
|
+
network: conn ? {
|
|
83
|
+
type: conn.type === "wifi" ? "wifi" : conn.type === "cellular" ? "cellular" : "unknown",
|
|
84
|
+
effectiveType: conn.effectiveType,
|
|
85
|
+
isConnected: navigator.onLine
|
|
86
|
+
} : void 0,
|
|
87
|
+
locale: navigator.language,
|
|
88
|
+
timezone: timezone()
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/auto.ts
|
|
93
|
+
var g = globalThis;
|
|
94
|
+
var ID_SEGMENT = /^(?:\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{16,}|(?=[^/]*\d)[\w-]{20,})$/i;
|
|
95
|
+
function normaliseUrl(raw) {
|
|
96
|
+
var _a;
|
|
97
|
+
const m = /^(?:([a-z][a-z0-9+.-]*:)?\/\/([^/?#]*))?([^?#]*)/i.exec(raw.trim());
|
|
98
|
+
let host = (_a = m == null ? void 0 : m[2]) != null ? _a : "";
|
|
99
|
+
const path2 = ((m == null ? void 0 : m[3]) || "/").split("/").map((s) => ID_SEGMENT.test(s) ? ":id" : s).join("/");
|
|
100
|
+
if (host.includes("@")) host = host.slice(host.lastIndexOf("@") + 1);
|
|
101
|
+
const here = typeof location !== "undefined" ? location.host : void 0;
|
|
102
|
+
return !host || host === here ? path2 : `${host}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
103
|
+
}
|
|
104
|
+
var insideFetch = 0;
|
|
105
|
+
function instrumentFetch(sink) {
|
|
106
|
+
const orig = g.fetch;
|
|
107
|
+
if (typeof orig !== "function" || orig.__debug) return;
|
|
108
|
+
const wrapped = function(input, init) {
|
|
109
|
+
var _a, _b, _c, _d;
|
|
110
|
+
const url = typeof input === "string" ? input : (_b = (_a = input == null ? void 0 : input.href) != null ? _a : input == null ? void 0 : input.url) != null ? _b : String(input);
|
|
111
|
+
if (sink.ignored(url)) return orig.call(this, input, init);
|
|
112
|
+
const method = String((_d = (_c = init == null ? void 0 : init.method) != null ? _c : input == null ? void 0 : input.method) != null ? _d : "GET").toUpperCase();
|
|
113
|
+
const start = Date.now();
|
|
114
|
+
const stack = new Error().stack;
|
|
115
|
+
let p;
|
|
116
|
+
insideFetch++;
|
|
117
|
+
try {
|
|
118
|
+
p = orig.call(this, input, init);
|
|
119
|
+
} finally {
|
|
120
|
+
insideFetch--;
|
|
121
|
+
}
|
|
122
|
+
return p.then(
|
|
123
|
+
(res) => {
|
|
124
|
+
sink.request({ method, url, status: res.status, durationMs: Date.now() - start, stack });
|
|
125
|
+
return res;
|
|
126
|
+
},
|
|
127
|
+
(error) => {
|
|
128
|
+
const aborted = (error == null ? void 0 : error.name) === "AbortError";
|
|
129
|
+
sink.request({ method, url, error, aborted, durationMs: Date.now() - start, stack });
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
wrapped.__debug = true;
|
|
135
|
+
g.fetch = wrapped;
|
|
136
|
+
return () => {
|
|
137
|
+
if (g.fetch === wrapped) g.fetch = orig;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function instrumentXhr(sink) {
|
|
141
|
+
const X = g.XMLHttpRequest;
|
|
142
|
+
const proto = X == null ? void 0 : X.prototype;
|
|
143
|
+
if (!proto || proto.open.__debug) return;
|
|
144
|
+
const open = proto.open;
|
|
145
|
+
const send = proto.send;
|
|
146
|
+
const wrappedOpen = function(method, url, ...rest) {
|
|
147
|
+
const u = String(url);
|
|
148
|
+
this.__debug = insideFetch > 0 || sink.ignored(u) ? null : { method: String(method).toUpperCase(), url: u };
|
|
149
|
+
return open.call(this, method, url, ...rest);
|
|
150
|
+
};
|
|
151
|
+
const wrappedSend = function(...args) {
|
|
152
|
+
const d = this.__debug;
|
|
153
|
+
if (d) {
|
|
154
|
+
const start = Date.now();
|
|
155
|
+
const stack = new Error().stack;
|
|
156
|
+
let aborted = false;
|
|
157
|
+
let timedOut = false;
|
|
158
|
+
this.addEventListener("abort", () => aborted = true);
|
|
159
|
+
this.addEventListener("timeout", () => timedOut = true);
|
|
160
|
+
this.addEventListener(
|
|
161
|
+
"loadend",
|
|
162
|
+
() => sink.request({ ...d, status: this.status || void 0, aborted, timedOut, durationMs: Date.now() - start, stack })
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return send.apply(this, args);
|
|
166
|
+
};
|
|
167
|
+
wrappedOpen.__debug = true;
|
|
168
|
+
proto.open = wrappedOpen;
|
|
169
|
+
proto.send = wrappedSend;
|
|
170
|
+
return () => {
|
|
171
|
+
proto.open = open;
|
|
172
|
+
proto.send = send;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function format(arg) {
|
|
176
|
+
var _a;
|
|
177
|
+
if (typeof arg === "string") return arg;
|
|
178
|
+
if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
|
|
179
|
+
try {
|
|
180
|
+
return (_a = JSON.stringify(arg)) != null ? _a : String(arg);
|
|
181
|
+
} catch {
|
|
182
|
+
return String(arg);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function instrumentConsole(sink) {
|
|
186
|
+
const c = g.console;
|
|
187
|
+
if (!c) return;
|
|
188
|
+
const restores = [];
|
|
189
|
+
for (const [method, level] of [["warn", "warning"], ["error", "error"]]) {
|
|
190
|
+
const orig = c[method];
|
|
191
|
+
if (typeof orig !== "function") continue;
|
|
192
|
+
c[method] = function(...args) {
|
|
193
|
+
if (args[0] !== "[deBug]") sink.crumb("log", args.map(format).join(" ").slice(0, 500), level);
|
|
194
|
+
return orig.apply(this, args);
|
|
195
|
+
};
|
|
196
|
+
restores.push(() => c[method] = orig);
|
|
197
|
+
}
|
|
198
|
+
return () => restores.forEach((r) => r());
|
|
199
|
+
}
|
|
200
|
+
var path = () => typeof location !== "undefined" ? location.pathname : "";
|
|
201
|
+
function instrumentHistory(sink) {
|
|
202
|
+
const h = g.history;
|
|
203
|
+
if (typeof window === "undefined" || !(h == null ? void 0 : h.pushState) || typeof g.addEventListener !== "function") return;
|
|
204
|
+
const orig = { pushState: h.pushState, replaceState: h.replaceState };
|
|
205
|
+
for (const k of ["pushState", "replaceState"]) {
|
|
206
|
+
h[k] = function(...args) {
|
|
207
|
+
const out = orig[k].apply(this, args);
|
|
208
|
+
sink.navigated(path());
|
|
209
|
+
return out;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const onPop = () => sink.navigated(path());
|
|
213
|
+
g.addEventListener("popstate", onPop);
|
|
214
|
+
sink.navigated(path());
|
|
215
|
+
return () => {
|
|
216
|
+
Object.assign(h, orig);
|
|
217
|
+
g.removeEventListener("popstate", onPop);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
var CLICKABLE = 'button,a,[role="button"],[role="link"],[role="tab"],[role="menuitem"],input[type="submit"],input[type="button"],input[type="checkbox"],input[type="radio"],summary,label,[data-testid]';
|
|
221
|
+
function describeElement(el) {
|
|
222
|
+
const tag = el.tagName.toLowerCase();
|
|
223
|
+
const label = el.getAttribute("aria-label") || el.getAttribute("data-testid") || el.getAttribute("title") || (tag === "input" ? el.getAttribute("name") || el.type : el.innerText || el.textContent || "");
|
|
224
|
+
const id = el.id ? `#${el.id}` : "";
|
|
225
|
+
const text = label.replace(/\s+/g, " ").trim().slice(0, 60);
|
|
226
|
+
return `${tag}${id}${text ? ` "${text}"` : ""}`;
|
|
227
|
+
}
|
|
228
|
+
function instrumentClicks(sink) {
|
|
229
|
+
const d = g.document;
|
|
230
|
+
if (!(d == null ? void 0 : d.addEventListener)) return;
|
|
231
|
+
const onClick = (ev) => {
|
|
232
|
+
var _a;
|
|
233
|
+
const t = ev.target;
|
|
234
|
+
const el = (_a = t == null ? void 0 : t.closest) == null ? void 0 : _a.call(t, CLICKABLE);
|
|
235
|
+
if (el) sink.crumb("ui", `Clicked ${describeElement(el)}`);
|
|
236
|
+
};
|
|
237
|
+
d.addEventListener("click", onClick, true);
|
|
238
|
+
return () => d.removeEventListener("click", onClick, true);
|
|
239
|
+
}
|
|
240
|
+
function instrumentWebLifecycle(sink) {
|
|
241
|
+
const d = g.document;
|
|
242
|
+
if (typeof window === "undefined" || !(d == null ? void 0 : d.addEventListener)) return;
|
|
243
|
+
const onVis = () => sink.lifecycle(d.visibilityState === "hidden" ? "background" : "foreground");
|
|
244
|
+
const onOnline = () => sink.crumb("network", "Device went online");
|
|
245
|
+
const onOffline = () => sink.crumb("network", "Device went offline", "warning");
|
|
246
|
+
d.addEventListener("visibilitychange", onVis);
|
|
247
|
+
g.addEventListener("online", onOnline);
|
|
248
|
+
g.addEventListener("offline", onOffline);
|
|
249
|
+
return () => {
|
|
250
|
+
d.removeEventListener("visibilitychange", onVis);
|
|
251
|
+
g.removeEventListener("online", onOnline);
|
|
252
|
+
g.removeEventListener("offline", onOffline);
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/client.ts
|
|
257
|
+
var SDK_NAME = "@debugkit/sdk";
|
|
258
|
+
var SDK_VERSION = "0.3.0";
|
|
259
|
+
var QUEUE_KEY = "@debugkit/queue";
|
|
260
|
+
var MAX_QUEUE = 500;
|
|
261
|
+
var uid = () => {
|
|
262
|
+
var _a, _b, _c;
|
|
263
|
+
return ((_c = (_b = (_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) == null ? void 0 : _b.call(_a)) != null ? _c : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`).replace(/-/g, "");
|
|
264
|
+
};
|
|
265
|
+
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
266
|
+
var DebugClient = class {
|
|
267
|
+
constructor() {
|
|
268
|
+
__publicField(this, "opts");
|
|
269
|
+
__publicField(this, "queue", []);
|
|
270
|
+
__publicField(this, "crumbs", []);
|
|
271
|
+
__publicField(this, "session", { id: uid(), startedAt: now() });
|
|
272
|
+
__publicField(this, "user", { anonymousId: uid() });
|
|
273
|
+
__publicField(this, "timer");
|
|
274
|
+
__publicField(this, "flushing");
|
|
275
|
+
__publicField(this, "backoffUntil", 0);
|
|
276
|
+
__publicField(this, "failures", 0);
|
|
277
|
+
__publicField(this, "ready", false);
|
|
278
|
+
__publicField(this, "handlersInstalled", false);
|
|
279
|
+
__publicField(this, "auto", { errors: false, network: false, console: false, navigation: false, clicks: false, lifecycle: false, sessions: false });
|
|
280
|
+
__publicField(this, "restores", []);
|
|
281
|
+
__publicField(this, "sessionState", { errored: false, crashed: false });
|
|
282
|
+
__publicField(this, "lastNav");
|
|
283
|
+
__publicField(this, "limits", /* @__PURE__ */ new Map());
|
|
284
|
+
/** fetch as it was before instrumentation, so our own uploads are never recorded. */
|
|
285
|
+
__publicField(this, "rawFetch");
|
|
286
|
+
/** Set when the ingest rejects 0.2 fields: it predates them, so send what it understands. */
|
|
287
|
+
__publicField(this, "legacyIngest", false);
|
|
288
|
+
}
|
|
289
|
+
init(options) {
|
|
290
|
+
var _a, _b, _c;
|
|
291
|
+
if (this.timer) clearInterval(this.timer);
|
|
292
|
+
this.uninstrument();
|
|
293
|
+
this.opts = { flushIntervalMs: 5e3, maxBatch: 50, maxBreadcrumbs: 30, ...options };
|
|
294
|
+
if (!this.opts.context && typeof window !== "undefined") this.opts.context = browserContext();
|
|
295
|
+
this.auto = resolveAuto(options);
|
|
296
|
+
this.rawFetch = typeof globalThis.fetch === "function" ? (_a = globalThis.fetch.__raw) != null ? _a : globalThis.fetch : void 0;
|
|
297
|
+
this.ready = true;
|
|
298
|
+
void this.restore();
|
|
299
|
+
if (this.auto.errors && !this.handlersInstalled) this.installGlobalHandlers();
|
|
300
|
+
this.instrument();
|
|
301
|
+
if (this.auto.sessions) this.startSession();
|
|
302
|
+
if (this.opts.flushIntervalMs > 0) {
|
|
303
|
+
this.timer = setInterval(() => void this.flush(), this.opts.flushIntervalMs);
|
|
304
|
+
(_c = (_b = this.timer).unref) == null ? void 0 : _c.call(_b);
|
|
305
|
+
}
|
|
306
|
+
return this;
|
|
307
|
+
}
|
|
308
|
+
/** Which automatic captures are on, after defaults. */
|
|
309
|
+
get autoCapture() {
|
|
310
|
+
return this.auto;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Begin a new session: a fresh id and a "started" marker. Called by `init()`, and by the
|
|
314
|
+
* React Native entry when the app returns after 30 minutes in the background.
|
|
315
|
+
*/
|
|
316
|
+
startSession() {
|
|
317
|
+
if (!this.ready) return;
|
|
318
|
+
void this.flush();
|
|
319
|
+
this.session = { id: uid(), startedAt: now() };
|
|
320
|
+
this.sessionState = { errored: false, crashed: false };
|
|
321
|
+
if (this.auto.sessions) this.push({ type: "session", id: uid(), ts: now(), status: "started" });
|
|
322
|
+
}
|
|
323
|
+
/** Record a navigation breadcrumb ("from → to"). Repeated paths are ignored. */
|
|
324
|
+
navigated(to) {
|
|
325
|
+
if (!to || to === this.lastNav) return;
|
|
326
|
+
this.breadcrumb("navigation", this.lastNav ? `${this.lastNav} \u2192 ${to}` : to);
|
|
327
|
+
this.lastNav = to;
|
|
328
|
+
}
|
|
329
|
+
get initialized() {
|
|
330
|
+
return this.ready;
|
|
331
|
+
}
|
|
332
|
+
/** Events waiting to be sent. */
|
|
333
|
+
get pending() {
|
|
334
|
+
return this.queue.length;
|
|
335
|
+
}
|
|
336
|
+
/** Merge extra context (e.g. after an async device lookup). Applies to events sent from now on. */
|
|
337
|
+
setContext(patch) {
|
|
338
|
+
var _a;
|
|
339
|
+
if (this.opts) this.opts.context = mergeContext((_a = this.opts.context) != null ? _a : {}, patch);
|
|
340
|
+
}
|
|
341
|
+
setUser(id) {
|
|
342
|
+
this.user = { ...this.user, id };
|
|
343
|
+
}
|
|
344
|
+
breadcrumb(category, message, data, level = "info") {
|
|
345
|
+
var _a, _b, _c, _d;
|
|
346
|
+
const msg = message.slice(0, 500);
|
|
347
|
+
const last = this.crumbs[this.crumbs.length - 1];
|
|
348
|
+
if (last && last.category === category && last.message === msg && last.level === level) {
|
|
349
|
+
last.ts = now();
|
|
350
|
+
last.data = { ...data, repeated: Number((_b = (_a = last.data) == null ? void 0 : _a.repeated) != null ? _b : 1) + 1 };
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.crumbs.push({ ts: now(), category, message: msg, data, level });
|
|
354
|
+
if (this.crumbs.length > ((_d = (_c = this.opts) == null ? void 0 : _c.maxBreadcrumbs) != null ? _d : 30)) this.crumbs.shift();
|
|
355
|
+
}
|
|
356
|
+
/** Log an error. `flow` is the part of the app it belongs to, e.g. "auth.login". */
|
|
357
|
+
error(err, meta = {}) {
|
|
358
|
+
this.captureError(normaliseError(err), meta);
|
|
359
|
+
}
|
|
360
|
+
/** Log a custom event. Use outcome to let deBug compute failure rates per flow. */
|
|
361
|
+
event(name, meta = {}) {
|
|
362
|
+
this.breadcrumb("event", name, meta.props);
|
|
363
|
+
this.enqueue({ type: "event", id: uid(), ts: now(), name, flow: meta.flow, step: meta.step, tags: meta.tags, outcome: meta.outcome, props: meta.props });
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Wraps an async step: logs a success or failure outcome for the flow, plus the error.
|
|
367
|
+
* Pass `{ captureError: false }` when the error is already reported elsewhere, e.g. a
|
|
368
|
+
* server function wrapped in withDebug, so it isn't counted twice.
|
|
369
|
+
*/
|
|
370
|
+
async track(flow, step, fn, opts = {}) {
|
|
371
|
+
try {
|
|
372
|
+
const out = await fn();
|
|
373
|
+
this.event(`${flow}.${step}`, { flow, step, outcome: "success" });
|
|
374
|
+
return out;
|
|
375
|
+
} catch (err) {
|
|
376
|
+
this.event(`${flow}.${step}`, { flow, step, outcome: "failure" });
|
|
377
|
+
if (opts.captureError !== false) this.error(err, { flow, step });
|
|
378
|
+
throw err;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async flush() {
|
|
382
|
+
if (this.flushing) await this.flushing.catch(() => {
|
|
383
|
+
});
|
|
384
|
+
if (this.flushing) return this.flushing;
|
|
385
|
+
if (!this.ready || this.queue.length === 0 || Date.now() < this.backoffUntil) return;
|
|
386
|
+
const batch = this.queue.splice(0, this.opts.maxBatch);
|
|
387
|
+
this.flushing = this.send(batch).then(() => {
|
|
388
|
+
this.failures = 0;
|
|
389
|
+
}).catch((err) => {
|
|
390
|
+
this.queue.unshift(...batch);
|
|
391
|
+
this.failures++;
|
|
392
|
+
this.backoffUntil = Date.now() + Math.min(6e4, 1e3 * 2 ** this.failures);
|
|
393
|
+
this.log("flush failed", err);
|
|
394
|
+
}).finally(() => {
|
|
395
|
+
this.flushing = void 0;
|
|
396
|
+
void this.persist();
|
|
397
|
+
});
|
|
398
|
+
return this.flushing;
|
|
399
|
+
}
|
|
400
|
+
/** Stop the flush timer, undo automatic instrumentation and send what is queued. */
|
|
401
|
+
close() {
|
|
402
|
+
if (this.timer) clearInterval(this.timer);
|
|
403
|
+
this.uninstrument();
|
|
404
|
+
return this.flush();
|
|
405
|
+
}
|
|
406
|
+
/* ---------------- internals ---------------- */
|
|
407
|
+
/** @internal Used by the platform entry points for errors that have no thrown value. */
|
|
408
|
+
captureError(e, meta, request) {
|
|
409
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
410
|
+
if (!this.ready) return;
|
|
411
|
+
const handled = (_a = meta.handled) != null ? _a : true;
|
|
412
|
+
const frames = (_b = e.frames) != null ? _b : parseStack(e.stack);
|
|
413
|
+
if (!meta.fatal && !this.allowError(`${e.name}|${e.message.slice(0, 120)}|${(_d = (_c = frames[0]) == null ? void 0 : _c.file) != null ? _d : ""}:${(_f = (_e = frames[0]) == null ? void 0 : _e.line) != null ? _f : ""}`)) return;
|
|
414
|
+
if (this.auto.sessions && (!handled || meta.fatal)) {
|
|
415
|
+
if (!this.sessionState.errored) {
|
|
416
|
+
this.sessionState.errored = true;
|
|
417
|
+
this.push({ type: "session", id: uid(), ts: now(), status: "errored" });
|
|
418
|
+
}
|
|
419
|
+
if (meta.fatal && !this.sessionState.crashed) {
|
|
420
|
+
this.sessionState.crashed = true;
|
|
421
|
+
this.push({ type: "session", id: uid(), ts: now(), status: "crashed" });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
(meta.fatal ? this.push : this.enqueue).call(this, {
|
|
425
|
+
type: "error",
|
|
426
|
+
id: uid(),
|
|
427
|
+
ts: now(),
|
|
428
|
+
flow: meta.flow,
|
|
429
|
+
step: meta.step,
|
|
430
|
+
tags: meta.tags,
|
|
431
|
+
severity: (_g = meta.severity) != null ? _g : meta.fatal ? "critical" : void 0,
|
|
432
|
+
handled,
|
|
433
|
+
error: { name: e.name, message: e.message, stack: e.stack, frames },
|
|
434
|
+
breadcrumbs: [...this.crumbs],
|
|
435
|
+
request
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/** Per-minute limits: one runaway error can't flood the queue, the network or your database. */
|
|
439
|
+
allowError(signature) {
|
|
440
|
+
var _a;
|
|
441
|
+
const max = (_a = this.opts.maxErrorsPerMinute) != null ? _a : 60;
|
|
442
|
+
const t = Date.now();
|
|
443
|
+
const hit = (key, cap) => {
|
|
444
|
+
let l = this.limits.get(key);
|
|
445
|
+
if (!l || t - l.since > 6e4) this.limits.set(key, l = { n: 0, since: t });
|
|
446
|
+
return ++l.n <= cap;
|
|
447
|
+
};
|
|
448
|
+
if (this.limits.size > 200) this.limits.clear();
|
|
449
|
+
const ok = hit("*", max) && hit(signature, Math.max(1, Math.ceil(max / 6)));
|
|
450
|
+
if (!ok) this.log("rate limited", signature);
|
|
451
|
+
return ok;
|
|
452
|
+
}
|
|
453
|
+
sampled() {
|
|
454
|
+
var _a;
|
|
455
|
+
const r = (_a = this.opts.sampleRate) != null ? _a : 1;
|
|
456
|
+
return r >= 1 || Math.random() < r;
|
|
457
|
+
}
|
|
458
|
+
enqueue(e) {
|
|
459
|
+
if (!this.ready || !this.sampled()) return;
|
|
460
|
+
this.push(e);
|
|
461
|
+
}
|
|
462
|
+
/** Queue without sampling (sessions must be complete for crash-free rates to be right). */
|
|
463
|
+
push(e) {
|
|
464
|
+
if (!this.ready) return;
|
|
465
|
+
const out = this.opts.beforeSend ? this.opts.beforeSend(e) : e;
|
|
466
|
+
if (!out) return;
|
|
467
|
+
this.queue.push(out);
|
|
468
|
+
if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE);
|
|
469
|
+
if (out.type === "error" || this.queue.length >= this.opts.maxBatch) void this.flush();
|
|
470
|
+
else void this.persist();
|
|
471
|
+
}
|
|
472
|
+
buildContext() {
|
|
473
|
+
var _a, _b, _c, _d, _e;
|
|
474
|
+
const c = (_a = this.opts.context) != null ? _a : {};
|
|
475
|
+
return {
|
|
476
|
+
...c,
|
|
477
|
+
app: { ...(_b = c.app) != null ? _b : {}, ...this.opts.app },
|
|
478
|
+
os: { name: "unknown", ...(_c = c.os) != null ? _c : {} },
|
|
479
|
+
runtime: { name: "unknown", ...(_d = c.runtime) != null ? _d : {} },
|
|
480
|
+
device: (_e = c.device) != null ? _e : {}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
async send(events) {
|
|
484
|
+
var _a, _b;
|
|
485
|
+
if (this.legacyIngest) events = toLegacy(events);
|
|
486
|
+
if (!events.length) return;
|
|
487
|
+
const body = {
|
|
488
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
489
|
+
context: this.buildContext(),
|
|
490
|
+
user: this.user,
|
|
491
|
+
session: this.session,
|
|
492
|
+
events
|
|
493
|
+
};
|
|
494
|
+
const f = (_b = (_a = this.opts.fetch) != null ? _a : this.rawFetch) != null ? _b : fetch;
|
|
495
|
+
const json = JSON.stringify(body);
|
|
496
|
+
const res = await f(this.opts.endpoint, {
|
|
497
|
+
method: "POST",
|
|
498
|
+
headers: { "content-type": "application/json", "x-debug-key": this.opts.ingestKey },
|
|
499
|
+
body: json,
|
|
500
|
+
// Lets the last batch finish when the page is closing. Browsers cap keepalive bodies at 64 KB.
|
|
501
|
+
...typeof window !== "undefined" && json.length < 6e4 ? { keepalive: true } : {}
|
|
502
|
+
});
|
|
503
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
504
|
+
this.log(`ingest rejected batch (${res.status})`, await res.text().catch(() => ""));
|
|
505
|
+
if (res.status === 400 && !this.legacyIngest && hasNewFields(events)) {
|
|
506
|
+
this.legacyIngest = true;
|
|
507
|
+
return this.send(events);
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (!res.ok) throw new Error(`ingest responded ${res.status}`);
|
|
512
|
+
}
|
|
513
|
+
async persist() {
|
|
514
|
+
var _a;
|
|
515
|
+
try {
|
|
516
|
+
await ((_a = this.opts.storage) == null ? void 0 : _a.setItem(QUEUE_KEY, JSON.stringify(this.queue)));
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async restore() {
|
|
521
|
+
var _a;
|
|
522
|
+
try {
|
|
523
|
+
const raw = await ((_a = this.opts.storage) == null ? void 0 : _a.getItem(QUEUE_KEY));
|
|
524
|
+
if (raw) this.queue.unshift(...JSON.parse(raw));
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
installGlobalHandlers() {
|
|
529
|
+
var _a, _b, _c;
|
|
530
|
+
this.handlersInstalled = true;
|
|
531
|
+
const g2 = globalThis;
|
|
532
|
+
if ((_a = g2.ErrorUtils) == null ? void 0 : _a.setGlobalHandler) {
|
|
533
|
+
const prev = (_c = (_b = g2.ErrorUtils).getGlobalHandler) == null ? void 0 : _c.call(_b);
|
|
534
|
+
g2.ErrorUtils.setGlobalHandler((err, isFatal) => {
|
|
535
|
+
this.error(err, { handled: false, fatal: !!isFatal, tags: { source: "global-handler" } });
|
|
536
|
+
void this.flush();
|
|
537
|
+
prev == null ? void 0 : prev(err, isFatal);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
if (typeof window !== "undefined" && typeof g2.addEventListener === "function") {
|
|
541
|
+
g2.addEventListener("error", (ev) => {
|
|
542
|
+
var _a2;
|
|
543
|
+
return this.error((_a2 = ev.error) != null ? _a2 : ev.message, { handled: false, tags: { source: "window.onerror" } });
|
|
544
|
+
});
|
|
545
|
+
g2.addEventListener("unhandledrejection", (ev) => this.error(ev.reason, { handled: false, tags: { source: "unhandledrejection" } }));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
instrument() {
|
|
549
|
+
const a = this.auto;
|
|
550
|
+
const sink = this.sink();
|
|
551
|
+
const add = (r) => r && this.restores.push(r);
|
|
552
|
+
if (a.network) {
|
|
553
|
+
const restoreFetch = instrumentFetch(sink);
|
|
554
|
+
if (restoreFetch) globalThis.fetch.__raw = this.rawFetch;
|
|
555
|
+
add(restoreFetch);
|
|
556
|
+
add(instrumentXhr(sink));
|
|
557
|
+
}
|
|
558
|
+
if (a.console) add(instrumentConsole(sink));
|
|
559
|
+
if (a.navigation) add(instrumentHistory(sink));
|
|
560
|
+
if (a.clicks) add(instrumentClicks(sink));
|
|
561
|
+
if (a.lifecycle) add(instrumentWebLifecycle(sink));
|
|
562
|
+
}
|
|
563
|
+
uninstrument() {
|
|
564
|
+
while (this.restores.length) this.restores.pop()();
|
|
565
|
+
}
|
|
566
|
+
/** The bridge the instrumentation (./auto, and the platform entry points) reports through. */
|
|
567
|
+
sink() {
|
|
568
|
+
return {
|
|
569
|
+
request: (r) => this.recordRequest(r),
|
|
570
|
+
crumb: (category, message, level = "info", data) => this.breadcrumb(category, message, data, level),
|
|
571
|
+
navigated: (to) => this.navigated(to),
|
|
572
|
+
lifecycle: (state) => {
|
|
573
|
+
this.breadcrumb("ui", state === "background" ? "App went to the background" : "App came to the foreground");
|
|
574
|
+
if (state === "background") void this.flush();
|
|
575
|
+
},
|
|
576
|
+
ignored: (url) => this.isIgnored(url)
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
isIgnored(url) {
|
|
580
|
+
var _a, _b;
|
|
581
|
+
if (!this.ready) return true;
|
|
582
|
+
const ep = this.opts.endpoint.replace(/^https?:/, "");
|
|
583
|
+
if (url.includes(ep) || /\/symbolicate\b|\/logs(?:\?|$)/.test(url)) return true;
|
|
584
|
+
return ((_b = (_a = this.opts.network) == null ? void 0 : _a.ignoreUrls) != null ? _b : []).some((p) => typeof p === "string" ? url.includes(p) : p.test(url));
|
|
585
|
+
}
|
|
586
|
+
recordRequest(r) {
|
|
587
|
+
var _a, _b, _c, _d, _e, _f;
|
|
588
|
+
if (!this.ready) return;
|
|
589
|
+
const net = (_a = this.opts.network) != null ? _a : {};
|
|
590
|
+
const url = normaliseUrl(r.url);
|
|
591
|
+
const ms = Math.round(r.durationMs);
|
|
592
|
+
const failed = r.status === void 0 ? !r.aborted : ((_b = net.isFailure) != null ? _b : ((s) => s >= 500))(r.status, url);
|
|
593
|
+
const slow = ms >= ((_c = net.slowMs) != null ? _c : 3e3);
|
|
594
|
+
const outcome = r.aborted ? "aborted" : r.timedOut ? "timed out" : r.status !== void 0 ? String(r.status) : "failed";
|
|
595
|
+
this.breadcrumb("network", `${r.method} ${url} \u2192 ${outcome} (${ms}ms)`, { status: (_d = r.status) != null ? _d : null, durationMs: ms }, failed ? "error" : slow ? "warning" : "info");
|
|
596
|
+
if (!failed) return;
|
|
597
|
+
const flow = (_f = (_e = net.flows) == null ? void 0 : _e.find(([p]) => typeof p === "string" ? url.includes(p) : p.test(url))) == null ? void 0 : _f[1];
|
|
598
|
+
const cause = r.error ? normaliseError(r.error) : void 0;
|
|
599
|
+
const message = r.status !== void 0 ? `${r.method} ${url} responded ${r.status}` : `${r.method} ${url} ${r.timedOut ? "timed out" : "failed"}${cause ? `: ${cause.message}` : ""}`;
|
|
600
|
+
const frames = parseStack(r.stack).slice(1);
|
|
601
|
+
this.captureError(
|
|
602
|
+
{ name: r.status !== void 0 ? "HttpError" : "NetworkError", message, stack: r.stack, frames },
|
|
603
|
+
{ flow, handled: true, tags: { source: "network" } },
|
|
604
|
+
{ method: r.method, url, status: r.status, durationMs: ms }
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
log(...args) {
|
|
608
|
+
var _a;
|
|
609
|
+
if ((_a = this.opts) == null ? void 0 : _a.debug) console.warn("[deBug]", ...args);
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
function normaliseError(err) {
|
|
613
|
+
if (err instanceof Error) return { name: err.name || "Error", message: err.message, stack: err.stack };
|
|
614
|
+
if (typeof err === "string") return { name: "Error", message: err };
|
|
615
|
+
try {
|
|
616
|
+
return { name: "NonError", message: JSON.stringify(err).slice(0, 2e3) };
|
|
617
|
+
} catch {
|
|
618
|
+
return { name: "NonError", message: String(err) };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function resolveAuto(o) {
|
|
622
|
+
var _a;
|
|
623
|
+
const all = o.autoCapture !== false;
|
|
624
|
+
const sel = typeof o.autoCapture === "object" ? o.autoCapture : {};
|
|
625
|
+
const pick = (k, dflt = all) => {
|
|
626
|
+
var _a2;
|
|
627
|
+
return (_a2 = sel[k]) != null ? _a2 : dflt;
|
|
628
|
+
};
|
|
629
|
+
return {
|
|
630
|
+
errors: (_a = o.captureGlobalErrors) != null ? _a : pick("errors"),
|
|
631
|
+
network: pick("network"),
|
|
632
|
+
console: pick("console"),
|
|
633
|
+
navigation: pick("navigation"),
|
|
634
|
+
clicks: pick("clicks"),
|
|
635
|
+
lifecycle: pick("lifecycle"),
|
|
636
|
+
sessions: pick("sessions")
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
var hasNewFields = (events) => events.some((e) => e.type === "session" || e.type === "error" && e.request);
|
|
640
|
+
function toLegacy(events) {
|
|
641
|
+
const out = [];
|
|
642
|
+
for (const e of events) {
|
|
643
|
+
if (e.type === "session") continue;
|
|
644
|
+
if (e.type === "error" && e.request) {
|
|
645
|
+
const { request: _r, ...rest } = e;
|
|
646
|
+
out.push(rest);
|
|
647
|
+
} else out.push(e);
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/index.ts
|
|
653
|
+
var deBug = new DebugClient();
|
|
654
|
+
|
|
655
|
+
exports.DebugClient = DebugClient;
|
|
656
|
+
exports.SDK_NAME = SDK_NAME;
|
|
657
|
+
exports.SDK_VERSION = SDK_VERSION;
|
|
658
|
+
exports.__publicField = __publicField;
|
|
659
|
+
exports.browserContext = browserContext;
|
|
660
|
+
exports.deBug = deBug;
|
|
661
|
+
exports.locale = locale;
|
|
662
|
+
exports.mergeContext = mergeContext;
|
|
663
|
+
exports.normaliseUrl = normaliseUrl;
|
|
664
|
+
exports.parseStack = parseStack;
|
|
665
|
+
exports.timezone = timezone;
|
|
666
|
+
//# sourceMappingURL=chunk-Q33W6RAG.cjs.map
|
|
667
|
+
//# sourceMappingURL=chunk-Q33W6RAG.cjs.map
|