@flareapp/react-native 2.10.0 → 2.12.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/dist/index.cjs +54 -122
- package/dist/index.d.cts +5 -7
- package/dist/index.d.mts +5 -7
- package/dist/index.mjs +55 -123
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5,13 +5,6 @@ let _flareapp_react_inject = require("@flareapp/react/inject");
|
|
|
5
5
|
let react = require("react");
|
|
6
6
|
|
|
7
7
|
//#region src/context/expo.ts
|
|
8
|
-
/**
|
|
9
|
-
* Lazy, synchronous Expo load. The `require(...)` calls MUST be direct string literals: Metro statically
|
|
10
|
-
* collects only literal `require('pkg')` calls, treating those inside a try/catch as optional deps
|
|
11
|
-
* (`allowOptionalDependencies` is on by default), so a missing package degrades to a caught throw not a
|
|
12
|
-
* build error. Do NOT alias `require` to a local; that defeats the static collection and the module never
|
|
13
|
-
* resolves even when installed. The `typeof require` guard keeps non-Metro/ESM envs (some test runners) safe.
|
|
14
|
-
*/
|
|
15
8
|
function loadExpoModules() {
|
|
16
9
|
const mods = {};
|
|
17
10
|
if (typeof require === "undefined") return mods;
|
|
@@ -23,71 +16,67 @@ function loadExpoModules() {
|
|
|
23
16
|
} catch {}
|
|
24
17
|
return mods;
|
|
25
18
|
}
|
|
26
|
-
/** Maps Expo's `DeviceType` enum (UNKNOWN=0, PHONE=1, TABLET=2, DESKTOP=3, TV=4) to a label. */
|
|
27
19
|
const DEVICE_TYPE_LABELS = {
|
|
28
20
|
1: "phone",
|
|
29
21
|
2: "tablet",
|
|
30
22
|
3: "desktop",
|
|
31
23
|
4: "tv"
|
|
32
24
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* Async Expo getters are not used, since the context collector is synchronous.
|
|
36
|
-
*/
|
|
37
|
-
function projectExpoContext(expo) {
|
|
38
|
-
const attrs = {};
|
|
25
|
+
function expoToDeviceInfo(expo) {
|
|
26
|
+
const info = {};
|
|
39
27
|
const device = expo.device;
|
|
40
28
|
if (device) {
|
|
41
|
-
|
|
42
|
-
if (device.osName != null)
|
|
43
|
-
if (device.osVersion != null)
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
29
|
+
const os = {};
|
|
30
|
+
if (device.osName != null) os.name = device.osName;
|
|
31
|
+
if (device.osVersion != null) os.version = device.osVersion;
|
|
32
|
+
if (Object.keys(os).length > 0) info.os = os;
|
|
33
|
+
const target = {};
|
|
34
|
+
if (device.modelName != null) target.model = device.modelName;
|
|
35
|
+
if (device.deviceType != null && DEVICE_TYPE_LABELS[device.deviceType]) target.type = DEVICE_TYPE_LABELS[device.deviceType];
|
|
36
|
+
if (Object.keys(target).length > 0) info.device = target;
|
|
48
37
|
}
|
|
49
38
|
const application = expo.application;
|
|
50
39
|
if (application) {
|
|
51
|
-
|
|
52
|
-
if (application.
|
|
40
|
+
const app = {};
|
|
41
|
+
if (application.nativeApplicationVersion != null) app.version = application.nativeApplicationVersion;
|
|
42
|
+
if (application.applicationId != null) app.id = application.applicationId;
|
|
43
|
+
if (Object.keys(app).length > 0) info.app = app;
|
|
53
44
|
}
|
|
54
|
-
return
|
|
45
|
+
return info;
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
//#endregion
|
|
58
|
-
//#region src/context/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*/
|
|
66
|
-
function makeReactNativeContextCollector(expo = loadExpoModules()) {
|
|
67
|
-
const expoAttrs = projectExpoContext(expo);
|
|
68
|
-
return (_config) => {
|
|
49
|
+
//#region src/context/deviceInfo.ts
|
|
50
|
+
var ReactNativeDeviceInfoProvider = class {
|
|
51
|
+
expoInfo;
|
|
52
|
+
constructor(expo = loadExpoModules()) {
|
|
53
|
+
this.expoInfo = expoToDeviceInfo(expo);
|
|
54
|
+
}
|
|
55
|
+
collect() {
|
|
69
56
|
const screen = react_native.Dimensions.get("window");
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
...
|
|
57
|
+
const device = {
|
|
58
|
+
screen: {
|
|
59
|
+
width: screen.width,
|
|
60
|
+
height: screen.height,
|
|
61
|
+
scale: screen.scale
|
|
62
|
+
},
|
|
63
|
+
...this.expoInfo.device
|
|
77
64
|
};
|
|
78
|
-
if (
|
|
65
|
+
if (device.model == null) {
|
|
79
66
|
const model = nativeModelName();
|
|
80
|
-
if (model)
|
|
67
|
+
if (model != null) device.model = model;
|
|
81
68
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
69
|
+
const info = {
|
|
70
|
+
os: {
|
|
71
|
+
name: this.expoInfo.os?.name ?? react_native.Platform.OS,
|
|
72
|
+
version: this.expoInfo.os?.version ?? String(react_native.Platform.Version)
|
|
73
|
+
},
|
|
74
|
+
device
|
|
75
|
+
};
|
|
76
|
+
if (this.expoInfo.app) info.app = this.expoInfo.app;
|
|
77
|
+
return info;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
91
80
|
function nativeModelName() {
|
|
92
81
|
if (react_native.Platform.OS !== "android") return;
|
|
93
82
|
const constants = react_native.Platform.constants;
|
|
@@ -95,35 +84,16 @@ function nativeModelName() {
|
|
|
95
84
|
const maker = constants.Manufacturer ?? constants.Brand;
|
|
96
85
|
return maker ? `${maker} ${constants.Model}` : constants.Model;
|
|
97
86
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (attrs["device.model.name"] != null) device.model = attrs["device.model.name"];
|
|
105
|
-
const os = [attrs["os.name"], attrs["os.version"]].filter((v) => v != null).join(" ");
|
|
106
|
-
if (os) device.OS = os;
|
|
107
|
-
const width = attrs["device.screen.width"];
|
|
108
|
-
const height = attrs["device.screen.height"];
|
|
109
|
-
const scale = attrs["device.screen.scale"];
|
|
110
|
-
if (width != null && height != null) device.screen = scale != null ? `${width} × ${height} @ ${scale}x` : `${width} × ${height}`;
|
|
111
|
-
if (attrs["app.version"] != null) device.appVersion = attrs["app.version"];
|
|
112
|
-
if (attrs["app.id"] != null) device.appId = attrs["app.id"];
|
|
113
|
-
return device;
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/context/collectReactNative.ts
|
|
90
|
+
function makeReactNativeContextCollector(expo = loadExpoModules()) {
|
|
91
|
+
const provider = new ReactNativeDeviceInfoProvider(expo);
|
|
92
|
+
return (_config) => (0, _flareapp_core.deviceInfoToAttributes)(provider.collect());
|
|
114
93
|
}
|
|
115
94
|
|
|
116
95
|
//#endregion
|
|
117
96
|
//#region src/handlers/appStateFlush.ts
|
|
118
|
-
/**
|
|
119
|
-
* Flush the log buffer when the app backgrounds. Gate on `background` only: iOS fires `inactive` on every
|
|
120
|
-
* transient interruption (app-switcher peek, Control Center, incoming call), so gating avoids flooding the
|
|
121
|
-
* network. Mirrors the browser scheduler gating on `hidden`, not every blur. Delivery is best-effort (see
|
|
122
|
-
* `ReactNativeFlushScheduler`).
|
|
123
|
-
*
|
|
124
|
-
* Returns an uninstaller that removes the listener via the subscription handle (modern RN API; do NOT use
|
|
125
|
-
* the removed `AppState.removeEventListener`).
|
|
126
|
-
*/
|
|
127
97
|
function installAppStateFlush(getFlush) {
|
|
128
98
|
const subscription = react_native.AppState.addEventListener("change", (state) => {
|
|
129
99
|
if (state === "background") getFlush()?.();
|
|
@@ -133,7 +103,6 @@ function installAppStateFlush(getFlush) {
|
|
|
133
103
|
|
|
134
104
|
//#endregion
|
|
135
105
|
//#region src/devMode.ts
|
|
136
|
-
/** True only in a React Native dev bundle. Safe (false) everywhere else. */
|
|
137
106
|
function inDevMode() {
|
|
138
107
|
return typeof __DEV__ !== "undefined" && __DEV__ === true;
|
|
139
108
|
}
|
|
@@ -143,16 +112,6 @@ function inDevMode() {
|
|
|
143
112
|
function getErrorUtils() {
|
|
144
113
|
return globalThis.ErrorUtils;
|
|
145
114
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Wraps RN's `ErrorUtils` global handler: observe, do not swallow. The wrapper reports and then delegates
|
|
148
|
-
* to the previous handler, so RN's own behaviour (red box in dev, crash in prod) is preserved.
|
|
149
|
-
*
|
|
150
|
-
* `onFatal` exists because a production fatal tears the app down while our report is still an async fetch
|
|
151
|
-
* the OS kills, so a bare report rarely sends. With it, the previous handler is deferred until the
|
|
152
|
-
* transport drains. Skipped in `__DEV__` so it does not fight the red box, and it only runs for the
|
|
153
|
-
* first fatal, so a second one mid-flush hands straight over instead of starting a second shutdown.
|
|
154
|
-
* Mirrors Sentry's RN SDK.
|
|
155
|
-
*/
|
|
156
115
|
function installGlobalErrorHandler(report, onFatal) {
|
|
157
116
|
const errorUtils = getErrorUtils();
|
|
158
117
|
if (!errorUtils) return () => {};
|
|
@@ -183,24 +142,15 @@ function installGlobalErrorHandler(report, onFatal) {
|
|
|
183
142
|
|
|
184
143
|
//#endregion
|
|
185
144
|
//#region src/handlers/rejectionTracking.ts
|
|
186
|
-
/** An injected `null` means "engine absent" and must win over the live global, so this tests for
|
|
187
|
-
* `undefined` rather than falsiness. */
|
|
188
145
|
function resolveHermes(deps) {
|
|
189
146
|
if (deps.hermes !== void 0) return deps.hermes;
|
|
190
147
|
return globalThis.HermesInternal;
|
|
191
148
|
}
|
|
192
|
-
/** Same `undefined`-not-falsy rule as `resolveHermes`. */
|
|
193
149
|
function resolveRequire(deps) {
|
|
194
150
|
if (deps.requirePolyfill !== void 0) return deps.requirePolyfill;
|
|
195
151
|
if (typeof require === "undefined") return null;
|
|
196
152
|
return require;
|
|
197
153
|
}
|
|
198
|
-
/**
|
|
199
|
-
* The rejection enabler for the active JS engine, null when neither is reachable. Order matters: on
|
|
200
|
-
* Hermes the `promise` npm polyfill is not the runtime Promise, so its `rejection-tracking.enable()`
|
|
201
|
-
* would hook unused objects and never fire. On JSC, RN does polyfill `global.Promise` with that package,
|
|
202
|
-
* making it the real hook. Exported with injectable deps so the ordering is unit-testable.
|
|
203
|
-
*/
|
|
204
154
|
function resolveRejectionEnabler(deps = {}) {
|
|
205
155
|
const hermes = resolveHermes(deps);
|
|
206
156
|
if (hermes && typeof hermes.enablePromiseRejectionTracker === "function") return (options) => hermes.enablePromiseRejectionTracker(options);
|
|
@@ -212,16 +162,6 @@ function resolveRejectionEnabler(deps = {}) {
|
|
|
212
162
|
} catch {}
|
|
213
163
|
return null;
|
|
214
164
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Best-effort, engine-aware capture of unhandled rejections. RN routes these through the engine's tracker
|
|
217
|
-
* rather than `window.onunhandledrejection`. With no engine hook reachable this is a no-op; uncaught
|
|
218
|
-
* throws still arrive via ErrorUtils.
|
|
219
|
-
*
|
|
220
|
-
* Enabling REPLACES the engine's current callbacks, including RN's own dev warning, and neither engine
|
|
221
|
-
* exposes a getter for the previous ones, so chaining is impossible. `onUnhandled` re-emits a
|
|
222
|
-
* `console.warn` in dev to avoid swallowing that signal. For the same reason the uninstaller re-enables
|
|
223
|
-
* with no-op callbacks: neither engine offers a clean disable.
|
|
224
|
-
*/
|
|
225
165
|
function installRejectionTracking(reporter, deps = {}) {
|
|
226
166
|
const enable = deps.enable !== void 0 ? deps.enable : resolveRejectionEnabler();
|
|
227
167
|
if (!enable) {
|
|
@@ -254,11 +194,6 @@ function installRejectionTracking(reporter, deps = {}) {
|
|
|
254
194
|
|
|
255
195
|
//#endregion
|
|
256
196
|
//#region src/ReactNativeFlushScheduler.ts
|
|
257
|
-
/**
|
|
258
|
-
* Passive: the AppState -> background trigger is wired separately in `Flare.install()`, to stay symmetric
|
|
259
|
-
* with handler teardown. Flushes without `{ keepalive: true }` because RN's fetch runs over XMLHttpRequest
|
|
260
|
-
* and does not reliably honour it, so a backgrounding flush is best-effort.
|
|
261
|
-
*/
|
|
262
197
|
var ReactNativeFlushScheduler = class {
|
|
263
198
|
flushFn = null;
|
|
264
199
|
register(flush) {
|
|
@@ -276,13 +211,11 @@ var ReactNativeFlushScheduler = class {
|
|
|
276
211
|
//#endregion
|
|
277
212
|
//#region src/Flare.ts
|
|
278
213
|
const RN_SDK_NAME = "@flareapp/react-native";
|
|
279
|
-
const RN_SDK_VERSION = "2.
|
|
280
|
-
/** How long a fatal JS crash holds the app open to drain the transport before RN's default handler runs. */
|
|
214
|
+
const RN_SDK_VERSION = "2.12.0";
|
|
281
215
|
const FATAL_FLUSH_TIMEOUT_MS = 2e3;
|
|
282
216
|
/**
|
|
283
|
-
* The RN `Flare` singleton, exposed as `flare
|
|
284
|
-
*
|
|
285
|
-
* RN has a single app scope.
|
|
217
|
+
* The RN `Flare` singleton, exposed as `flare`. Uses `NullFileReader` (no runtime snippets on device) and
|
|
218
|
+
* `GlobalScopeProvider` (RN has a single app scope).
|
|
286
219
|
*/
|
|
287
220
|
var ReactNativeFlare = class extends _flareapp_core.Flare {
|
|
288
221
|
scheduler;
|
|
@@ -348,10 +281,9 @@ const flare = new ReactNativeFlare();
|
|
|
348
281
|
//#endregion
|
|
349
282
|
//#region src/FlareErrorBoundary.ts
|
|
350
283
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
* (a superset); safe at runtime since the boundary only calls `reportSilently`, which RN inherits.
|
|
284
|
+
* Wraps `@flareapp/react`'s `/inject` boundary with the RN `flare` singleton, applied after `{...props}`
|
|
285
|
+
* so callers can't override it. The `Flare` cast is safe: the boundary only calls `reportSilently`, which
|
|
286
|
+
* RN implements too.
|
|
355
287
|
*/
|
|
356
288
|
function FlareErrorBoundary(props) {
|
|
357
289
|
return (0, react.createElement)(_flareapp_react_inject.FlareErrorBoundary, {
|
package/dist/index.d.cts
CHANGED
|
@@ -15,9 +15,8 @@ type RejectionDeps = {
|
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/Flare.d.ts
|
|
17
17
|
/**
|
|
18
|
-
* The RN `Flare` singleton, exposed as `flare
|
|
19
|
-
*
|
|
20
|
-
* RN has a single app scope.
|
|
18
|
+
* The RN `Flare` singleton, exposed as `flare`. Uses `NullFileReader` (no runtime snippets on device) and
|
|
19
|
+
* `GlobalScopeProvider` (RN has a single app scope).
|
|
21
20
|
*/
|
|
22
21
|
declare class ReactNativeFlare extends Flare$1 {
|
|
23
22
|
private readonly scheduler;
|
|
@@ -46,10 +45,9 @@ declare const flare: ReactNativeFlare;
|
|
|
46
45
|
//#endregion
|
|
47
46
|
//#region src/FlareErrorBoundary.d.ts
|
|
48
47
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* (a superset); safe at runtime since the boundary only calls `reportSilently`, which RN inherits.
|
|
48
|
+
* Wraps `@flareapp/react`'s `/inject` boundary with the RN `flare` singleton, applied after `{...props}`
|
|
49
|
+
* so callers can't override it. The `Flare` cast is safe: the boundary only calls `reportSilently`, which
|
|
50
|
+
* RN implements too.
|
|
53
51
|
*/
|
|
54
52
|
declare function FlareErrorBoundary(props: Omit<FlareErrorBoundaryProps, 'flare'>): ReactElement;
|
|
55
53
|
//#endregion
|
package/dist/index.d.mts
CHANGED
|
@@ -15,9 +15,8 @@ type RejectionDeps = {
|
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/Flare.d.ts
|
|
17
17
|
/**
|
|
18
|
-
* The RN `Flare` singleton, exposed as `flare
|
|
19
|
-
*
|
|
20
|
-
* RN has a single app scope.
|
|
18
|
+
* The RN `Flare` singleton, exposed as `flare`. Uses `NullFileReader` (no runtime snippets on device) and
|
|
19
|
+
* `GlobalScopeProvider` (RN has a single app scope).
|
|
21
20
|
*/
|
|
22
21
|
declare class ReactNativeFlare extends Flare$1 {
|
|
23
22
|
private readonly scheduler;
|
|
@@ -46,10 +45,9 @@ declare const flare: ReactNativeFlare;
|
|
|
46
45
|
//#endregion
|
|
47
46
|
//#region src/FlareErrorBoundary.d.ts
|
|
48
47
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* (a superset); safe at runtime since the boundary only calls `reportSilently`, which RN inherits.
|
|
48
|
+
* Wraps `@flareapp/react`'s `/inject` boundary with the RN `flare` singleton, applied after `{...props}`
|
|
49
|
+
* so callers can't override it. The `Flare` cast is safe: the boundary only calls `reportSilently`, which
|
|
50
|
+
* RN implements too.
|
|
53
51
|
*/
|
|
54
52
|
declare function FlareErrorBoundary(props: Omit<FlareErrorBoundaryProps, 'flare'>): ReactElement;
|
|
55
53
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { Api, DEFAULT_URL_DENYLIST, Flare, Flare as Flare$1, FrameworkName, GlobalScopeProvider, GlobalScopeProvider as GlobalScopeProvider$1, NullFileReader, NullFileReader as NullFileReader$1, convertToError, convertToError as convertToError$1, redactUrlQuery, resolveDenylist, routeRejection } from "@flareapp/core";
|
|
2
|
+
import { Api, DEFAULT_URL_DENYLIST, Flare, Flare as Flare$1, FrameworkName, GlobalScopeProvider, GlobalScopeProvider as GlobalScopeProvider$1, NullFileReader, NullFileReader as NullFileReader$1, convertToError, convertToError as convertToError$1, deviceInfoToAttributes, redactUrlQuery, resolveDenylist, routeRejection } from "@flareapp/core";
|
|
3
3
|
import { AppState, Dimensions, Platform } from "react-native";
|
|
4
4
|
import { FlareErrorBoundary as FlareErrorBoundary$1 } from "@flareapp/react/inject";
|
|
5
5
|
import { createElement } from "react";
|
|
@@ -9,13 +9,6 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
9
9
|
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/context/expo.ts
|
|
12
|
-
/**
|
|
13
|
-
* Lazy, synchronous Expo load. The `require(...)` calls MUST be direct string literals: Metro statically
|
|
14
|
-
* collects only literal `require('pkg')` calls, treating those inside a try/catch as optional deps
|
|
15
|
-
* (`allowOptionalDependencies` is on by default), so a missing package degrades to a caught throw not a
|
|
16
|
-
* build error. Do NOT alias `require` to a local; that defeats the static collection and the module never
|
|
17
|
-
* resolves even when installed. The `typeof require` guard keeps non-Metro/ESM envs (some test runners) safe.
|
|
18
|
-
*/
|
|
19
12
|
function loadExpoModules() {
|
|
20
13
|
const mods = {};
|
|
21
14
|
if (typeof __require === "undefined") return mods;
|
|
@@ -27,71 +20,67 @@ function loadExpoModules() {
|
|
|
27
20
|
} catch {}
|
|
28
21
|
return mods;
|
|
29
22
|
}
|
|
30
|
-
/** Maps Expo's `DeviceType` enum (UNKNOWN=0, PHONE=1, TABLET=2, DESKTOP=3, TV=4) to a label. */
|
|
31
23
|
const DEVICE_TYPE_LABELS = {
|
|
32
24
|
1: "phone",
|
|
33
25
|
2: "tablet",
|
|
34
26
|
3: "desktop",
|
|
35
27
|
4: "tv"
|
|
36
28
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* Async Expo getters are not used, since the context collector is synchronous.
|
|
40
|
-
*/
|
|
41
|
-
function projectExpoContext(expo) {
|
|
42
|
-
const attrs = {};
|
|
29
|
+
function expoToDeviceInfo(expo) {
|
|
30
|
+
const info = {};
|
|
43
31
|
const device = expo.device;
|
|
44
32
|
if (device) {
|
|
45
|
-
|
|
46
|
-
if (device.osName != null)
|
|
47
|
-
if (device.osVersion != null)
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
const os = {};
|
|
34
|
+
if (device.osName != null) os.name = device.osName;
|
|
35
|
+
if (device.osVersion != null) os.version = device.osVersion;
|
|
36
|
+
if (Object.keys(os).length > 0) info.os = os;
|
|
37
|
+
const target = {};
|
|
38
|
+
if (device.modelName != null) target.model = device.modelName;
|
|
39
|
+
if (device.deviceType != null && DEVICE_TYPE_LABELS[device.deviceType]) target.type = DEVICE_TYPE_LABELS[device.deviceType];
|
|
40
|
+
if (Object.keys(target).length > 0) info.device = target;
|
|
52
41
|
}
|
|
53
42
|
const application = expo.application;
|
|
54
43
|
if (application) {
|
|
55
|
-
|
|
56
|
-
if (application.
|
|
44
|
+
const app = {};
|
|
45
|
+
if (application.nativeApplicationVersion != null) app.version = application.nativeApplicationVersion;
|
|
46
|
+
if (application.applicationId != null) app.id = application.applicationId;
|
|
47
|
+
if (Object.keys(app).length > 0) info.app = app;
|
|
57
48
|
}
|
|
58
|
-
return
|
|
49
|
+
return info;
|
|
59
50
|
}
|
|
60
51
|
|
|
61
52
|
//#endregion
|
|
62
|
-
//#region src/context/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*/
|
|
70
|
-
function makeReactNativeContextCollector(expo = loadExpoModules()) {
|
|
71
|
-
const expoAttrs = projectExpoContext(expo);
|
|
72
|
-
return (_config) => {
|
|
53
|
+
//#region src/context/deviceInfo.ts
|
|
54
|
+
var ReactNativeDeviceInfoProvider = class {
|
|
55
|
+
expoInfo;
|
|
56
|
+
constructor(expo = loadExpoModules()) {
|
|
57
|
+
this.expoInfo = expoToDeviceInfo(expo);
|
|
58
|
+
}
|
|
59
|
+
collect() {
|
|
73
60
|
const screen = Dimensions.get("window");
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
...
|
|
61
|
+
const device = {
|
|
62
|
+
screen: {
|
|
63
|
+
width: screen.width,
|
|
64
|
+
height: screen.height,
|
|
65
|
+
scale: screen.scale
|
|
66
|
+
},
|
|
67
|
+
...this.expoInfo.device
|
|
81
68
|
};
|
|
82
|
-
if (
|
|
69
|
+
if (device.model == null) {
|
|
83
70
|
const model = nativeModelName();
|
|
84
|
-
if (model)
|
|
71
|
+
if (model != null) device.model = model;
|
|
85
72
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
const info = {
|
|
74
|
+
os: {
|
|
75
|
+
name: this.expoInfo.os?.name ?? Platform.OS,
|
|
76
|
+
version: this.expoInfo.os?.version ?? String(Platform.Version)
|
|
77
|
+
},
|
|
78
|
+
device
|
|
79
|
+
};
|
|
80
|
+
if (this.expoInfo.app) info.app = this.expoInfo.app;
|
|
81
|
+
return info;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
95
84
|
function nativeModelName() {
|
|
96
85
|
if (Platform.OS !== "android") return;
|
|
97
86
|
const constants = Platform.constants;
|
|
@@ -99,35 +88,16 @@ function nativeModelName() {
|
|
|
99
88
|
const maker = constants.Manufacturer ?? constants.Brand;
|
|
100
89
|
return maker ? `${maker} ${constants.Model}` : constants.Model;
|
|
101
90
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (attrs["device.model.name"] != null) device.model = attrs["device.model.name"];
|
|
109
|
-
const os = [attrs["os.name"], attrs["os.version"]].filter((v) => v != null).join(" ");
|
|
110
|
-
if (os) device.OS = os;
|
|
111
|
-
const width = attrs["device.screen.width"];
|
|
112
|
-
const height = attrs["device.screen.height"];
|
|
113
|
-
const scale = attrs["device.screen.scale"];
|
|
114
|
-
if (width != null && height != null) device.screen = scale != null ? `${width} × ${height} @ ${scale}x` : `${width} × ${height}`;
|
|
115
|
-
if (attrs["app.version"] != null) device.appVersion = attrs["app.version"];
|
|
116
|
-
if (attrs["app.id"] != null) device.appId = attrs["app.id"];
|
|
117
|
-
return device;
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/context/collectReactNative.ts
|
|
94
|
+
function makeReactNativeContextCollector(expo = loadExpoModules()) {
|
|
95
|
+
const provider = new ReactNativeDeviceInfoProvider(expo);
|
|
96
|
+
return (_config) => deviceInfoToAttributes(provider.collect());
|
|
118
97
|
}
|
|
119
98
|
|
|
120
99
|
//#endregion
|
|
121
100
|
//#region src/handlers/appStateFlush.ts
|
|
122
|
-
/**
|
|
123
|
-
* Flush the log buffer when the app backgrounds. Gate on `background` only: iOS fires `inactive` on every
|
|
124
|
-
* transient interruption (app-switcher peek, Control Center, incoming call), so gating avoids flooding the
|
|
125
|
-
* network. Mirrors the browser scheduler gating on `hidden`, not every blur. Delivery is best-effort (see
|
|
126
|
-
* `ReactNativeFlushScheduler`).
|
|
127
|
-
*
|
|
128
|
-
* Returns an uninstaller that removes the listener via the subscription handle (modern RN API; do NOT use
|
|
129
|
-
* the removed `AppState.removeEventListener`).
|
|
130
|
-
*/
|
|
131
101
|
function installAppStateFlush(getFlush) {
|
|
132
102
|
const subscription = AppState.addEventListener("change", (state) => {
|
|
133
103
|
if (state === "background") getFlush()?.();
|
|
@@ -137,7 +107,6 @@ function installAppStateFlush(getFlush) {
|
|
|
137
107
|
|
|
138
108
|
//#endregion
|
|
139
109
|
//#region src/devMode.ts
|
|
140
|
-
/** True only in a React Native dev bundle. Safe (false) everywhere else. */
|
|
141
110
|
function inDevMode() {
|
|
142
111
|
return typeof __DEV__ !== "undefined" && __DEV__ === true;
|
|
143
112
|
}
|
|
@@ -147,16 +116,6 @@ function inDevMode() {
|
|
|
147
116
|
function getErrorUtils() {
|
|
148
117
|
return globalThis.ErrorUtils;
|
|
149
118
|
}
|
|
150
|
-
/**
|
|
151
|
-
* Wraps RN's `ErrorUtils` global handler: observe, do not swallow. The wrapper reports and then delegates
|
|
152
|
-
* to the previous handler, so RN's own behaviour (red box in dev, crash in prod) is preserved.
|
|
153
|
-
*
|
|
154
|
-
* `onFatal` exists because a production fatal tears the app down while our report is still an async fetch
|
|
155
|
-
* the OS kills, so a bare report rarely sends. With it, the previous handler is deferred until the
|
|
156
|
-
* transport drains. Skipped in `__DEV__` so it does not fight the red box, and it only runs for the
|
|
157
|
-
* first fatal, so a second one mid-flush hands straight over instead of starting a second shutdown.
|
|
158
|
-
* Mirrors Sentry's RN SDK.
|
|
159
|
-
*/
|
|
160
119
|
function installGlobalErrorHandler(report, onFatal) {
|
|
161
120
|
const errorUtils = getErrorUtils();
|
|
162
121
|
if (!errorUtils) return () => {};
|
|
@@ -187,24 +146,15 @@ function installGlobalErrorHandler(report, onFatal) {
|
|
|
187
146
|
|
|
188
147
|
//#endregion
|
|
189
148
|
//#region src/handlers/rejectionTracking.ts
|
|
190
|
-
/** An injected `null` means "engine absent" and must win over the live global, so this tests for
|
|
191
|
-
* `undefined` rather than falsiness. */
|
|
192
149
|
function resolveHermes(deps) {
|
|
193
150
|
if (deps.hermes !== void 0) return deps.hermes;
|
|
194
151
|
return globalThis.HermesInternal;
|
|
195
152
|
}
|
|
196
|
-
/** Same `undefined`-not-falsy rule as `resolveHermes`. */
|
|
197
153
|
function resolveRequire(deps) {
|
|
198
154
|
if (deps.requirePolyfill !== void 0) return deps.requirePolyfill;
|
|
199
155
|
if (typeof __require === "undefined") return null;
|
|
200
156
|
return __require;
|
|
201
157
|
}
|
|
202
|
-
/**
|
|
203
|
-
* The rejection enabler for the active JS engine, null when neither is reachable. Order matters: on
|
|
204
|
-
* Hermes the `promise` npm polyfill is not the runtime Promise, so its `rejection-tracking.enable()`
|
|
205
|
-
* would hook unused objects and never fire. On JSC, RN does polyfill `global.Promise` with that package,
|
|
206
|
-
* making it the real hook. Exported with injectable deps so the ordering is unit-testable.
|
|
207
|
-
*/
|
|
208
158
|
function resolveRejectionEnabler(deps = {}) {
|
|
209
159
|
const hermes = resolveHermes(deps);
|
|
210
160
|
if (hermes && typeof hermes.enablePromiseRejectionTracker === "function") return (options) => hermes.enablePromiseRejectionTracker(options);
|
|
@@ -216,16 +166,6 @@ function resolveRejectionEnabler(deps = {}) {
|
|
|
216
166
|
} catch {}
|
|
217
167
|
return null;
|
|
218
168
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Best-effort, engine-aware capture of unhandled rejections. RN routes these through the engine's tracker
|
|
221
|
-
* rather than `window.onunhandledrejection`. With no engine hook reachable this is a no-op; uncaught
|
|
222
|
-
* throws still arrive via ErrorUtils.
|
|
223
|
-
*
|
|
224
|
-
* Enabling REPLACES the engine's current callbacks, including RN's own dev warning, and neither engine
|
|
225
|
-
* exposes a getter for the previous ones, so chaining is impossible. `onUnhandled` re-emits a
|
|
226
|
-
* `console.warn` in dev to avoid swallowing that signal. For the same reason the uninstaller re-enables
|
|
227
|
-
* with no-op callbacks: neither engine offers a clean disable.
|
|
228
|
-
*/
|
|
229
169
|
function installRejectionTracking(reporter, deps = {}) {
|
|
230
170
|
const enable = deps.enable !== void 0 ? deps.enable : resolveRejectionEnabler();
|
|
231
171
|
if (!enable) {
|
|
@@ -258,11 +198,6 @@ function installRejectionTracking(reporter, deps = {}) {
|
|
|
258
198
|
|
|
259
199
|
//#endregion
|
|
260
200
|
//#region src/ReactNativeFlushScheduler.ts
|
|
261
|
-
/**
|
|
262
|
-
* Passive: the AppState -> background trigger is wired separately in `Flare.install()`, to stay symmetric
|
|
263
|
-
* with handler teardown. Flushes without `{ keepalive: true }` because RN's fetch runs over XMLHttpRequest
|
|
264
|
-
* and does not reliably honour it, so a backgrounding flush is best-effort.
|
|
265
|
-
*/
|
|
266
201
|
var ReactNativeFlushScheduler = class {
|
|
267
202
|
flushFn = null;
|
|
268
203
|
register(flush) {
|
|
@@ -280,13 +215,11 @@ var ReactNativeFlushScheduler = class {
|
|
|
280
215
|
//#endregion
|
|
281
216
|
//#region src/Flare.ts
|
|
282
217
|
const RN_SDK_NAME = "@flareapp/react-native";
|
|
283
|
-
const RN_SDK_VERSION = "2.
|
|
284
|
-
/** How long a fatal JS crash holds the app open to drain the transport before RN's default handler runs. */
|
|
218
|
+
const RN_SDK_VERSION = "2.12.0";
|
|
285
219
|
const FATAL_FLUSH_TIMEOUT_MS = 2e3;
|
|
286
220
|
/**
|
|
287
|
-
* The RN `Flare` singleton, exposed as `flare
|
|
288
|
-
*
|
|
289
|
-
* RN has a single app scope.
|
|
221
|
+
* The RN `Flare` singleton, exposed as `flare`. Uses `NullFileReader` (no runtime snippets on device) and
|
|
222
|
+
* `GlobalScopeProvider` (RN has a single app scope).
|
|
290
223
|
*/
|
|
291
224
|
var ReactNativeFlare = class extends Flare$1 {
|
|
292
225
|
scheduler;
|
|
@@ -352,10 +285,9 @@ const flare = new ReactNativeFlare();
|
|
|
352
285
|
//#endregion
|
|
353
286
|
//#region src/FlareErrorBoundary.ts
|
|
354
287
|
/**
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* (a superset); safe at runtime since the boundary only calls `reportSilently`, which RN inherits.
|
|
288
|
+
* Wraps `@flareapp/react`'s `/inject` boundary with the RN `flare` singleton, applied after `{...props}`
|
|
289
|
+
* so callers can't override it. The `Flare` cast is safe: the boundary only calls `reportSilently`, which
|
|
290
|
+
* RN implements too.
|
|
359
291
|
*/
|
|
360
292
|
function FlareErrorBoundary(props) {
|
|
361
293
|
return createElement(FlareErrorBoundary$1, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flareapp/react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "React Native SDK for flareapp.io",
|
|
5
5
|
"homepage": "https://flareapp.io",
|
|
6
6
|
"bugs": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"release": "release-it"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@flareapp/core": "2.
|
|
52
|
+
"@flareapp/core": "2.12.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@flareapp/react": "^2.5.0",
|