@lumin-monitor/react-native 0.1.0 → 0.3.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/CHANGELOG.md +113 -0
- package/README.md +58 -8
- package/dist/index.cjs +105 -25
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +104 -28
- package/dist/react-navigation.d.cts +1 -1
- package/dist/react-navigation.d.ts +1 -1
- package/dist/react-navigation.js +0 -2
- package/dist/{types-BeHbtR1J.d.cts → types-DMUQWor6.d.cts} +51 -7
- package/dist/{types-BeHbtR1J.d.ts → types-DMUQWor6.d.ts} +51 -7
- package/package.json +3 -6
- package/dist/chunk-3RG5ZIWI.js +0 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@lumin-monitor/react-native` are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-05-26
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`init()` no longer throws under Metro / Expo** ([report]). The 0.1.0–0.2.0
|
|
13
|
+
releases used a runtime `require("react-native")` inside `detectAppState()`.
|
|
14
|
+
Metro's static dependency scanner doesn't see requires nested in function
|
|
15
|
+
bodies, so `react-native` was excluded from the bundle and the call threw
|
|
16
|
+
`Requiring unknown module "react-native"` at app startup. Replaced with a
|
|
17
|
+
top-level `import { AppState } from "react-native"`, which Metro picks up
|
|
18
|
+
and resolves through the consumer's already-installed peer dep. tsup keeps
|
|
19
|
+
`react-native` externalised so the import survives into the published
|
|
20
|
+
artifact untouched.
|
|
21
|
+
|
|
22
|
+
### Changed (BREAKING)
|
|
23
|
+
|
|
24
|
+
- **AsyncStorage is no longer auto-detected.** The previous behaviour did a
|
|
25
|
+
runtime `require("@react-native-async-storage/async-storage")`, which has
|
|
26
|
+
the same Metro problem as the `react-native` require above — and forced
|
|
27
|
+
every consumer to declare an unused peer dep even when they didn't want
|
|
28
|
+
persistence. Apps that want persistence now import AsyncStorage themselves
|
|
29
|
+
and pass it through:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
33
|
+
import { init } from "@lumin-monitor/react-native";
|
|
34
|
+
|
|
35
|
+
const lumin = init({ apiKey, storage: AsyncStorage });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Omitting `storage` falls back to in-memory ids (the same behaviour 0.2.0
|
|
39
|
+
exhibited when the optional peer dep wasn't installed). `@react-native-async-storage/async-storage`
|
|
40
|
+
has been removed from `peerDependencies` and `peerDependenciesMeta`; the
|
|
41
|
+
SDK itself no longer references the package.
|
|
42
|
+
|
|
43
|
+
### Migration
|
|
44
|
+
|
|
45
|
+
- If you were on 0.2.0 and had `@react-native-async-storage/async-storage`
|
|
46
|
+
installed: import it and pass it as `storage` to `init()`. Two lines of
|
|
47
|
+
app code.
|
|
48
|
+
- If you didn't have it installed: nothing to change. You were already getting
|
|
49
|
+
in-memory ids (silently), which is still the default in 0.3.0.
|
|
50
|
+
|
|
51
|
+
[report]: https://github.com/tamso-labs/lumin/issues <!-- replace with the actual issue link when filed -->
|
|
52
|
+
|
|
53
|
+
## [0.2.0] - 2026-05-26
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- `captureError(err, properties?)` — capture an exception as a Lumin event.
|
|
58
|
+
Accepts a real `Error` (preferred — preserves stack and constructor name as
|
|
59
|
+
`error_type`), a string, or a plain object with a `message` field. `null` /
|
|
60
|
+
`undefined` are silently ignored. The same `Error` instance captured twice
|
|
61
|
+
in one tick is deduped.
|
|
62
|
+
- Automatic capture of unhandled JS errors via `ErrorUtils.setGlobalHandler`.
|
|
63
|
+
Enabled by default; opt out with `captureUnhandledErrors: false`. The
|
|
64
|
+
handler **chains to the previous handler**, so RN's red-box, LogBox, and any
|
|
65
|
+
other error tool already installed still fires — the SDK never swallows the
|
|
66
|
+
throw.
|
|
67
|
+
- Fatal errors (`isFatal: true`) trigger an immediate `flush()` so the error
|
|
68
|
+
row has a chance to leave the device before the RN runtime tears down, and
|
|
69
|
+
are stamped with `properties: { fatal: true }`.
|
|
70
|
+
- `errorUtils` init option — override the auto-detected global ErrorUtils
|
|
71
|
+
(mainly for tests).
|
|
72
|
+
- New wire `EventType` value `"error"` plus optional `error_message`,
|
|
73
|
+
`error_stack`, and `error_type` fields on `WireEvent`.
|
|
74
|
+
|
|
75
|
+
### Notes
|
|
76
|
+
|
|
77
|
+
- Native iOS / Android crashes are not captured. `ErrorUtils` is JS-only;
|
|
78
|
+
native crash reporting requires a native module (Crashlytics, Sentry-native,
|
|
79
|
+
Bugsnag's native bridge) which is out of scope for this SDK.
|
|
80
|
+
- Source maps are not symbolicated server-side. Stacks ship as the RN bundle's
|
|
81
|
+
minified frames.
|
|
82
|
+
- Requires Lumin API v0.x (this release) or newer — older servers will reject
|
|
83
|
+
`type: "error"` events with a 400.
|
|
84
|
+
|
|
85
|
+
## [0.1.0] - 2026-05-26
|
|
86
|
+
|
|
87
|
+
Initial release. Mirrors `@lumin-monitor/browser` for React Native; same
|
|
88
|
+
`/v1/events` ingest, same `lmn_pub_*` API key kind, so a single Lumin project
|
|
89
|
+
can hold events from web and mobile and tie them to the same user via
|
|
90
|
+
`identify`.
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- `init(options)` returning a `LuminClient` with bound `screen` / `track` /
|
|
95
|
+
`identify` / `flush` / `close` / `getSessionId` / `getAnonymousId` methods.
|
|
96
|
+
- Synchronous `init()` with lazy AsyncStorage id hydration — buffered events
|
|
97
|
+
await hydration before the first flush, so apps can call `screen` /
|
|
98
|
+
`track` immediately after `init`.
|
|
99
|
+
- Persistent `anonymous_id` and `session_id` via
|
|
100
|
+
`@react-native-async-storage/async-storage` (optional peer dep); graceful
|
|
101
|
+
in-memory fallback when the peer dep is missing.
|
|
102
|
+
- Idle-based session rotation (`sessionIdleMs`, default 30 min) — matches the
|
|
103
|
+
GA / Mixpanel convention for mobile sessions.
|
|
104
|
+
- Auto-flush on `AppState` transition to `background` or `inactive`.
|
|
105
|
+
- `screen()` method (RN idiom); emits wire `type: "page"` for cross-platform
|
|
106
|
+
compatibility with the browser SDK.
|
|
107
|
+
- Optional `@lumin-monitor/react-native/react-navigation` subpath exporting
|
|
108
|
+
`useLuminScreenviews(client, navigationRef)` for React Navigation v6+ apps.
|
|
109
|
+
- Ships dual ESM + CJS bundle with full `.d.ts` types.
|
|
110
|
+
|
|
111
|
+
[0.3.0]: https://github.com/tamso-labs/lumin/releases/tag/sdk-react-native-v0.3.0
|
|
112
|
+
[0.2.0]: https://github.com/tamso-labs/lumin/releases/tag/sdk-react-native-v0.2.0
|
|
113
|
+
[0.1.0]: https://github.com/tamso-labs/lumin/releases/tag/sdk-react-native-v0.1.0
|
package/README.md
CHANGED
|
@@ -13,26 +13,30 @@ the same user.
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
pnpm add @lumin-monitor/react-native
|
|
17
|
-
#
|
|
16
|
+
pnpm add @lumin-monitor/react-native
|
|
17
|
+
# plus, if you want persistent ids across app launches (recommended):
|
|
18
|
+
pnpm add @react-native-async-storage/async-storage
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
`@react-native-async-storage/async-storage` is
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
`@react-native-async-storage/async-storage` is no longer auto-detected
|
|
22
|
+
(it broke Metro bundling — see [CHANGELOG 0.3.0](./CHANGELOG.md)). Install
|
|
23
|
+
it and pass it to `init({ storage })` to persist `anonymous_id` across app
|
|
24
|
+
launches and let the 30-minute idle session window survive a cold start.
|
|
25
|
+
Omit `storage` (or pass `null`) to use in-memory ids that reset on every
|
|
26
|
+
cold start — fine for prototypes.
|
|
25
27
|
|
|
26
|
-
React Navigation is
|
|
28
|
+
React Navigation is an optional peer dep, only needed if you import
|
|
27
29
|
`@lumin-monitor/react-native/react-navigation`.
|
|
28
30
|
|
|
29
31
|
## Quick start
|
|
30
32
|
|
|
31
33
|
```ts
|
|
34
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
32
35
|
import { init } from "@lumin-monitor/react-native";
|
|
33
36
|
|
|
34
37
|
export const lumin = init({
|
|
35
38
|
apiKey: process.env.EXPO_PUBLIC_LUMIN_BROWSER_API_KEY!,
|
|
39
|
+
storage: AsyncStorage,
|
|
36
40
|
});
|
|
37
41
|
|
|
38
42
|
// Bound methods, safe to destructure:
|
|
@@ -74,6 +78,8 @@ hydration to complete before sending.
|
|
|
74
78
|
| `fetch` | global `fetch` | Override for tests. |
|
|
75
79
|
| `storage` | auto-detect AsyncStorage | Pass a custom `AsyncStorageLike`, or `null` to opt out (in-memory ids). |
|
|
76
80
|
| `appState` | auto-detect react-native | Pass `null` to disable the background-flush listener. |
|
|
81
|
+
| `captureUnhandledErrors` | `true` | Install an `ErrorUtils.setGlobalHandler` chain. See *Error capture* below. |
|
|
82
|
+
| `errorUtils` | auto-detect global ErrorUtils | Override the ErrorUtils-like object the SDK installs into. |
|
|
77
83
|
|
|
78
84
|
### `screen(name?, properties?)`
|
|
79
85
|
|
|
@@ -115,6 +121,50 @@ identify("user_abc123", { plan: "indie", signedUpAt: "2026-05-01" });
|
|
|
115
121
|
Re-call on every app launch while the user is signed in. It is cheap
|
|
116
122
|
and ensures a cold start still binds the session.
|
|
117
123
|
|
|
124
|
+
### `captureError(err, properties?)`
|
|
125
|
+
|
|
126
|
+
Capture an error. The auto-installed global handler catches uncaught JS
|
|
127
|
+
throws (and rejected promises that bubble up to RN); call this directly
|
|
128
|
+
from `try/catch` blocks where you'd otherwise swallow the failure.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
try {
|
|
132
|
+
await applyDiscount(code);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
captureError(err, { code, step: "checkout" });
|
|
135
|
+
showToast("Discount didn't apply");
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Accepts a real `Error` (preferred — preserves stack and constructor name
|
|
140
|
+
as `error_type`), or any value that will be stringified for the message.
|
|
141
|
+
`null` / `undefined` are silently ignored. The same `Error` object
|
|
142
|
+
captured twice in the same tick is deduped.
|
|
143
|
+
|
|
144
|
+
### Auto error capture
|
|
145
|
+
|
|
146
|
+
When `captureUnhandledErrors` is true (the default), the SDK installs a
|
|
147
|
+
handler via `ErrorUtils.setGlobalHandler` that **chains to the previous
|
|
148
|
+
handler**, so RN's red-box, the LogBox, and any other error tool already
|
|
149
|
+
installed still fire. The SDK does not swallow the throw.
|
|
150
|
+
|
|
151
|
+
Fatal errors (`isFatal: true`) trigger an immediate `flush()` so the
|
|
152
|
+
error row has a chance to leave the device before the RN runtime tears
|
|
153
|
+
down. Best-effort: the OS may suspend the JS thread before the request
|
|
154
|
+
lands, but the standard ~30 s background grace window covers the
|
|
155
|
+
common case.
|
|
156
|
+
|
|
157
|
+
**What is NOT captured**:
|
|
158
|
+
- **Native iOS / Android crashes.** RN's `ErrorUtils` is JS-only. Native
|
|
159
|
+
crashes need a native module (Crashlytics, Sentry-native, Bugsnag's
|
|
160
|
+
native bridge) — out of scope for this SDK.
|
|
161
|
+
- **Source-mapped stacks.** Stacks ship as the minified RN bundle frames
|
|
162
|
+
ship them. Symbolication against the bundle's source maps is a v2
|
|
163
|
+
concern.
|
|
164
|
+
|
|
165
|
+
Opt out with `captureUnhandledErrors: false` if another tool already
|
|
166
|
+
owns the global handler; manual `captureError(err)` still works.
|
|
167
|
+
|
|
118
168
|
### `flush(): Promise<void>`
|
|
119
169
|
|
|
120
170
|
Force a flush of any buffered events. The SDK auto-flushes on AppState
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,9 @@ __export(src_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(src_exports);
|
|
27
27
|
|
|
28
|
+
// src/client.ts
|
|
29
|
+
var import_react_native = require("react-native");
|
|
30
|
+
|
|
28
31
|
// src/ids.ts
|
|
29
32
|
function uuidv4() {
|
|
30
33
|
const c = globalThis.crypto;
|
|
@@ -41,19 +44,6 @@ function uuidv4() {
|
|
|
41
44
|
var ANON_KEY = "lumin.anonymous_id";
|
|
42
45
|
var SESSION_KEY = "lumin.session_id";
|
|
43
46
|
var SESSION_LAST_SEEN_KEY = "lumin.session_last_seen";
|
|
44
|
-
function detectAsyncStorage() {
|
|
45
|
-
try {
|
|
46
|
-
if (typeof require === "undefined") return null;
|
|
47
|
-
const mod = require("@react-native-async-storage/async-storage");
|
|
48
|
-
const candidate = mod?.default ?? mod;
|
|
49
|
-
if (candidate && typeof candidate.getItem === "function" && typeof candidate.setItem === "function") {
|
|
50
|
-
return candidate;
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
47
|
function memoryStorage() {
|
|
58
48
|
const m = /* @__PURE__ */ new Map();
|
|
59
49
|
return {
|
|
@@ -109,6 +99,11 @@ var SERVER_MAX_BATCH = 1e3;
|
|
|
109
99
|
var DEFAULT_ENDPOINT = "https://api.getlumin.dev";
|
|
110
100
|
var Client = class {
|
|
111
101
|
constructor(opts) {
|
|
102
|
+
this.prevErrorHandler = void 0;
|
|
103
|
+
this.errorHandlerInstalled = false;
|
|
104
|
+
// Dedup the same Error captured twice (e.g. via captureError + the global
|
|
105
|
+
// handler firing on the same throw). WeakSet so we don't pin GC.
|
|
106
|
+
this.recentErrors = /* @__PURE__ */ new WeakSet();
|
|
112
107
|
this.userId = null;
|
|
113
108
|
this.cachedIds = null;
|
|
114
109
|
// Pending events queued before ids hydrate. `user_id` is stamped at enqueue
|
|
@@ -131,11 +126,14 @@ var Client = class {
|
|
|
131
126
|
console.warn("[lumin] flush failed", { err, dropped });
|
|
132
127
|
});
|
|
133
128
|
this.fetchImpl = opts.fetch ?? fetch.bind(globalThis);
|
|
134
|
-
|
|
135
|
-
this.storage = resolvedStorage;
|
|
129
|
+
this.storage = opts.storage ?? memoryStorage();
|
|
136
130
|
this.appState = opts.appState === void 0 ? detectAppState() : opts.appState;
|
|
131
|
+
this.errorUtils = opts.errorUtils === void 0 ? detectErrorUtils() : opts.errorUtils;
|
|
137
132
|
this.idsReady = this.hydrateIds();
|
|
138
133
|
this.installBackgroundFlush();
|
|
134
|
+
if (opts.captureUnhandledErrors !== false) {
|
|
135
|
+
this.installErrorHandler();
|
|
136
|
+
}
|
|
139
137
|
}
|
|
140
138
|
screen(name, properties) {
|
|
141
139
|
if (this.closed) return;
|
|
@@ -153,6 +151,22 @@ var Client = class {
|
|
|
153
151
|
}
|
|
154
152
|
this.enqueue({ type: "track", name, ...properties !== void 0 ? { properties } : {} });
|
|
155
153
|
}
|
|
154
|
+
captureError(err, properties) {
|
|
155
|
+
if (this.closed) return;
|
|
156
|
+
const e = normalizeError(err);
|
|
157
|
+
if (!e) return;
|
|
158
|
+
if (typeof err === "object" && err !== null) {
|
|
159
|
+
if (this.recentErrors.has(err)) return;
|
|
160
|
+
this.recentErrors.add(err);
|
|
161
|
+
}
|
|
162
|
+
this.enqueue({
|
|
163
|
+
type: "error",
|
|
164
|
+
...properties !== void 0 ? { properties } : {},
|
|
165
|
+
error_message: e.message,
|
|
166
|
+
...e.stack ? { error_stack: e.stack } : {},
|
|
167
|
+
...e.type ? { error_type: e.type } : {}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
156
170
|
identify(userId, traits) {
|
|
157
171
|
if (this.closed) return;
|
|
158
172
|
if (!userId) {
|
|
@@ -189,6 +203,7 @@ var Client = class {
|
|
|
189
203
|
if (this.closed) return;
|
|
190
204
|
this.closed = true;
|
|
191
205
|
this.removeBackgroundFlush();
|
|
206
|
+
this.restoreErrorHandler();
|
|
192
207
|
await this.flush();
|
|
193
208
|
await Promise.allSettled(this.inflight);
|
|
194
209
|
}
|
|
@@ -314,24 +329,88 @@ var Client = class {
|
|
|
314
329
|
this.appStateSub = null;
|
|
315
330
|
}
|
|
316
331
|
}
|
|
332
|
+
// --- error handler -----------------------------------------------------
|
|
333
|
+
/**
|
|
334
|
+
* Install a global JS error handler via ErrorUtils.setGlobalHandler that
|
|
335
|
+
* captures + chains to whatever handler was already in place. RN's
|
|
336
|
+
* red-box and any other observability tool that installed before us
|
|
337
|
+
* still fires — we do not swallow the throw.
|
|
338
|
+
*/
|
|
339
|
+
installErrorHandler() {
|
|
340
|
+
if (!this.errorUtils) return;
|
|
341
|
+
this.prevErrorHandler = this.errorUtils.getGlobalHandler?.();
|
|
342
|
+
const handler = (err, isFatal) => {
|
|
343
|
+
try {
|
|
344
|
+
this.captureError(err, isFatal ? { fatal: true } : void 0);
|
|
345
|
+
if (isFatal) void this.flush();
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
if (this.prevErrorHandler) {
|
|
349
|
+
this.prevErrorHandler(err, isFatal);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
this.errorUtils.setGlobalHandler(handler);
|
|
353
|
+
this.errorHandlerInstalled = true;
|
|
354
|
+
}
|
|
355
|
+
restoreErrorHandler() {
|
|
356
|
+
if (!this.errorHandlerInstalled || !this.errorUtils) return;
|
|
357
|
+
this.errorUtils.setGlobalHandler(
|
|
358
|
+
this.prevErrorHandler ?? defaultNoopErrorHandler
|
|
359
|
+
);
|
|
360
|
+
this.errorHandlerInstalled = false;
|
|
361
|
+
}
|
|
317
362
|
};
|
|
363
|
+
function defaultNoopErrorHandler() {
|
|
364
|
+
}
|
|
365
|
+
function detectErrorUtils() {
|
|
366
|
+
const g = globalThis;
|
|
367
|
+
const eu = g.ErrorUtils;
|
|
368
|
+
if (eu && typeof eu.setGlobalHandler === "function") {
|
|
369
|
+
return eu;
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
function normalizeError(raw) {
|
|
374
|
+
if (raw == null) return null;
|
|
375
|
+
if (raw instanceof Error) {
|
|
376
|
+
return {
|
|
377
|
+
message: raw.message || raw.name || "Error",
|
|
378
|
+
...raw.stack ? { stack: raw.stack } : {},
|
|
379
|
+
type: raw.name || raw.constructor?.name
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (typeof raw === "string") {
|
|
383
|
+
return { message: raw };
|
|
384
|
+
}
|
|
385
|
+
if (typeof raw === "object") {
|
|
386
|
+
const obj = raw;
|
|
387
|
+
const message = typeof obj.message === "string" ? obj.message : safeStringify(raw);
|
|
388
|
+
return {
|
|
389
|
+
message,
|
|
390
|
+
...typeof obj.stack === "string" ? { stack: obj.stack } : {},
|
|
391
|
+
...typeof obj.name === "string" ? { type: obj.name } : {}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return { message: String(raw) };
|
|
395
|
+
}
|
|
396
|
+
function safeStringify(v) {
|
|
397
|
+
try {
|
|
398
|
+
return JSON.stringify(v) ?? String(v);
|
|
399
|
+
} catch {
|
|
400
|
+
return String(v);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
318
403
|
function clampBatch(n) {
|
|
319
404
|
if (!Number.isFinite(n) || n < 1) return 1;
|
|
320
405
|
if (n > SERVER_MAX_BATCH) return SERVER_MAX_BATCH;
|
|
321
406
|
return Math.floor(n);
|
|
322
407
|
}
|
|
323
408
|
function detectAppState() {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const candidate = rn?.AppState;
|
|
328
|
-
if (candidate && typeof candidate.addEventListener === "function") {
|
|
329
|
-
return candidate;
|
|
330
|
-
}
|
|
331
|
-
return null;
|
|
332
|
-
} catch {
|
|
333
|
-
return null;
|
|
409
|
+
const candidate = import_react_native.AppState;
|
|
410
|
+
if (candidate && typeof candidate.addEventListener === "function") {
|
|
411
|
+
return candidate;
|
|
334
412
|
}
|
|
413
|
+
return null;
|
|
335
414
|
}
|
|
336
415
|
function validateEndpoint(raw) {
|
|
337
416
|
let url;
|
|
@@ -369,6 +448,7 @@ function init(opts) {
|
|
|
369
448
|
screen: c.screen.bind(c),
|
|
370
449
|
track: c.track.bind(c),
|
|
371
450
|
identify: c.identify.bind(c),
|
|
451
|
+
captureError: c.captureError.bind(c),
|
|
372
452
|
flush: c.flush.bind(c),
|
|
373
453
|
close: c.close.bind(c),
|
|
374
454
|
getSessionId: c.getSessionId.bind(c),
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LuminClient, I as InitOptions } from './types-
|
|
2
|
-
export { A as AppStateLike, a as AsyncStorageLike, E as EventType, W as WireEvent } from './types-
|
|
1
|
+
import { L as LuminClient, I as InitOptions } from './types-DMUQWor6.cjs';
|
|
2
|
+
export { A as AppStateLike, a as AsyncStorageLike, E as ErrorUtilsLike, b as EventType, W as WireEvent } from './types-DMUQWor6.cjs';
|
|
3
3
|
|
|
4
4
|
declare class Client implements LuminClient {
|
|
5
5
|
private readonly endpoint;
|
|
@@ -11,6 +11,10 @@ declare class Client implements LuminClient {
|
|
|
11
11
|
private readonly fetchImpl;
|
|
12
12
|
private readonly storage;
|
|
13
13
|
private readonly appState;
|
|
14
|
+
private readonly errorUtils;
|
|
15
|
+
private prevErrorHandler;
|
|
16
|
+
private errorHandlerInstalled;
|
|
17
|
+
private readonly recentErrors;
|
|
14
18
|
private userId;
|
|
15
19
|
private idsReady;
|
|
16
20
|
private cachedIds;
|
|
@@ -23,6 +27,7 @@ declare class Client implements LuminClient {
|
|
|
23
27
|
constructor(opts: InitOptions);
|
|
24
28
|
screen(name?: string, properties?: Record<string, unknown>): void;
|
|
25
29
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
30
|
+
captureError(err: unknown, properties?: Record<string, unknown>): void;
|
|
26
31
|
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
27
32
|
/**
|
|
28
33
|
* Drain the buffer to the server. Returns when the in-flight request
|
|
@@ -59,6 +64,14 @@ declare class Client implements LuminClient {
|
|
|
59
64
|
*/
|
|
60
65
|
private installBackgroundFlush;
|
|
61
66
|
private removeBackgroundFlush;
|
|
67
|
+
/**
|
|
68
|
+
* Install a global JS error handler via ErrorUtils.setGlobalHandler that
|
|
69
|
+
* captures + chains to whatever handler was already in place. RN's
|
|
70
|
+
* red-box and any other observability tool that installed before us
|
|
71
|
+
* still fires — we do not swallow the throw.
|
|
72
|
+
*/
|
|
73
|
+
private installErrorHandler;
|
|
74
|
+
private restoreErrorHandler;
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LuminClient, I as InitOptions } from './types-
|
|
2
|
-
export { A as AppStateLike, a as AsyncStorageLike, E as EventType, W as WireEvent } from './types-
|
|
1
|
+
import { L as LuminClient, I as InitOptions } from './types-DMUQWor6.js';
|
|
2
|
+
export { A as AppStateLike, a as AsyncStorageLike, E as ErrorUtilsLike, b as EventType, W as WireEvent } from './types-DMUQWor6.js';
|
|
3
3
|
|
|
4
4
|
declare class Client implements LuminClient {
|
|
5
5
|
private readonly endpoint;
|
|
@@ -11,6 +11,10 @@ declare class Client implements LuminClient {
|
|
|
11
11
|
private readonly fetchImpl;
|
|
12
12
|
private readonly storage;
|
|
13
13
|
private readonly appState;
|
|
14
|
+
private readonly errorUtils;
|
|
15
|
+
private prevErrorHandler;
|
|
16
|
+
private errorHandlerInstalled;
|
|
17
|
+
private readonly recentErrors;
|
|
14
18
|
private userId;
|
|
15
19
|
private idsReady;
|
|
16
20
|
private cachedIds;
|
|
@@ -23,6 +27,7 @@ declare class Client implements LuminClient {
|
|
|
23
27
|
constructor(opts: InitOptions);
|
|
24
28
|
screen(name?: string, properties?: Record<string, unknown>): void;
|
|
25
29
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
30
|
+
captureError(err: unknown, properties?: Record<string, unknown>): void;
|
|
26
31
|
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
27
32
|
/**
|
|
28
33
|
* Drain the buffer to the server. Returns when the in-flight request
|
|
@@ -59,6 +64,14 @@ declare class Client implements LuminClient {
|
|
|
59
64
|
*/
|
|
60
65
|
private installBackgroundFlush;
|
|
61
66
|
private removeBackgroundFlush;
|
|
67
|
+
/**
|
|
68
|
+
* Install a global JS error handler via ErrorUtils.setGlobalHandler that
|
|
69
|
+
* captures + chains to whatever handler was already in place. RN's
|
|
70
|
+
* red-box and any other observability tool that installed before us
|
|
71
|
+
* still fires — we do not swallow the throw.
|
|
72
|
+
*/
|
|
73
|
+
private installErrorHandler;
|
|
74
|
+
private restoreErrorHandler;
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
} from "./chunk-3RG5ZIWI.js";
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { AppState as RNAppState } from "react-native";
|
|
4
3
|
|
|
5
4
|
// src/ids.ts
|
|
6
5
|
function uuidv4() {
|
|
@@ -18,19 +17,6 @@ function uuidv4() {
|
|
|
18
17
|
var ANON_KEY = "lumin.anonymous_id";
|
|
19
18
|
var SESSION_KEY = "lumin.session_id";
|
|
20
19
|
var SESSION_LAST_SEEN_KEY = "lumin.session_last_seen";
|
|
21
|
-
function detectAsyncStorage() {
|
|
22
|
-
try {
|
|
23
|
-
if (typeof __require === "undefined") return null;
|
|
24
|
-
const mod = __require("@react-native-async-storage/async-storage");
|
|
25
|
-
const candidate = mod?.default ?? mod;
|
|
26
|
-
if (candidate && typeof candidate.getItem === "function" && typeof candidate.setItem === "function") {
|
|
27
|
-
return candidate;
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
20
|
function memoryStorage() {
|
|
35
21
|
const m = /* @__PURE__ */ new Map();
|
|
36
22
|
return {
|
|
@@ -86,6 +72,11 @@ var SERVER_MAX_BATCH = 1e3;
|
|
|
86
72
|
var DEFAULT_ENDPOINT = "https://api.getlumin.dev";
|
|
87
73
|
var Client = class {
|
|
88
74
|
constructor(opts) {
|
|
75
|
+
this.prevErrorHandler = void 0;
|
|
76
|
+
this.errorHandlerInstalled = false;
|
|
77
|
+
// Dedup the same Error captured twice (e.g. via captureError + the global
|
|
78
|
+
// handler firing on the same throw). WeakSet so we don't pin GC.
|
|
79
|
+
this.recentErrors = /* @__PURE__ */ new WeakSet();
|
|
89
80
|
this.userId = null;
|
|
90
81
|
this.cachedIds = null;
|
|
91
82
|
// Pending events queued before ids hydrate. `user_id` is stamped at enqueue
|
|
@@ -108,11 +99,14 @@ var Client = class {
|
|
|
108
99
|
console.warn("[lumin] flush failed", { err, dropped });
|
|
109
100
|
});
|
|
110
101
|
this.fetchImpl = opts.fetch ?? fetch.bind(globalThis);
|
|
111
|
-
|
|
112
|
-
this.storage = resolvedStorage;
|
|
102
|
+
this.storage = opts.storage ?? memoryStorage();
|
|
113
103
|
this.appState = opts.appState === void 0 ? detectAppState() : opts.appState;
|
|
104
|
+
this.errorUtils = opts.errorUtils === void 0 ? detectErrorUtils() : opts.errorUtils;
|
|
114
105
|
this.idsReady = this.hydrateIds();
|
|
115
106
|
this.installBackgroundFlush();
|
|
107
|
+
if (opts.captureUnhandledErrors !== false) {
|
|
108
|
+
this.installErrorHandler();
|
|
109
|
+
}
|
|
116
110
|
}
|
|
117
111
|
screen(name, properties) {
|
|
118
112
|
if (this.closed) return;
|
|
@@ -130,6 +124,22 @@ var Client = class {
|
|
|
130
124
|
}
|
|
131
125
|
this.enqueue({ type: "track", name, ...properties !== void 0 ? { properties } : {} });
|
|
132
126
|
}
|
|
127
|
+
captureError(err, properties) {
|
|
128
|
+
if (this.closed) return;
|
|
129
|
+
const e = normalizeError(err);
|
|
130
|
+
if (!e) return;
|
|
131
|
+
if (typeof err === "object" && err !== null) {
|
|
132
|
+
if (this.recentErrors.has(err)) return;
|
|
133
|
+
this.recentErrors.add(err);
|
|
134
|
+
}
|
|
135
|
+
this.enqueue({
|
|
136
|
+
type: "error",
|
|
137
|
+
...properties !== void 0 ? { properties } : {},
|
|
138
|
+
error_message: e.message,
|
|
139
|
+
...e.stack ? { error_stack: e.stack } : {},
|
|
140
|
+
...e.type ? { error_type: e.type } : {}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
133
143
|
identify(userId, traits) {
|
|
134
144
|
if (this.closed) return;
|
|
135
145
|
if (!userId) {
|
|
@@ -166,6 +176,7 @@ var Client = class {
|
|
|
166
176
|
if (this.closed) return;
|
|
167
177
|
this.closed = true;
|
|
168
178
|
this.removeBackgroundFlush();
|
|
179
|
+
this.restoreErrorHandler();
|
|
169
180
|
await this.flush();
|
|
170
181
|
await Promise.allSettled(this.inflight);
|
|
171
182
|
}
|
|
@@ -291,24 +302,88 @@ var Client = class {
|
|
|
291
302
|
this.appStateSub = null;
|
|
292
303
|
}
|
|
293
304
|
}
|
|
305
|
+
// --- error handler -----------------------------------------------------
|
|
306
|
+
/**
|
|
307
|
+
* Install a global JS error handler via ErrorUtils.setGlobalHandler that
|
|
308
|
+
* captures + chains to whatever handler was already in place. RN's
|
|
309
|
+
* red-box and any other observability tool that installed before us
|
|
310
|
+
* still fires — we do not swallow the throw.
|
|
311
|
+
*/
|
|
312
|
+
installErrorHandler() {
|
|
313
|
+
if (!this.errorUtils) return;
|
|
314
|
+
this.prevErrorHandler = this.errorUtils.getGlobalHandler?.();
|
|
315
|
+
const handler = (err, isFatal) => {
|
|
316
|
+
try {
|
|
317
|
+
this.captureError(err, isFatal ? { fatal: true } : void 0);
|
|
318
|
+
if (isFatal) void this.flush();
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
if (this.prevErrorHandler) {
|
|
322
|
+
this.prevErrorHandler(err, isFatal);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
this.errorUtils.setGlobalHandler(handler);
|
|
326
|
+
this.errorHandlerInstalled = true;
|
|
327
|
+
}
|
|
328
|
+
restoreErrorHandler() {
|
|
329
|
+
if (!this.errorHandlerInstalled || !this.errorUtils) return;
|
|
330
|
+
this.errorUtils.setGlobalHandler(
|
|
331
|
+
this.prevErrorHandler ?? defaultNoopErrorHandler
|
|
332
|
+
);
|
|
333
|
+
this.errorHandlerInstalled = false;
|
|
334
|
+
}
|
|
294
335
|
};
|
|
336
|
+
function defaultNoopErrorHandler() {
|
|
337
|
+
}
|
|
338
|
+
function detectErrorUtils() {
|
|
339
|
+
const g = globalThis;
|
|
340
|
+
const eu = g.ErrorUtils;
|
|
341
|
+
if (eu && typeof eu.setGlobalHandler === "function") {
|
|
342
|
+
return eu;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
function normalizeError(raw) {
|
|
347
|
+
if (raw == null) return null;
|
|
348
|
+
if (raw instanceof Error) {
|
|
349
|
+
return {
|
|
350
|
+
message: raw.message || raw.name || "Error",
|
|
351
|
+
...raw.stack ? { stack: raw.stack } : {},
|
|
352
|
+
type: raw.name || raw.constructor?.name
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (typeof raw === "string") {
|
|
356
|
+
return { message: raw };
|
|
357
|
+
}
|
|
358
|
+
if (typeof raw === "object") {
|
|
359
|
+
const obj = raw;
|
|
360
|
+
const message = typeof obj.message === "string" ? obj.message : safeStringify(raw);
|
|
361
|
+
return {
|
|
362
|
+
message,
|
|
363
|
+
...typeof obj.stack === "string" ? { stack: obj.stack } : {},
|
|
364
|
+
...typeof obj.name === "string" ? { type: obj.name } : {}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return { message: String(raw) };
|
|
368
|
+
}
|
|
369
|
+
function safeStringify(v) {
|
|
370
|
+
try {
|
|
371
|
+
return JSON.stringify(v) ?? String(v);
|
|
372
|
+
} catch {
|
|
373
|
+
return String(v);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
295
376
|
function clampBatch(n) {
|
|
296
377
|
if (!Number.isFinite(n) || n < 1) return 1;
|
|
297
378
|
if (n > SERVER_MAX_BATCH) return SERVER_MAX_BATCH;
|
|
298
379
|
return Math.floor(n);
|
|
299
380
|
}
|
|
300
381
|
function detectAppState() {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const candidate = rn?.AppState;
|
|
305
|
-
if (candidate && typeof candidate.addEventListener === "function") {
|
|
306
|
-
return candidate;
|
|
307
|
-
}
|
|
308
|
-
return null;
|
|
309
|
-
} catch {
|
|
310
|
-
return null;
|
|
382
|
+
const candidate = RNAppState;
|
|
383
|
+
if (candidate && typeof candidate.addEventListener === "function") {
|
|
384
|
+
return candidate;
|
|
311
385
|
}
|
|
386
|
+
return null;
|
|
312
387
|
}
|
|
313
388
|
function validateEndpoint(raw) {
|
|
314
389
|
let url;
|
|
@@ -346,6 +421,7 @@ function init(opts) {
|
|
|
346
421
|
screen: c.screen.bind(c),
|
|
347
422
|
track: c.track.bind(c),
|
|
348
423
|
identify: c.identify.bind(c),
|
|
424
|
+
captureError: c.captureError.bind(c),
|
|
349
425
|
flush: c.flush.bind(c),
|
|
350
426
|
close: c.close.bind(c),
|
|
351
427
|
getSessionId: c.getSessionId.bind(c),
|
package/dist/react-navigation.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type EventType = "page" | "track" | "identify";
|
|
1
|
+
type EventType = "page" | "track" | "identify" | "error";
|
|
2
2
|
interface WireEvent {
|
|
3
3
|
ts?: number;
|
|
4
4
|
session_id: string;
|
|
@@ -10,6 +10,9 @@ interface WireEvent {
|
|
|
10
10
|
referrer?: string;
|
|
11
11
|
properties?: Record<string, unknown>;
|
|
12
12
|
trace_id?: string;
|
|
13
|
+
error_message?: string;
|
|
14
|
+
error_stack?: string;
|
|
15
|
+
error_type?: string;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Minimal AsyncStorage shape the SDK actually consumes. Matches both
|
|
@@ -58,11 +61,20 @@ interface InitOptions {
|
|
|
58
61
|
*/
|
|
59
62
|
fetch?: typeof fetch;
|
|
60
63
|
/**
|
|
61
|
-
*
|
|
62
|
-
* `@react-native-async-storage/async-storage`
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* Storage backend for the persistent `anonymous_id` and `session_id`.
|
|
65
|
+
* Pass `AsyncStorage` from `@react-native-async-storage/async-storage`
|
|
66
|
+
* (recommended) or any object satisfying `AsyncStorageLike`. Omit /
|
|
67
|
+
* pass `null` for ephemeral in-memory ids that reset on every cold
|
|
68
|
+
* start.
|
|
69
|
+
*
|
|
70
|
+
* The SDK no longer auto-detects AsyncStorage as of 0.3.0 — the
|
|
71
|
+
* runtime require broke Metro bundling, and forcing every consumer
|
|
72
|
+
* to install an unused peer dep was the wrong trade. See CHANGELOG.
|
|
73
|
+
*
|
|
74
|
+
* ```ts
|
|
75
|
+
* import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
76
|
+
* init({ apiKey, storage: AsyncStorage });
|
|
77
|
+
* ```
|
|
66
78
|
*/
|
|
67
79
|
storage?: AsyncStorageLike | null;
|
|
68
80
|
/**
|
|
@@ -70,6 +82,31 @@ interface InitOptions {
|
|
|
70
82
|
* AppState; tests pass a stub.
|
|
71
83
|
*/
|
|
72
84
|
appState?: AppStateLike | null;
|
|
85
|
+
/**
|
|
86
|
+
* Auto-capture unhandled JS errors via ErrorUtils.setGlobalHandler.
|
|
87
|
+
* Default true. Set false to disable (e.g. when another tool like Sentry
|
|
88
|
+
* already owns the global handler). When enabled, the SDK chains to any
|
|
89
|
+
* previously installed handler so RN's red-box and other reporters
|
|
90
|
+
* still fire.
|
|
91
|
+
*
|
|
92
|
+
* Native crashes (iOS / Android) are NOT captured — that requires native
|
|
93
|
+
* modules outside this SDK's scope.
|
|
94
|
+
*/
|
|
95
|
+
captureUnhandledErrors?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Override the global ErrorUtils-like object the SDK installs into.
|
|
98
|
+
* The SDK auto-detects React Native's ErrorUtils on globalThis at
|
|
99
|
+
* runtime; tests pass a stub.
|
|
100
|
+
*/
|
|
101
|
+
errorUtils?: ErrorUtilsLike | null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Minimal shape of React Native's ErrorUtils. The SDK uses these three
|
|
105
|
+
* methods to chain into any previously installed handler.
|
|
106
|
+
*/
|
|
107
|
+
interface ErrorUtilsLike {
|
|
108
|
+
setGlobalHandler(handler: (err: unknown, isFatal?: boolean) => void): void;
|
|
109
|
+
getGlobalHandler?(): ((err: unknown, isFatal?: boolean) => void) | undefined;
|
|
73
110
|
}
|
|
74
111
|
/**
|
|
75
112
|
* Minimal AppState shape the SDK consumes (subset of react-native's AppState).
|
|
@@ -85,6 +122,13 @@ interface LuminClient {
|
|
|
85
122
|
screen(name?: string, properties?: Record<string, unknown>): void;
|
|
86
123
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
87
124
|
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
125
|
+
/**
|
|
126
|
+
* Capture an error. Accepts a real Error (preferred — preserves stack +
|
|
127
|
+
* constructor name as error_type), or any value that will be stringified
|
|
128
|
+
* for error_message. `properties` is forwarded verbatim so callers can
|
|
129
|
+
* attach context (route, user action, request id).
|
|
130
|
+
*/
|
|
131
|
+
captureError(err: unknown, properties?: Record<string, unknown>): void;
|
|
88
132
|
flush(): Promise<void>;
|
|
89
133
|
close(): Promise<void>;
|
|
90
134
|
/** Current session id. Resolves once AsyncStorage hydration finishes. */
|
|
@@ -93,4 +137,4 @@ interface LuminClient {
|
|
|
93
137
|
getAnonymousId(): Promise<string>;
|
|
94
138
|
}
|
|
95
139
|
|
|
96
|
-
export type { AppStateLike as A,
|
|
140
|
+
export type { AppStateLike as A, ErrorUtilsLike as E, InitOptions as I, LuminClient as L, WireEvent as W, AsyncStorageLike as a, EventType as b };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type EventType = "page" | "track" | "identify";
|
|
1
|
+
type EventType = "page" | "track" | "identify" | "error";
|
|
2
2
|
interface WireEvent {
|
|
3
3
|
ts?: number;
|
|
4
4
|
session_id: string;
|
|
@@ -10,6 +10,9 @@ interface WireEvent {
|
|
|
10
10
|
referrer?: string;
|
|
11
11
|
properties?: Record<string, unknown>;
|
|
12
12
|
trace_id?: string;
|
|
13
|
+
error_message?: string;
|
|
14
|
+
error_stack?: string;
|
|
15
|
+
error_type?: string;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Minimal AsyncStorage shape the SDK actually consumes. Matches both
|
|
@@ -58,11 +61,20 @@ interface InitOptions {
|
|
|
58
61
|
*/
|
|
59
62
|
fetch?: typeof fetch;
|
|
60
63
|
/**
|
|
61
|
-
*
|
|
62
|
-
* `@react-native-async-storage/async-storage`
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* Storage backend for the persistent `anonymous_id` and `session_id`.
|
|
65
|
+
* Pass `AsyncStorage` from `@react-native-async-storage/async-storage`
|
|
66
|
+
* (recommended) or any object satisfying `AsyncStorageLike`. Omit /
|
|
67
|
+
* pass `null` for ephemeral in-memory ids that reset on every cold
|
|
68
|
+
* start.
|
|
69
|
+
*
|
|
70
|
+
* The SDK no longer auto-detects AsyncStorage as of 0.3.0 — the
|
|
71
|
+
* runtime require broke Metro bundling, and forcing every consumer
|
|
72
|
+
* to install an unused peer dep was the wrong trade. See CHANGELOG.
|
|
73
|
+
*
|
|
74
|
+
* ```ts
|
|
75
|
+
* import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
76
|
+
* init({ apiKey, storage: AsyncStorage });
|
|
77
|
+
* ```
|
|
66
78
|
*/
|
|
67
79
|
storage?: AsyncStorageLike | null;
|
|
68
80
|
/**
|
|
@@ -70,6 +82,31 @@ interface InitOptions {
|
|
|
70
82
|
* AppState; tests pass a stub.
|
|
71
83
|
*/
|
|
72
84
|
appState?: AppStateLike | null;
|
|
85
|
+
/**
|
|
86
|
+
* Auto-capture unhandled JS errors via ErrorUtils.setGlobalHandler.
|
|
87
|
+
* Default true. Set false to disable (e.g. when another tool like Sentry
|
|
88
|
+
* already owns the global handler). When enabled, the SDK chains to any
|
|
89
|
+
* previously installed handler so RN's red-box and other reporters
|
|
90
|
+
* still fire.
|
|
91
|
+
*
|
|
92
|
+
* Native crashes (iOS / Android) are NOT captured — that requires native
|
|
93
|
+
* modules outside this SDK's scope.
|
|
94
|
+
*/
|
|
95
|
+
captureUnhandledErrors?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Override the global ErrorUtils-like object the SDK installs into.
|
|
98
|
+
* The SDK auto-detects React Native's ErrorUtils on globalThis at
|
|
99
|
+
* runtime; tests pass a stub.
|
|
100
|
+
*/
|
|
101
|
+
errorUtils?: ErrorUtilsLike | null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Minimal shape of React Native's ErrorUtils. The SDK uses these three
|
|
105
|
+
* methods to chain into any previously installed handler.
|
|
106
|
+
*/
|
|
107
|
+
interface ErrorUtilsLike {
|
|
108
|
+
setGlobalHandler(handler: (err: unknown, isFatal?: boolean) => void): void;
|
|
109
|
+
getGlobalHandler?(): ((err: unknown, isFatal?: boolean) => void) | undefined;
|
|
73
110
|
}
|
|
74
111
|
/**
|
|
75
112
|
* Minimal AppState shape the SDK consumes (subset of react-native's AppState).
|
|
@@ -85,6 +122,13 @@ interface LuminClient {
|
|
|
85
122
|
screen(name?: string, properties?: Record<string, unknown>): void;
|
|
86
123
|
track(name: string, properties?: Record<string, unknown>): void;
|
|
87
124
|
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
125
|
+
/**
|
|
126
|
+
* Capture an error. Accepts a real Error (preferred — preserves stack +
|
|
127
|
+
* constructor name as error_type), or any value that will be stringified
|
|
128
|
+
* for error_message. `properties` is forwarded verbatim so callers can
|
|
129
|
+
* attach context (route, user action, request id).
|
|
130
|
+
*/
|
|
131
|
+
captureError(err: unknown, properties?: Record<string, unknown>): void;
|
|
88
132
|
flush(): Promise<void>;
|
|
89
133
|
close(): Promise<void>;
|
|
90
134
|
/** Current session id. Resolves once AsyncStorage hydration finishes. */
|
|
@@ -93,4 +137,4 @@ interface LuminClient {
|
|
|
93
137
|
getAnonymousId(): Promise<string>;
|
|
94
138
|
}
|
|
95
139
|
|
|
96
|
-
export type { AppStateLike as A,
|
|
140
|
+
export type { AppStateLike as A, ErrorUtilsLike as E, InitOptions as I, LuminClient as L, WireEvent as W, AsyncStorageLike as a, EventType as b };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumin-monitor/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React Native SDK for Lumin: screen / track / identify with batched ingest.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lumin",
|
|
@@ -50,18 +50,15 @@
|
|
|
50
50
|
"files": [
|
|
51
51
|
"dist",
|
|
52
52
|
"LICENSE",
|
|
53
|
-
"README.md"
|
|
53
|
+
"README.md",
|
|
54
|
+
"CHANGELOG.md"
|
|
54
55
|
],
|
|
55
56
|
"peerDependencies": {
|
|
56
|
-
"@react-native-async-storage/async-storage": ">=1.21.0",
|
|
57
57
|
"@react-navigation/native": ">=6",
|
|
58
58
|
"react": ">=18",
|
|
59
59
|
"react-native": ">=0.72"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
|
-
"@react-native-async-storage/async-storage": {
|
|
63
|
-
"optional": true
|
|
64
|
-
},
|
|
65
62
|
"@react-navigation/native": {
|
|
66
63
|
"optional": true
|
|
67
64
|
}
|
package/dist/chunk-3RG5ZIWI.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
__require
|
|
10
|
-
};
|