@booboo.dev/js 0.1.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.
@@ -0,0 +1,365 @@
1
+ // src/stacktrace.ts
2
+ var CHROME_RE = /^\s*at\s+(?:(new\s+)?(.+?)\s+\((.+?):(\d+):(\d+)\)|(.+?):(\d+):(\d+))\s*$/;
3
+ var FIREFOX_RE = /^\s*(.+?)@(.+?):(\d+):(\d+)\s*$/;
4
+ function parseStack(error) {
5
+ const stack = error.stack;
6
+ if (!stack) return [];
7
+ const lines = stack.split("\n");
8
+ const frames = [];
9
+ for (const line of lines) {
10
+ let match = CHROME_RE.exec(line);
11
+ if (match) {
12
+ if (match[3]) {
13
+ frames.push({
14
+ filename: match[3],
15
+ function: match[2] || "<anonymous>",
16
+ lineno: parseInt(match[4], 10),
17
+ colno: parseInt(match[5], 10)
18
+ });
19
+ } else {
20
+ frames.push({
21
+ filename: match[6],
22
+ function: "<anonymous>",
23
+ lineno: parseInt(match[7], 10),
24
+ colno: parseInt(match[8], 10)
25
+ });
26
+ }
27
+ continue;
28
+ }
29
+ match = FIREFOX_RE.exec(line);
30
+ if (match) {
31
+ frames.push({
32
+ filename: match[2],
33
+ function: match[1] || "<anonymous>",
34
+ lineno: parseInt(match[3], 10),
35
+ colno: parseInt(match[4], 10)
36
+ });
37
+ }
38
+ }
39
+ return frames.reverse();
40
+ }
41
+
42
+ // src/breadcrumbs.ts
43
+ var DEFAULT_MAX = 30;
44
+ var breadcrumbBuffer = [];
45
+ var maxBreadcrumbs = DEFAULT_MAX;
46
+ function getBreadcrumbs() {
47
+ return breadcrumbBuffer.slice();
48
+ }
49
+ function addBreadcrumb(crumb) {
50
+ breadcrumbBuffer.push({
51
+ ...crumb,
52
+ timestamp: crumb.timestamp ?? Date.now()
53
+ });
54
+ if (breadcrumbBuffer.length > maxBreadcrumbs) {
55
+ breadcrumbBuffer = breadcrumbBuffer.slice(-maxBreadcrumbs);
56
+ }
57
+ }
58
+ function clearBreadcrumbs() {
59
+ breadcrumbBuffer = [];
60
+ }
61
+ function instrumentConsole() {
62
+ const originals = {
63
+ log: console.log,
64
+ warn: console.warn,
65
+ error: console.error,
66
+ info: console.info,
67
+ debug: console.debug
68
+ };
69
+ for (const level of Object.keys(originals)) {
70
+ const original = originals[level];
71
+ console[level] = function(...args) {
72
+ addBreadcrumb({
73
+ type: "console",
74
+ category: level,
75
+ message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")
76
+ });
77
+ return original.apply(console, args);
78
+ };
79
+ }
80
+ return () => {
81
+ for (const level of Object.keys(originals)) {
82
+ console[level] = originals[level];
83
+ }
84
+ };
85
+ }
86
+ function instrumentClicks() {
87
+ const handler = (event) => {
88
+ const target = event.target;
89
+ if (!target) return;
90
+ const tag = target.tagName?.toLowerCase() || "";
91
+ const text = target.innerText?.slice(0, 50) || "";
92
+ const id = target.id ? `#${target.id}` : "";
93
+ const cls = target.className && typeof target.className === "string" ? `.${target.className.split(" ").slice(0, 2).join(".")}` : "";
94
+ addBreadcrumb({
95
+ type: "click",
96
+ category: "ui",
97
+ message: `${tag}${id}${cls}` + (text ? ` "${text}"` : "")
98
+ });
99
+ };
100
+ document.addEventListener("click", handler, true);
101
+ return () => document.removeEventListener("click", handler, true);
102
+ }
103
+ function instrumentNavigation() {
104
+ const origPushState = history.pushState;
105
+ const origReplaceState = history.replaceState;
106
+ const record = (from, to) => {
107
+ addBreadcrumb({
108
+ type: "navigation",
109
+ category: "navigation",
110
+ message: `${from} -> ${to}`,
111
+ data: { from, to }
112
+ });
113
+ };
114
+ history.pushState = function(...args) {
115
+ const from = location.href;
116
+ origPushState.apply(this, args);
117
+ record(from, location.href);
118
+ };
119
+ history.replaceState = function(...args) {
120
+ const from = location.href;
121
+ origReplaceState.apply(this, args);
122
+ record(from, location.href);
123
+ };
124
+ const popHandler = () => {
125
+ addBreadcrumb({
126
+ type: "navigation",
127
+ category: "navigation",
128
+ message: `popstate -> ${location.href}`
129
+ });
130
+ };
131
+ window.addEventListener("popstate", popHandler);
132
+ return () => {
133
+ history.pushState = origPushState;
134
+ history.replaceState = origReplaceState;
135
+ window.removeEventListener("popstate", popHandler);
136
+ };
137
+ }
138
+ function instrumentFetch() {
139
+ const origFetch = window.fetch;
140
+ window.fetch = async function(input, init2) {
141
+ const method = init2?.method?.toUpperCase() || "GET";
142
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
143
+ const start = Date.now();
144
+ try {
145
+ const response = await origFetch.apply(this, [input, init2]);
146
+ addBreadcrumb({
147
+ type: "fetch",
148
+ category: "http",
149
+ message: `${method} ${url} [${response.status}]`,
150
+ data: { method, url, status: response.status, duration: Date.now() - start }
151
+ });
152
+ return response;
153
+ } catch (err) {
154
+ addBreadcrumb({
155
+ type: "fetch",
156
+ category: "http",
157
+ message: `${method} ${url} [failed]`,
158
+ data: { method, url, duration: Date.now() - start }
159
+ });
160
+ throw err;
161
+ }
162
+ };
163
+ return () => {
164
+ window.fetch = origFetch;
165
+ };
166
+ }
167
+ function setupBreadcrumbs(options, max) {
168
+ maxBreadcrumbs = max ?? DEFAULT_MAX;
169
+ breadcrumbBuffer = [];
170
+ const teardowns = [];
171
+ const opts = typeof options === "boolean" ? { console: true, clicks: true, navigation: true, fetch: true } : options;
172
+ if (opts.console !== false) teardowns.push(instrumentConsole());
173
+ if (opts.clicks !== false) teardowns.push(instrumentClicks());
174
+ if (opts.navigation !== false) teardowns.push(instrumentNavigation());
175
+ if (opts.fetch !== false) teardowns.push(instrumentFetch());
176
+ return () => {
177
+ for (const fn of teardowns) fn();
178
+ };
179
+ }
180
+
181
+ // src/transport.ts
182
+ var Transport = class {
183
+ constructor(endpoint, dsn) {
184
+ this.queue = [];
185
+ this.flushing = false;
186
+ this.endpoint = endpoint;
187
+ this.dsn = dsn;
188
+ if (typeof document !== "undefined") {
189
+ document.addEventListener("visibilitychange", () => {
190
+ if (document.visibilityState === "hidden") this.flush();
191
+ });
192
+ }
193
+ if (typeof window !== "undefined") {
194
+ window.addEventListener("pagehide", () => this.flush());
195
+ }
196
+ }
197
+ send(event) {
198
+ this.queue.push(event);
199
+ this.drain();
200
+ }
201
+ async drain() {
202
+ if (this.flushing) return;
203
+ this.flushing = true;
204
+ while (this.queue.length > 0) {
205
+ const event = this.queue.shift();
206
+ try {
207
+ await fetch(this.endpoint, {
208
+ method: "POST",
209
+ headers: {
210
+ "Content-Type": "application/json",
211
+ "X-Booboo-DSN": this.dsn
212
+ },
213
+ body: JSON.stringify(event),
214
+ keepalive: true
215
+ });
216
+ } catch {
217
+ }
218
+ }
219
+ this.flushing = false;
220
+ }
221
+ flush() {
222
+ while (this.queue.length > 0) {
223
+ const event = this.queue.shift();
224
+ try {
225
+ fetch(this.endpoint, {
226
+ method: "POST",
227
+ headers: {
228
+ "Content-Type": "application/json",
229
+ "X-Booboo-DSN": this.dsn
230
+ },
231
+ body: JSON.stringify(event),
232
+ keepalive: true
233
+ });
234
+ } catch {
235
+ }
236
+ }
237
+ }
238
+ };
239
+
240
+ // src/client.ts
241
+ var DEFAULT_ENDPOINT = "https://api.booboo.dev/ingest/";
242
+ var BoobooClient = class {
243
+ constructor(options) {
244
+ this.options = options;
245
+ const endpoint = options.endpoint || DEFAULT_ENDPOINT;
246
+ this.transport = new Transport(endpoint, options.dsn);
247
+ this.prevOnError = window.onerror;
248
+ window.onerror = (message, source, lineno, colno, error) => {
249
+ if (error) {
250
+ this.captureException(error);
251
+ } else {
252
+ this.captureMessage(String(message));
253
+ }
254
+ if (typeof this.prevOnError === "function") {
255
+ this.prevOnError(message, source, lineno, colno, error);
256
+ }
257
+ };
258
+ this.prevOnUnhandledRejection = window.onunhandledrejection;
259
+ window.onunhandledrejection = (event) => {
260
+ const reason = event.reason;
261
+ if (reason instanceof Error) {
262
+ this.captureException(reason);
263
+ } else {
264
+ this.captureMessage(String(reason));
265
+ }
266
+ if (typeof this.prevOnUnhandledRejection === "function") {
267
+ this.prevOnUnhandledRejection(event);
268
+ }
269
+ };
270
+ if (options.breadcrumbs !== false) {
271
+ this.teardownBreadcrumbs = setupBreadcrumbs(
272
+ options.breadcrumbs ?? true,
273
+ options.maxBreadcrumbs
274
+ );
275
+ }
276
+ }
277
+ captureException(error, extra) {
278
+ const frames = parseStack(error);
279
+ const event = this.buildEvent(
280
+ error.message,
281
+ error.constructor.name || "Error",
282
+ frames,
283
+ extra
284
+ );
285
+ this.sendEvent(event);
286
+ }
287
+ captureMessage(message, level = "error") {
288
+ const event = this.buildEvent(message, "Error", [], void 0, level);
289
+ this.sendEvent(event);
290
+ }
291
+ addBreadcrumb(crumb) {
292
+ addBreadcrumb(crumb);
293
+ }
294
+ destroy() {
295
+ window.onerror = this.prevOnError ?? null;
296
+ window.onunhandledrejection = this.prevOnUnhandledRejection ?? null;
297
+ this.teardownBreadcrumbs?.();
298
+ clearBreadcrumbs();
299
+ }
300
+ buildEvent(message, exceptionType, stacktrace, extra, level = "error") {
301
+ return {
302
+ message,
303
+ level,
304
+ exception_type: exceptionType,
305
+ stacktrace,
306
+ context: {
307
+ ...this.options.context,
308
+ ...extra,
309
+ breadcrumbs: getBreadcrumbs(),
310
+ browser: {
311
+ url: window.location.href,
312
+ userAgent: navigator.userAgent,
313
+ screen: `${screen.width}x${screen.height}`,
314
+ viewport: `${window.innerWidth}x${window.innerHeight}`
315
+ }
316
+ },
317
+ request: {
318
+ url: window.location.href,
319
+ headers: {
320
+ "User-Agent": navigator.userAgent
321
+ }
322
+ },
323
+ tags: { ...this.options.tags, runtime: "browser" }
324
+ };
325
+ }
326
+ sendEvent(event) {
327
+ if (this.options.beforeSend) {
328
+ const modified = this.options.beforeSend(event);
329
+ if (modified === null) return;
330
+ event = modified;
331
+ }
332
+ this.transport.send(event);
333
+ }
334
+ };
335
+
336
+ // src/index.ts
337
+ var client = null;
338
+ function init(options) {
339
+ if (client) {
340
+ client.destroy();
341
+ }
342
+ client = new BoobooClient(options);
343
+ return client;
344
+ }
345
+ function captureException(error, extra) {
346
+ client?.captureException(error, extra);
347
+ }
348
+ function captureMessage(message, level) {
349
+ client?.captureMessage(message, level);
350
+ }
351
+ function addBreadcrumb2(crumb) {
352
+ client?.addBreadcrumb(crumb);
353
+ }
354
+ function getClient() {
355
+ return client;
356
+ }
357
+
358
+ export {
359
+ parseStack,
360
+ init,
361
+ captureException,
362
+ captureMessage,
363
+ addBreadcrumb2 as addBreadcrumb,
364
+ getClient
365
+ };
@@ -0,0 +1,365 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/stacktrace.ts
2
+ var CHROME_RE = /^\s*at\s+(?:(new\s+)?(.+?)\s+\((.+?):(\d+):(\d+)\)|(.+?):(\d+):(\d+))\s*$/;
3
+ var FIREFOX_RE = /^\s*(.+?)@(.+?):(\d+):(\d+)\s*$/;
4
+ function parseStack(error) {
5
+ const stack = error.stack;
6
+ if (!stack) return [];
7
+ const lines = stack.split("\n");
8
+ const frames = [];
9
+ for (const line of lines) {
10
+ let match = CHROME_RE.exec(line);
11
+ if (match) {
12
+ if (match[3]) {
13
+ frames.push({
14
+ filename: match[3],
15
+ function: match[2] || "<anonymous>",
16
+ lineno: parseInt(match[4], 10),
17
+ colno: parseInt(match[5], 10)
18
+ });
19
+ } else {
20
+ frames.push({
21
+ filename: match[6],
22
+ function: "<anonymous>",
23
+ lineno: parseInt(match[7], 10),
24
+ colno: parseInt(match[8], 10)
25
+ });
26
+ }
27
+ continue;
28
+ }
29
+ match = FIREFOX_RE.exec(line);
30
+ if (match) {
31
+ frames.push({
32
+ filename: match[2],
33
+ function: match[1] || "<anonymous>",
34
+ lineno: parseInt(match[3], 10),
35
+ colno: parseInt(match[4], 10)
36
+ });
37
+ }
38
+ }
39
+ return frames.reverse();
40
+ }
41
+
42
+ // src/breadcrumbs.ts
43
+ var DEFAULT_MAX = 30;
44
+ var breadcrumbBuffer = [];
45
+ var maxBreadcrumbs = DEFAULT_MAX;
46
+ function getBreadcrumbs() {
47
+ return breadcrumbBuffer.slice();
48
+ }
49
+ function addBreadcrumb(crumb) {
50
+ breadcrumbBuffer.push({
51
+ ...crumb,
52
+ timestamp: _nullishCoalesce(crumb.timestamp, () => ( Date.now()))
53
+ });
54
+ if (breadcrumbBuffer.length > maxBreadcrumbs) {
55
+ breadcrumbBuffer = breadcrumbBuffer.slice(-maxBreadcrumbs);
56
+ }
57
+ }
58
+ function clearBreadcrumbs() {
59
+ breadcrumbBuffer = [];
60
+ }
61
+ function instrumentConsole() {
62
+ const originals = {
63
+ log: console.log,
64
+ warn: console.warn,
65
+ error: console.error,
66
+ info: console.info,
67
+ debug: console.debug
68
+ };
69
+ for (const level of Object.keys(originals)) {
70
+ const original = originals[level];
71
+ console[level] = function(...args) {
72
+ addBreadcrumb({
73
+ type: "console",
74
+ category: level,
75
+ message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")
76
+ });
77
+ return original.apply(console, args);
78
+ };
79
+ }
80
+ return () => {
81
+ for (const level of Object.keys(originals)) {
82
+ console[level] = originals[level];
83
+ }
84
+ };
85
+ }
86
+ function instrumentClicks() {
87
+ const handler = (event) => {
88
+ const target = event.target;
89
+ if (!target) return;
90
+ const tag = _optionalChain([target, 'access', _ => _.tagName, 'optionalAccess', _2 => _2.toLowerCase, 'call', _3 => _3()]) || "";
91
+ const text = _optionalChain([target, 'access', _4 => _4.innerText, 'optionalAccess', _5 => _5.slice, 'call', _6 => _6(0, 50)]) || "";
92
+ const id = target.id ? `#${target.id}` : "";
93
+ const cls = target.className && typeof target.className === "string" ? `.${target.className.split(" ").slice(0, 2).join(".")}` : "";
94
+ addBreadcrumb({
95
+ type: "click",
96
+ category: "ui",
97
+ message: `${tag}${id}${cls}` + (text ? ` "${text}"` : "")
98
+ });
99
+ };
100
+ document.addEventListener("click", handler, true);
101
+ return () => document.removeEventListener("click", handler, true);
102
+ }
103
+ function instrumentNavigation() {
104
+ const origPushState = history.pushState;
105
+ const origReplaceState = history.replaceState;
106
+ const record = (from, to) => {
107
+ addBreadcrumb({
108
+ type: "navigation",
109
+ category: "navigation",
110
+ message: `${from} -> ${to}`,
111
+ data: { from, to }
112
+ });
113
+ };
114
+ history.pushState = function(...args) {
115
+ const from = location.href;
116
+ origPushState.apply(this, args);
117
+ record(from, location.href);
118
+ };
119
+ history.replaceState = function(...args) {
120
+ const from = location.href;
121
+ origReplaceState.apply(this, args);
122
+ record(from, location.href);
123
+ };
124
+ const popHandler = () => {
125
+ addBreadcrumb({
126
+ type: "navigation",
127
+ category: "navigation",
128
+ message: `popstate -> ${location.href}`
129
+ });
130
+ };
131
+ window.addEventListener("popstate", popHandler);
132
+ return () => {
133
+ history.pushState = origPushState;
134
+ history.replaceState = origReplaceState;
135
+ window.removeEventListener("popstate", popHandler);
136
+ };
137
+ }
138
+ function instrumentFetch() {
139
+ const origFetch = window.fetch;
140
+ window.fetch = async function(input, init2) {
141
+ const method = _optionalChain([init2, 'optionalAccess', _7 => _7.method, 'optionalAccess', _8 => _8.toUpperCase, 'call', _9 => _9()]) || "GET";
142
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
143
+ const start = Date.now();
144
+ try {
145
+ const response = await origFetch.apply(this, [input, init2]);
146
+ addBreadcrumb({
147
+ type: "fetch",
148
+ category: "http",
149
+ message: `${method} ${url} [${response.status}]`,
150
+ data: { method, url, status: response.status, duration: Date.now() - start }
151
+ });
152
+ return response;
153
+ } catch (err) {
154
+ addBreadcrumb({
155
+ type: "fetch",
156
+ category: "http",
157
+ message: `${method} ${url} [failed]`,
158
+ data: { method, url, duration: Date.now() - start }
159
+ });
160
+ throw err;
161
+ }
162
+ };
163
+ return () => {
164
+ window.fetch = origFetch;
165
+ };
166
+ }
167
+ function setupBreadcrumbs(options, max) {
168
+ maxBreadcrumbs = _nullishCoalesce(max, () => ( DEFAULT_MAX));
169
+ breadcrumbBuffer = [];
170
+ const teardowns = [];
171
+ const opts = typeof options === "boolean" ? { console: true, clicks: true, navigation: true, fetch: true } : options;
172
+ if (opts.console !== false) teardowns.push(instrumentConsole());
173
+ if (opts.clicks !== false) teardowns.push(instrumentClicks());
174
+ if (opts.navigation !== false) teardowns.push(instrumentNavigation());
175
+ if (opts.fetch !== false) teardowns.push(instrumentFetch());
176
+ return () => {
177
+ for (const fn of teardowns) fn();
178
+ };
179
+ }
180
+
181
+ // src/transport.ts
182
+ var Transport = class {
183
+ constructor(endpoint, dsn) {
184
+ this.queue = [];
185
+ this.flushing = false;
186
+ this.endpoint = endpoint;
187
+ this.dsn = dsn;
188
+ if (typeof document !== "undefined") {
189
+ document.addEventListener("visibilitychange", () => {
190
+ if (document.visibilityState === "hidden") this.flush();
191
+ });
192
+ }
193
+ if (typeof window !== "undefined") {
194
+ window.addEventListener("pagehide", () => this.flush());
195
+ }
196
+ }
197
+ send(event) {
198
+ this.queue.push(event);
199
+ this.drain();
200
+ }
201
+ async drain() {
202
+ if (this.flushing) return;
203
+ this.flushing = true;
204
+ while (this.queue.length > 0) {
205
+ const event = this.queue.shift();
206
+ try {
207
+ await fetch(this.endpoint, {
208
+ method: "POST",
209
+ headers: {
210
+ "Content-Type": "application/json",
211
+ "X-Booboo-DSN": this.dsn
212
+ },
213
+ body: JSON.stringify(event),
214
+ keepalive: true
215
+ });
216
+ } catch (e) {
217
+ }
218
+ }
219
+ this.flushing = false;
220
+ }
221
+ flush() {
222
+ while (this.queue.length > 0) {
223
+ const event = this.queue.shift();
224
+ try {
225
+ fetch(this.endpoint, {
226
+ method: "POST",
227
+ headers: {
228
+ "Content-Type": "application/json",
229
+ "X-Booboo-DSN": this.dsn
230
+ },
231
+ body: JSON.stringify(event),
232
+ keepalive: true
233
+ });
234
+ } catch (e2) {
235
+ }
236
+ }
237
+ }
238
+ };
239
+
240
+ // src/client.ts
241
+ var DEFAULT_ENDPOINT = "https://api.booboo.dev/ingest/";
242
+ var BoobooClient = class {
243
+ constructor(options) {
244
+ this.options = options;
245
+ const endpoint = options.endpoint || DEFAULT_ENDPOINT;
246
+ this.transport = new Transport(endpoint, options.dsn);
247
+ this.prevOnError = window.onerror;
248
+ window.onerror = (message, source, lineno, colno, error) => {
249
+ if (error) {
250
+ this.captureException(error);
251
+ } else {
252
+ this.captureMessage(String(message));
253
+ }
254
+ if (typeof this.prevOnError === "function") {
255
+ this.prevOnError(message, source, lineno, colno, error);
256
+ }
257
+ };
258
+ this.prevOnUnhandledRejection = window.onunhandledrejection;
259
+ window.onunhandledrejection = (event) => {
260
+ const reason = event.reason;
261
+ if (reason instanceof Error) {
262
+ this.captureException(reason);
263
+ } else {
264
+ this.captureMessage(String(reason));
265
+ }
266
+ if (typeof this.prevOnUnhandledRejection === "function") {
267
+ this.prevOnUnhandledRejection(event);
268
+ }
269
+ };
270
+ if (options.breadcrumbs !== false) {
271
+ this.teardownBreadcrumbs = setupBreadcrumbs(
272
+ _nullishCoalesce(options.breadcrumbs, () => ( true)),
273
+ options.maxBreadcrumbs
274
+ );
275
+ }
276
+ }
277
+ captureException(error, extra) {
278
+ const frames = parseStack(error);
279
+ const event = this.buildEvent(
280
+ error.message,
281
+ error.constructor.name || "Error",
282
+ frames,
283
+ extra
284
+ );
285
+ this.sendEvent(event);
286
+ }
287
+ captureMessage(message, level = "error") {
288
+ const event = this.buildEvent(message, "Error", [], void 0, level);
289
+ this.sendEvent(event);
290
+ }
291
+ addBreadcrumb(crumb) {
292
+ addBreadcrumb(crumb);
293
+ }
294
+ destroy() {
295
+ window.onerror = _nullishCoalesce(this.prevOnError, () => ( null));
296
+ window.onunhandledrejection = _nullishCoalesce(this.prevOnUnhandledRejection, () => ( null));
297
+ _optionalChain([this, 'access', _10 => _10.teardownBreadcrumbs, 'optionalCall', _11 => _11()]);
298
+ clearBreadcrumbs();
299
+ }
300
+ buildEvent(message, exceptionType, stacktrace, extra, level = "error") {
301
+ return {
302
+ message,
303
+ level,
304
+ exception_type: exceptionType,
305
+ stacktrace,
306
+ context: {
307
+ ...this.options.context,
308
+ ...extra,
309
+ breadcrumbs: getBreadcrumbs(),
310
+ browser: {
311
+ url: window.location.href,
312
+ userAgent: navigator.userAgent,
313
+ screen: `${screen.width}x${screen.height}`,
314
+ viewport: `${window.innerWidth}x${window.innerHeight}`
315
+ }
316
+ },
317
+ request: {
318
+ url: window.location.href,
319
+ headers: {
320
+ "User-Agent": navigator.userAgent
321
+ }
322
+ },
323
+ tags: { ...this.options.tags, runtime: "browser" }
324
+ };
325
+ }
326
+ sendEvent(event) {
327
+ if (this.options.beforeSend) {
328
+ const modified = this.options.beforeSend(event);
329
+ if (modified === null) return;
330
+ event = modified;
331
+ }
332
+ this.transport.send(event);
333
+ }
334
+ };
335
+
336
+ // src/index.ts
337
+ var client = null;
338
+ function init(options) {
339
+ if (client) {
340
+ client.destroy();
341
+ }
342
+ client = new BoobooClient(options);
343
+ return client;
344
+ }
345
+ function captureException(error, extra) {
346
+ _optionalChain([client, 'optionalAccess', _12 => _12.captureException, 'call', _13 => _13(error, extra)]);
347
+ }
348
+ function captureMessage(message, level) {
349
+ _optionalChain([client, 'optionalAccess', _14 => _14.captureMessage, 'call', _15 => _15(message, level)]);
350
+ }
351
+ function addBreadcrumb2(crumb) {
352
+ _optionalChain([client, 'optionalAccess', _16 => _16.addBreadcrumb, 'call', _17 => _17(crumb)]);
353
+ }
354
+ function getClient() {
355
+ return client;
356
+ }
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+ exports.parseStack = parseStack; exports.init = init; exports.captureException = captureException; exports.captureMessage = captureMessage; exports.addBreadcrumb = addBreadcrumb2; exports.getClient = getClient;
package/dist/index.cjs ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+ var _chunkO4OOPXTScjs = require('./chunk-O4OOPXTS.cjs');
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ exports.addBreadcrumb = _chunkO4OOPXTScjs.addBreadcrumb; exports.captureException = _chunkO4OOPXTScjs.captureException; exports.captureMessage = _chunkO4OOPXTScjs.captureMessage; exports.getClient = _chunkO4OOPXTScjs.getClient; exports.init = _chunkO4OOPXTScjs.init; exports.parseStack = _chunkO4OOPXTScjs.parseStack;
@@ -0,0 +1,71 @@
1
+ interface StackFrame {
2
+ filename: string;
3
+ function: string;
4
+ lineno: number;
5
+ colno?: number;
6
+ }
7
+ interface Breadcrumb {
8
+ type: "console" | "click" | "navigation" | "fetch" | "custom";
9
+ category?: string;
10
+ message: string;
11
+ data?: Record<string, unknown>;
12
+ timestamp: number;
13
+ }
14
+ interface BreadcrumbOptions {
15
+ console?: boolean;
16
+ clicks?: boolean;
17
+ navigation?: boolean;
18
+ fetch?: boolean;
19
+ }
20
+ interface BoobooOptions {
21
+ dsn: string;
22
+ endpoint?: string;
23
+ breadcrumbs?: boolean | BreadcrumbOptions;
24
+ maxBreadcrumbs?: number;
25
+ beforeSend?: (event: BoobooEvent) => BoobooEvent | null;
26
+ tags?: Record<string, string>;
27
+ context?: Record<string, unknown>;
28
+ }
29
+ interface BoobooEvent {
30
+ message: string;
31
+ level: "error" | "warning" | "info";
32
+ exception_type: string;
33
+ stacktrace: StackFrame[];
34
+ context: Record<string, unknown>;
35
+ request: {
36
+ url: string;
37
+ headers: Record<string, string>;
38
+ };
39
+ tags: Record<string, string>;
40
+ }
41
+
42
+ declare function addBreadcrumb$1(crumb: Omit<Breadcrumb, "timestamp"> & {
43
+ timestamp?: number;
44
+ }): void;
45
+
46
+ declare class BoobooClient {
47
+ private options;
48
+ private transport;
49
+ private teardownBreadcrumbs?;
50
+ private prevOnError?;
51
+ private prevOnUnhandledRejection?;
52
+ constructor(options: BoobooOptions);
53
+ captureException(error: Error, extra?: Record<string, unknown>): void;
54
+ captureMessage(message: string, level?: "error" | "warning" | "info"): void;
55
+ addBreadcrumb(crumb: Parameters<typeof addBreadcrumb$1>[0]): void;
56
+ destroy(): void;
57
+ private buildEvent;
58
+ private sendEvent;
59
+ }
60
+
61
+ declare function parseStack(error: Error): StackFrame[];
62
+
63
+ declare function init(options: BoobooOptions): BoobooClient;
64
+ declare function captureException(error: Error, extra?: Record<string, unknown>): void;
65
+ declare function captureMessage(message: string, level?: "error" | "warning" | "info"): void;
66
+ declare function addBreadcrumb(crumb: Omit<Breadcrumb, "timestamp"> & {
67
+ timestamp?: number;
68
+ }): void;
69
+ declare function getClient(): BoobooClient | null;
70
+
71
+ export { type BoobooEvent, type BoobooOptions, type Breadcrumb, type BreadcrumbOptions, type StackFrame, addBreadcrumb, captureException, captureMessage, getClient, init, parseStack };
@@ -0,0 +1,71 @@
1
+ interface StackFrame {
2
+ filename: string;
3
+ function: string;
4
+ lineno: number;
5
+ colno?: number;
6
+ }
7
+ interface Breadcrumb {
8
+ type: "console" | "click" | "navigation" | "fetch" | "custom";
9
+ category?: string;
10
+ message: string;
11
+ data?: Record<string, unknown>;
12
+ timestamp: number;
13
+ }
14
+ interface BreadcrumbOptions {
15
+ console?: boolean;
16
+ clicks?: boolean;
17
+ navigation?: boolean;
18
+ fetch?: boolean;
19
+ }
20
+ interface BoobooOptions {
21
+ dsn: string;
22
+ endpoint?: string;
23
+ breadcrumbs?: boolean | BreadcrumbOptions;
24
+ maxBreadcrumbs?: number;
25
+ beforeSend?: (event: BoobooEvent) => BoobooEvent | null;
26
+ tags?: Record<string, string>;
27
+ context?: Record<string, unknown>;
28
+ }
29
+ interface BoobooEvent {
30
+ message: string;
31
+ level: "error" | "warning" | "info";
32
+ exception_type: string;
33
+ stacktrace: StackFrame[];
34
+ context: Record<string, unknown>;
35
+ request: {
36
+ url: string;
37
+ headers: Record<string, string>;
38
+ };
39
+ tags: Record<string, string>;
40
+ }
41
+
42
+ declare function addBreadcrumb$1(crumb: Omit<Breadcrumb, "timestamp"> & {
43
+ timestamp?: number;
44
+ }): void;
45
+
46
+ declare class BoobooClient {
47
+ private options;
48
+ private transport;
49
+ private teardownBreadcrumbs?;
50
+ private prevOnError?;
51
+ private prevOnUnhandledRejection?;
52
+ constructor(options: BoobooOptions);
53
+ captureException(error: Error, extra?: Record<string, unknown>): void;
54
+ captureMessage(message: string, level?: "error" | "warning" | "info"): void;
55
+ addBreadcrumb(crumb: Parameters<typeof addBreadcrumb$1>[0]): void;
56
+ destroy(): void;
57
+ private buildEvent;
58
+ private sendEvent;
59
+ }
60
+
61
+ declare function parseStack(error: Error): StackFrame[];
62
+
63
+ declare function init(options: BoobooOptions): BoobooClient;
64
+ declare function captureException(error: Error, extra?: Record<string, unknown>): void;
65
+ declare function captureMessage(message: string, level?: "error" | "warning" | "info"): void;
66
+ declare function addBreadcrumb(crumb: Omit<Breadcrumb, "timestamp"> & {
67
+ timestamp?: number;
68
+ }): void;
69
+ declare function getClient(): BoobooClient | null;
70
+
71
+ export { type BoobooEvent, type BoobooOptions, type Breadcrumb, type BreadcrumbOptions, type StackFrame, addBreadcrumb, captureException, captureMessage, getClient, init, parseStack };
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ addBreadcrumb,
3
+ captureException,
4
+ captureMessage,
5
+ getClient,
6
+ init,
7
+ parseStack
8
+ } from "./chunk-5PWHK5SB.js";
9
+ export {
10
+ addBreadcrumb,
11
+ captureException,
12
+ captureMessage,
13
+ getClient,
14
+ init,
15
+ parseStack
16
+ };
package/dist/react.cjs ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+ var _chunkO4OOPXTScjs = require('./chunk-O4OOPXTS.cjs');
5
+
6
+ // src/react.ts
7
+ var _react = require('react');
8
+ var ErrorBoundary = class extends _react.Component {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.state = { error: null };
12
+ this.reset = () => {
13
+ this.setState({ error: null });
14
+ };
15
+ }
16
+ static getDerivedStateFromError(error) {
17
+ return { error };
18
+ }
19
+ componentDidCatch(error, errorInfo) {
20
+ const client = _chunkO4OOPXTScjs.getClient.call(void 0, );
21
+ if (client) {
22
+ const extra = {};
23
+ if (errorInfo.componentStack) {
24
+ extra.componentStack = errorInfo.componentStack;
25
+ }
26
+ _chunkO4OOPXTScjs.captureException.call(void 0, error, extra);
27
+ }
28
+ _optionalChain([this, 'access', _ => _.props, 'access', _2 => _2.onError, 'optionalCall', _3 => _3(error, errorInfo)]);
29
+ }
30
+ render() {
31
+ if (this.state.error) {
32
+ const { fallback } = this.props;
33
+ if (typeof fallback === "function") {
34
+ return fallback(this.state.error, this.reset);
35
+ }
36
+ return _nullishCoalesce(fallback, () => ( null));
37
+ }
38
+ return this.props.children;
39
+ }
40
+ };
41
+
42
+
43
+ exports.ErrorBoundary = ErrorBoundary;
@@ -0,0 +1,19 @@
1
+ import { Component, ReactNode, ErrorInfo } from 'react';
2
+
3
+ interface ErrorBoundaryProps {
4
+ children: ReactNode;
5
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
6
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
7
+ }
8
+ interface ErrorBoundaryState {
9
+ error: Error | null;
10
+ }
11
+ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
12
+ state: ErrorBoundaryState;
13
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
14
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
15
+ reset: () => void;
16
+ render(): ReactNode;
17
+ }
18
+
19
+ export { ErrorBoundary };
@@ -0,0 +1,19 @@
1
+ import { Component, ReactNode, ErrorInfo } from 'react';
2
+
3
+ interface ErrorBoundaryProps {
4
+ children: ReactNode;
5
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
6
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
7
+ }
8
+ interface ErrorBoundaryState {
9
+ error: Error | null;
10
+ }
11
+ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
12
+ state: ErrorBoundaryState;
13
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
14
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
15
+ reset: () => void;
16
+ render(): ReactNode;
17
+ }
18
+
19
+ export { ErrorBoundary };
package/dist/react.js ADDED
@@ -0,0 +1,43 @@
1
+ import {
2
+ captureException,
3
+ getClient
4
+ } from "./chunk-5PWHK5SB.js";
5
+
6
+ // src/react.ts
7
+ import { Component } from "react";
8
+ var ErrorBoundary = class extends Component {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.state = { error: null };
12
+ this.reset = () => {
13
+ this.setState({ error: null });
14
+ };
15
+ }
16
+ static getDerivedStateFromError(error) {
17
+ return { error };
18
+ }
19
+ componentDidCatch(error, errorInfo) {
20
+ const client = getClient();
21
+ if (client) {
22
+ const extra = {};
23
+ if (errorInfo.componentStack) {
24
+ extra.componentStack = errorInfo.componentStack;
25
+ }
26
+ captureException(error, extra);
27
+ }
28
+ this.props.onError?.(error, errorInfo);
29
+ }
30
+ render() {
31
+ if (this.state.error) {
32
+ const { fallback } = this.props;
33
+ if (typeof fallback === "function") {
34
+ return fallback(this.state.error, this.reset);
35
+ }
36
+ return fallback ?? null;
37
+ }
38
+ return this.props.children;
39
+ }
40
+ };
41
+ export {
42
+ ErrorBoundary
43
+ };
package/dist/vue.cjs ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+ var _chunkO4OOPXTScjs = require('./chunk-O4OOPXTS.cjs');
4
+
5
+ // src/vue.ts
6
+ function BoobooVue(_options) {
7
+ return {
8
+ install(app) {
9
+ const prevHandler = app.config.errorHandler;
10
+ app.config.errorHandler = (err, instance, info) => {
11
+ const error = err instanceof Error ? err : new Error(String(err));
12
+ const extra = {
13
+ vueInfo: info
14
+ };
15
+ if (instance) {
16
+ extra.componentName = _optionalChain([instance, 'access', _ => _.$options, 'optionalAccess', _2 => _2.name]) || _optionalChain([instance, 'access', _3 => _3.$options, 'optionalAccess', _4 => _4.__name]) || "<Anonymous>";
17
+ if (instance.$props && typeof instance.$props === "object") {
18
+ extra.propKeys = Object.keys(instance.$props);
19
+ }
20
+ }
21
+ _chunkO4OOPXTScjs.captureException.call(void 0, error, extra);
22
+ if (typeof prevHandler === "function") {
23
+ prevHandler(err, instance, info);
24
+ }
25
+ };
26
+ }
27
+ };
28
+ }
29
+
30
+
31
+ exports.BoobooVue = BoobooVue;
package/dist/vue.d.cts ADDED
@@ -0,0 +1,10 @@
1
+ import { App } from 'vue';
2
+
3
+ interface BoobooVueOptions {
4
+ dsn?: string;
5
+ }
6
+ declare function BoobooVue(_options?: BoobooVueOptions): {
7
+ install(app: App): void;
8
+ };
9
+
10
+ export { BoobooVue };
package/dist/vue.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { App } from 'vue';
2
+
3
+ interface BoobooVueOptions {
4
+ dsn?: string;
5
+ }
6
+ declare function BoobooVue(_options?: BoobooVueOptions): {
7
+ install(app: App): void;
8
+ };
9
+
10
+ export { BoobooVue };
package/dist/vue.js ADDED
@@ -0,0 +1,31 @@
1
+ import {
2
+ captureException
3
+ } from "./chunk-5PWHK5SB.js";
4
+
5
+ // src/vue.ts
6
+ function BoobooVue(_options) {
7
+ return {
8
+ install(app) {
9
+ const prevHandler = app.config.errorHandler;
10
+ app.config.errorHandler = (err, instance, info) => {
11
+ const error = err instanceof Error ? err : new Error(String(err));
12
+ const extra = {
13
+ vueInfo: info
14
+ };
15
+ if (instance) {
16
+ extra.componentName = instance.$options?.name || instance.$options?.__name || "<Anonymous>";
17
+ if (instance.$props && typeof instance.$props === "object") {
18
+ extra.propKeys = Object.keys(instance.$props);
19
+ }
20
+ }
21
+ captureException(error, extra);
22
+ if (typeof prevHandler === "function") {
23
+ prevHandler(err, instance, info);
24
+ }
25
+ };
26
+ }
27
+ };
28
+ }
29
+ export {
30
+ BoobooVue
31
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@booboo.dev/js",
3
+ "version": "0.1.0",
4
+ "description": "Official JavaScript SDK for booboo.dev error tracking",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react.d.ts",
17
+ "import": "./dist/react.js",
18
+ "require": "./dist/react.cjs"
19
+ },
20
+ "./vue": {
21
+ "types": "./dist/vue.d.ts",
22
+ "import": "./dist/vue.js",
23
+ "require": "./dist/vue.cjs"
24
+ }
25
+ },
26
+ "files": ["dist"],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^19.0.0",
33
+ "tsup": "^8.0.0",
34
+ "typescript": "^5.4.0",
35
+ "vue": "^3.5.0"
36
+ },
37
+ "peerDependencies": {
38
+ "react": ">=17.0.0",
39
+ "vue": ">=3.0.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "react": { "optional": true },
43
+ "vue": { "optional": true }
44
+ },
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/booboo-dev/booboo.dev"
49
+ },
50
+ "keywords": ["error-tracking", "booboo", "monitoring", "javascript", "react", "vue"]
51
+ }