@flareapp/vue 1.0.1 → 2.0.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/.oxlintrc.json +8 -0
- package/CHANGELOG.md +23 -0
- package/README.md +39 -3
- package/dist/index.cjs +445 -0
- package/dist/index.d.cts +176 -0
- package/dist/index.d.mts +175 -27
- package/dist/index.mjs +441 -19
- package/package.json +42 -12
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -45
- package/src/index.js +0 -26
package/.oxlintrc.json
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## 2.0.0
|
|
2
|
+
|
|
3
|
+
### Breaking changes
|
|
4
|
+
|
|
5
|
+
- Bumps peer dependency `@flareapp/js` to `^2.0.0`. See its CHANGELOG for wire format changes.
|
|
6
|
+
- `flareVue` no longer passes a third `extraSolutionParameters` arg to `flare.report`.
|
|
7
|
+
- Vue error context now lands on the report as `attributes['context.custom'].vue.*` (was nested under `context.vue.*`). Key fields: `vue.info`, `vue.errorOrigin`, `vue.componentName`, `vue.componentHierarchy`, `vue.componentHierarchyFrames`, `vue.componentProps`, `vue.route`.
|
|
8
|
+
- `vue-router` moved from a required to an optional peer dependency.
|
|
9
|
+
- Dropped Vue 2 support. Only Vue 3 (`^3.0.0`) is supported.
|
|
10
|
+
|
|
11
|
+
### New
|
|
12
|
+
|
|
13
|
+
- **`FlareErrorBoundary` component.** Vue equivalent of the React error boundary. Catches errors in its subtree via `onErrorCaptured`, reports to Flare with full Vue context, and renders a `#fallback` slot with `error`, `componentProps`, `componentHierarchy`, `componentHierarchyFrames`, and `resetErrorBoundary`. Supports `resetKeys` for auto-reset and `onReset` callback.
|
|
14
|
+
- **Lifecycle hooks on `flareVue()`.** `beforeEvaluate`, `beforeSubmit`, and `afterSubmit` options give control over the report pipeline at the app level.
|
|
15
|
+
- **`captureWarnings` option.** When enabled, Vue warnings are reported via `flare.reportMessage()` at `warning` level.
|
|
16
|
+
- **Prop serialization.** `attachProps` (boolean), `propsMaxDepth` (number), `propsDenylist` (regex), and `replaceDefaultDenylist` (boolean) control which component props are included in reports. `DEFAULT_PROPS_DENYLIST` is exported.
|
|
17
|
+
- **Component hierarchy frames.** Reports include structured `componentHierarchyFrames` with component name, file path, and serialized props per frame.
|
|
18
|
+
- Route context is automatically captured from `app.config.globalProperties.$router` when an error occurs. No explicit router option needed.
|
|
19
|
+
- Non-`Error` values are coerced to `Error`.
|
|
20
|
+
- Chains existing `app.config.errorHandler` instead of replacing it silently.
|
|
21
|
+
- Duplicate-install guard prevents double-reporting when `app.use(flareVue)` is called more than once.
|
|
22
|
+
- The package self-identifies via `flare.setSdkInfo({ name: '@flareapp/vue', version })` and `flare.setFramework({ name: 'Vue', version: app.version })` on install.
|
|
23
|
+
- New exported types: `ComponentHierarchyFrame`, `ErrorOrigin`, `FlareErrorBoundaryFallbackProps`, `FlareErrorBoundaryHookParams`, `FlareVueContext`, `FlareVueOptions`, `FlareVueWarningContext`, `RouteContext`, `RouteParamValue`, `RouteQueryValue`.
|
package/README.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flareapp/vue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vue integration for [Flare](https://flareapp.io) error tracking. Installs a Vue error handler that catches component errors and reports them to Flare with Vue-specific context (component name, lifecycle info).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @flareapp/vue @flareapp/js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
Initialize the Flare client and register the Vue error handler:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { createApp } from 'vue';
|
|
17
|
+
import { flare } from '@flareapp/js';
|
|
18
|
+
import { flareVue } from '@flareapp/vue';
|
|
19
|
+
|
|
20
|
+
import App from './App.vue';
|
|
21
|
+
|
|
22
|
+
flare.light('YOUR_FLARE_API_KEY');
|
|
23
|
+
|
|
24
|
+
const app = createApp(App);
|
|
25
|
+
|
|
26
|
+
flareVue(app);
|
|
27
|
+
|
|
28
|
+
app.mount('#app');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
Full documentation on the Vue error handler and its options is available at [flareapp.io/docs/vue/general/installation](https://flareapp.io/docs/vue/general/installation).
|
|
34
|
+
|
|
35
|
+
## Compatibility
|
|
36
|
+
|
|
37
|
+
- Vue 3
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
The MIT License (MIT). Please see [License File](../../LICENSE.md) for more information.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let _flareapp_js = require("@flareapp/js");
|
|
3
|
+
let vue = require("vue");
|
|
4
|
+
|
|
5
|
+
//#region src/constants.ts
|
|
6
|
+
const PACKAGE_VERSION = typeof process !== "undefined" && typeof process.env?.PACKAGE_VERSION !== "undefined" ? process.env.PACKAGE_VERSION : "?";
|
|
7
|
+
const MAX_HIERARCHY_DEPTH = 50;
|
|
8
|
+
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;
|
|
9
|
+
function resolveDenylist(custom, replaceDefault = false) {
|
|
10
|
+
return (0, _flareapp_js.resolveDenylist)(custom, replaceDefault, DEFAULT_PROPS_DENYLIST);
|
|
11
|
+
}
|
|
12
|
+
const MAX_PROP_STRING_LENGTH = 1e3;
|
|
13
|
+
const MAX_PROP_ARRAY_LENGTH = 100;
|
|
14
|
+
const MAX_PROP_OBJECT_KEYS = 100;
|
|
15
|
+
const INFO_TO_ORIGIN = {
|
|
16
|
+
"setup function": "setup",
|
|
17
|
+
"render function": "render",
|
|
18
|
+
"component update": "render",
|
|
19
|
+
"watcher getter": "watcher",
|
|
20
|
+
"watcher callback": "watcher",
|
|
21
|
+
"watcher cleanup function": "watcher",
|
|
22
|
+
"native event handler": "event",
|
|
23
|
+
"component event handler": "event",
|
|
24
|
+
"beforeCreate hook": "lifecycle",
|
|
25
|
+
"created hook": "lifecycle",
|
|
26
|
+
"beforeMount hook": "lifecycle",
|
|
27
|
+
"mounted hook": "lifecycle",
|
|
28
|
+
"beforeUpdate hook": "lifecycle",
|
|
29
|
+
"updated hook": "lifecycle",
|
|
30
|
+
"beforeUnmount hook": "lifecycle",
|
|
31
|
+
"unmounted hook": "lifecycle",
|
|
32
|
+
"activated hook": "lifecycle",
|
|
33
|
+
"deactivated hook": "lifecycle",
|
|
34
|
+
"errorCaptured hook": "lifecycle",
|
|
35
|
+
"renderTracked hook": "lifecycle",
|
|
36
|
+
"renderTriggered hook": "lifecycle",
|
|
37
|
+
"serverPrefetch hook": "lifecycle",
|
|
38
|
+
"vnode hook": "lifecycle",
|
|
39
|
+
"directive hook": "lifecycle",
|
|
40
|
+
"transition hook": "lifecycle",
|
|
41
|
+
"ref function": "setup",
|
|
42
|
+
"async component loader": "setup",
|
|
43
|
+
"scheduler flush": "render",
|
|
44
|
+
"app errorHandler": "lifecycle",
|
|
45
|
+
"app warnHandler": "lifecycle",
|
|
46
|
+
"app unmount cleanup function": "lifecycle",
|
|
47
|
+
"0": "setup",
|
|
48
|
+
"1": "render",
|
|
49
|
+
"2": "watcher",
|
|
50
|
+
"3": "watcher",
|
|
51
|
+
"4": "watcher",
|
|
52
|
+
"5": "event",
|
|
53
|
+
"6": "event",
|
|
54
|
+
"7": "lifecycle",
|
|
55
|
+
"8": "lifecycle",
|
|
56
|
+
"9": "lifecycle",
|
|
57
|
+
"10": "lifecycle",
|
|
58
|
+
"11": "lifecycle",
|
|
59
|
+
"12": "setup",
|
|
60
|
+
"13": "setup",
|
|
61
|
+
"14": "render",
|
|
62
|
+
"15": "render",
|
|
63
|
+
"16": "lifecycle",
|
|
64
|
+
"sp": "lifecycle",
|
|
65
|
+
"bc": "lifecycle",
|
|
66
|
+
"c": "lifecycle",
|
|
67
|
+
"bm": "lifecycle",
|
|
68
|
+
"m": "lifecycle",
|
|
69
|
+
"bu": "lifecycle",
|
|
70
|
+
"u": "lifecycle",
|
|
71
|
+
"bum": "lifecycle",
|
|
72
|
+
"um": "lifecycle",
|
|
73
|
+
"a": "lifecycle",
|
|
74
|
+
"da": "lifecycle",
|
|
75
|
+
"ec": "lifecycle",
|
|
76
|
+
"rtc": "lifecycle",
|
|
77
|
+
"rtg": "lifecycle"
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/getComponentName.ts
|
|
82
|
+
function getComponentName(instance) {
|
|
83
|
+
if (!instance) return "AnonymousComponent";
|
|
84
|
+
const options = instance.$options;
|
|
85
|
+
return options.__name || options.name || "AnonymousComponent";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/buildComponentHierarchy.ts
|
|
90
|
+
function buildComponentHierarchy(instance) {
|
|
91
|
+
const hierarchy = [];
|
|
92
|
+
let current = instance;
|
|
93
|
+
while (current && hierarchy.length < MAX_HIERARCHY_DEPTH) {
|
|
94
|
+
hierarchy.push(getComponentName(current));
|
|
95
|
+
current = current.$parent;
|
|
96
|
+
}
|
|
97
|
+
return hierarchy;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/serializeProps.ts
|
|
102
|
+
function serializeProps(value, maxDepth, denylist = DEFAULT_PROPS_DENYLIST) {
|
|
103
|
+
return serialize(value, 0, maxDepth, /* @__PURE__ */ new WeakSet(), denylist);
|
|
104
|
+
}
|
|
105
|
+
function serialize(value, depth, maxDepth, seen, denylist) {
|
|
106
|
+
if (value === null) return null;
|
|
107
|
+
const type = typeof value;
|
|
108
|
+
if (type === "function") return "[Function]";
|
|
109
|
+
if (type === "symbol") return "[Symbol]";
|
|
110
|
+
if (type === "bigint") return value.toString();
|
|
111
|
+
if (type === "string") return truncateString(value);
|
|
112
|
+
if (type !== "object") return value;
|
|
113
|
+
if (seen.has(value)) return "[Circular]";
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
if (depth > maxDepth) return "[Array]";
|
|
116
|
+
seen.add(value);
|
|
117
|
+
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));
|
|
118
|
+
if (value.length > MAX_PROP_ARRAY_LENGTH) out.push(`[… ${value.length - MAX_PROP_ARRAY_LENGTH} more items]`);
|
|
119
|
+
seen.delete(value);
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
if (!isPlainObject(value)) return "[Object]";
|
|
123
|
+
if (depth > maxDepth) return "[Object]";
|
|
124
|
+
seen.add(value);
|
|
125
|
+
const out = {};
|
|
126
|
+
const keys = Object.keys(value);
|
|
127
|
+
const limitedKeys = keys.length > MAX_PROP_OBJECT_KEYS ? keys.slice(0, MAX_PROP_OBJECT_KEYS) : keys;
|
|
128
|
+
for (const key of limitedKeys) {
|
|
129
|
+
if (denylist.test(key)) {
|
|
130
|
+
out[key] = "[redacted]";
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
out[key] = serialize(value[key], depth + 1, maxDepth, seen, denylist);
|
|
134
|
+
}
|
|
135
|
+
if (keys.length > MAX_PROP_OBJECT_KEYS) out["…"] = `[${keys.length - MAX_PROP_OBJECT_KEYS} more keys]`;
|
|
136
|
+
seen.delete(value);
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function truncateString(value) {
|
|
140
|
+
if (value.length <= MAX_PROP_STRING_LENGTH) return value;
|
|
141
|
+
return `${value.slice(0, MAX_PROP_STRING_LENGTH)}…[truncated ${value.length - MAX_PROP_STRING_LENGTH} chars]`;
|
|
142
|
+
}
|
|
143
|
+
function isPlainObject(value) {
|
|
144
|
+
if (value === null || typeof value !== "object") return false;
|
|
145
|
+
const prototype = Object.getPrototypeOf(value);
|
|
146
|
+
return prototype === null || prototype === Object.prototype;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/buildComponentHierarchyFrames.ts
|
|
151
|
+
function buildComponentHierarchyFrames(instance, options) {
|
|
152
|
+
const frames = [];
|
|
153
|
+
let current = instance;
|
|
154
|
+
while (current && frames.length < MAX_HIERARCHY_DEPTH) {
|
|
155
|
+
const frameOptions = current.$options;
|
|
156
|
+
const frame = {
|
|
157
|
+
component: getComponentName(current),
|
|
158
|
+
file: frameOptions.__file ?? null
|
|
159
|
+
};
|
|
160
|
+
if (options.attachProps && current.$props) frame.props = serializeProps(current.$props, options.propsMaxDepth, options.propsDenylist);
|
|
161
|
+
frames.push(frame);
|
|
162
|
+
current = current.$parent;
|
|
163
|
+
}
|
|
164
|
+
return frames;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/convertToError.ts
|
|
169
|
+
function convertToError(error) {
|
|
170
|
+
if (error instanceof Error) return error;
|
|
171
|
+
if (typeof error === "string") return new Error(error);
|
|
172
|
+
return new Error(String(error));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/getErrorOrigin.ts
|
|
177
|
+
function getErrorOrigin(info) {
|
|
178
|
+
return INFO_TO_ORIGIN[info] ?? "unknown";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/getRouteContext.ts
|
|
183
|
+
const ROUTE_PARAMS_DEPTH = 2;
|
|
184
|
+
function getRouteContext(router, options = {}) {
|
|
185
|
+
if (!router || typeof router !== "object" || !("currentRoute" in router)) return null;
|
|
186
|
+
const currentRouteRef = router.currentRoute;
|
|
187
|
+
if (!currentRouteRef || typeof currentRouteRef !== "object" || !("value" in currentRouteRef)) return null;
|
|
188
|
+
const route = currentRouteRef.value;
|
|
189
|
+
if (!route || typeof route !== "object") return null;
|
|
190
|
+
const r = route;
|
|
191
|
+
const name = r.name;
|
|
192
|
+
const denylist = options.denylist ?? DEFAULT_PROPS_DENYLIST;
|
|
193
|
+
const params = r.params && typeof r.params === "object" ? r.params : {};
|
|
194
|
+
const query = r.query && typeof r.query === "object" ? r.query : {};
|
|
195
|
+
return {
|
|
196
|
+
name: typeof name === "string" ? name : typeof name === "symbol" ? name.toString() : null,
|
|
197
|
+
path: typeof r.path === "string" ? r.path : "",
|
|
198
|
+
fullPath: typeof r.fullPath === "string" ? (0, _flareapp_js.redactFullPath)(r.fullPath, denylist) : "",
|
|
199
|
+
params: serializeProps(params, ROUTE_PARAMS_DEPTH, denylist),
|
|
200
|
+
query: serializeProps(query, ROUTE_PARAMS_DEPTH, denylist),
|
|
201
|
+
hash: typeof r.hash === "string" ? r.hash : "",
|
|
202
|
+
matched: Array.isArray(r.matched) ? r.matched.map((record) => {
|
|
203
|
+
if (!record || typeof record !== "object") return "unknown";
|
|
204
|
+
const n = record.name;
|
|
205
|
+
return typeof n === "string" ? n : typeof n === "symbol" ? n.toString() : "unknown";
|
|
206
|
+
}) : []
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/flareVue.ts
|
|
212
|
+
function vueContextToAttributes(context) {
|
|
213
|
+
const vue = {
|
|
214
|
+
info: context.vue.info,
|
|
215
|
+
errorOrigin: context.vue.errorOrigin,
|
|
216
|
+
componentName: context.vue.componentName,
|
|
217
|
+
componentHierarchy: context.vue.componentHierarchy,
|
|
218
|
+
componentHierarchyFrames: context.vue.componentHierarchyFrames
|
|
219
|
+
};
|
|
220
|
+
if (context.vue.componentProps) vue.componentProps = context.vue.componentProps;
|
|
221
|
+
if (context.vue.route) vue.route = context.vue.route;
|
|
222
|
+
return { "context.custom": {
|
|
223
|
+
framework: "vue",
|
|
224
|
+
vue
|
|
225
|
+
} };
|
|
226
|
+
}
|
|
227
|
+
function vueWarningContextToAttributes(context) {
|
|
228
|
+
const vue = {
|
|
229
|
+
type: context.vue.type,
|
|
230
|
+
info: context.vue.info,
|
|
231
|
+
componentName: context.vue.componentName,
|
|
232
|
+
componentTrace: context.vue.componentTrace
|
|
233
|
+
};
|
|
234
|
+
if (context.vue.route) vue.route = context.vue.route;
|
|
235
|
+
return { "context.custom": {
|
|
236
|
+
framework: "vue",
|
|
237
|
+
vue
|
|
238
|
+
} };
|
|
239
|
+
}
|
|
240
|
+
const installedApps = /* @__PURE__ */ new WeakSet();
|
|
241
|
+
const flareVue = (app, options) => {
|
|
242
|
+
if (installedApps.has(app)) return;
|
|
243
|
+
installedApps.add(app);
|
|
244
|
+
_flareapp_js.flare.setSdkInfo({
|
|
245
|
+
name: "@flareapp/vue",
|
|
246
|
+
version: PACKAGE_VERSION
|
|
247
|
+
});
|
|
248
|
+
_flareapp_js.flare.setFramework({
|
|
249
|
+
name: "Vue",
|
|
250
|
+
version: app.version
|
|
251
|
+
});
|
|
252
|
+
const attachProps = options?.attachProps ?? false;
|
|
253
|
+
const propsMaxDepth = options?.propsMaxDepth ?? 2;
|
|
254
|
+
const propsDenylist = resolveDenylist(options?.propsDenylist, options?.replaceDefaultDenylist);
|
|
255
|
+
const initialErrorHandler = app.config.errorHandler;
|
|
256
|
+
app.config.errorHandler = (error, instance, info) => {
|
|
257
|
+
const errorToReport = convertToError(error);
|
|
258
|
+
options?.beforeEvaluate?.({
|
|
259
|
+
error: errorToReport,
|
|
260
|
+
instance,
|
|
261
|
+
info
|
|
262
|
+
});
|
|
263
|
+
const errorOrigin = getErrorOrigin(info);
|
|
264
|
+
const componentName = getComponentName(instance);
|
|
265
|
+
const componentProps = attachProps && instance?.$props ? serializeProps(instance.$props, propsMaxDepth, propsDenylist) : void 0;
|
|
266
|
+
const componentHierarchy = buildComponentHierarchy(instance);
|
|
267
|
+
const componentHierarchyFrames = buildComponentHierarchyFrames(instance, {
|
|
268
|
+
attachProps,
|
|
269
|
+
propsMaxDepth,
|
|
270
|
+
propsDenylist
|
|
271
|
+
});
|
|
272
|
+
const route = getRouteContext(app.config.globalProperties.$router, { denylist: propsDenylist });
|
|
273
|
+
const context = { vue: {
|
|
274
|
+
info,
|
|
275
|
+
errorOrigin,
|
|
276
|
+
componentName,
|
|
277
|
+
...componentProps && { componentProps },
|
|
278
|
+
componentHierarchy,
|
|
279
|
+
componentHierarchyFrames,
|
|
280
|
+
...route && { route }
|
|
281
|
+
} };
|
|
282
|
+
const finalContext = options?.beforeSubmit?.({
|
|
283
|
+
error: errorToReport,
|
|
284
|
+
instance,
|
|
285
|
+
info,
|
|
286
|
+
context
|
|
287
|
+
}) ?? context;
|
|
288
|
+
Promise.resolve(_flareapp_js.flare.report(errorToReport, vueContextToAttributes(finalContext))).catch(() => {});
|
|
289
|
+
options?.afterSubmit?.({
|
|
290
|
+
error: errorToReport,
|
|
291
|
+
instance,
|
|
292
|
+
info,
|
|
293
|
+
context: finalContext
|
|
294
|
+
});
|
|
295
|
+
if (typeof initialErrorHandler === "function") {
|
|
296
|
+
initialErrorHandler(error, instance, info);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
console.error(error);
|
|
300
|
+
};
|
|
301
|
+
if (options?.captureWarnings) {
|
|
302
|
+
const initialWarnHandler = app.config.warnHandler;
|
|
303
|
+
app.config.warnHandler = (msg, instance, trace) => {
|
|
304
|
+
const componentName = getComponentName(instance);
|
|
305
|
+
const route = getRouteContext(app.config.globalProperties.$router, { denylist: propsDenylist });
|
|
306
|
+
const context = { vue: {
|
|
307
|
+
type: "warning",
|
|
308
|
+
info: msg,
|
|
309
|
+
componentName,
|
|
310
|
+
componentTrace: trace,
|
|
311
|
+
...route && { route }
|
|
312
|
+
} };
|
|
313
|
+
Promise.resolve(_flareapp_js.flare.reportMessage(msg, "warning", vueWarningContextToAttributes(context))).catch(() => {});
|
|
314
|
+
if (typeof initialWarnHandler === "function") initialWarnHandler(msg, instance, trace);
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/FlareErrorBoundary.ts
|
|
321
|
+
const FlareErrorBoundary = (0, vue.defineComponent)({
|
|
322
|
+
name: "FlareErrorBoundary",
|
|
323
|
+
props: {
|
|
324
|
+
beforeEvaluate: {
|
|
325
|
+
type: Function,
|
|
326
|
+
default: void 0
|
|
327
|
+
},
|
|
328
|
+
beforeSubmit: {
|
|
329
|
+
type: Function,
|
|
330
|
+
default: void 0
|
|
331
|
+
},
|
|
332
|
+
afterSubmit: {
|
|
333
|
+
type: Function,
|
|
334
|
+
default: void 0
|
|
335
|
+
},
|
|
336
|
+
onReset: {
|
|
337
|
+
type: Function,
|
|
338
|
+
default: void 0
|
|
339
|
+
},
|
|
340
|
+
resetKeys: {
|
|
341
|
+
type: Array,
|
|
342
|
+
default: void 0
|
|
343
|
+
},
|
|
344
|
+
attachProps: {
|
|
345
|
+
type: Boolean,
|
|
346
|
+
default: false
|
|
347
|
+
},
|
|
348
|
+
propsMaxDepth: {
|
|
349
|
+
type: Number,
|
|
350
|
+
default: 2
|
|
351
|
+
},
|
|
352
|
+
propsDenylist: {
|
|
353
|
+
type: RegExp,
|
|
354
|
+
default: void 0
|
|
355
|
+
},
|
|
356
|
+
replaceDefaultDenylist: {
|
|
357
|
+
type: Boolean,
|
|
358
|
+
default: false
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
setup(props, { slots }) {
|
|
362
|
+
const currentInstance = (0, vue.getCurrentInstance)();
|
|
363
|
+
const error = (0, vue.ref)(null);
|
|
364
|
+
const componentProps = (0, vue.ref)(void 0);
|
|
365
|
+
const componentHierarchy = (0, vue.ref)([]);
|
|
366
|
+
const componentHierarchyFrames = (0, vue.ref)([]);
|
|
367
|
+
const resetErrorBoundary = () => {
|
|
368
|
+
props.onReset?.(error.value);
|
|
369
|
+
error.value = null;
|
|
370
|
+
componentProps.value = void 0;
|
|
371
|
+
componentHierarchy.value = [];
|
|
372
|
+
componentHierarchyFrames.value = [];
|
|
373
|
+
};
|
|
374
|
+
(0, vue.watch)(() => props.resetKeys, (nextKeys, prevKeys) => {
|
|
375
|
+
if (error.value === null || !nextKeys || !prevKeys) return;
|
|
376
|
+
const lengthChanged = prevKeys.length !== nextKeys.length;
|
|
377
|
+
const valuesChanged = nextKeys.some((key, i) => !Object.is(key, prevKeys[i]));
|
|
378
|
+
if (lengthChanged || valuesChanged) resetErrorBoundary();
|
|
379
|
+
});
|
|
380
|
+
(0, vue.onErrorCaptured)((currentError, instance, info) => {
|
|
381
|
+
const errorToReport = convertToError(currentError);
|
|
382
|
+
props.beforeEvaluate?.({
|
|
383
|
+
error: errorToReport,
|
|
384
|
+
instance,
|
|
385
|
+
info
|
|
386
|
+
});
|
|
387
|
+
const resolvedDenylist = resolveDenylist(props.propsDenylist, props.replaceDefaultDenylist);
|
|
388
|
+
const hierarchy = buildComponentHierarchy(instance);
|
|
389
|
+
const hierarchyFrames = buildComponentHierarchyFrames(instance, {
|
|
390
|
+
attachProps: props.attachProps,
|
|
391
|
+
propsMaxDepth: props.propsMaxDepth,
|
|
392
|
+
propsDenylist: resolvedDenylist
|
|
393
|
+
});
|
|
394
|
+
const componentName = getComponentName(instance);
|
|
395
|
+
error.value = errorToReport;
|
|
396
|
+
const instanceProps = props.attachProps && instance?.$props ? serializeProps(instance.$props, props.propsMaxDepth, resolvedDenylist) : void 0;
|
|
397
|
+
const errorOrigin = getErrorOrigin(info);
|
|
398
|
+
const route = getRouteContext(currentInstance?.appContext.config.globalProperties.$router, { denylist: resolvedDenylist });
|
|
399
|
+
const context = { vue: {
|
|
400
|
+
info,
|
|
401
|
+
errorOrigin,
|
|
402
|
+
...route && { route },
|
|
403
|
+
componentName,
|
|
404
|
+
...instanceProps && { componentProps: instanceProps },
|
|
405
|
+
componentHierarchy: hierarchy,
|
|
406
|
+
componentHierarchyFrames: hierarchyFrames
|
|
407
|
+
} };
|
|
408
|
+
const finalContext = props.beforeSubmit?.({
|
|
409
|
+
error: errorToReport,
|
|
410
|
+
instance,
|
|
411
|
+
info,
|
|
412
|
+
context
|
|
413
|
+
}) ?? context;
|
|
414
|
+
componentProps.value = finalContext.vue.componentProps;
|
|
415
|
+
componentHierarchy.value = finalContext.vue.componentHierarchy;
|
|
416
|
+
componentHierarchyFrames.value = finalContext.vue.componentHierarchyFrames;
|
|
417
|
+
Promise.resolve(_flareapp_js.flare.report(errorToReport, vueContextToAttributes(finalContext))).catch(() => {});
|
|
418
|
+
props.afterSubmit?.({
|
|
419
|
+
error: errorToReport,
|
|
420
|
+
instance,
|
|
421
|
+
info,
|
|
422
|
+
context: finalContext
|
|
423
|
+
});
|
|
424
|
+
return false;
|
|
425
|
+
});
|
|
426
|
+
return () => {
|
|
427
|
+
if (error.value !== null) {
|
|
428
|
+
if (slots.fallback) return slots.fallback({
|
|
429
|
+
error: error.value,
|
|
430
|
+
...componentProps.value && { componentProps: componentProps.value },
|
|
431
|
+
componentHierarchy: componentHierarchy.value,
|
|
432
|
+
componentHierarchyFrames: componentHierarchyFrames.value,
|
|
433
|
+
resetErrorBoundary
|
|
434
|
+
});
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return slots.default?.();
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
exports.DEFAULT_PROPS_DENYLIST = DEFAULT_PROPS_DENYLIST;
|
|
444
|
+
exports.FlareErrorBoundary = FlareErrorBoundary;
|
|
445
|
+
exports.flareVue = flareVue;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as vue from "vue";
|
|
2
|
+
import { ComponentPublicInstance, Plugin, PropType } from "vue";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type ErrorOrigin = 'setup' | 'render' | 'lifecycle' | 'event' | 'watcher' | 'unknown';
|
|
6
|
+
type ComponentHierarchyFrame = {
|
|
7
|
+
component: string;
|
|
8
|
+
file: string | null;
|
|
9
|
+
props?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
type RouteParamValue = string | string[];
|
|
12
|
+
type RouteQueryValue = string | null;
|
|
13
|
+
type RouteContext = {
|
|
14
|
+
name: string | null;
|
|
15
|
+
path: string;
|
|
16
|
+
fullPath: string;
|
|
17
|
+
params: Record<string, RouteParamValue>;
|
|
18
|
+
query: Record<string, RouteQueryValue | RouteQueryValue[]>;
|
|
19
|
+
hash: string;
|
|
20
|
+
matched: string[];
|
|
21
|
+
};
|
|
22
|
+
type FlareVueContext = {
|
|
23
|
+
vue: {
|
|
24
|
+
info: string;
|
|
25
|
+
errorOrigin: ErrorOrigin;
|
|
26
|
+
componentName: string;
|
|
27
|
+
componentProps?: Record<string, unknown>;
|
|
28
|
+
componentHierarchy: string[];
|
|
29
|
+
componentHierarchyFrames: ComponentHierarchyFrame[];
|
|
30
|
+
route?: RouteContext;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
type FlareVueWarningContext = {
|
|
34
|
+
vue: {
|
|
35
|
+
type: 'warning';
|
|
36
|
+
info: string;
|
|
37
|
+
componentName: string;
|
|
38
|
+
componentTrace: string;
|
|
39
|
+
route?: RouteContext;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type FlareErrorBoundaryHookParams = {
|
|
43
|
+
error: Error;
|
|
44
|
+
instance: ComponentPublicInstance | null;
|
|
45
|
+
info: string;
|
|
46
|
+
};
|
|
47
|
+
type FlareVueOptions = {
|
|
48
|
+
captureWarnings?: boolean;
|
|
49
|
+
attachProps?: boolean;
|
|
50
|
+
propsMaxDepth?: number;
|
|
51
|
+
propsDenylist?: RegExp;
|
|
52
|
+
replaceDefaultDenylist?: boolean;
|
|
53
|
+
beforeEvaluate?: (params: FlareErrorBoundaryHookParams) => void;
|
|
54
|
+
beforeSubmit?: (params: FlareErrorBoundaryHookParams & {
|
|
55
|
+
context: FlareVueContext;
|
|
56
|
+
}) => FlareVueContext;
|
|
57
|
+
afterSubmit?: (params: FlareErrorBoundaryHookParams & {
|
|
58
|
+
context: FlareVueContext;
|
|
59
|
+
}) => void;
|
|
60
|
+
};
|
|
61
|
+
type FlareErrorBoundaryFallbackProps = {
|
|
62
|
+
error: Error;
|
|
63
|
+
componentProps?: Record<string, unknown>;
|
|
64
|
+
componentHierarchy: string[];
|
|
65
|
+
componentHierarchyFrames: ComponentHierarchyFrame[];
|
|
66
|
+
resetErrorBoundary: () => void;
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/FlareErrorBoundary.d.ts
|
|
70
|
+
declare const FlareErrorBoundary: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
71
|
+
beforeEvaluate: {
|
|
72
|
+
type: PropType<(params: FlareErrorBoundaryHookParams) => void>;
|
|
73
|
+
default: undefined;
|
|
74
|
+
};
|
|
75
|
+
beforeSubmit: {
|
|
76
|
+
type: PropType<(params: FlareErrorBoundaryHookParams & {
|
|
77
|
+
context: FlareVueContext;
|
|
78
|
+
}) => FlareVueContext>;
|
|
79
|
+
default: undefined;
|
|
80
|
+
};
|
|
81
|
+
afterSubmit: {
|
|
82
|
+
type: PropType<(params: FlareErrorBoundaryHookParams & {
|
|
83
|
+
context: FlareVueContext;
|
|
84
|
+
}) => void>;
|
|
85
|
+
default: undefined;
|
|
86
|
+
};
|
|
87
|
+
onReset: {
|
|
88
|
+
type: PropType<(error: Error | null) => void>;
|
|
89
|
+
default: undefined;
|
|
90
|
+
};
|
|
91
|
+
resetKeys: {
|
|
92
|
+
type: PropType<unknown[]>;
|
|
93
|
+
default: undefined;
|
|
94
|
+
};
|
|
95
|
+
attachProps: {
|
|
96
|
+
type: BooleanConstructor;
|
|
97
|
+
default: boolean;
|
|
98
|
+
};
|
|
99
|
+
propsMaxDepth: {
|
|
100
|
+
type: NumberConstructor;
|
|
101
|
+
default: number;
|
|
102
|
+
};
|
|
103
|
+
propsDenylist: {
|
|
104
|
+
type: PropType<RegExp>;
|
|
105
|
+
default: undefined;
|
|
106
|
+
};
|
|
107
|
+
replaceDefaultDenylist: {
|
|
108
|
+
type: BooleanConstructor;
|
|
109
|
+
default: boolean;
|
|
110
|
+
};
|
|
111
|
+
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
112
|
+
[key: string]: any;
|
|
113
|
+
}>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
114
|
+
beforeEvaluate: {
|
|
115
|
+
type: PropType<(params: FlareErrorBoundaryHookParams) => void>;
|
|
116
|
+
default: undefined;
|
|
117
|
+
};
|
|
118
|
+
beforeSubmit: {
|
|
119
|
+
type: PropType<(params: FlareErrorBoundaryHookParams & {
|
|
120
|
+
context: FlareVueContext;
|
|
121
|
+
}) => FlareVueContext>;
|
|
122
|
+
default: undefined;
|
|
123
|
+
};
|
|
124
|
+
afterSubmit: {
|
|
125
|
+
type: PropType<(params: FlareErrorBoundaryHookParams & {
|
|
126
|
+
context: FlareVueContext;
|
|
127
|
+
}) => void>;
|
|
128
|
+
default: undefined;
|
|
129
|
+
};
|
|
130
|
+
onReset: {
|
|
131
|
+
type: PropType<(error: Error | null) => void>;
|
|
132
|
+
default: undefined;
|
|
133
|
+
};
|
|
134
|
+
resetKeys: {
|
|
135
|
+
type: PropType<unknown[]>;
|
|
136
|
+
default: undefined;
|
|
137
|
+
};
|
|
138
|
+
attachProps: {
|
|
139
|
+
type: BooleanConstructor;
|
|
140
|
+
default: boolean;
|
|
141
|
+
};
|
|
142
|
+
propsMaxDepth: {
|
|
143
|
+
type: NumberConstructor;
|
|
144
|
+
default: number;
|
|
145
|
+
};
|
|
146
|
+
propsDenylist: {
|
|
147
|
+
type: PropType<RegExp>;
|
|
148
|
+
default: undefined;
|
|
149
|
+
};
|
|
150
|
+
replaceDefaultDenylist: {
|
|
151
|
+
type: BooleanConstructor;
|
|
152
|
+
default: boolean;
|
|
153
|
+
};
|
|
154
|
+
}>> & Readonly<{}>, {
|
|
155
|
+
beforeEvaluate: (params: FlareErrorBoundaryHookParams) => void;
|
|
156
|
+
beforeSubmit: (params: FlareErrorBoundaryHookParams & {
|
|
157
|
+
context: FlareVueContext;
|
|
158
|
+
}) => FlareVueContext;
|
|
159
|
+
afterSubmit: (params: FlareErrorBoundaryHookParams & {
|
|
160
|
+
context: FlareVueContext;
|
|
161
|
+
}) => void;
|
|
162
|
+
onReset: (error: Error | null) => void;
|
|
163
|
+
resetKeys: unknown[];
|
|
164
|
+
attachProps: boolean;
|
|
165
|
+
propsMaxDepth: number;
|
|
166
|
+
propsDenylist: RegExp;
|
|
167
|
+
replaceDefaultDenylist: boolean;
|
|
168
|
+
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/flareVue.d.ts
|
|
171
|
+
declare const flareVue: Plugin<[FlareVueOptions?]>;
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/constants.d.ts
|
|
174
|
+
declare const DEFAULT_PROPS_DENYLIST: RegExp;
|
|
175
|
+
//#endregion
|
|
176
|
+
export { type ComponentHierarchyFrame, DEFAULT_PROPS_DENYLIST, type ErrorOrigin, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryHookParams, type FlareVueContext, type FlareVueOptions, type FlareVueWarningContext, type RouteContext, type RouteParamValue, type RouteQueryValue, flareVue };
|