@error-explorer/vue 1.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,639 @@
1
+ import { ErrorExplorer } from '@error-explorer/browser';
2
+ export { ErrorExplorer } from '@error-explorer/browser';
3
+ import { defineComponent, ref, onErrorCaptured, h, inject, getCurrentInstance, onMounted, onUnmounted } from 'vue';
4
+
5
+ // src/plugin.ts
6
+ function getComponentName(instance) {
7
+ if (!instance) return void 0;
8
+ const options = instance.$.type;
9
+ return options.name || options.__name || extractNameFromFile(options.__file);
10
+ }
11
+ function extractNameFromFile(file) {
12
+ if (!file) return void 0;
13
+ const match = file.match(/([^/\\]+)\.vue$/);
14
+ return match ? match[1] : void 0;
15
+ }
16
+ function getComponentTrace(instance) {
17
+ const trace = [];
18
+ let current = instance;
19
+ while (current) {
20
+ const name = getComponentName(current);
21
+ if (name) {
22
+ trace.push(name);
23
+ }
24
+ current = current.$.parent?.proxy ?? null;
25
+ }
26
+ return trace;
27
+ }
28
+ function serializeProps(props, maxDepth) {
29
+ const serialize = (value, depth) => {
30
+ if (depth > maxDepth) {
31
+ return "[Max depth reached]";
32
+ }
33
+ if (value === null || value === void 0) {
34
+ return value;
35
+ }
36
+ if (typeof value === "function") {
37
+ return "[Function]";
38
+ }
39
+ if (typeof value === "symbol") {
40
+ return value.toString();
41
+ }
42
+ if (value instanceof Date) {
43
+ return value.toISOString();
44
+ }
45
+ if (value instanceof Error) {
46
+ return { name: value.name, message: value.message };
47
+ }
48
+ if (Array.isArray(value)) {
49
+ return value.slice(0, 10).map((item) => serialize(item, depth + 1));
50
+ }
51
+ if (typeof value === "object") {
52
+ const serialized = {};
53
+ const keys = Object.keys(value).slice(0, 20);
54
+ for (const key of keys) {
55
+ serialized[key] = serialize(value[key], depth + 1);
56
+ }
57
+ return serialized;
58
+ }
59
+ return value;
60
+ };
61
+ return serialize(props, 0);
62
+ }
63
+ function buildVueContext(instance, info, options) {
64
+ const context = {
65
+ info
66
+ };
67
+ if (options.captureComponentName !== false && instance) {
68
+ context.name = getComponentName(instance);
69
+ const type = instance.$.type;
70
+ if (type.__file) {
71
+ context.file = type.__file;
72
+ }
73
+ context.trace = getComponentTrace(instance);
74
+ }
75
+ if (options.captureComponentProps && instance) {
76
+ const props = instance.$.props;
77
+ if (props && Object.keys(props).length > 0) {
78
+ context.props = serializeProps(props, options.propsDepth ?? 2);
79
+ }
80
+ }
81
+ return context;
82
+ }
83
+ var originalErrorHandler;
84
+ var originalWarnHandler;
85
+ function setupErrorHandler(app, options) {
86
+ if (options.vueErrorHandler === false) {
87
+ return;
88
+ }
89
+ originalErrorHandler = app.config.errorHandler;
90
+ app.config.errorHandler = (err, instance, info) => {
91
+ if (options.beforeVueCapture) {
92
+ const error2 = err instanceof Error ? err : new Error(String(err));
93
+ if (!options.beforeVueCapture(error2, instance, info)) {
94
+ if (originalErrorHandler) {
95
+ originalErrorHandler(err, instance, info);
96
+ }
97
+ return;
98
+ }
99
+ }
100
+ const vueContext = buildVueContext(instance, info, options);
101
+ ErrorExplorer.addBreadcrumb({
102
+ type: "error",
103
+ category: "vue.error",
104
+ message: err instanceof Error ? err.message : String(err),
105
+ level: "error",
106
+ data: {
107
+ component: vueContext.name,
108
+ info: vueContext.info
109
+ }
110
+ });
111
+ const error = err instanceof Error ? err : new Error(String(err));
112
+ ErrorExplorer.captureException(error, {
113
+ tags: {
114
+ "vue.component": vueContext.name || "unknown",
115
+ "vue.info": info
116
+ },
117
+ extra: {
118
+ vue: vueContext
119
+ }
120
+ });
121
+ if (originalErrorHandler) {
122
+ originalErrorHandler(err, instance, info);
123
+ }
124
+ if (options.environment === "development" || import.meta.env?.DEV) {
125
+ console.error("[Vue Error]", err);
126
+ }
127
+ };
128
+ }
129
+ function setupWarnHandler(app, options) {
130
+ const enableWarn = options.vueWarnHandler ?? (options.environment === "development" || import.meta.env?.DEV);
131
+ if (!enableWarn) {
132
+ return;
133
+ }
134
+ originalWarnHandler = app.config.warnHandler;
135
+ app.config.warnHandler = (msg, instance, trace) => {
136
+ const componentName = getComponentName(instance);
137
+ ErrorExplorer.addBreadcrumb({
138
+ type: "debug",
139
+ category: "vue.warn",
140
+ message: msg,
141
+ level: "warning",
142
+ data: {
143
+ component: componentName,
144
+ trace: trace.split("\n").slice(0, 5)
145
+ }
146
+ });
147
+ if (originalWarnHandler) {
148
+ originalWarnHandler(msg, instance, trace);
149
+ }
150
+ if (import.meta.env?.DEV) {
151
+ console.warn("[Vue Warn]", msg);
152
+ if (trace) {
153
+ console.warn(trace);
154
+ }
155
+ }
156
+ };
157
+ }
158
+ function restoreHandlers(app) {
159
+ if (originalErrorHandler !== void 0) {
160
+ app.config.errorHandler = originalErrorHandler;
161
+ originalErrorHandler = void 0;
162
+ }
163
+ if (originalWarnHandler !== void 0) {
164
+ app.config.warnHandler = originalWarnHandler;
165
+ originalWarnHandler = void 0;
166
+ }
167
+ }
168
+ function createErrorExplorerPlugin(options = {}) {
169
+ return {
170
+ install(app) {
171
+ if (!ErrorExplorer.isInitialized()) {
172
+ ErrorExplorer.init(options);
173
+ }
174
+ setupErrorHandler(app, options);
175
+ setupWarnHandler(app, options);
176
+ app.provide("errorExplorer", ErrorExplorer);
177
+ app.config.globalProperties.$errorExplorer = ErrorExplorer;
178
+ ErrorExplorer.addBreadcrumb({
179
+ type: "navigation",
180
+ category: "vue.lifecycle",
181
+ message: "Vue app mounted",
182
+ level: "info"
183
+ });
184
+ }
185
+ };
186
+ }
187
+ function defaultGetRouteName(route) {
188
+ if (route.name && typeof route.name === "string") {
189
+ return route.name;
190
+ }
191
+ return route.path || "/";
192
+ }
193
+ function buildRouteData(route, options) {
194
+ const data = {
195
+ path: route.path,
196
+ name: route.name,
197
+ fullPath: route.fullPath
198
+ };
199
+ if (options.trackParams && Object.keys(route.params).length > 0) {
200
+ data.params = { ...route.params };
201
+ }
202
+ if (options.trackQuery && Object.keys(route.query).length > 0) {
203
+ data.query = { ...route.query };
204
+ }
205
+ if (route.meta && Object.keys(route.meta).length > 0) {
206
+ const safeMeta = {};
207
+ for (const [key, value] of Object.entries(route.meta)) {
208
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
209
+ safeMeta[key] = value;
210
+ }
211
+ }
212
+ if (Object.keys(safeMeta).length > 0) {
213
+ data.meta = safeMeta;
214
+ }
215
+ }
216
+ return data;
217
+ }
218
+ function setupRouterIntegration(router, options = {}) {
219
+ if (options.trackNavigation === false) {
220
+ return () => {
221
+ };
222
+ }
223
+ const getRouteName = options.getRouteName || defaultGetRouteName;
224
+ let navigationStartTime = null;
225
+ const beforeEachGuard = router.beforeEach((to, from) => {
226
+ navigationStartTime = performance.now();
227
+ if (from.name || from.path !== "/") {
228
+ const shouldAdd = options.beforeNavigationBreadcrumb ? options.beforeNavigationBreadcrumb(from, to) : true;
229
+ if (shouldAdd) {
230
+ ErrorExplorer.addBreadcrumb({
231
+ type: "navigation",
232
+ category: "router",
233
+ message: `Navigating from ${getRouteName(from)} to ${getRouteName(to)}`,
234
+ level: "info",
235
+ data: {
236
+ from: buildRouteData(from, options),
237
+ to: buildRouteData(to, options)
238
+ }
239
+ });
240
+ }
241
+ }
242
+ return true;
243
+ });
244
+ const afterEachGuard = router.afterEach((to, from, failure) => {
245
+ const duration = navigationStartTime ? performance.now() - navigationStartTime : void 0;
246
+ navigationStartTime = null;
247
+ if (failure) {
248
+ ErrorExplorer.addBreadcrumb({
249
+ type: "navigation",
250
+ category: "router.error",
251
+ message: `Navigation failed to ${getRouteName(to)}`,
252
+ level: "error",
253
+ data: {
254
+ to: buildRouteData(to, options),
255
+ error: failure.message,
256
+ type: failure.type,
257
+ duration
258
+ }
259
+ });
260
+ if (failure.type !== 4) {
261
+ ErrorExplorer.captureException(failure, {
262
+ tags: {
263
+ "router.error": "navigation_failure",
264
+ "router.to": getRouteName(to)
265
+ }
266
+ });
267
+ }
268
+ } else {
269
+ ErrorExplorer.addBreadcrumb({
270
+ type: "navigation",
271
+ category: "router",
272
+ message: `Navigated to ${getRouteName(to)}`,
273
+ level: "info",
274
+ data: {
275
+ route: buildRouteData(to, options),
276
+ duration
277
+ }
278
+ });
279
+ }
280
+ });
281
+ const errorHandler = router.onError((error) => {
282
+ ErrorExplorer.addBreadcrumb({
283
+ type: "error",
284
+ category: "router.error",
285
+ message: error.message,
286
+ level: "error"
287
+ });
288
+ ErrorExplorer.captureException(error, {
289
+ tags: {
290
+ "router.error": "unhandled"
291
+ }
292
+ });
293
+ });
294
+ return () => {
295
+ beforeEachGuard();
296
+ afterEachGuard();
297
+ errorHandler();
298
+ };
299
+ }
300
+ function createRouterIntegration(options = {}) {
301
+ let cleanup = null;
302
+ return {
303
+ /**
304
+ * Install the router integration
305
+ */
306
+ install(router) {
307
+ cleanup = setupRouterIntegration(router, options);
308
+ },
309
+ /**
310
+ * Uninstall the router integration
311
+ */
312
+ uninstall() {
313
+ if (cleanup) {
314
+ cleanup();
315
+ cleanup = null;
316
+ }
317
+ }
318
+ };
319
+ }
320
+ var ErrorBoundary = defineComponent({
321
+ name: "ErrorBoundary",
322
+ props: {
323
+ /**
324
+ * Whether to capture the error to Error Explorer
325
+ */
326
+ capture: {
327
+ type: Boolean,
328
+ default: true
329
+ },
330
+ /**
331
+ * Additional tags to add when capturing
332
+ */
333
+ tags: {
334
+ type: Object,
335
+ default: () => ({})
336
+ },
337
+ /**
338
+ * Additional context to add when capturing
339
+ */
340
+ context: {
341
+ type: Object,
342
+ default: () => ({})
343
+ },
344
+ /**
345
+ * Fallback component to render when an error occurs
346
+ */
347
+ fallback: {
348
+ type: [Object, Function, null],
349
+ default: null
350
+ },
351
+ /**
352
+ * Whether to stop error propagation
353
+ */
354
+ stopPropagation: {
355
+ type: Boolean,
356
+ default: true
357
+ }
358
+ },
359
+ emits: {
360
+ /**
361
+ * Emitted when an error is caught
362
+ */
363
+ error: (error, info) => true,
364
+ /**
365
+ * Emitted when the error state is reset
366
+ */
367
+ reset: () => true
368
+ },
369
+ setup(props, { slots, emit, expose }) {
370
+ const error = ref(null);
371
+ const errorInfo = ref("");
372
+ const reset = () => {
373
+ error.value = null;
374
+ errorInfo.value = "";
375
+ emit("reset");
376
+ };
377
+ onErrorCaptured((err, instance, info) => {
378
+ const capturedError = err instanceof Error ? err : new Error(String(err));
379
+ error.value = capturedError;
380
+ errorInfo.value = info;
381
+ emit("error", capturedError, info);
382
+ ErrorExplorer.addBreadcrumb({
383
+ type: "error",
384
+ category: "vue.errorBoundary",
385
+ message: capturedError.message,
386
+ level: "error",
387
+ data: {
388
+ info,
389
+ component: instance?.$.type ? instance.$.type.name : void 0
390
+ }
391
+ });
392
+ if (props.capture) {
393
+ ErrorExplorer.captureException(capturedError, {
394
+ tags: {
395
+ "errorBoundary": "true",
396
+ "vue.info": info,
397
+ ...props.tags
398
+ },
399
+ extra: {
400
+ errorBoundary: {
401
+ info,
402
+ context: props.context
403
+ }
404
+ }
405
+ });
406
+ }
407
+ return props.stopPropagation;
408
+ });
409
+ expose({
410
+ reset,
411
+ error
412
+ });
413
+ return () => {
414
+ if (error.value) {
415
+ if (slots.fallback) {
416
+ return slots.fallback({
417
+ error: error.value,
418
+ info: errorInfo.value,
419
+ reset
420
+ });
421
+ }
422
+ if (props.fallback) {
423
+ if (typeof props.fallback === "function") {
424
+ return props.fallback();
425
+ }
426
+ return props.fallback;
427
+ }
428
+ return h("div", { class: "error-boundary-fallback" }, [
429
+ h("p", { style: "color: red;" }, `Error: ${error.value.message}`),
430
+ h(
431
+ "button",
432
+ {
433
+ onClick: reset,
434
+ style: "margin-top: 8px; padding: 4px 12px; cursor: pointer;"
435
+ },
436
+ "Try Again"
437
+ )
438
+ ]);
439
+ }
440
+ return slots.default?.();
441
+ };
442
+ }
443
+ });
444
+ function withErrorBoundary(Component, options = {}) {
445
+ return defineComponent({
446
+ name: `WithErrorBoundary(${Component.name || "Component"})`,
447
+ setup(_, { attrs, slots }) {
448
+ return () => h(
449
+ ErrorBoundary,
450
+ {
451
+ capture: options.capture ?? true,
452
+ tags: options.tags ?? {},
453
+ context: options.context ?? {},
454
+ fallback: options.fallback ?? null,
455
+ onError: options.onError
456
+ },
457
+ {
458
+ default: () => h(Component, attrs, slots)
459
+ }
460
+ );
461
+ }
462
+ });
463
+ }
464
+ var ErrorExplorerKey = /* @__PURE__ */ Symbol("errorExplorer");
465
+ function useErrorExplorer() {
466
+ const injected = inject(ErrorExplorerKey, null) || inject("errorExplorer", null);
467
+ const errorExplorer = injected || ErrorExplorer;
468
+ return {
469
+ /**
470
+ * Check if Error Explorer is initialized
471
+ */
472
+ isInitialized: () => errorExplorer.isInitialized(),
473
+ /**
474
+ * Capture an exception
475
+ */
476
+ captureException: (error, context) => errorExplorer.captureException(error, context),
477
+ /**
478
+ * Capture a message
479
+ */
480
+ captureMessage: (message, level) => errorExplorer.captureMessage(message, level),
481
+ /**
482
+ * Add a breadcrumb
483
+ */
484
+ addBreadcrumb: (breadcrumb) => errorExplorer.addBreadcrumb(breadcrumb),
485
+ /**
486
+ * Set user context
487
+ */
488
+ setUser: (user) => errorExplorer.setUser(user),
489
+ /**
490
+ * Clear user context
491
+ */
492
+ clearUser: () => errorExplorer.clearUser(),
493
+ /**
494
+ * Set a tag
495
+ */
496
+ setTag: (key, value) => errorExplorer.setTag(key, value),
497
+ /**
498
+ * Set multiple tags
499
+ */
500
+ setTags: (tags) => errorExplorer.setTags(tags),
501
+ /**
502
+ * Set extra context
503
+ */
504
+ setExtra: (extra) => errorExplorer.setExtra(extra),
505
+ /**
506
+ * Set named context
507
+ */
508
+ setContext: (name, context) => errorExplorer.setContext(name, context),
509
+ /**
510
+ * Flush pending events
511
+ */
512
+ flush: (timeout) => errorExplorer.flush(timeout),
513
+ /**
514
+ * Close the SDK
515
+ */
516
+ close: (timeout) => errorExplorer.close(timeout)
517
+ };
518
+ }
519
+ function useComponentBreadcrumbs() {
520
+ const instance = getCurrentInstance();
521
+ const componentName = instance?.type ? instance.type.name || instance.type.__name || "Unknown" : "Unknown";
522
+ onMounted(() => {
523
+ ErrorExplorer.addBreadcrumb({
524
+ type: "debug",
525
+ category: "vue.lifecycle",
526
+ message: `${componentName} mounted`,
527
+ level: "debug"
528
+ });
529
+ });
530
+ onUnmounted(() => {
531
+ ErrorExplorer.addBreadcrumb({
532
+ type: "debug",
533
+ category: "vue.lifecycle",
534
+ message: `${componentName} unmounted`,
535
+ level: "debug"
536
+ });
537
+ });
538
+ }
539
+ function useActionTracker() {
540
+ const instance = getCurrentInstance();
541
+ const componentName = instance?.type ? instance.type.name || instance.type.__name || "Unknown" : "Unknown";
542
+ return {
543
+ /**
544
+ * Track a user action
545
+ */
546
+ trackAction: (action, data) => {
547
+ ErrorExplorer.addBreadcrumb({
548
+ type: "user-action",
549
+ category: "action",
550
+ message: action,
551
+ level: "info",
552
+ data: {
553
+ component: componentName,
554
+ ...data
555
+ }
556
+ });
557
+ },
558
+ /**
559
+ * Track a UI interaction
560
+ */
561
+ trackInteraction: (element, action, data) => {
562
+ ErrorExplorer.addBreadcrumb({
563
+ type: "user-action",
564
+ category: `ui.${action}`,
565
+ message: `${action} on ${element}`,
566
+ level: "info",
567
+ data: {
568
+ component: componentName,
569
+ element,
570
+ ...data
571
+ }
572
+ });
573
+ }
574
+ };
575
+ }
576
+ function useErrorHandler(defaultContext) {
577
+ const instance = getCurrentInstance();
578
+ const componentName = instance?.type ? instance.type.name || instance.type.__name || "Unknown" : "Unknown";
579
+ const handleError = (error, context) => {
580
+ const err = error instanceof Error ? error : new Error(String(error));
581
+ ErrorExplorer.captureException(err, {
582
+ ...defaultContext,
583
+ ...context,
584
+ tags: {
585
+ "vue.component": componentName,
586
+ ...defaultContext?.tags,
587
+ ...context?.tags
588
+ }
589
+ });
590
+ return err;
591
+ };
592
+ const wrapAsync = (fn, context) => {
593
+ return async (...args) => {
594
+ try {
595
+ return await fn(...args);
596
+ } catch (error) {
597
+ handleError(error, context);
598
+ return void 0;
599
+ }
600
+ };
601
+ };
602
+ const tryCatch = (fn, context) => {
603
+ try {
604
+ return fn();
605
+ } catch (error) {
606
+ handleError(error, context);
607
+ return void 0;
608
+ }
609
+ };
610
+ return {
611
+ handleError,
612
+ wrapAsync,
613
+ tryCatch
614
+ };
615
+ }
616
+ function useUserContext(user) {
617
+ const setUserFromValue = (value) => {
618
+ if (value) {
619
+ ErrorExplorer.setUser(value);
620
+ } else {
621
+ ErrorExplorer.clearUser();
622
+ }
623
+ };
624
+ if (user && typeof user === "object" && "value" in user) {
625
+ const refValue = user.value;
626
+ setUserFromValue(refValue);
627
+ } else {
628
+ setUserFromValue(user);
629
+ }
630
+ return {
631
+ setUser: (newUser) => ErrorExplorer.setUser(newUser),
632
+ clearUser: () => ErrorExplorer.clearUser()
633
+ };
634
+ }
635
+ var src_default = createErrorExplorerPlugin;
636
+
637
+ export { ErrorBoundary, ErrorExplorerKey, createErrorExplorerPlugin, createRouterIntegration, src_default as default, restoreHandlers, setupRouterIntegration, useActionTracker, useComponentBreadcrumbs, useErrorExplorer, useErrorHandler, useUserContext, withErrorBoundary };
638
+ //# sourceMappingURL=index.js.map
639
+ //# sourceMappingURL=index.js.map