@lumin-monitor/react-native 0.1.0 → 0.4.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 +139 -0
- package/README.md +105 -9
- package/dist/index.cjs +135 -29
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +134 -32
- 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-B6rqS9Vp.d.cts +169 -0
- package/dist/types-B6rqS9Vp.d.ts +169 -0
- package/package.json +3 -6
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/types-BeHbtR1J.d.cts +0 -96
- package/dist/types-BeHbtR1J.d.ts +0 -96
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, Platform as RNPlatform } 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 {
|
|
@@ -84,8 +70,14 @@ var DEFAULT_FLUSH_MS = 500;
|
|
|
84
70
|
var DEFAULT_SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
85
71
|
var SERVER_MAX_BATCH = 1e3;
|
|
86
72
|
var DEFAULT_ENDPOINT = "https://api.getlumin.dev";
|
|
73
|
+
var SDK_VERSION = "0.4.0";
|
|
87
74
|
var Client = class {
|
|
88
75
|
constructor(opts) {
|
|
76
|
+
this.prevErrorHandler = void 0;
|
|
77
|
+
this.errorHandlerInstalled = false;
|
|
78
|
+
// Dedup the same Error captured twice (e.g. via captureError + the global
|
|
79
|
+
// handler firing on the same throw). WeakSet so we don't pin GC.
|
|
80
|
+
this.recentErrors = /* @__PURE__ */ new WeakSet();
|
|
89
81
|
this.userId = null;
|
|
90
82
|
this.cachedIds = null;
|
|
91
83
|
// Pending events queued before ids hydrate. `user_id` is stamped at enqueue
|
|
@@ -108,11 +100,15 @@ var Client = class {
|
|
|
108
100
|
console.warn("[lumin] flush failed", { err, dropped });
|
|
109
101
|
});
|
|
110
102
|
this.fetchImpl = opts.fetch ?? fetch.bind(globalThis);
|
|
111
|
-
|
|
112
|
-
this.storage = resolvedStorage;
|
|
103
|
+
this.storage = opts.storage ?? memoryStorage();
|
|
113
104
|
this.appState = opts.appState === void 0 ? detectAppState() : opts.appState;
|
|
105
|
+
this.errorUtils = opts.errorUtils === void 0 ? detectErrorUtils() : opts.errorUtils;
|
|
106
|
+
this.clientHeader = buildClientHeader(opts.deviceInfo ?? null);
|
|
114
107
|
this.idsReady = this.hydrateIds();
|
|
115
108
|
this.installBackgroundFlush();
|
|
109
|
+
if (opts.captureUnhandledErrors !== false) {
|
|
110
|
+
this.installErrorHandler();
|
|
111
|
+
}
|
|
116
112
|
}
|
|
117
113
|
screen(name, properties) {
|
|
118
114
|
if (this.closed) return;
|
|
@@ -130,6 +126,22 @@ var Client = class {
|
|
|
130
126
|
}
|
|
131
127
|
this.enqueue({ type: "track", name, ...properties !== void 0 ? { properties } : {} });
|
|
132
128
|
}
|
|
129
|
+
captureError(err, properties) {
|
|
130
|
+
if (this.closed) return;
|
|
131
|
+
const e = normalizeError(err);
|
|
132
|
+
if (!e) return;
|
|
133
|
+
if (typeof err === "object" && err !== null) {
|
|
134
|
+
if (this.recentErrors.has(err)) return;
|
|
135
|
+
this.recentErrors.add(err);
|
|
136
|
+
}
|
|
137
|
+
this.enqueue({
|
|
138
|
+
type: "error",
|
|
139
|
+
...properties !== void 0 ? { properties } : {},
|
|
140
|
+
error_message: e.message,
|
|
141
|
+
...e.stack ? { error_stack: e.stack } : {},
|
|
142
|
+
...e.type ? { error_type: e.type } : {}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
133
145
|
identify(userId, traits) {
|
|
134
146
|
if (this.closed) return;
|
|
135
147
|
if (!userId) {
|
|
@@ -166,6 +178,7 @@ var Client = class {
|
|
|
166
178
|
if (this.closed) return;
|
|
167
179
|
this.closed = true;
|
|
168
180
|
this.removeBackgroundFlush();
|
|
181
|
+
this.restoreErrorHandler();
|
|
169
182
|
await this.flush();
|
|
170
183
|
await Promise.allSettled(this.inflight);
|
|
171
184
|
}
|
|
@@ -253,13 +266,15 @@ var Client = class {
|
|
|
253
266
|
return;
|
|
254
267
|
}
|
|
255
268
|
const body = JSON.stringify({ events: batch });
|
|
269
|
+
const headers = {
|
|
270
|
+
"Content-Type": "application/json",
|
|
271
|
+
Authorization: "Bearer " + this.apiKey
|
|
272
|
+
};
|
|
273
|
+
if (this.clientHeader) headers["X-Lumin-Client"] = this.clientHeader;
|
|
256
274
|
try {
|
|
257
275
|
const res = await this.fetchImpl(this.endpoint + "/v1/events", {
|
|
258
276
|
method: "POST",
|
|
259
|
-
headers
|
|
260
|
-
"Content-Type": "application/json",
|
|
261
|
-
Authorization: "Bearer " + this.apiKey
|
|
262
|
-
},
|
|
277
|
+
headers,
|
|
263
278
|
body
|
|
264
279
|
});
|
|
265
280
|
if (!res.ok) {
|
|
@@ -291,24 +306,110 @@ var Client = class {
|
|
|
291
306
|
this.appStateSub = null;
|
|
292
307
|
}
|
|
293
308
|
}
|
|
309
|
+
// --- error handler -----------------------------------------------------
|
|
310
|
+
/**
|
|
311
|
+
* Install a global JS error handler via ErrorUtils.setGlobalHandler that
|
|
312
|
+
* captures + chains to whatever handler was already in place. RN's
|
|
313
|
+
* red-box and any other observability tool that installed before us
|
|
314
|
+
* still fires — we do not swallow the throw.
|
|
315
|
+
*/
|
|
316
|
+
installErrorHandler() {
|
|
317
|
+
if (!this.errorUtils) return;
|
|
318
|
+
this.prevErrorHandler = this.errorUtils.getGlobalHandler?.();
|
|
319
|
+
const handler = (err, isFatal) => {
|
|
320
|
+
try {
|
|
321
|
+
this.captureError(err, isFatal ? { fatal: true } : void 0);
|
|
322
|
+
if (isFatal) void this.flush();
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
if (this.prevErrorHandler) {
|
|
326
|
+
this.prevErrorHandler(err, isFatal);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
this.errorUtils.setGlobalHandler(handler);
|
|
330
|
+
this.errorHandlerInstalled = true;
|
|
331
|
+
}
|
|
332
|
+
restoreErrorHandler() {
|
|
333
|
+
if (!this.errorHandlerInstalled || !this.errorUtils) return;
|
|
334
|
+
this.errorUtils.setGlobalHandler(
|
|
335
|
+
this.prevErrorHandler ?? defaultNoopErrorHandler
|
|
336
|
+
);
|
|
337
|
+
this.errorHandlerInstalled = false;
|
|
338
|
+
}
|
|
294
339
|
};
|
|
340
|
+
function defaultNoopErrorHandler() {
|
|
341
|
+
}
|
|
342
|
+
function detectErrorUtils() {
|
|
343
|
+
const g = globalThis;
|
|
344
|
+
const eu = g.ErrorUtils;
|
|
345
|
+
if (eu && typeof eu.setGlobalHandler === "function") {
|
|
346
|
+
return eu;
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
function normalizeError(raw) {
|
|
351
|
+
if (raw == null) return null;
|
|
352
|
+
if (raw instanceof Error) {
|
|
353
|
+
return {
|
|
354
|
+
message: raw.message || raw.name || "Error",
|
|
355
|
+
...raw.stack ? { stack: raw.stack } : {},
|
|
356
|
+
type: raw.name || raw.constructor?.name
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (typeof raw === "string") {
|
|
360
|
+
return { message: raw };
|
|
361
|
+
}
|
|
362
|
+
if (typeof raw === "object") {
|
|
363
|
+
const obj = raw;
|
|
364
|
+
const message = typeof obj.message === "string" ? obj.message : safeStringify(raw);
|
|
365
|
+
return {
|
|
366
|
+
message,
|
|
367
|
+
...typeof obj.stack === "string" ? { stack: obj.stack } : {},
|
|
368
|
+
...typeof obj.name === "string" ? { type: obj.name } : {}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
return { message: String(raw) };
|
|
372
|
+
}
|
|
373
|
+
function safeStringify(v) {
|
|
374
|
+
try {
|
|
375
|
+
return JSON.stringify(v) ?? String(v);
|
|
376
|
+
} catch {
|
|
377
|
+
return String(v);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
295
380
|
function clampBatch(n) {
|
|
296
381
|
if (!Number.isFinite(n) || n < 1) return 1;
|
|
297
382
|
if (n > SERVER_MAX_BATCH) return SERVER_MAX_BATCH;
|
|
298
383
|
return Math.floor(n);
|
|
299
384
|
}
|
|
300
|
-
function
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
385
|
+
function buildClientHeader(deviceInfo) {
|
|
386
|
+
const parts = [`sdk=rn/${SDK_VERSION}`];
|
|
387
|
+
const platform = RNPlatform;
|
|
388
|
+
const osRaw = platform?.OS;
|
|
389
|
+
if (typeof osRaw === "string" && osRaw) {
|
|
390
|
+
parts.push(`os=${encodeURIComponent(osRaw)}`);
|
|
391
|
+
} else {
|
|
392
|
+
return "";
|
|
393
|
+
}
|
|
394
|
+
const versionRaw = platform?.Version;
|
|
395
|
+
if (versionRaw !== void 0 && versionRaw !== null && versionRaw !== "") {
|
|
396
|
+
parts.push(`os_version=${encodeURIComponent(String(versionRaw))}`);
|
|
397
|
+
}
|
|
398
|
+
if (deviceInfo && typeof deviceInfo.getModel === "function") {
|
|
399
|
+
try {
|
|
400
|
+
const model = deviceInfo.getModel();
|
|
401
|
+
if (model) parts.push(`device=${encodeURIComponent(model)}`);
|
|
402
|
+
} catch {
|
|
307
403
|
}
|
|
308
|
-
return null;
|
|
309
|
-
} catch {
|
|
310
|
-
return null;
|
|
311
404
|
}
|
|
405
|
+
return parts.join("; ");
|
|
406
|
+
}
|
|
407
|
+
function detectAppState() {
|
|
408
|
+
const candidate = RNAppState;
|
|
409
|
+
if (candidate && typeof candidate.addEventListener === "function") {
|
|
410
|
+
return candidate;
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
312
413
|
}
|
|
313
414
|
function validateEndpoint(raw) {
|
|
314
415
|
let url;
|
|
@@ -346,6 +447,7 @@ function init(opts) {
|
|
|
346
447
|
screen: c.screen.bind(c),
|
|
347
448
|
track: c.track.bind(c),
|
|
348
449
|
identify: c.identify.bind(c),
|
|
450
|
+
captureError: c.captureError.bind(c),
|
|
349
451
|
flush: c.flush.bind(c),
|
|
350
452
|
close: c.close.bind(c),
|
|
351
453
|
getSessionId: c.getSessionId.bind(c),
|
package/dist/react-navigation.js
CHANGED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
type EventType = "page" | "track" | "identify" | "error";
|
|
2
|
+
interface WireEvent {
|
|
3
|
+
ts?: number;
|
|
4
|
+
session_id: string;
|
|
5
|
+
anonymous_id?: string;
|
|
6
|
+
user_id?: string;
|
|
7
|
+
type: EventType;
|
|
8
|
+
name?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
referrer?: string;
|
|
11
|
+
properties?: Record<string, unknown>;
|
|
12
|
+
trace_id?: string;
|
|
13
|
+
error_message?: string;
|
|
14
|
+
error_stack?: string;
|
|
15
|
+
error_type?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Minimal AsyncStorage shape the SDK actually consumes. Matches both
|
|
19
|
+
* @react-native-async-storage/async-storage (community) and the legacy
|
|
20
|
+
* react-native built-in. Apps that ship a different storage backend can
|
|
21
|
+
* implement this directly.
|
|
22
|
+
*/
|
|
23
|
+
interface AsyncStorageLike {
|
|
24
|
+
getItem(key: string): Promise<string | null>;
|
|
25
|
+
setItem(key: string, value: string): Promise<void>;
|
|
26
|
+
removeItem?(key: string): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
interface InitOptions {
|
|
29
|
+
/** API key minted in the Lumin UI (settings → API keys). Must be `lmn_pub_*`. */
|
|
30
|
+
apiKey: string;
|
|
31
|
+
/**
|
|
32
|
+
* Base URL of the Lumin ingest endpoint. Defaults to
|
|
33
|
+
* `https://api.getlumin.dev`. Override only for local development against
|
|
34
|
+
* a Lumin instance you are running yourself or for a customer-side
|
|
35
|
+
* same-origin proxy.
|
|
36
|
+
*
|
|
37
|
+
* Validation: must parse as a valid URL with no path beyond `/`. `https://`
|
|
38
|
+
* is required except when the hostname is `localhost`, `127.0.0.1`, or a
|
|
39
|
+
* `.localhost` subdomain (where `http://` is allowed). Invalid shapes
|
|
40
|
+
* throw synchronously from `init()` so misconfigurations surface at boot.
|
|
41
|
+
*/
|
|
42
|
+
endpoint?: string;
|
|
43
|
+
/** Max events to buffer before forcing a flush. Default 50. */
|
|
44
|
+
batchSize?: number;
|
|
45
|
+
/** Max ms between flushes. Default 500. */
|
|
46
|
+
flushIntervalMs?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Session idle timeout. A new session_id is minted on the next event if
|
|
49
|
+
* the time since the last event exceeds this value. Default 30 min
|
|
50
|
+
* (matches the industry-standard analytics convention).
|
|
51
|
+
*/
|
|
52
|
+
sessionIdleMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Optional error sink. Called with the failed batch + reason so apps can
|
|
55
|
+
* surface drops. Default: console.warn.
|
|
56
|
+
*/
|
|
57
|
+
onError?: (err: unknown, dropped: number) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Override the global fetch. Tests use this to capture requests without
|
|
60
|
+
* spinning up a network stub.
|
|
61
|
+
*/
|
|
62
|
+
fetch?: typeof fetch;
|
|
63
|
+
/**
|
|
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
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
storage?: AsyncStorageLike | null;
|
|
80
|
+
/**
|
|
81
|
+
* Override the React Native AppState module. Defaults to `react-native`'s
|
|
82
|
+
* AppState; tests pass a stub.
|
|
83
|
+
*/
|
|
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
|
+
* Optional `react-native-device-info` instance (or any object satisfying
|
|
104
|
+
* `DeviceInfoLike`). When passed, the SDK adds `device=<model>` to the
|
|
105
|
+
* `X-Lumin-Client` header so the server can surface the device model in
|
|
106
|
+
* the sessions UI.
|
|
107
|
+
*
|
|
108
|
+
* Not auto-detected — passing it is opt-in, matching the `storage`
|
|
109
|
+
* pattern. The SDK never `require()`s the package, which keeps Metro
|
|
110
|
+
* bundling clean.
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* import DeviceInfo from "react-native-device-info";
|
|
114
|
+
* init({ apiKey, deviceInfo: DeviceInfo });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
deviceInfo?: DeviceInfoLike | null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Minimal shape of React Native's ErrorUtils. The SDK uses these three
|
|
121
|
+
* methods to chain into any previously installed handler.
|
|
122
|
+
*/
|
|
123
|
+
interface ErrorUtilsLike {
|
|
124
|
+
setGlobalHandler(handler: (err: unknown, isFatal?: boolean) => void): void;
|
|
125
|
+
getGlobalHandler?(): ((err: unknown, isFatal?: boolean) => void) | undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Minimal shape the SDK consumes from `react-native-device-info` (or any
|
|
129
|
+
* compatible shim). Structural so we don't pull react-native-device-info
|
|
130
|
+
* types into the SDK's public surface — the app already has them. Only
|
|
131
|
+
* `getModel` is required; future fields can be added the same way.
|
|
132
|
+
*
|
|
133
|
+
* Passed via `init({ deviceInfo: DeviceInfo })`. If omitted, the SDK still
|
|
134
|
+
* reports `os` + `os_version` from React Native's `Platform` constants —
|
|
135
|
+
* device model is the only thing that needs the optional peer dep.
|
|
136
|
+
*/
|
|
137
|
+
interface DeviceInfoLike {
|
|
138
|
+
getModel(): string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Minimal AppState shape the SDK consumes (subset of react-native's AppState).
|
|
142
|
+
* The SDK auto-detects `react-native` at runtime; pass this only for tests or
|
|
143
|
+
* non-RN environments.
|
|
144
|
+
*/
|
|
145
|
+
interface AppStateLike {
|
|
146
|
+
addEventListener(type: "change", listener: (state: string) => void): {
|
|
147
|
+
remove: () => void;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
interface LuminClient {
|
|
151
|
+
screen(name?: string, properties?: Record<string, unknown>): void;
|
|
152
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
153
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
154
|
+
/**
|
|
155
|
+
* Capture an error. Accepts a real Error (preferred — preserves stack +
|
|
156
|
+
* constructor name as error_type), or any value that will be stringified
|
|
157
|
+
* for error_message. `properties` is forwarded verbatim so callers can
|
|
158
|
+
* attach context (route, user action, request id).
|
|
159
|
+
*/
|
|
160
|
+
captureError(err: unknown, properties?: Record<string, unknown>): void;
|
|
161
|
+
flush(): Promise<void>;
|
|
162
|
+
close(): Promise<void>;
|
|
163
|
+
/** Current session id. Resolves once AsyncStorage hydration finishes. */
|
|
164
|
+
getSessionId(): Promise<string>;
|
|
165
|
+
/** Current anonymous id. Resolves once AsyncStorage hydration finishes. */
|
|
166
|
+
getAnonymousId(): Promise<string>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type { AppStateLike as A, DeviceInfoLike as D, ErrorUtilsLike as E, InitOptions as I, LuminClient as L, WireEvent as W, AsyncStorageLike as a, EventType as b };
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
type EventType = "page" | "track" | "identify" | "error";
|
|
2
|
+
interface WireEvent {
|
|
3
|
+
ts?: number;
|
|
4
|
+
session_id: string;
|
|
5
|
+
anonymous_id?: string;
|
|
6
|
+
user_id?: string;
|
|
7
|
+
type: EventType;
|
|
8
|
+
name?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
referrer?: string;
|
|
11
|
+
properties?: Record<string, unknown>;
|
|
12
|
+
trace_id?: string;
|
|
13
|
+
error_message?: string;
|
|
14
|
+
error_stack?: string;
|
|
15
|
+
error_type?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Minimal AsyncStorage shape the SDK actually consumes. Matches both
|
|
19
|
+
* @react-native-async-storage/async-storage (community) and the legacy
|
|
20
|
+
* react-native built-in. Apps that ship a different storage backend can
|
|
21
|
+
* implement this directly.
|
|
22
|
+
*/
|
|
23
|
+
interface AsyncStorageLike {
|
|
24
|
+
getItem(key: string): Promise<string | null>;
|
|
25
|
+
setItem(key: string, value: string): Promise<void>;
|
|
26
|
+
removeItem?(key: string): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
interface InitOptions {
|
|
29
|
+
/** API key minted in the Lumin UI (settings → API keys). Must be `lmn_pub_*`. */
|
|
30
|
+
apiKey: string;
|
|
31
|
+
/**
|
|
32
|
+
* Base URL of the Lumin ingest endpoint. Defaults to
|
|
33
|
+
* `https://api.getlumin.dev`. Override only for local development against
|
|
34
|
+
* a Lumin instance you are running yourself or for a customer-side
|
|
35
|
+
* same-origin proxy.
|
|
36
|
+
*
|
|
37
|
+
* Validation: must parse as a valid URL with no path beyond `/`. `https://`
|
|
38
|
+
* is required except when the hostname is `localhost`, `127.0.0.1`, or a
|
|
39
|
+
* `.localhost` subdomain (where `http://` is allowed). Invalid shapes
|
|
40
|
+
* throw synchronously from `init()` so misconfigurations surface at boot.
|
|
41
|
+
*/
|
|
42
|
+
endpoint?: string;
|
|
43
|
+
/** Max events to buffer before forcing a flush. Default 50. */
|
|
44
|
+
batchSize?: number;
|
|
45
|
+
/** Max ms between flushes. Default 500. */
|
|
46
|
+
flushIntervalMs?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Session idle timeout. A new session_id is minted on the next event if
|
|
49
|
+
* the time since the last event exceeds this value. Default 30 min
|
|
50
|
+
* (matches the industry-standard analytics convention).
|
|
51
|
+
*/
|
|
52
|
+
sessionIdleMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Optional error sink. Called with the failed batch + reason so apps can
|
|
55
|
+
* surface drops. Default: console.warn.
|
|
56
|
+
*/
|
|
57
|
+
onError?: (err: unknown, dropped: number) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Override the global fetch. Tests use this to capture requests without
|
|
60
|
+
* spinning up a network stub.
|
|
61
|
+
*/
|
|
62
|
+
fetch?: typeof fetch;
|
|
63
|
+
/**
|
|
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
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
storage?: AsyncStorageLike | null;
|
|
80
|
+
/**
|
|
81
|
+
* Override the React Native AppState module. Defaults to `react-native`'s
|
|
82
|
+
* AppState; tests pass a stub.
|
|
83
|
+
*/
|
|
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
|
+
* Optional `react-native-device-info` instance (or any object satisfying
|
|
104
|
+
* `DeviceInfoLike`). When passed, the SDK adds `device=<model>` to the
|
|
105
|
+
* `X-Lumin-Client` header so the server can surface the device model in
|
|
106
|
+
* the sessions UI.
|
|
107
|
+
*
|
|
108
|
+
* Not auto-detected — passing it is opt-in, matching the `storage`
|
|
109
|
+
* pattern. The SDK never `require()`s the package, which keeps Metro
|
|
110
|
+
* bundling clean.
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* import DeviceInfo from "react-native-device-info";
|
|
114
|
+
* init({ apiKey, deviceInfo: DeviceInfo });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
deviceInfo?: DeviceInfoLike | null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Minimal shape of React Native's ErrorUtils. The SDK uses these three
|
|
121
|
+
* methods to chain into any previously installed handler.
|
|
122
|
+
*/
|
|
123
|
+
interface ErrorUtilsLike {
|
|
124
|
+
setGlobalHandler(handler: (err: unknown, isFatal?: boolean) => void): void;
|
|
125
|
+
getGlobalHandler?(): ((err: unknown, isFatal?: boolean) => void) | undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Minimal shape the SDK consumes from `react-native-device-info` (or any
|
|
129
|
+
* compatible shim). Structural so we don't pull react-native-device-info
|
|
130
|
+
* types into the SDK's public surface — the app already has them. Only
|
|
131
|
+
* `getModel` is required; future fields can be added the same way.
|
|
132
|
+
*
|
|
133
|
+
* Passed via `init({ deviceInfo: DeviceInfo })`. If omitted, the SDK still
|
|
134
|
+
* reports `os` + `os_version` from React Native's `Platform` constants —
|
|
135
|
+
* device model is the only thing that needs the optional peer dep.
|
|
136
|
+
*/
|
|
137
|
+
interface DeviceInfoLike {
|
|
138
|
+
getModel(): string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Minimal AppState shape the SDK consumes (subset of react-native's AppState).
|
|
142
|
+
* The SDK auto-detects `react-native` at runtime; pass this only for tests or
|
|
143
|
+
* non-RN environments.
|
|
144
|
+
*/
|
|
145
|
+
interface AppStateLike {
|
|
146
|
+
addEventListener(type: "change", listener: (state: string) => void): {
|
|
147
|
+
remove: () => void;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
interface LuminClient {
|
|
151
|
+
screen(name?: string, properties?: Record<string, unknown>): void;
|
|
152
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
153
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
154
|
+
/**
|
|
155
|
+
* Capture an error. Accepts a real Error (preferred — preserves stack +
|
|
156
|
+
* constructor name as error_type), or any value that will be stringified
|
|
157
|
+
* for error_message. `properties` is forwarded verbatim so callers can
|
|
158
|
+
* attach context (route, user action, request id).
|
|
159
|
+
*/
|
|
160
|
+
captureError(err: unknown, properties?: Record<string, unknown>): void;
|
|
161
|
+
flush(): Promise<void>;
|
|
162
|
+
close(): Promise<void>;
|
|
163
|
+
/** Current session id. Resolves once AsyncStorage hydration finishes. */
|
|
164
|
+
getSessionId(): Promise<string>;
|
|
165
|
+
/** Current anonymous id. Resolves once AsyncStorage hydration finishes. */
|
|
166
|
+
getAnonymousId(): Promise<string>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type { AppStateLike as A, DeviceInfoLike as D, 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.4.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
|
-
};
|