@flareapp/vue 2.4.0 → 2.5.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,474 @@
1
+ import { convertToError, redactUrlQuery, resolveDenylist } from "@flareapp/core";
2
+ import { defineComponent, getCurrentInstance, onErrorCaptured, ref, watch } from "vue";
3
+
4
+ //#region src/resolveFlare.ts
5
+ let defaultProvider = null;
6
+ function isDevMode() {
7
+ try {
8
+ return process.env.NODE_ENV !== "production";
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+ function registerDefaultFlare(provider) {
14
+ if (typeof window !== "undefined" && window.__flare) {
15
+ const message = "[flare] @flareapp/vue (web root) was imported in a renderer where the Electron bridge is present, pulling the keyed @flareapp/js singleton into the renderer. Import @flareapp/vue/inject and pass the @flareapp/electron/renderer instance instead.";
16
+ if (isDevMode()) throw new Error(message);
17
+ console.warn(message);
18
+ }
19
+ defaultProvider = provider;
20
+ }
21
+ function resolveFlare(explicit) {
22
+ if (explicit) return explicit;
23
+ if (defaultProvider) return defaultProvider();
24
+ throw new Error("[flare] No Flare instance available. Pass `flare` (e.g. from @flareapp/electron/renderer), or import @flareapp/vue (the package root) to use the @flareapp/js default singleton.");
25
+ }
26
+
27
+ //#endregion
28
+ //#region src/constants.ts
29
+ const PACKAGE_VERSION = typeof process !== "undefined" && typeof process.env?.PACKAGE_VERSION !== "undefined" ? process.env.PACKAGE_VERSION : "?";
30
+ const MAX_HIERARCHY_DEPTH = 50;
31
+ const DEFAULT_PROPS_DENYLIST = /password|passwd|pwd|token|secret|authorization|\bauth\b|bearer|oauth|credentials?|cookie|api[-_]?key|private[-_]?key|session|csrf|xsrf|\bpin\b|\bssn\b|card[-_]?number|\bcvv\b/i;
32
+ function resolveDenylist$1(custom, replaceDefault = false) {
33
+ return resolveDenylist(custom, replaceDefault, DEFAULT_PROPS_DENYLIST);
34
+ }
35
+ const MAX_PROP_STRING_LENGTH = 1e3;
36
+ const MAX_PROP_ARRAY_LENGTH = 100;
37
+ const MAX_PROP_OBJECT_KEYS = 100;
38
+ const INFO_TO_ORIGIN = {
39
+ "setup function": "setup",
40
+ "render function": "render",
41
+ "component update": "render",
42
+ "watcher getter": "watcher",
43
+ "watcher callback": "watcher",
44
+ "watcher cleanup function": "watcher",
45
+ "native event handler": "event",
46
+ "component event handler": "event",
47
+ "beforeCreate hook": "lifecycle",
48
+ "created hook": "lifecycle",
49
+ "beforeMount hook": "lifecycle",
50
+ "mounted hook": "lifecycle",
51
+ "beforeUpdate hook": "lifecycle",
52
+ "updated hook": "lifecycle",
53
+ "beforeUnmount hook": "lifecycle",
54
+ "unmounted hook": "lifecycle",
55
+ "activated hook": "lifecycle",
56
+ "deactivated hook": "lifecycle",
57
+ "errorCaptured hook": "lifecycle",
58
+ "renderTracked hook": "lifecycle",
59
+ "renderTriggered hook": "lifecycle",
60
+ "serverPrefetch hook": "lifecycle",
61
+ "vnode hook": "lifecycle",
62
+ "directive hook": "lifecycle",
63
+ "transition hook": "lifecycle",
64
+ "ref function": "setup",
65
+ "async component loader": "setup",
66
+ "scheduler flush": "render",
67
+ "app errorHandler": "lifecycle",
68
+ "app warnHandler": "lifecycle",
69
+ "app unmount cleanup function": "lifecycle",
70
+ "0": "setup",
71
+ "1": "render",
72
+ "2": "watcher",
73
+ "3": "watcher",
74
+ "4": "watcher",
75
+ "5": "event",
76
+ "6": "event",
77
+ "7": "lifecycle",
78
+ "8": "lifecycle",
79
+ "9": "lifecycle",
80
+ "10": "lifecycle",
81
+ "11": "lifecycle",
82
+ "12": "setup",
83
+ "13": "setup",
84
+ "14": "render",
85
+ "15": "render",
86
+ "16": "lifecycle",
87
+ "sp": "lifecycle",
88
+ "bc": "lifecycle",
89
+ "c": "lifecycle",
90
+ "bm": "lifecycle",
91
+ "m": "lifecycle",
92
+ "bu": "lifecycle",
93
+ "u": "lifecycle",
94
+ "bum": "lifecycle",
95
+ "um": "lifecycle",
96
+ "a": "lifecycle",
97
+ "da": "lifecycle",
98
+ "ec": "lifecycle",
99
+ "rtc": "lifecycle",
100
+ "rtg": "lifecycle"
101
+ };
102
+
103
+ //#endregion
104
+ //#region src/getComponentName.ts
105
+ function getComponentName(instance) {
106
+ if (!instance) return "AnonymousComponent";
107
+ const options = instance.$options;
108
+ return options.__name || options.name || "AnonymousComponent";
109
+ }
110
+
111
+ //#endregion
112
+ //#region src/buildComponentHierarchy.ts
113
+ function buildComponentHierarchy(instance) {
114
+ const hierarchy = [];
115
+ let current = instance;
116
+ while (current && hierarchy.length < MAX_HIERARCHY_DEPTH) {
117
+ hierarchy.push(getComponentName(current));
118
+ current = current.$parent;
119
+ }
120
+ return hierarchy;
121
+ }
122
+
123
+ //#endregion
124
+ //#region src/serializeProps.ts
125
+ function serializeProps(value, maxDepth, denylist = DEFAULT_PROPS_DENYLIST) {
126
+ return serialize(value, 0, maxDepth, /* @__PURE__ */ new WeakSet(), denylist);
127
+ }
128
+ function serialize(value, depth, maxDepth, seen, denylist) {
129
+ if (value === null) return null;
130
+ const type = typeof value;
131
+ if (type === "function") return "[Function]";
132
+ if (type === "symbol") return "[Symbol]";
133
+ if (type === "bigint") return value.toString();
134
+ if (type === "string") return truncateString(value);
135
+ if (type !== "object") return value;
136
+ if (seen.has(value)) return "[Circular]";
137
+ if (Array.isArray(value)) {
138
+ if (depth > maxDepth) return "[Array]";
139
+ seen.add(value);
140
+ const out = (value.length > MAX_PROP_ARRAY_LENGTH ? value.slice(0, MAX_PROP_ARRAY_LENGTH) : value).map((item) => serialize(item, depth + 1, maxDepth, seen, denylist));
141
+ if (value.length > MAX_PROP_ARRAY_LENGTH) out.push(`[… ${value.length - MAX_PROP_ARRAY_LENGTH} more items]`);
142
+ seen.delete(value);
143
+ return out;
144
+ }
145
+ if (!isPlainObject(value)) return "[Object]";
146
+ if (depth > maxDepth) return "[Object]";
147
+ seen.add(value);
148
+ const out = {};
149
+ const keys = Object.keys(value);
150
+ const limitedKeys = keys.length > MAX_PROP_OBJECT_KEYS ? keys.slice(0, MAX_PROP_OBJECT_KEYS) : keys;
151
+ for (const key of limitedKeys) {
152
+ if (denylist.test(key)) {
153
+ out[key] = "[redacted]";
154
+ continue;
155
+ }
156
+ out[key] = serialize(value[key], depth + 1, maxDepth, seen, denylist);
157
+ }
158
+ if (keys.length > MAX_PROP_OBJECT_KEYS) out["…"] = `[${keys.length - MAX_PROP_OBJECT_KEYS} more keys]`;
159
+ seen.delete(value);
160
+ return out;
161
+ }
162
+ function truncateString(value) {
163
+ if (value.length <= MAX_PROP_STRING_LENGTH) return value;
164
+ return `${value.slice(0, MAX_PROP_STRING_LENGTH)}…[truncated ${value.length - MAX_PROP_STRING_LENGTH} chars]`;
165
+ }
166
+ function isPlainObject(value) {
167
+ if (value === null || typeof value !== "object") return false;
168
+ const prototype = Object.getPrototypeOf(value);
169
+ return prototype === null || prototype === Object.prototype;
170
+ }
171
+
172
+ //#endregion
173
+ //#region src/buildComponentHierarchyFrames.ts
174
+ function buildComponentHierarchyFrames(instance, options) {
175
+ const frames = [];
176
+ let current = instance;
177
+ while (current && frames.length < MAX_HIERARCHY_DEPTH) {
178
+ const frameOptions = current.$options;
179
+ const frame = {
180
+ component: getComponentName(current),
181
+ file: frameOptions.__file ?? null
182
+ };
183
+ if (options.attachProps && current.$props) frame.props = serializeProps(current.$props, options.propsMaxDepth, options.propsDenylist);
184
+ frames.push(frame);
185
+ current = current.$parent;
186
+ }
187
+ return frames;
188
+ }
189
+
190
+ //#endregion
191
+ //#region src/getErrorOrigin.ts
192
+ function getErrorOrigin(info) {
193
+ return INFO_TO_ORIGIN[info] ?? "unknown";
194
+ }
195
+
196
+ //#endregion
197
+ //#region src/getRouteContext.ts
198
+ const ROUTE_PARAMS_DEPTH = 2;
199
+ function getRouteContext(router, options = {}) {
200
+ if (!router || typeof router !== "object" || !("currentRoute" in router)) return null;
201
+ const currentRouteRef = router.currentRoute;
202
+ if (!currentRouteRef || typeof currentRouteRef !== "object" || !("value" in currentRouteRef)) return null;
203
+ const route = currentRouteRef.value;
204
+ if (!route || typeof route !== "object") return null;
205
+ const r = route;
206
+ const name = r.name;
207
+ const denylist = options.denylist ?? DEFAULT_PROPS_DENYLIST;
208
+ const params = r.params && typeof r.params === "object" ? r.params : {};
209
+ const query = r.query && typeof r.query === "object" ? r.query : {};
210
+ return {
211
+ name: typeof name === "string" ? name : typeof name === "symbol" ? name.toString() : null,
212
+ path: typeof r.path === "string" ? r.path : "",
213
+ fullPath: typeof r.fullPath === "string" ? redactUrlQuery(r.fullPath, denylist) : "",
214
+ params: serializeProps(params, ROUTE_PARAMS_DEPTH, denylist),
215
+ query: serializeProps(query, ROUTE_PARAMS_DEPTH, denylist),
216
+ hash: typeof r.hash === "string" ? r.hash : "",
217
+ matched: Array.isArray(r.matched) ? r.matched.map((record) => {
218
+ if (!record || typeof record !== "object") return "unknown";
219
+ const n = record.name;
220
+ return typeof n === "string" ? n : typeof n === "symbol" ? n.toString() : "unknown";
221
+ }) : []
222
+ };
223
+ }
224
+
225
+ //#endregion
226
+ //#region src/identify.ts
227
+ const sdkTagged = /* @__PURE__ */ new WeakSet();
228
+ const frameworkTagged = /* @__PURE__ */ new WeakSet();
229
+ function registerVueSdkInfo(flare) {
230
+ if (sdkTagged.has(flare)) return;
231
+ sdkTagged.add(flare);
232
+ flare.setSdkInfo({
233
+ name: "@flareapp/vue",
234
+ version: PACKAGE_VERSION
235
+ });
236
+ }
237
+ function tagVueFramework(flare, appVersion) {
238
+ if (frameworkTagged.has(flare)) return;
239
+ frameworkTagged.add(flare);
240
+ flare.setFramework({
241
+ name: "Vue",
242
+ version: appVersion
243
+ });
244
+ }
245
+
246
+ //#endregion
247
+ //#region src/flareVue.ts
248
+ function vueContextToAttributes(context) {
249
+ const vue = {
250
+ info: context.vue.info,
251
+ errorOrigin: context.vue.errorOrigin,
252
+ componentName: context.vue.componentName,
253
+ componentHierarchy: context.vue.componentHierarchy,
254
+ componentHierarchyFrames: context.vue.componentHierarchyFrames
255
+ };
256
+ if (context.vue.componentProps) vue.componentProps = context.vue.componentProps;
257
+ if (context.vue.route) vue.route = context.vue.route;
258
+ return { "context.custom": { vue } };
259
+ }
260
+ function vueWarningContextToAttributes(context) {
261
+ const vue = {
262
+ type: context.vue.type,
263
+ info: context.vue.info,
264
+ componentName: context.vue.componentName,
265
+ componentTrace: context.vue.componentTrace
266
+ };
267
+ if (context.vue.route) vue.route = context.vue.route;
268
+ return { "context.custom": { vue } };
269
+ }
270
+ const installedApps = /* @__PURE__ */ new WeakSet();
271
+ const flareVue = (app, options) => {
272
+ if (installedApps.has(app)) return;
273
+ const flare = resolveFlare(options?.flare);
274
+ installedApps.add(app);
275
+ if (!options?.flare) registerVueSdkInfo(flare);
276
+ tagVueFramework(flare, app.version);
277
+ const attachProps = options?.attachProps ?? false;
278
+ const propsMaxDepth = options?.propsMaxDepth ?? 2;
279
+ const propsDenylist = resolveDenylist$1(options?.propsDenylist, options?.replaceDefaultDenylist);
280
+ const initialErrorHandler = app.config.errorHandler;
281
+ app.config.errorHandler = (error, instance, info) => {
282
+ const errorToReport = convertToError(error);
283
+ options?.beforeEvaluate?.({
284
+ error: errorToReport,
285
+ instance,
286
+ info
287
+ });
288
+ const errorOrigin = getErrorOrigin(info);
289
+ const componentName = getComponentName(instance);
290
+ const componentProps = attachProps && instance?.$props ? serializeProps(instance.$props, propsMaxDepth, propsDenylist) : void 0;
291
+ const componentHierarchy = buildComponentHierarchy(instance);
292
+ const componentHierarchyFrames = buildComponentHierarchyFrames(instance, {
293
+ attachProps,
294
+ propsMaxDepth,
295
+ propsDenylist
296
+ });
297
+ const route = getRouteContext(app.config.globalProperties.$router, { denylist: propsDenylist });
298
+ const context = { vue: {
299
+ info,
300
+ errorOrigin,
301
+ componentName,
302
+ ...componentProps && { componentProps },
303
+ componentHierarchy,
304
+ componentHierarchyFrames,
305
+ ...route && { route }
306
+ } };
307
+ const finalContext = options?.beforeSubmit?.({
308
+ error: errorToReport,
309
+ instance,
310
+ info,
311
+ context
312
+ }) ?? context;
313
+ flare.reportSilently(errorToReport, vueContextToAttributes(finalContext));
314
+ options?.afterSubmit?.({
315
+ error: errorToReport,
316
+ instance,
317
+ info,
318
+ context: finalContext
319
+ });
320
+ if (typeof initialErrorHandler === "function") {
321
+ initialErrorHandler(error, instance, info);
322
+ return;
323
+ }
324
+ console.error(error);
325
+ };
326
+ if (options?.captureWarnings) {
327
+ const initialWarnHandler = app.config.warnHandler;
328
+ app.config.warnHandler = (msg, instance, trace) => {
329
+ const componentName = getComponentName(instance);
330
+ const route = getRouteContext(app.config.globalProperties.$router, { denylist: propsDenylist });
331
+ const context = { vue: {
332
+ type: "warning",
333
+ info: msg,
334
+ componentName,
335
+ componentTrace: trace,
336
+ ...route && { route }
337
+ } };
338
+ Promise.resolve(flare.reportMessage(msg, "warning", vueWarningContextToAttributes(context))).catch(() => {});
339
+ if (typeof initialWarnHandler === "function") initialWarnHandler(msg, instance, trace);
340
+ };
341
+ }
342
+ };
343
+
344
+ //#endregion
345
+ //#region src/FlareErrorBoundary.ts
346
+ const FlareErrorBoundary = defineComponent({
347
+ name: "FlareErrorBoundary",
348
+ props: {
349
+ flare: {
350
+ type: Object,
351
+ default: void 0
352
+ },
353
+ beforeEvaluate: {
354
+ type: Function,
355
+ default: void 0
356
+ },
357
+ beforeSubmit: {
358
+ type: Function,
359
+ default: void 0
360
+ },
361
+ afterSubmit: {
362
+ type: Function,
363
+ default: void 0
364
+ },
365
+ onReset: {
366
+ type: Function,
367
+ default: void 0
368
+ },
369
+ resetKeys: {
370
+ type: Array,
371
+ default: void 0
372
+ },
373
+ attachProps: {
374
+ type: Boolean,
375
+ default: false
376
+ },
377
+ propsMaxDepth: {
378
+ type: Number,
379
+ default: 2
380
+ },
381
+ propsDenylist: {
382
+ type: RegExp,
383
+ default: void 0
384
+ },
385
+ replaceDefaultDenylist: {
386
+ type: Boolean,
387
+ default: false
388
+ }
389
+ },
390
+ setup(props, { slots }) {
391
+ const flareInstance = resolveFlare(props.flare);
392
+ tagVueFramework(flareInstance, getCurrentInstance()?.appContext.app.version);
393
+ const currentInstance = getCurrentInstance();
394
+ const error = ref(null);
395
+ const componentProps = ref(void 0);
396
+ const componentHierarchy = ref([]);
397
+ const componentHierarchyFrames = ref([]);
398
+ const resetErrorBoundary = () => {
399
+ props.onReset?.(error.value);
400
+ error.value = null;
401
+ componentProps.value = void 0;
402
+ componentHierarchy.value = [];
403
+ componentHierarchyFrames.value = [];
404
+ };
405
+ watch(() => props.resetKeys, (nextKeys, prevKeys) => {
406
+ if (error.value === null || !nextKeys || !prevKeys) return;
407
+ const lengthChanged = prevKeys.length !== nextKeys.length;
408
+ const valuesChanged = nextKeys.some((key, i) => !Object.is(key, prevKeys[i]));
409
+ if (lengthChanged || valuesChanged) resetErrorBoundary();
410
+ });
411
+ onErrorCaptured((currentError, instance, info) => {
412
+ const errorToReport = convertToError(currentError);
413
+ props.beforeEvaluate?.({
414
+ error: errorToReport,
415
+ instance,
416
+ info
417
+ });
418
+ const resolvedDenylist = resolveDenylist$1(props.propsDenylist, props.replaceDefaultDenylist);
419
+ const hierarchy = buildComponentHierarchy(instance);
420
+ const hierarchyFrames = buildComponentHierarchyFrames(instance, {
421
+ attachProps: props.attachProps,
422
+ propsMaxDepth: props.propsMaxDepth,
423
+ propsDenylist: resolvedDenylist
424
+ });
425
+ const componentName = getComponentName(instance);
426
+ error.value = errorToReport;
427
+ const instanceProps = props.attachProps && instance?.$props ? serializeProps(instance.$props, props.propsMaxDepth, resolvedDenylist) : void 0;
428
+ const errorOrigin = getErrorOrigin(info);
429
+ const route = getRouteContext(currentInstance?.appContext.config.globalProperties.$router, { denylist: resolvedDenylist });
430
+ const context = { vue: {
431
+ info,
432
+ errorOrigin,
433
+ ...route && { route },
434
+ componentName,
435
+ ...instanceProps && { componentProps: instanceProps },
436
+ componentHierarchy: hierarchy,
437
+ componentHierarchyFrames: hierarchyFrames
438
+ } };
439
+ const finalContext = props.beforeSubmit?.({
440
+ error: errorToReport,
441
+ instance,
442
+ info,
443
+ context
444
+ }) ?? context;
445
+ componentProps.value = finalContext.vue.componentProps;
446
+ componentHierarchy.value = finalContext.vue.componentHierarchy;
447
+ componentHierarchyFrames.value = finalContext.vue.componentHierarchyFrames;
448
+ flareInstance.reportSilently(errorToReport, vueContextToAttributes(finalContext));
449
+ props.afterSubmit?.({
450
+ error: errorToReport,
451
+ instance,
452
+ info,
453
+ context: finalContext
454
+ });
455
+ return false;
456
+ });
457
+ return () => {
458
+ if (error.value !== null) {
459
+ if (slots.fallback) return slots.fallback({
460
+ error: error.value,
461
+ ...componentProps.value && { componentProps: componentProps.value },
462
+ componentHierarchy: componentHierarchy.value,
463
+ componentHierarchyFrames: componentHierarchyFrames.value,
464
+ resetErrorBoundary
465
+ });
466
+ return null;
467
+ }
468
+ return slots.default?.();
469
+ };
470
+ }
471
+ });
472
+
473
+ //#endregion
474
+ export { registerDefaultFlare as i, flareVue as n, DEFAULT_PROPS_DENYLIST as r, FlareErrorBoundary as t };