@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 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 structured fields and attaches them, along with the running React version, to the report
48
- context:
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
- react: {
52
- version: '19.0.0',
53
- minifiedError: {
54
- number: 418,
55
- args: ['Foo', 'Bar'],
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 uses `react.minifiedError` and `react.version` on the backend to look up React's error-code map and surface the
62
- full, human-readable message. No error-code map is bundled into the client. Non-minified errors are reported unchanged.
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.
@@ -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 };
@@ -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 _flareapp_core = require("@flareapp/core");
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
- const PACKAGE_VERSION = typeof process !== "undefined" && true ? "2.6.0" : "?";
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 sdkTagged = /* @__PURE__ */ new WeakSet();
40
- const frameworkTagged = /* @__PURE__ */ new WeakSet();
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
- if (!sdkTagged.has(flare)) {
43
- sdkTagged.add(flare);
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
- if (frameworkTagged.has(flare)) return;
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
- let defaultProvider = null;
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 ?? "", error);
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 ?? "", errorObject);
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 { convertToError } from "@flareapp/core";
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
- const PACKAGE_VERSION = typeof process !== "undefined" && true ? "2.6.0" : "?";
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 sdkTagged = /* @__PURE__ */ new WeakSet();
13
- const frameworkTagged = /* @__PURE__ */ new WeakSet();
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
- if (!sdkTagged.has(flare)) {
16
- sdkTagged.add(flare);
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
- if (frameworkTagged.has(flare)) return;
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
- let defaultProvider = null;
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 ?? "", error);
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 ?? "", errorObject);
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-BKUV8piX.cjs');
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-4Vgqlths.cjs";
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-BcVYRMtC.mjs";
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-CfcBSmhG.mjs";
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-BKUV8piX.cjs');
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-4Vgqlths.cjs";
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 };