@flareapp/react 1.2.0 → 2.0.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/.oxlintrc.json +8 -0
- package/CHANGELOG.md +13 -8
- package/README.md +41 -3
- package/dist/index.cjs +194 -0
- package/dist/index.d.cts +83 -0
- package/dist/index.d.mts +81 -7
- package/dist/index.mjs +161 -18
- package/package.json +36 -12
- package/.release-it.json +0 -16
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -47
- package/src/index.ts +0 -28
- package/tsconfig.json +0 -4
package/.oxlintrc.json
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
## 2.0.0
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
### Breaking changes
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
-
|
|
5
|
+
- Bumps peer dependency `@flareapp/js` to `^2.0.0`. See its CHANGELOG for wire format changes.
|
|
6
|
+
- `flareReactErrorHandler` no longer passes a third `extraSolutionParameters` arg to `flare.report` (solutions API removed in `@flareapp/js` v2).
|
|
7
|
+
- The React component stack now lands on the report as `attributes['context.custom'].react.componentStack` and `attributes['context.custom'].react.componentStackFrames` (was nested under `context.react.*`).
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
### New
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
- **`FlareErrorBoundary` lifecycle hooks.** `beforeEvaluate`, `beforeSubmit`, and `afterSubmit` props give full control over the report pipeline from within the boundary.
|
|
12
|
+
- **`resetKeys` prop.** Mirrors `react-error-boundary`'s contract: when any element changes by `Object.is`, the boundary auto-resets. Pairs with the `onReset` callback.
|
|
13
|
+
- **`FlareErrorBoundaryFallbackProps`** now includes `resetErrorBoundary()` for programmatic reset from fallback UI.
|
|
14
|
+
- **`flareReactErrorHandler()` lifecycle hooks.** Same `beforeEvaluate`, `beforeSubmit`, and `afterSubmit` options, for use with third-party error boundaries.
|
|
15
|
+
- Non-`Error` values passed to `flareReactErrorHandler` are coerced to `Error`.
|
|
16
|
+
- The package self-identifies via `flare.setSdkInfo({ name: '@flareapp/react', version })` and `flare.setFramework({ name: 'React', version: React.version })` on import.
|
|
17
|
+
- New exported types: `ComponentStackFrame`, `FlareReactContext`, `FlareReactErrorHandlerCallback`, `FlareReactErrorHandlerOptions`.
|
package/README.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flareapp/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React integration for [Flare](https://flareapp.io) error tracking. Provides an error boundary component and a React 19+
|
|
4
|
+
error handler for catching and reporting React component errors to Flare.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @flareapp/react @flareapp/js
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
Initialize the Flare client and wrap your component tree with the error boundary:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { flare } from '@flareapp/js';
|
|
18
|
+
import { FlareErrorBoundary } from '@flareapp/react';
|
|
19
|
+
|
|
20
|
+
flare.light('YOUR_FLARE_API_KEY');
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<FlareErrorBoundary fallback={<p>Something went wrong.</p>}>
|
|
25
|
+
<MyComponent />
|
|
26
|
+
</FlareErrorBoundary>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
Full documentation on the error boundary, the React 19+ error handler, lifecycle callbacks, and more is available
|
|
34
|
+
at [flareapp.io/docs/react/general/installation](https://flareapp.io/docs/react/general/installation).
|
|
35
|
+
|
|
36
|
+
## Compatibility
|
|
37
|
+
|
|
38
|
+
- React 16, 17, 18, 19
|
|
39
|
+
- `flareReactErrorHandler` requires React 19+
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
The MIT License (MIT). Please see [License File](../../LICENSE.md) for more information.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let _flareapp_js = require("@flareapp/js");
|
|
30
|
+
let react = require("react");
|
|
31
|
+
react = __toESM(react);
|
|
32
|
+
|
|
33
|
+
//#region src/constants.ts
|
|
34
|
+
const CHROMIUM_STACK_REGEX = /^at\s+(\S+)(?:\s+\((.+):(\d+):(\d+)\))?$/;
|
|
35
|
+
const FIREFOX_SAFARI_STACK_REGEX = /^(\S+?)@(.+):(\d+):(\d+)$/;
|
|
36
|
+
const PACKAGE_VERSION = typeof process !== "undefined" && true ? "2.0.1" : "?";
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/identify.ts
|
|
40
|
+
let registered = false;
|
|
41
|
+
function registerReactSdkIdentity() {
|
|
42
|
+
if (registered) return;
|
|
43
|
+
registered = true;
|
|
44
|
+
_flareapp_js.flare.setSdkInfo({
|
|
45
|
+
name: "@flareapp/react",
|
|
46
|
+
version: PACKAGE_VERSION
|
|
47
|
+
});
|
|
48
|
+
_flareapp_js.flare.setFramework({
|
|
49
|
+
name: "React",
|
|
50
|
+
version: react.version
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/contextToAttributes.ts
|
|
56
|
+
function contextToAttributes(context) {
|
|
57
|
+
return { "context.custom": { react: {
|
|
58
|
+
componentStack: context.react.componentStack,
|
|
59
|
+
componentStackFrames: context.react.componentStackFrames
|
|
60
|
+
} } };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/formatComponentStack.ts
|
|
65
|
+
function formatComponentStack(stack) {
|
|
66
|
+
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/parseComponentStack.ts
|
|
71
|
+
function parseComponentStack(stack) {
|
|
72
|
+
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0).map((line) => {
|
|
73
|
+
const chromeMatch = line.match(CHROMIUM_STACK_REGEX);
|
|
74
|
+
if (chromeMatch) return {
|
|
75
|
+
component: chromeMatch[1],
|
|
76
|
+
file: chromeMatch[2] ?? null,
|
|
77
|
+
line: chromeMatch[3] ? Number(chromeMatch[3]) : null,
|
|
78
|
+
column: chromeMatch[4] ? Number(chromeMatch[4]) : null
|
|
79
|
+
};
|
|
80
|
+
const firefoxSafariMatch = line.match(FIREFOX_SAFARI_STACK_REGEX);
|
|
81
|
+
if (firefoxSafariMatch) return {
|
|
82
|
+
component: firefoxSafariMatch[1],
|
|
83
|
+
file: firefoxSafariMatch[2],
|
|
84
|
+
line: Number(firefoxSafariMatch[3]),
|
|
85
|
+
column: Number(firefoxSafariMatch[4])
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
component: line.replace(/^at\s+/, ""),
|
|
89
|
+
file: null,
|
|
90
|
+
line: null,
|
|
91
|
+
column: null
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/FlareErrorBoundary.ts
|
|
98
|
+
var FlareErrorBoundary = class extends react.Component {
|
|
99
|
+
state = {
|
|
100
|
+
error: null,
|
|
101
|
+
componentStack: []
|
|
102
|
+
};
|
|
103
|
+
static getDerivedStateFromError(error) {
|
|
104
|
+
return { error };
|
|
105
|
+
}
|
|
106
|
+
componentDidCatch(error, errorInfo) {
|
|
107
|
+
this.props.beforeEvaluate?.({
|
|
108
|
+
error,
|
|
109
|
+
errorInfo
|
|
110
|
+
});
|
|
111
|
+
const rawStack = errorInfo.componentStack ?? "";
|
|
112
|
+
const context = { react: {
|
|
113
|
+
componentStack: formatComponentStack(rawStack),
|
|
114
|
+
componentStackFrames: parseComponentStack(rawStack)
|
|
115
|
+
} };
|
|
116
|
+
const finalContext = this.props.beforeSubmit?.({
|
|
117
|
+
error,
|
|
118
|
+
errorInfo,
|
|
119
|
+
context
|
|
120
|
+
}) ?? context;
|
|
121
|
+
this.setState({ componentStack: finalContext.react.componentStack });
|
|
122
|
+
_flareapp_js.flare.reportSilently(error, contextToAttributes(finalContext));
|
|
123
|
+
this.props.afterSubmit?.({
|
|
124
|
+
error,
|
|
125
|
+
errorInfo,
|
|
126
|
+
context: finalContext
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
componentDidUpdate(prevProps) {
|
|
130
|
+
if (this.state.error === null || !this.props.resetKeys) return;
|
|
131
|
+
const prevKeys = prevProps.resetKeys;
|
|
132
|
+
const nextKeys = this.props.resetKeys;
|
|
133
|
+
const lengthChanged = prevKeys?.length !== nextKeys.length;
|
|
134
|
+
const valuesChanged = nextKeys.some((key, i) => !Object.is(key, prevKeys?.[i]));
|
|
135
|
+
if (lengthChanged || valuesChanged) this.reset();
|
|
136
|
+
}
|
|
137
|
+
reset = () => {
|
|
138
|
+
const { error } = this.state;
|
|
139
|
+
this.props.onReset?.(error);
|
|
140
|
+
this.setState({
|
|
141
|
+
error: null,
|
|
142
|
+
componentStack: []
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
render() {
|
|
146
|
+
const { error } = this.state;
|
|
147
|
+
if (error !== null) {
|
|
148
|
+
const { fallback } = this.props;
|
|
149
|
+
if (typeof fallback === "function") return fallback({
|
|
150
|
+
error,
|
|
151
|
+
componentStack: this.state.componentStack,
|
|
152
|
+
resetErrorBoundary: this.reset
|
|
153
|
+
});
|
|
154
|
+
return fallback ?? null;
|
|
155
|
+
}
|
|
156
|
+
return this.props.children;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/flareReactErrorHandler.ts
|
|
162
|
+
function flareReactErrorHandler(options) {
|
|
163
|
+
return (error, errorInfo) => {
|
|
164
|
+
const errorObject = (0, _flareapp_js.convertToError)(error);
|
|
165
|
+
options?.beforeEvaluate?.({
|
|
166
|
+
error: errorObject,
|
|
167
|
+
errorInfo
|
|
168
|
+
});
|
|
169
|
+
const rawStack = errorInfo.componentStack ?? "";
|
|
170
|
+
const context = { react: {
|
|
171
|
+
componentStack: formatComponentStack(rawStack),
|
|
172
|
+
componentStackFrames: parseComponentStack(rawStack)
|
|
173
|
+
} };
|
|
174
|
+
const finalContext = options?.beforeSubmit?.({
|
|
175
|
+
error: errorObject,
|
|
176
|
+
errorInfo,
|
|
177
|
+
context
|
|
178
|
+
}) ?? context;
|
|
179
|
+
_flareapp_js.flare.reportSilently(errorObject, contextToAttributes(finalContext));
|
|
180
|
+
options?.afterSubmit?.({
|
|
181
|
+
error: errorObject,
|
|
182
|
+
errorInfo,
|
|
183
|
+
context: finalContext
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/index.ts
|
|
190
|
+
registerReactSdkIdentity();
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
exports.FlareErrorBoundary = FlareErrorBoundary;
|
|
194
|
+
exports.flareReactErrorHandler = flareReactErrorHandler;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Component, ErrorInfo, PropsWithChildren, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type ComponentStackFrame = {
|
|
5
|
+
component: string;
|
|
6
|
+
file: string | null;
|
|
7
|
+
line: number | null;
|
|
8
|
+
column: number | null;
|
|
9
|
+
};
|
|
10
|
+
type FlareReactContext = {
|
|
11
|
+
react: {
|
|
12
|
+
componentStack: string[];
|
|
13
|
+
componentStackFrames: ComponentStackFrame[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/FlareErrorBoundary.d.ts
|
|
18
|
+
type FlareErrorBoundaryFallbackProps = {
|
|
19
|
+
error: Error;
|
|
20
|
+
componentStack: string[];
|
|
21
|
+
resetErrorBoundary: () => void;
|
|
22
|
+
};
|
|
23
|
+
type FlareErrorBoundaryProps = PropsWithChildren<{
|
|
24
|
+
fallback?: ReactNode | ((props: FlareErrorBoundaryFallbackProps) => ReactNode);
|
|
25
|
+
resetKeys?: unknown[];
|
|
26
|
+
beforeEvaluate?: (params: {
|
|
27
|
+
error: Error;
|
|
28
|
+
errorInfo: ErrorInfo;
|
|
29
|
+
}) => void;
|
|
30
|
+
beforeSubmit?: (params: {
|
|
31
|
+
error: Error;
|
|
32
|
+
errorInfo: ErrorInfo;
|
|
33
|
+
context: FlareReactContext;
|
|
34
|
+
}) => FlareReactContext;
|
|
35
|
+
afterSubmit?: (params: {
|
|
36
|
+
error: Error;
|
|
37
|
+
errorInfo: ErrorInfo;
|
|
38
|
+
context: FlareReactContext;
|
|
39
|
+
}) => void;
|
|
40
|
+
onReset?: (error: Error | null) => void;
|
|
41
|
+
}>;
|
|
42
|
+
type FlareErrorBoundaryState = {
|
|
43
|
+
error: Error | null;
|
|
44
|
+
componentStack: string[];
|
|
45
|
+
};
|
|
46
|
+
declare class FlareErrorBoundary extends Component<FlareErrorBoundaryProps, FlareErrorBoundaryState> {
|
|
47
|
+
state: FlareErrorBoundaryState;
|
|
48
|
+
static getDerivedStateFromError(error: Error): Partial<FlareErrorBoundaryState>;
|
|
49
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
|
|
50
|
+
componentDidUpdate(prevProps: FlareErrorBoundaryProps): void;
|
|
51
|
+
reset: () => void;
|
|
52
|
+
render(): ReactNode;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/flareReactErrorHandler.d.ts
|
|
56
|
+
type FlareReactErrorHandlerCallback = (error: unknown, errorInfo: {
|
|
57
|
+
componentStack?: string;
|
|
58
|
+
}) => void;
|
|
59
|
+
type FlareReactErrorHandlerOptions = {
|
|
60
|
+
beforeEvaluate?: (params: {
|
|
61
|
+
error: Error;
|
|
62
|
+
errorInfo: {
|
|
63
|
+
componentStack?: string;
|
|
64
|
+
};
|
|
65
|
+
}) => void;
|
|
66
|
+
beforeSubmit?: (params: {
|
|
67
|
+
error: Error;
|
|
68
|
+
errorInfo: {
|
|
69
|
+
componentStack?: string;
|
|
70
|
+
};
|
|
71
|
+
context: FlareReactContext;
|
|
72
|
+
}) => FlareReactContext;
|
|
73
|
+
afterSubmit?: (params: {
|
|
74
|
+
error: Error;
|
|
75
|
+
errorInfo: {
|
|
76
|
+
componentStack?: string;
|
|
77
|
+
};
|
|
78
|
+
context: FlareReactContext;
|
|
79
|
+
}) => void;
|
|
80
|
+
};
|
|
81
|
+
declare function flareReactErrorHandler(options?: FlareReactErrorHandlerOptions): FlareReactErrorHandlerCallback;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { type ComponentStackFrame, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryProps, type FlareReactContext, type FlareReactErrorHandlerCallback, type FlareReactErrorHandlerOptions, flareReactErrorHandler };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,83 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Component, PropsWithChildren } from 'react';
|
|
1
|
+
import { Component, ErrorInfo, PropsWithChildren, ReactNode } from "react";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type ComponentStackFrame = {
|
|
5
|
+
component: string;
|
|
6
|
+
file: string | null;
|
|
7
|
+
line: number | null;
|
|
8
|
+
column: number | null;
|
|
9
|
+
};
|
|
10
|
+
type FlareReactContext = {
|
|
11
|
+
react: {
|
|
12
|
+
componentStack: string[];
|
|
13
|
+
componentStackFrames: ComponentStackFrame[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/FlareErrorBoundary.d.ts
|
|
18
|
+
type FlareErrorBoundaryFallbackProps = {
|
|
19
|
+
error: Error;
|
|
20
|
+
componentStack: string[];
|
|
21
|
+
resetErrorBoundary: () => void;
|
|
22
|
+
};
|
|
23
|
+
type FlareErrorBoundaryProps = PropsWithChildren<{
|
|
24
|
+
fallback?: ReactNode | ((props: FlareErrorBoundaryFallbackProps) => ReactNode);
|
|
25
|
+
resetKeys?: unknown[];
|
|
26
|
+
beforeEvaluate?: (params: {
|
|
27
|
+
error: Error;
|
|
28
|
+
errorInfo: ErrorInfo;
|
|
29
|
+
}) => void;
|
|
30
|
+
beforeSubmit?: (params: {
|
|
31
|
+
error: Error;
|
|
32
|
+
errorInfo: ErrorInfo;
|
|
33
|
+
context: FlareReactContext;
|
|
34
|
+
}) => FlareReactContext;
|
|
35
|
+
afterSubmit?: (params: {
|
|
36
|
+
error: Error;
|
|
37
|
+
errorInfo: ErrorInfo;
|
|
38
|
+
context: FlareReactContext;
|
|
39
|
+
}) => void;
|
|
40
|
+
onReset?: (error: Error | null) => void;
|
|
41
|
+
}>;
|
|
42
|
+
type FlareErrorBoundaryState = {
|
|
43
|
+
error: Error | null;
|
|
44
|
+
componentStack: string[];
|
|
45
|
+
};
|
|
46
|
+
declare class FlareErrorBoundary extends Component<FlareErrorBoundaryProps, FlareErrorBoundaryState> {
|
|
47
|
+
state: FlareErrorBoundaryState;
|
|
48
|
+
static getDerivedStateFromError(error: Error): Partial<FlareErrorBoundaryState>;
|
|
49
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
|
|
50
|
+
componentDidUpdate(prevProps: FlareErrorBoundaryProps): void;
|
|
51
|
+
reset: () => void;
|
|
52
|
+
render(): ReactNode;
|
|
7
53
|
}
|
|
8
|
-
|
|
9
|
-
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/flareReactErrorHandler.d.ts
|
|
56
|
+
type FlareReactErrorHandlerCallback = (error: unknown, errorInfo: {
|
|
57
|
+
componentStack?: string;
|
|
58
|
+
}) => void;
|
|
59
|
+
type FlareReactErrorHandlerOptions = {
|
|
60
|
+
beforeEvaluate?: (params: {
|
|
61
|
+
error: Error;
|
|
62
|
+
errorInfo: {
|
|
63
|
+
componentStack?: string;
|
|
64
|
+
};
|
|
65
|
+
}) => void;
|
|
66
|
+
beforeSubmit?: (params: {
|
|
67
|
+
error: Error;
|
|
68
|
+
errorInfo: {
|
|
69
|
+
componentStack?: string;
|
|
70
|
+
};
|
|
71
|
+
context: FlareReactContext;
|
|
72
|
+
}) => FlareReactContext;
|
|
73
|
+
afterSubmit?: (params: {
|
|
74
|
+
error: Error;
|
|
75
|
+
errorInfo: {
|
|
76
|
+
componentStack?: string;
|
|
77
|
+
};
|
|
78
|
+
context: FlareReactContext;
|
|
79
|
+
}) => void;
|
|
80
|
+
};
|
|
81
|
+
declare function flareReactErrorHandler(options?: FlareReactErrorHandlerOptions): FlareReactErrorHandlerCallback;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { type ComponentStackFrame, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryProps, type FlareReactContext, type FlareReactErrorHandlerCallback, type FlareReactErrorHandlerOptions, flareReactErrorHandler };
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,165 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import { convertToError, flare } from "@flareapp/js";
|
|
2
|
+
import * as React from "react";
|
|
3
3
|
import { Component } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/constants.ts
|
|
6
|
+
const CHROMIUM_STACK_REGEX = /^at\s+(\S+)(?:\s+\((.+):(\d+):(\d+)\))?$/;
|
|
7
|
+
const FIREFOX_SAFARI_STACK_REGEX = /^(\S+?)@(.+):(\d+):(\d+)$/;
|
|
8
|
+
const PACKAGE_VERSION = typeof process !== "undefined" && true ? "2.0.1" : "?";
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/identify.ts
|
|
12
|
+
let registered = false;
|
|
13
|
+
function registerReactSdkIdentity() {
|
|
14
|
+
if (registered) return;
|
|
15
|
+
registered = true;
|
|
16
|
+
flare.setSdkInfo({
|
|
17
|
+
name: "@flareapp/react",
|
|
18
|
+
version: PACKAGE_VERSION
|
|
19
|
+
});
|
|
20
|
+
flare.setFramework({
|
|
21
|
+
name: "React",
|
|
22
|
+
version: React.version
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/contextToAttributes.ts
|
|
28
|
+
function contextToAttributes(context) {
|
|
29
|
+
return { "context.custom": { react: {
|
|
30
|
+
componentStack: context.react.componentStack,
|
|
31
|
+
componentStackFrames: context.react.componentStackFrames
|
|
32
|
+
} } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/formatComponentStack.ts
|
|
37
|
+
function formatComponentStack(stack) {
|
|
38
|
+
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/parseComponentStack.ts
|
|
43
|
+
function parseComponentStack(stack) {
|
|
44
|
+
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0).map((line) => {
|
|
45
|
+
const chromeMatch = line.match(CHROMIUM_STACK_REGEX);
|
|
46
|
+
if (chromeMatch) return {
|
|
47
|
+
component: chromeMatch[1],
|
|
48
|
+
file: chromeMatch[2] ?? null,
|
|
49
|
+
line: chromeMatch[3] ? Number(chromeMatch[3]) : null,
|
|
50
|
+
column: chromeMatch[4] ? Number(chromeMatch[4]) : null
|
|
51
|
+
};
|
|
52
|
+
const firefoxSafariMatch = line.match(FIREFOX_SAFARI_STACK_REGEX);
|
|
53
|
+
if (firefoxSafariMatch) return {
|
|
54
|
+
component: firefoxSafariMatch[1],
|
|
55
|
+
file: firefoxSafariMatch[2],
|
|
56
|
+
line: Number(firefoxSafariMatch[3]),
|
|
57
|
+
column: Number(firefoxSafariMatch[4])
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
component: line.replace(/^at\s+/, ""),
|
|
61
|
+
file: null,
|
|
62
|
+
line: null,
|
|
63
|
+
column: null
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/FlareErrorBoundary.ts
|
|
4
70
|
var FlareErrorBoundary = class extends Component {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
71
|
+
state = {
|
|
72
|
+
error: null,
|
|
73
|
+
componentStack: []
|
|
74
|
+
};
|
|
75
|
+
static getDerivedStateFromError(error) {
|
|
76
|
+
return { error };
|
|
77
|
+
}
|
|
78
|
+
componentDidCatch(error, errorInfo) {
|
|
79
|
+
this.props.beforeEvaluate?.({
|
|
80
|
+
error,
|
|
81
|
+
errorInfo
|
|
82
|
+
});
|
|
83
|
+
const rawStack = errorInfo.componentStack ?? "";
|
|
84
|
+
const context = { react: {
|
|
85
|
+
componentStack: formatComponentStack(rawStack),
|
|
86
|
+
componentStackFrames: parseComponentStack(rawStack)
|
|
87
|
+
} };
|
|
88
|
+
const finalContext = this.props.beforeSubmit?.({
|
|
89
|
+
error,
|
|
90
|
+
errorInfo,
|
|
91
|
+
context
|
|
92
|
+
}) ?? context;
|
|
93
|
+
this.setState({ componentStack: finalContext.react.componentStack });
|
|
94
|
+
flare.reportSilently(error, contextToAttributes(finalContext));
|
|
95
|
+
this.props.afterSubmit?.({
|
|
96
|
+
error,
|
|
97
|
+
errorInfo,
|
|
98
|
+
context: finalContext
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
componentDidUpdate(prevProps) {
|
|
102
|
+
if (this.state.error === null || !this.props.resetKeys) return;
|
|
103
|
+
const prevKeys = prevProps.resetKeys;
|
|
104
|
+
const nextKeys = this.props.resetKeys;
|
|
105
|
+
const lengthChanged = prevKeys?.length !== nextKeys.length;
|
|
106
|
+
const valuesChanged = nextKeys.some((key, i) => !Object.is(key, prevKeys?.[i]));
|
|
107
|
+
if (lengthChanged || valuesChanged) this.reset();
|
|
108
|
+
}
|
|
109
|
+
reset = () => {
|
|
110
|
+
const { error } = this.state;
|
|
111
|
+
this.props.onReset?.(error);
|
|
112
|
+
this.setState({
|
|
113
|
+
error: null,
|
|
114
|
+
componentStack: []
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
render() {
|
|
118
|
+
const { error } = this.state;
|
|
119
|
+
if (error !== null) {
|
|
120
|
+
const { fallback } = this.props;
|
|
121
|
+
if (typeof fallback === "function") return fallback({
|
|
122
|
+
error,
|
|
123
|
+
componentStack: this.state.componentStack,
|
|
124
|
+
resetErrorBoundary: this.reset
|
|
125
|
+
});
|
|
126
|
+
return fallback ?? null;
|
|
127
|
+
}
|
|
128
|
+
return this.props.children;
|
|
129
|
+
}
|
|
16
130
|
};
|
|
17
|
-
|
|
18
|
-
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/flareReactErrorHandler.ts
|
|
134
|
+
function flareReactErrorHandler(options) {
|
|
135
|
+
return (error, errorInfo) => {
|
|
136
|
+
const errorObject = convertToError(error);
|
|
137
|
+
options?.beforeEvaluate?.({
|
|
138
|
+
error: errorObject,
|
|
139
|
+
errorInfo
|
|
140
|
+
});
|
|
141
|
+
const rawStack = errorInfo.componentStack ?? "";
|
|
142
|
+
const context = { react: {
|
|
143
|
+
componentStack: formatComponentStack(rawStack),
|
|
144
|
+
componentStackFrames: parseComponentStack(rawStack)
|
|
145
|
+
} };
|
|
146
|
+
const finalContext = options?.beforeSubmit?.({
|
|
147
|
+
error: errorObject,
|
|
148
|
+
errorInfo,
|
|
149
|
+
context
|
|
150
|
+
}) ?? context;
|
|
151
|
+
flare.reportSilently(errorObject, contextToAttributes(finalContext));
|
|
152
|
+
options?.afterSubmit?.({
|
|
153
|
+
error: errorObject,
|
|
154
|
+
errorInfo,
|
|
155
|
+
context: finalContext
|
|
156
|
+
});
|
|
157
|
+
};
|
|
19
158
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/index.ts
|
|
162
|
+
registerReactSdkIdentity();
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
export { FlareErrorBoundary, flareReactErrorHandler };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flareapp/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "React client for flareapp.io",
|
|
5
5
|
"homepage": "https://flareapp.io",
|
|
6
6
|
"bugs": "https://github.com/spatie/flare-client-js/issues",
|
|
@@ -9,31 +9,55 @@
|
|
|
9
9
|
"url": "git+https://github.com/spatie/flare-client-js.git"
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"author":
|
|
13
|
-
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "Spatie",
|
|
14
|
+
"email": "info@spatie.be"
|
|
15
|
+
},
|
|
16
|
+
"contributors": [
|
|
17
|
+
"Adriaan Marain <adriaan@spatie.be>",
|
|
18
|
+
"Alex Vanderbist <alex@spatie.be>",
|
|
19
|
+
"Dries Heyninck <dries@spatie.be>",
|
|
20
|
+
"Freek Van der Herten <freek@spatie.be>",
|
|
21
|
+
"Sebastian De Deyne <sebastian@spatie.be>",
|
|
22
|
+
"Sébastien Henau <seba@spatie.be>"
|
|
23
|
+
],
|
|
24
|
+
"main": "./dist/index.cjs",
|
|
14
25
|
"module": "./dist/index.mjs",
|
|
15
|
-
"types": "./dist/index.d.
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
16
27
|
"exports": {
|
|
17
28
|
".": {
|
|
18
|
-
"
|
|
19
|
-
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"default": "./dist/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./dist/index.d.cts",
|
|
35
|
+
"default": "./dist/index.cjs"
|
|
36
|
+
}
|
|
20
37
|
}
|
|
21
38
|
},
|
|
22
39
|
"scripts": {
|
|
23
40
|
"prepublishOnly": "npm run build",
|
|
24
|
-
"build": "
|
|
41
|
+
"build": "tsdown src/index.ts --format cjs,esm --dts --env.PACKAGE_VERSION=$(node -p \"require('./package.json').version\") --clean",
|
|
42
|
+
"test": "vitest run",
|
|
25
43
|
"typescript": "tsc --noEmit",
|
|
26
|
-
"release": "
|
|
44
|
+
"release": "release-it"
|
|
27
45
|
},
|
|
28
46
|
"devDependencies": {
|
|
29
47
|
"@flareapp/js": "file:../js",
|
|
30
|
-
"@
|
|
48
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
49
|
+
"@testing-library/react": "^16.0.0",
|
|
50
|
+
"@types/react": "^19.0.0",
|
|
51
|
+
"@types/react-dom": "^19.0.0",
|
|
52
|
+
"jsdom": "^26.1.0",
|
|
31
53
|
"react": "^19.0.0",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
54
|
+
"react-dom": "^19.0.0",
|
|
55
|
+
"tsdown": "^0.20.3",
|
|
56
|
+
"typescript": "^5.7.0",
|
|
57
|
+
"vitest": "^4.0.0"
|
|
34
58
|
},
|
|
35
59
|
"peerDependencies": {
|
|
36
|
-
"@flareapp/js": "^
|
|
60
|
+
"@flareapp/js": "^2.0.1",
|
|
37
61
|
"react": "^16.0.0||^17.0.0||^18.0.0||^19.0.0"
|
|
38
62
|
},
|
|
39
63
|
"publishConfig": {
|
package/.release-it.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"git": {
|
|
3
|
-
"commitMessage": "Release @flareapp/react v${version}",
|
|
4
|
-
"tagName": "@flareapp/react@${version}",
|
|
5
|
-
"tagAnnotation": "Release @flareapp/react v${version}"
|
|
6
|
-
},
|
|
7
|
-
"github": {
|
|
8
|
-
"release": false
|
|
9
|
-
},
|
|
10
|
-
"npm": {
|
|
11
|
-
"publish": true
|
|
12
|
-
},
|
|
13
|
-
"hooks": {
|
|
14
|
-
"after:bump": "npm run build"
|
|
15
|
-
}
|
|
16
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { Component, PropsWithChildren } from 'react';
|
|
3
|
-
|
|
4
|
-
declare class FlareErrorBoundary extends Component<PropsWithChildren> {
|
|
5
|
-
componentDidCatch(error: Error, reactErrorInfo: React.ErrorInfo): void;
|
|
6
|
-
render(): react.ReactNode;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export { FlareErrorBoundary };
|
package/dist/index.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
FlareErrorBoundary: () => FlareErrorBoundary
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
var import_js = require("@flareapp/js");
|
|
27
|
-
var import_react = require("react");
|
|
28
|
-
var FlareErrorBoundary = class extends import_react.Component {
|
|
29
|
-
componentDidCatch(error, reactErrorInfo) {
|
|
30
|
-
const context = {
|
|
31
|
-
react: {
|
|
32
|
-
componentStack: formatReactComponentStack(reactErrorInfo?.componentStack ?? "")
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
import_js.flare.report(error, context, { react: { errorInfo: reactErrorInfo } });
|
|
36
|
-
}
|
|
37
|
-
render() {
|
|
38
|
-
return this.props.children;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
function formatReactComponentStack(stack) {
|
|
42
|
-
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0);
|
|
43
|
-
}
|
|
44
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
45
|
-
0 && (module.exports = {
|
|
46
|
-
FlareErrorBoundary
|
|
47
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { flare } from '@flareapp/js';
|
|
2
|
-
import { Component, PropsWithChildren } from 'react';
|
|
3
|
-
|
|
4
|
-
interface Context {
|
|
5
|
-
react: {
|
|
6
|
-
componentStack: Array<String>;
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class FlareErrorBoundary extends Component<PropsWithChildren> {
|
|
11
|
-
componentDidCatch(error: Error, reactErrorInfo: React.ErrorInfo) {
|
|
12
|
-
const context: Context = {
|
|
13
|
-
react: {
|
|
14
|
-
componentStack: formatReactComponentStack(reactErrorInfo?.componentStack ?? ''),
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
flare.report(error, context, { react: { errorInfo: reactErrorInfo } });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
render() {
|
|
22
|
-
return this.props.children;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function formatReactComponentStack(stack: string) {
|
|
27
|
-
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0);
|
|
28
|
-
}
|
package/tsconfig.json
DELETED