@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/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
- import {
2
- __require
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
- const resolvedStorage = opts.storage === null ? memoryStorage() : opts.storage ?? detectAsyncStorage() ?? memoryStorage();
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 detectAppState() {
301
- try {
302
- if (typeof __require === "undefined") return null;
303
- const rn = __require("react-native");
304
- const candidate = rn?.AppState;
305
- if (candidate && typeof candidate.addEventListener === "function") {
306
- return candidate;
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),
@@ -1,4 +1,4 @@
1
- import { L as LuminClient } from './types-BeHbtR1J.cjs';
1
+ import { L as LuminClient } from './types-B6rqS9Vp.cjs';
2
2
 
3
3
  /**
4
4
  * Subset of @react-navigation/native's NavigationContainerRef the hook needs.
@@ -1,4 +1,4 @@
1
- import { L as LuminClient } from './types-BeHbtR1J.js';
1
+ import { L as LuminClient } from './types-B6rqS9Vp.js';
2
2
 
3
3
  /**
4
4
  * Subset of @react-navigation/native's NavigationContainerRef the hook needs.
@@ -1,5 +1,3 @@
1
- import "./chunk-3RG5ZIWI.js";
2
-
3
1
  // src/react-navigation.ts
4
2
  import { useEffect, useRef } from "react";
5
3
  function useLuminScreenviews(client, navigationRef) {
@@ -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.1.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
  }
@@ -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
- };