@flareapp/react 2.6.0 → 2.8.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/README.md +65 -11
- package/dist/{flareReactErrorHandler-4Vgqlths.d.cts → flareReactErrorHandler-Ce0sCyMK.d.mts} +5 -2
- package/dist/{flareReactErrorHandler-BcVYRMtC.d.mts → flareReactErrorHandler-CnFo8otu.d.cts} +5 -2
- package/dist/{flareReactErrorHandler-BKUV8piX.cjs → flareReactErrorHandler-DBmb7u7U.cjs} +80 -74
- package/dist/{flareReactErrorHandler-CfcBSmhG.mjs → flareReactErrorHandler-sRmFoJAG.mjs} +79 -73
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/inject.cjs +1 -1
- package/dist/inject.d.cts +1 -1
- package/dist/inject.d.mts +1 -1
- package/dist/inject.mjs +1 -1
- package/dist/profiler.cjs +63 -0
- package/dist/profiler.d.cts +19 -0
- package/dist/profiler.d.mts +19 -0
- package/dist/profiler.mjs +60 -0
- package/dist/react-router.cjs +94 -0
- package/dist/react-router.d.cts +54 -0
- package/dist/react-router.d.mts +54 -0
- package/dist/react-router.mjs +91 -0
- package/dist/tanstack-router.cjs +89 -0
- package/dist/tanstack-router.d.cts +45 -0
- package/dist/tanstack-router.d.mts +45 -0
- package/dist/tanstack-router.mjs +86 -0
- package/package.json +47 -5
package/README.md
CHANGED
|
@@ -44,22 +44,24 @@ flare.logger.info('Checkout started', { cartId: cart.id, total: cart.total });
|
|
|
44
44
|
## Minified production errors
|
|
45
45
|
|
|
46
46
|
In production, React throws minified errors like `Minified React error #418; visit https://react.dev/errors/418?args[]=Foo`.
|
|
47
|
-
The client parses these into
|
|
48
|
-
|
|
47
|
+
The client parses these into a single self-contained field, `flare.exception.react_minified_error`, carrying the running
|
|
48
|
+
React version alongside the parsed pieces:
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
url: 'https://react.dev/errors/418?args[]=Foo&args[]=Bar',
|
|
57
|
-
},
|
|
51
|
+
'flare.exception.react_minified_error': {
|
|
52
|
+
number: 418,
|
|
53
|
+
args: ['Foo', 'Bar'],
|
|
54
|
+
url: 'https://react.dev/errors/418?args[]=Foo&args[]=Bar',
|
|
55
|
+
react_version: '19.0.0',
|
|
58
56
|
}
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
Flare
|
|
62
|
-
|
|
59
|
+
Flare reads this field on the backend to look up React's error-code map (keyed on `react_version`) and surface the full,
|
|
60
|
+
human-readable message. It is a Flare-internal field, not part of the display context, so it is emitted only when an
|
|
61
|
+
error actually parses as a minified React error and cannot be stripped by a `beforeSubmit` hook. No error-code map is
|
|
62
|
+
bundled into the client. Non-minified errors are reported unchanged.
|
|
63
|
+
|
|
64
|
+
`context.custom.react` continues to carry `componentStack`, `componentStackFrames` and `version` for display.
|
|
63
65
|
|
|
64
66
|
## Identifying users
|
|
65
67
|
|
|
@@ -80,7 +82,59 @@ at [flareapp.io/docs/react/general/installation](https://flareapp.io/docs/react/
|
|
|
80
82
|
|
|
81
83
|
- React 16, 17, 18, 19
|
|
82
84
|
- `flareReactErrorHandler` requires React 19+
|
|
85
|
+
- `withFlareProfiler` forwards `ref` on React 19 only
|
|
83
86
|
|
|
84
87
|
## License
|
|
85
88
|
|
|
86
89
|
The MIT License (MIT). Please see [License File](../../LICENSE.md) for more information.
|
|
90
|
+
|
|
91
|
+
## Component profiler (`@flareapp/react/profiler`)
|
|
92
|
+
|
|
93
|
+
Opt-in mount profiling: wrap a component to record a `browser_component` span for
|
|
94
|
+
its mount, nested under the active page-load / navigation trace. Requires tracing to be
|
|
95
|
+
enabled (`enableTracing: true`).
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { FlareProfiler, withFlareProfiler } from '@flareapp/react/profiler';
|
|
99
|
+
|
|
100
|
+
// Wrap at the definition:
|
|
101
|
+
export default withFlareProfiler(ProductPage);
|
|
102
|
+
|
|
103
|
+
// Or wrap an inline subtree:
|
|
104
|
+
<FlareProfiler name="Gallery">
|
|
105
|
+
<ProductGallery />
|
|
106
|
+
</FlareProfiler>;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Spans nest into a tree: a profiled child nests under its nearest profiled ancestor;
|
|
110
|
+
unprofiled components in between are transparent. A component with no active trace
|
|
111
|
+
(tracing off, or no page-load/navigation root) records nothing and renders normally.
|
|
112
|
+
|
|
113
|
+
A component that mounts later inside an already-mounted profiled ancestor still nests
|
|
114
|
+
under that ancestor, whose own span closed when it finished mounting. The tree is
|
|
115
|
+
correct, but the waterfall shows the child starting after its parent ended. A page body
|
|
116
|
+
swapped inside a persistent layout is the usual way to see this.
|
|
117
|
+
|
|
118
|
+
**Import the main entry somewhere too.** `@flareapp/react/profiler` is deliberately dependency-free, so
|
|
119
|
+
it does not register React as the framework. That identity (`flare.framework.name`) is what tells Flare a
|
|
120
|
+
component span came from React, and importing `@flareapp/react` anywhere in the app sets it. An app that
|
|
121
|
+
uses only the `/profiler` entry reports `js` instead, and its component spans are attributed to plain
|
|
122
|
+
JavaScript rather than React.
|
|
123
|
+
|
|
124
|
+
**Naming:** the span name is `name` (prop or `withFlareProfiler(Component, { name })`),
|
|
125
|
+
then `Component.displayName`, then `Component.name`. Minified production builds can
|
|
126
|
+
mangle `Component.name`, so pass an explicit `name` or set `displayName` for production.
|
|
127
|
+
|
|
128
|
+
**Suspense (v1 limitation):** a `<Suspense>` boundary inside a profiled subtree can end a
|
|
129
|
+
parent span before a suspended child resumes, so the child may appear outside its parent
|
|
130
|
+
in the waterfall, and its duration includes the data wait. If the wait outlasts the
|
|
131
|
+
trace's idle window the child span is dropped rather than attached to a closed trace.
|
|
132
|
+
|
|
133
|
+
**Render-phase start:** the span starts when the component first renders, not when React commits it. A
|
|
134
|
+
render React defers or discards (`useDeferredValue`, an interrupted transition, an `<Activity mode="hidden">`
|
|
135
|
+
prerender revealed later) bills that whole gap to the component.
|
|
136
|
+
|
|
137
|
+
**Refs and statics:** the wrapper is a plain function component. It forwards no `ref` through `forwardRef`
|
|
138
|
+
and hoists no statics. On React 19 `ref` is a normal prop and passes straight through, so this only affects
|
|
139
|
+
the React 16-18 half of the peer range; there, wrap with `FlareProfiler` inside the component instead of
|
|
140
|
+
applying `withFlareProfiler` to it.
|
package/dist/{flareReactErrorHandler-4Vgqlths.d.cts → flareReactErrorHandler-Ce0sCyMK.d.mts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Flare } from "@flareapp/js/browser";
|
|
2
1
|
import { Component, ErrorInfo, PropsWithChildren, ReactNode } from "react";
|
|
2
|
+
import { Flare } from "@flareapp/js/browser";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
type ComponentStackFrame = {
|
|
@@ -18,7 +18,6 @@ type FlareReactContext = {
|
|
|
18
18
|
componentStack: string[];
|
|
19
19
|
componentStackFrames: ComponentStackFrame[];
|
|
20
20
|
version?: string;
|
|
21
|
-
minifiedError?: MinifiedReactError;
|
|
22
21
|
};
|
|
23
22
|
};
|
|
24
23
|
//#endregion
|
|
@@ -90,6 +89,10 @@ type FlareReactErrorHandlerOptions = {
|
|
|
90
89
|
context: FlareReactContext;
|
|
91
90
|
}) => void;
|
|
92
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Callback shaped to match react-error-boundary's `onError` prop, so consumers using that library
|
|
94
|
+
* can report to Flare without our own boundary.
|
|
95
|
+
*/
|
|
93
96
|
declare function flareReactErrorHandler(options?: FlareReactErrorHandlerOptions): FlareReactErrorHandlerCallback;
|
|
94
97
|
//#endregion
|
|
95
98
|
export { FlareErrorBoundaryFallbackProps as a, FlareReactContext as c, FlareErrorBoundary as i, MinifiedReactError as l, FlareReactErrorHandlerOptions as n, FlareErrorBoundaryProps as o, flareReactErrorHandler as r, ComponentStackFrame as s, FlareReactErrorHandlerCallback as t };
|
package/dist/{flareReactErrorHandler-BcVYRMtC.d.mts → flareReactErrorHandler-CnFo8otu.d.cts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Component, ErrorInfo, PropsWithChildren, ReactNode } from "react";
|
|
2
1
|
import { Flare } from "@flareapp/js/browser";
|
|
2
|
+
import { Component, ErrorInfo, PropsWithChildren, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
type ComponentStackFrame = {
|
|
@@ -18,7 +18,6 @@ type FlareReactContext = {
|
|
|
18
18
|
componentStack: string[];
|
|
19
19
|
componentStackFrames: ComponentStackFrame[];
|
|
20
20
|
version?: string;
|
|
21
|
-
minifiedError?: MinifiedReactError;
|
|
22
21
|
};
|
|
23
22
|
};
|
|
24
23
|
//#endregion
|
|
@@ -90,6 +89,10 @@ type FlareReactErrorHandlerOptions = {
|
|
|
90
89
|
context: FlareReactContext;
|
|
91
90
|
}) => void;
|
|
92
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Callback shaped to match react-error-boundary's `onError` prop, so consumers using that library
|
|
94
|
+
* can report to Flare without our own boundary.
|
|
95
|
+
*/
|
|
93
96
|
declare function flareReactErrorHandler(options?: FlareReactErrorHandlerOptions): FlareReactErrorHandlerCallback;
|
|
94
97
|
//#endregion
|
|
95
98
|
export { FlareErrorBoundaryFallbackProps as a, FlareReactContext as c, FlareErrorBoundary as i, MinifiedReactError as l, FlareReactErrorHandlerOptions as n, FlareErrorBoundaryProps as o, flareReactErrorHandler as r, ComponentStackFrame as s, FlareReactErrorHandlerCallback as t };
|
|
@@ -25,61 +25,52 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
|
+
let _flareapp_core = require("@flareapp/core");
|
|
28
29
|
let react = require("react");
|
|
29
30
|
react = __toESM(react);
|
|
30
|
-
let
|
|
31
|
+
let _flareapp_js_browser = require("@flareapp/js/browser");
|
|
31
32
|
|
|
32
33
|
//#region src/constants.ts
|
|
34
|
+
/**
|
|
35
|
+
* Chrome: `at ComponentName (http://localhost:5173/src/App.tsx:12:9)`; no source: `at div`.
|
|
36
|
+
*/
|
|
33
37
|
const CHROMIUM_STACK_REGEX = /^at\s+(\S+)(?:\s+\((.+):(\d+):(\d+)\))?$/;
|
|
38
|
+
/**
|
|
39
|
+
* Firefox/Safari: `ComponentName@http://localhost:5173/src/App.tsx:12:9`; no source: `div`.
|
|
40
|
+
*/
|
|
34
41
|
const FIREFOX_SAFARI_STACK_REGEX = /^(\S+?)@(.+):(\d+):(\d+)$/;
|
|
35
|
-
|
|
42
|
+
/**
|
|
43
|
+
* React 16/17/18 synthetic component stack: `in ComponentName (at App.jsx:10)`; with an optional
|
|
44
|
+
* column `in ComponentName (at App.jsx:10:5)`; no source: `in ComponentName`. These versions usually
|
|
45
|
+
* emit a line only, so the column capture group is optional. The file capture is lazy so the trailing
|
|
46
|
+
* `:line(:column)` binds to the numeric tail even when the file path itself contains colons. Without
|
|
47
|
+
* `__source` React names the owner instead (`in App (created by Root)`), so that suffix is matched and
|
|
48
|
+
* discarded; the group is greedy because an owner name can carry brackets of its own (`Connect(Form)`).
|
|
49
|
+
*/
|
|
50
|
+
const REACT_LEGACY_STACK_REGEX = /^in\s+(\S+)(?:\s+\(at\s+(.+?):(\d+)(?::(\d+))?\))?(?:\s+\(created by\s+.+\))?$/;
|
|
51
|
+
/** Injected at build time via tsdown --env.PACKAGE_VERSION (reads package.json version). */
|
|
52
|
+
const PACKAGE_VERSION = typeof process !== "undefined" && true ? "2.8.0" : "?";
|
|
36
53
|
|
|
37
54
|
//#endregion
|
|
38
55
|
//#region src/identify.ts
|
|
39
|
-
const
|
|
40
|
-
|
|
56
|
+
const tagger = (0, _flareapp_core.createIdentityTagger)({
|
|
57
|
+
sdkName: "@flareapp/react",
|
|
58
|
+
sdkVersion: PACKAGE_VERSION,
|
|
59
|
+
frameworkName: _flareapp_core.FrameworkName.React
|
|
60
|
+
});
|
|
61
|
+
/** Web path: full identity on the default singleton (sdk + framework). */
|
|
41
62
|
function registerReactSdkIdentity(flare) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
flare.setSdkInfo({
|
|
45
|
-
name: "@flareapp/react",
|
|
46
|
-
version: PACKAGE_VERSION
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
tagReactFramework(flare);
|
|
63
|
+
tagger.registerSdkIdentity(flare);
|
|
64
|
+
tagger.tagFramework(flare, react.version);
|
|
50
65
|
}
|
|
66
|
+
/** Injected path: framework tag only, never sdkInfo (would clobber the injected SDK name). */
|
|
51
67
|
function tagReactFramework(flare) {
|
|
52
|
-
|
|
53
|
-
frameworkTagged.add(flare);
|
|
54
|
-
flare.setFramework({
|
|
55
|
-
name: "React",
|
|
56
|
-
version: react.version
|
|
57
|
-
});
|
|
68
|
+
tagger.tagFramework(flare, react.version);
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
//#endregion
|
|
61
72
|
//#region src/resolveFlare.ts
|
|
62
|
-
|
|
63
|
-
function isDevMode() {
|
|
64
|
-
try {
|
|
65
|
-
return process.env.NODE_ENV !== "production";
|
|
66
|
-
} catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function registerDefaultFlare(provider) {
|
|
71
|
-
if (typeof window !== "undefined" && window.__flare) {
|
|
72
|
-
const message = "[flare] @flareapp/react (web root) was imported in a renderer where the Electron bridge is present, pulling the keyed @flareapp/js singleton into the renderer. Import @flareapp/react/inject and pass the @flareapp/electron/renderer instance instead.";
|
|
73
|
-
if (isDevMode()) throw new Error(message);
|
|
74
|
-
console.warn(message);
|
|
75
|
-
}
|
|
76
|
-
defaultProvider = provider;
|
|
77
|
-
}
|
|
78
|
-
function resolveFlare(explicit) {
|
|
79
|
-
if (explicit) return explicit;
|
|
80
|
-
if (defaultProvider) return defaultProvider();
|
|
81
|
-
throw new Error("[flare] No Flare instance available. Pass `flare` (e.g. from @flareapp/electron/renderer), or import @flareapp/react (the package root) to use the @flareapp/js default singleton.");
|
|
82
|
-
}
|
|
73
|
+
const { registerDefaultFlare, resolveFlare } = (0, _flareapp_js_browser.createFlareResolver)({ packageName: "@flareapp/react" });
|
|
83
74
|
|
|
84
75
|
//#endregion
|
|
85
76
|
//#region src/formatComponentStack.ts
|
|
@@ -89,6 +80,12 @@ function formatComponentStack(stack) {
|
|
|
89
80
|
|
|
90
81
|
//#endregion
|
|
91
82
|
//#region src/parseComponentStack.ts
|
|
83
|
+
/**
|
|
84
|
+
* Parse React's newline-separated `errorInfo.componentStack` into structured frames so the Flare UI
|
|
85
|
+
* can render file/line links. The line format is browser-native in React 19 (Chromium / Firefox /
|
|
86
|
+
* Safari shapes) and React-synthetic in 16-18 (`in X (at File:line)`). Unrecognised lines fall back
|
|
87
|
+
* to component-name-only rather than being dropped.
|
|
88
|
+
*/
|
|
92
89
|
function parseComponentStack(stack) {
|
|
93
90
|
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0).map((line) => {
|
|
94
91
|
const chromeMatch = line.match(CHROMIUM_STACK_REGEX);
|
|
@@ -105,6 +102,13 @@ function parseComponentStack(stack) {
|
|
|
105
102
|
line: Number(firefoxSafariMatch[3]),
|
|
106
103
|
column: Number(firefoxSafariMatch[4])
|
|
107
104
|
};
|
|
105
|
+
const reactLegacyMatch = line.match(REACT_LEGACY_STACK_REGEX);
|
|
106
|
+
if (reactLegacyMatch) return {
|
|
107
|
+
component: reactLegacyMatch[1],
|
|
108
|
+
file: reactLegacyMatch[2] ?? null,
|
|
109
|
+
line: reactLegacyMatch[3] ? Number(reactLegacyMatch[3]) : null,
|
|
110
|
+
column: reactLegacyMatch[4] ? Number(reactLegacyMatch[4]) : null
|
|
111
|
+
};
|
|
108
112
|
return {
|
|
109
113
|
component: line.replace(/^at\s+/, ""),
|
|
110
114
|
file: null,
|
|
@@ -114,25 +118,46 @@ function parseComponentStack(stack) {
|
|
|
114
118
|
});
|
|
115
119
|
}
|
|
116
120
|
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/buildReactContext.ts
|
|
123
|
+
function buildReactContext(rawStack) {
|
|
124
|
+
return { react: {
|
|
125
|
+
componentStack: formatComponentStack(rawStack),
|
|
126
|
+
componentStackFrames: parseComponentStack(rawStack),
|
|
127
|
+
version: react.version
|
|
128
|
+
} };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/contextToAttributes.ts
|
|
133
|
+
function contextToAttributes(context, minifiedError) {
|
|
134
|
+
return {
|
|
135
|
+
...(0, _flareapp_core.toCustomContext)("react", {
|
|
136
|
+
componentStack: context.react.componentStack,
|
|
137
|
+
componentStackFrames: context.react.componentStackFrames,
|
|
138
|
+
...context.react.version ? { version: context.react.version } : {}
|
|
139
|
+
}),
|
|
140
|
+
...minifiedError ? { "flare.exception.react_minified_error": {
|
|
141
|
+
number: minifiedError.number,
|
|
142
|
+
args: minifiedError.args,
|
|
143
|
+
url: minifiedError.url,
|
|
144
|
+
react_version: react.version
|
|
145
|
+
} } : {}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
117
149
|
//#endregion
|
|
118
150
|
//#region src/parseMinifiedReactError.ts
|
|
119
151
|
const NUMBER_PATTERN = /Minified React error #(\d+)/;
|
|
120
152
|
const ARG_PATTERN = /args\[\]=([^&\s]*)/g;
|
|
121
153
|
const URL_PATTERN = /(https?:\/\/\S+)/;
|
|
122
|
-
function safeDecode(value) {
|
|
123
|
-
try {
|
|
124
|
-
return decodeURIComponent(value);
|
|
125
|
-
} catch {
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
154
|
function parseMinifiedReactError(error) {
|
|
130
155
|
const message = error?.message;
|
|
131
156
|
if (!message) return null;
|
|
132
157
|
const numberMatch = message.match(NUMBER_PATTERN);
|
|
133
158
|
if (!numberMatch) return null;
|
|
134
159
|
const args = [];
|
|
135
|
-
for (const match of message.matchAll(ARG_PATTERN)) args.push(safeDecode(match[1]));
|
|
160
|
+
for (const match of message.matchAll(ARG_PATTERN)) args.push((0, _flareapp_core.safeDecode)(match[1]));
|
|
136
161
|
const urlMatch = message.match(URL_PATTERN);
|
|
137
162
|
return {
|
|
138
163
|
number: Number(numberMatch[1]),
|
|
@@ -141,29 +166,6 @@ function parseMinifiedReactError(error) {
|
|
|
141
166
|
};
|
|
142
167
|
}
|
|
143
168
|
|
|
144
|
-
//#endregion
|
|
145
|
-
//#region src/buildReactContext.ts
|
|
146
|
-
function buildReactContext(rawStack, error) {
|
|
147
|
-
const minifiedError = parseMinifiedReactError(error);
|
|
148
|
-
return { react: {
|
|
149
|
-
componentStack: formatComponentStack(rawStack),
|
|
150
|
-
componentStackFrames: parseComponentStack(rawStack),
|
|
151
|
-
version: react.version,
|
|
152
|
-
...minifiedError ? { minifiedError } : {}
|
|
153
|
-
} };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/contextToAttributes.ts
|
|
158
|
-
function contextToAttributes(context) {
|
|
159
|
-
return { "context.custom": { react: {
|
|
160
|
-
componentStack: context.react.componentStack,
|
|
161
|
-
componentStackFrames: context.react.componentStackFrames,
|
|
162
|
-
...context.react.version ? { version: context.react.version } : {},
|
|
163
|
-
...context.react.minifiedError ? { minifiedError: context.react.minifiedError } : {}
|
|
164
|
-
} } };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
169
|
//#endregion
|
|
168
170
|
//#region src/FlareErrorBoundary.ts
|
|
169
171
|
var FlareErrorBoundary = class extends react.Component {
|
|
@@ -185,14 +187,14 @@ var FlareErrorBoundary = class extends react.Component {
|
|
|
185
187
|
error,
|
|
186
188
|
errorInfo
|
|
187
189
|
});
|
|
188
|
-
const context = buildReactContext(errorInfo.componentStack ?? ""
|
|
190
|
+
const context = buildReactContext(errorInfo.componentStack ?? "");
|
|
189
191
|
const finalContext = this.props.beforeSubmit?.({
|
|
190
192
|
error,
|
|
191
193
|
errorInfo,
|
|
192
194
|
context
|
|
193
195
|
}) ?? context;
|
|
194
196
|
this.setState({ componentStack: finalContext.react.componentStack });
|
|
195
|
-
this.flare.reportSilently(error, contextToAttributes(finalContext));
|
|
197
|
+
this.flare.reportSilently(error, contextToAttributes(finalContext, parseMinifiedReactError(error)));
|
|
196
198
|
this.props.afterSubmit?.({
|
|
197
199
|
error,
|
|
198
200
|
errorInfo,
|
|
@@ -232,6 +234,10 @@ var FlareErrorBoundary = class extends react.Component {
|
|
|
232
234
|
|
|
233
235
|
//#endregion
|
|
234
236
|
//#region src/flareReactErrorHandler.ts
|
|
237
|
+
/**
|
|
238
|
+
* Callback shaped to match react-error-boundary's `onError` prop, so consumers using that library
|
|
239
|
+
* can report to Flare without our own boundary.
|
|
240
|
+
*/
|
|
235
241
|
function flareReactErrorHandler(options) {
|
|
236
242
|
const flare = resolveFlare(options?.flare);
|
|
237
243
|
tagReactFramework(flare);
|
|
@@ -241,13 +247,13 @@ function flareReactErrorHandler(options) {
|
|
|
241
247
|
error: errorObject,
|
|
242
248
|
errorInfo
|
|
243
249
|
});
|
|
244
|
-
const context = buildReactContext(errorInfo.componentStack ?? ""
|
|
250
|
+
const context = buildReactContext(errorInfo.componentStack ?? "");
|
|
245
251
|
const finalContext = options?.beforeSubmit?.({
|
|
246
252
|
error: errorObject,
|
|
247
253
|
errorInfo,
|
|
248
254
|
context
|
|
249
255
|
}) ?? context;
|
|
250
|
-
flare.reportSilently(errorObject, contextToAttributes(finalContext));
|
|
256
|
+
flare.reportSilently(errorObject, contextToAttributes(finalContext, parseMinifiedReactError(errorObject)));
|
|
251
257
|
options?.afterSubmit?.({
|
|
252
258
|
error: errorObject,
|
|
253
259
|
errorInfo,
|
|
@@ -1,58 +1,49 @@
|
|
|
1
|
+
import { FrameworkName, convertToError, createIdentityTagger, safeDecode, toCustomContext } from "@flareapp/core";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { Component, version } from "react";
|
|
3
|
-
import {
|
|
4
|
+
import { createFlareResolver } from "@flareapp/js/browser";
|
|
4
5
|
|
|
5
6
|
//#region src/constants.ts
|
|
7
|
+
/**
|
|
8
|
+
* Chrome: `at ComponentName (http://localhost:5173/src/App.tsx:12:9)`; no source: `at div`.
|
|
9
|
+
*/
|
|
6
10
|
const CHROMIUM_STACK_REGEX = /^at\s+(\S+)(?:\s+\((.+):(\d+):(\d+)\))?$/;
|
|
11
|
+
/**
|
|
12
|
+
* Firefox/Safari: `ComponentName@http://localhost:5173/src/App.tsx:12:9`; no source: `div`.
|
|
13
|
+
*/
|
|
7
14
|
const FIREFOX_SAFARI_STACK_REGEX = /^(\S+?)@(.+):(\d+):(\d+)$/;
|
|
8
|
-
|
|
15
|
+
/**
|
|
16
|
+
* React 16/17/18 synthetic component stack: `in ComponentName (at App.jsx:10)`; with an optional
|
|
17
|
+
* column `in ComponentName (at App.jsx:10:5)`; no source: `in ComponentName`. These versions usually
|
|
18
|
+
* emit a line only, so the column capture group is optional. The file capture is lazy so the trailing
|
|
19
|
+
* `:line(:column)` binds to the numeric tail even when the file path itself contains colons. Without
|
|
20
|
+
* `__source` React names the owner instead (`in App (created by Root)`), so that suffix is matched and
|
|
21
|
+
* discarded; the group is greedy because an owner name can carry brackets of its own (`Connect(Form)`).
|
|
22
|
+
*/
|
|
23
|
+
const REACT_LEGACY_STACK_REGEX = /^in\s+(\S+)(?:\s+\(at\s+(.+?):(\d+)(?::(\d+))?\))?(?:\s+\(created by\s+.+\))?$/;
|
|
24
|
+
/** Injected at build time via tsdown --env.PACKAGE_VERSION (reads package.json version). */
|
|
25
|
+
const PACKAGE_VERSION = typeof process !== "undefined" && true ? "2.8.0" : "?";
|
|
9
26
|
|
|
10
27
|
//#endregion
|
|
11
28
|
//#region src/identify.ts
|
|
12
|
-
const
|
|
13
|
-
|
|
29
|
+
const tagger = createIdentityTagger({
|
|
30
|
+
sdkName: "@flareapp/react",
|
|
31
|
+
sdkVersion: PACKAGE_VERSION,
|
|
32
|
+
frameworkName: FrameworkName.React
|
|
33
|
+
});
|
|
34
|
+
/** Web path: full identity on the default singleton (sdk + framework). */
|
|
14
35
|
function registerReactSdkIdentity(flare) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
flare.setSdkInfo({
|
|
18
|
-
name: "@flareapp/react",
|
|
19
|
-
version: PACKAGE_VERSION
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
tagReactFramework(flare);
|
|
36
|
+
tagger.registerSdkIdentity(flare);
|
|
37
|
+
tagger.tagFramework(flare, React.version);
|
|
23
38
|
}
|
|
39
|
+
/** Injected path: framework tag only, never sdkInfo (would clobber the injected SDK name). */
|
|
24
40
|
function tagReactFramework(flare) {
|
|
25
|
-
|
|
26
|
-
frameworkTagged.add(flare);
|
|
27
|
-
flare.setFramework({
|
|
28
|
-
name: "React",
|
|
29
|
-
version: React.version
|
|
30
|
-
});
|
|
41
|
+
tagger.tagFramework(flare, React.version);
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
//#endregion
|
|
34
45
|
//#region src/resolveFlare.ts
|
|
35
|
-
|
|
36
|
-
function isDevMode() {
|
|
37
|
-
try {
|
|
38
|
-
return process.env.NODE_ENV !== "production";
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function registerDefaultFlare(provider) {
|
|
44
|
-
if (typeof window !== "undefined" && window.__flare) {
|
|
45
|
-
const message = "[flare] @flareapp/react (web root) was imported in a renderer where the Electron bridge is present, pulling the keyed @flareapp/js singleton into the renderer. Import @flareapp/react/inject and pass the @flareapp/electron/renderer instance instead.";
|
|
46
|
-
if (isDevMode()) throw new Error(message);
|
|
47
|
-
console.warn(message);
|
|
48
|
-
}
|
|
49
|
-
defaultProvider = provider;
|
|
50
|
-
}
|
|
51
|
-
function resolveFlare(explicit) {
|
|
52
|
-
if (explicit) return explicit;
|
|
53
|
-
if (defaultProvider) return defaultProvider();
|
|
54
|
-
throw new Error("[flare] No Flare instance available. Pass `flare` (e.g. from @flareapp/electron/renderer), or import @flareapp/react (the package root) to use the @flareapp/js default singleton.");
|
|
55
|
-
}
|
|
46
|
+
const { registerDefaultFlare, resolveFlare } = createFlareResolver({ packageName: "@flareapp/react" });
|
|
56
47
|
|
|
57
48
|
//#endregion
|
|
58
49
|
//#region src/formatComponentStack.ts
|
|
@@ -62,6 +53,12 @@ function formatComponentStack(stack) {
|
|
|
62
53
|
|
|
63
54
|
//#endregion
|
|
64
55
|
//#region src/parseComponentStack.ts
|
|
56
|
+
/**
|
|
57
|
+
* Parse React's newline-separated `errorInfo.componentStack` into structured frames so the Flare UI
|
|
58
|
+
* can render file/line links. The line format is browser-native in React 19 (Chromium / Firefox /
|
|
59
|
+
* Safari shapes) and React-synthetic in 16-18 (`in X (at File:line)`). Unrecognised lines fall back
|
|
60
|
+
* to component-name-only rather than being dropped.
|
|
61
|
+
*/
|
|
65
62
|
function parseComponentStack(stack) {
|
|
66
63
|
return stack.split(/\s*\n\s*/g).filter((line) => line.length > 0).map((line) => {
|
|
67
64
|
const chromeMatch = line.match(CHROMIUM_STACK_REGEX);
|
|
@@ -78,6 +75,13 @@ function parseComponentStack(stack) {
|
|
|
78
75
|
line: Number(firefoxSafariMatch[3]),
|
|
79
76
|
column: Number(firefoxSafariMatch[4])
|
|
80
77
|
};
|
|
78
|
+
const reactLegacyMatch = line.match(REACT_LEGACY_STACK_REGEX);
|
|
79
|
+
if (reactLegacyMatch) return {
|
|
80
|
+
component: reactLegacyMatch[1],
|
|
81
|
+
file: reactLegacyMatch[2] ?? null,
|
|
82
|
+
line: reactLegacyMatch[3] ? Number(reactLegacyMatch[3]) : null,
|
|
83
|
+
column: reactLegacyMatch[4] ? Number(reactLegacyMatch[4]) : null
|
|
84
|
+
};
|
|
81
85
|
return {
|
|
82
86
|
component: line.replace(/^at\s+/, ""),
|
|
83
87
|
file: null,
|
|
@@ -87,18 +91,39 @@ function parseComponentStack(stack) {
|
|
|
87
91
|
});
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/buildReactContext.ts
|
|
96
|
+
function buildReactContext(rawStack) {
|
|
97
|
+
return { react: {
|
|
98
|
+
componentStack: formatComponentStack(rawStack),
|
|
99
|
+
componentStackFrames: parseComponentStack(rawStack),
|
|
100
|
+
version
|
|
101
|
+
} };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/contextToAttributes.ts
|
|
106
|
+
function contextToAttributes(context, minifiedError) {
|
|
107
|
+
return {
|
|
108
|
+
...toCustomContext("react", {
|
|
109
|
+
componentStack: context.react.componentStack,
|
|
110
|
+
componentStackFrames: context.react.componentStackFrames,
|
|
111
|
+
...context.react.version ? { version: context.react.version } : {}
|
|
112
|
+
}),
|
|
113
|
+
...minifiedError ? { "flare.exception.react_minified_error": {
|
|
114
|
+
number: minifiedError.number,
|
|
115
|
+
args: minifiedError.args,
|
|
116
|
+
url: minifiedError.url,
|
|
117
|
+
react_version: version
|
|
118
|
+
} } : {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
90
122
|
//#endregion
|
|
91
123
|
//#region src/parseMinifiedReactError.ts
|
|
92
124
|
const NUMBER_PATTERN = /Minified React error #(\d+)/;
|
|
93
125
|
const ARG_PATTERN = /args\[\]=([^&\s]*)/g;
|
|
94
126
|
const URL_PATTERN = /(https?:\/\/\S+)/;
|
|
95
|
-
function safeDecode(value) {
|
|
96
|
-
try {
|
|
97
|
-
return decodeURIComponent(value);
|
|
98
|
-
} catch {
|
|
99
|
-
return value;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
127
|
function parseMinifiedReactError(error) {
|
|
103
128
|
const message = error?.message;
|
|
104
129
|
if (!message) return null;
|
|
@@ -114,29 +139,6 @@ function parseMinifiedReactError(error) {
|
|
|
114
139
|
};
|
|
115
140
|
}
|
|
116
141
|
|
|
117
|
-
//#endregion
|
|
118
|
-
//#region src/buildReactContext.ts
|
|
119
|
-
function buildReactContext(rawStack, error) {
|
|
120
|
-
const minifiedError = parseMinifiedReactError(error);
|
|
121
|
-
return { react: {
|
|
122
|
-
componentStack: formatComponentStack(rawStack),
|
|
123
|
-
componentStackFrames: parseComponentStack(rawStack),
|
|
124
|
-
version,
|
|
125
|
-
...minifiedError ? { minifiedError } : {}
|
|
126
|
-
} };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region src/contextToAttributes.ts
|
|
131
|
-
function contextToAttributes(context) {
|
|
132
|
-
return { "context.custom": { react: {
|
|
133
|
-
componentStack: context.react.componentStack,
|
|
134
|
-
componentStackFrames: context.react.componentStackFrames,
|
|
135
|
-
...context.react.version ? { version: context.react.version } : {},
|
|
136
|
-
...context.react.minifiedError ? { minifiedError: context.react.minifiedError } : {}
|
|
137
|
-
} } };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
142
|
//#endregion
|
|
141
143
|
//#region src/FlareErrorBoundary.ts
|
|
142
144
|
var FlareErrorBoundary = class extends Component {
|
|
@@ -158,14 +160,14 @@ var FlareErrorBoundary = class extends Component {
|
|
|
158
160
|
error,
|
|
159
161
|
errorInfo
|
|
160
162
|
});
|
|
161
|
-
const context = buildReactContext(errorInfo.componentStack ?? ""
|
|
163
|
+
const context = buildReactContext(errorInfo.componentStack ?? "");
|
|
162
164
|
const finalContext = this.props.beforeSubmit?.({
|
|
163
165
|
error,
|
|
164
166
|
errorInfo,
|
|
165
167
|
context
|
|
166
168
|
}) ?? context;
|
|
167
169
|
this.setState({ componentStack: finalContext.react.componentStack });
|
|
168
|
-
this.flare.reportSilently(error, contextToAttributes(finalContext));
|
|
170
|
+
this.flare.reportSilently(error, contextToAttributes(finalContext, parseMinifiedReactError(error)));
|
|
169
171
|
this.props.afterSubmit?.({
|
|
170
172
|
error,
|
|
171
173
|
errorInfo,
|
|
@@ -205,6 +207,10 @@ var FlareErrorBoundary = class extends Component {
|
|
|
205
207
|
|
|
206
208
|
//#endregion
|
|
207
209
|
//#region src/flareReactErrorHandler.ts
|
|
210
|
+
/**
|
|
211
|
+
* Callback shaped to match react-error-boundary's `onError` prop, so consumers using that library
|
|
212
|
+
* can report to Flare without our own boundary.
|
|
213
|
+
*/
|
|
208
214
|
function flareReactErrorHandler(options) {
|
|
209
215
|
const flare = resolveFlare(options?.flare);
|
|
210
216
|
tagReactFramework(flare);
|
|
@@ -214,13 +220,13 @@ function flareReactErrorHandler(options) {
|
|
|
214
220
|
error: errorObject,
|
|
215
221
|
errorInfo
|
|
216
222
|
});
|
|
217
|
-
const context = buildReactContext(errorInfo.componentStack ?? ""
|
|
223
|
+
const context = buildReactContext(errorInfo.componentStack ?? "");
|
|
218
224
|
const finalContext = options?.beforeSubmit?.({
|
|
219
225
|
error: errorObject,
|
|
220
226
|
errorInfo,
|
|
221
227
|
context
|
|
222
228
|
}) ?? context;
|
|
223
|
-
flare.reportSilently(errorObject, contextToAttributes(finalContext));
|
|
229
|
+
flare.reportSilently(errorObject, contextToAttributes(finalContext, parseMinifiedReactError(errorObject)));
|
|
224
230
|
options?.afterSubmit?.({
|
|
225
231
|
error: errorObject,
|
|
226
232
|
errorInfo,
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_flareReactErrorHandler = require('./flareReactErrorHandler-
|
|
2
|
+
const require_flareReactErrorHandler = require('./flareReactErrorHandler-DBmb7u7U.cjs');
|
|
3
3
|
let _flareapp_js = require("@flareapp/js");
|
|
4
4
|
|
|
5
5
|
//#region src/index.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as FlareErrorBoundaryFallbackProps, c as FlareReactContext, i as FlareErrorBoundary, l as MinifiedReactError, n as FlareReactErrorHandlerOptions, o as FlareErrorBoundaryProps, r as flareReactErrorHandler, s as ComponentStackFrame, t as FlareReactErrorHandlerCallback } from "./flareReactErrorHandler-
|
|
1
|
+
import { a as FlareErrorBoundaryFallbackProps, c as FlareReactContext, i as FlareErrorBoundary, l as MinifiedReactError, n as FlareReactErrorHandlerOptions, o as FlareErrorBoundaryProps, r as flareReactErrorHandler, s as ComponentStackFrame, t as FlareReactErrorHandlerCallback } from "./flareReactErrorHandler-CnFo8otu.cjs";
|
|
2
2
|
export { type ComponentStackFrame, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryProps, type FlareReactContext, type FlareReactErrorHandlerCallback, type FlareReactErrorHandlerOptions, type MinifiedReactError, flareReactErrorHandler };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as FlareErrorBoundaryFallbackProps, c as FlareReactContext, i as FlareErrorBoundary, l as MinifiedReactError, n as FlareReactErrorHandlerOptions, o as FlareErrorBoundaryProps, r as flareReactErrorHandler, s as ComponentStackFrame, t as FlareReactErrorHandlerCallback } from "./flareReactErrorHandler-
|
|
1
|
+
import { a as FlareErrorBoundaryFallbackProps, c as FlareReactContext, i as FlareErrorBoundary, l as MinifiedReactError, n as FlareReactErrorHandlerOptions, o as FlareErrorBoundaryProps, r as flareReactErrorHandler, s as ComponentStackFrame, t as FlareReactErrorHandlerCallback } from "./flareReactErrorHandler-Ce0sCyMK.mjs";
|
|
2
2
|
export { type ComponentStackFrame, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryProps, type FlareReactContext, type FlareReactErrorHandlerCallback, type FlareReactErrorHandlerOptions, type MinifiedReactError, flareReactErrorHandler };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as registerReactSdkIdentity, n as FlareErrorBoundary, r as registerDefaultFlare, t as flareReactErrorHandler } from "./flareReactErrorHandler-
|
|
1
|
+
import { i as registerReactSdkIdentity, n as FlareErrorBoundary, r as registerDefaultFlare, t as flareReactErrorHandler } from "./flareReactErrorHandler-sRmFoJAG.mjs";
|
|
2
2
|
import { flare } from "@flareapp/js";
|
|
3
3
|
|
|
4
4
|
//#region src/index.ts
|
package/dist/inject.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_flareReactErrorHandler = require('./flareReactErrorHandler-
|
|
2
|
+
const require_flareReactErrorHandler = require('./flareReactErrorHandler-DBmb7u7U.cjs');
|
|
3
3
|
|
|
4
4
|
exports.FlareErrorBoundary = require_flareReactErrorHandler.FlareErrorBoundary;
|
|
5
5
|
exports.flareReactErrorHandler = require_flareReactErrorHandler.flareReactErrorHandler;
|
package/dist/inject.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as FlareErrorBoundaryFallbackProps, c as FlareReactContext, i as FlareErrorBoundary, l as MinifiedReactError, n as FlareReactErrorHandlerOptions, o as FlareErrorBoundaryProps, r as flareReactErrorHandler, s as ComponentStackFrame, t as FlareReactErrorHandlerCallback } from "./flareReactErrorHandler-
|
|
1
|
+
import { a as FlareErrorBoundaryFallbackProps, c as FlareReactContext, i as FlareErrorBoundary, l as MinifiedReactError, n as FlareReactErrorHandlerOptions, o as FlareErrorBoundaryProps, r as flareReactErrorHandler, s as ComponentStackFrame, t as FlareReactErrorHandlerCallback } from "./flareReactErrorHandler-CnFo8otu.cjs";
|
|
2
2
|
export { type ComponentStackFrame, FlareErrorBoundary, type FlareErrorBoundaryFallbackProps, type FlareErrorBoundaryProps, type FlareReactContext, type FlareReactErrorHandlerCallback, type FlareReactErrorHandlerOptions, type MinifiedReactError, flareReactErrorHandler };
|