eventlog-rn 0.3.1

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 ADDED
@@ -0,0 +1,1170 @@
1
+ 'use strict';
2
+
3
+ var MMKVModule = require('react-native-mmkv');
4
+ var React5 = require('react');
5
+ var reactNative = require('react-native');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var MMKVModule__namespace = /*#__PURE__*/_interopNamespace(MMKVModule);
28
+ var React5__default = /*#__PURE__*/_interopDefault(React5);
29
+
30
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
31
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
32
+ }) : x)(function(x) {
33
+ if (typeof require !== "undefined") return require.apply(this, arguments);
34
+ throw Error('Dynamic require of "' + x + '" is not supported');
35
+ });
36
+
37
+ // src/core/utils.ts
38
+ var generateUUID = () => {
39
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
40
+ const r = Math.random() * 16 | 0;
41
+ const v = c === "x" ? r : r & 3 | 8;
42
+ return v.toString(16);
43
+ });
44
+ };
45
+ var getTimestamp = () => Date.now();
46
+ var getTimestampISO = () => (/* @__PURE__ */ new Date()).toISOString();
47
+ var getTimezone = () => {
48
+ try {
49
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
50
+ } catch {
51
+ return "UTC";
52
+ }
53
+ };
54
+ var debounce = (func, wait) => {
55
+ let timeout = null;
56
+ return (...args) => {
57
+ if (timeout !== null) {
58
+ clearTimeout(timeout);
59
+ }
60
+ timeout = setTimeout(() => {
61
+ func(...args);
62
+ }, wait);
63
+ };
64
+ };
65
+
66
+ // src/core/events.ts
67
+ var createEvent = (session, context, category, payload) => {
68
+ return {
69
+ eventId: generateUUID(),
70
+ sessionId: session.sessionId,
71
+ seq: session.seq,
72
+ timestamp: getTimestamp(),
73
+ timestampISO: getTimestampISO(),
74
+ timezone: getTimezone(),
75
+ category,
76
+ payload,
77
+ context
78
+ };
79
+ };
80
+ var createScreenPayload = (name, params) => {
81
+ return { name, params };
82
+ };
83
+ var createActionPayload = (name, data) => {
84
+ return { name, data };
85
+ };
86
+ var createLogPayload = (level, message, data) => {
87
+ return { level, message, data };
88
+ };
89
+ var createErrorPayload = (error, context) => {
90
+ const err = error;
91
+ const payload = {
92
+ message: err.message ?? String(error),
93
+ ...err.stack !== void 0 && { stack: err.stack },
94
+ ...context !== void 0 && { context }
95
+ };
96
+ return payload;
97
+ };
98
+ var incrementSeq = (session) => {
99
+ return {
100
+ ...session,
101
+ seq: session.seq + 1
102
+ };
103
+ };
104
+ var updateSessionActivity = (session) => {
105
+ return {
106
+ ...session,
107
+ lastActivityTime: getTimestamp()
108
+ };
109
+ };
110
+ var createSession = (startType) => {
111
+ const now = getTimestamp();
112
+ return {
113
+ sessionId: generateUUID(),
114
+ sessionStart: now,
115
+ startType,
116
+ seq: 0,
117
+ lastActivityTime: now
118
+ };
119
+ };
120
+ var SENSITIVE_KEYS = [
121
+ "password",
122
+ "token",
123
+ "authorization",
124
+ "secret",
125
+ "apiKey",
126
+ "api_key",
127
+ "accessToken",
128
+ "access_token",
129
+ "refreshToken",
130
+ "refresh_token"
131
+ ];
132
+ var MAX_DEPTH = 10;
133
+ var MAX_SIZE = 100 * 1024;
134
+ var isSensitiveKey = (key) => {
135
+ const lowerKey = key.toLowerCase();
136
+ return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive));
137
+ };
138
+ var sanitizeValue = (value, depth) => {
139
+ if (depth > MAX_DEPTH) {
140
+ return { _maxDepthExceeded: true };
141
+ }
142
+ if (value === null || value === void 0) {
143
+ return value;
144
+ }
145
+ if (typeof value !== "object") {
146
+ return value;
147
+ }
148
+ if (Array.isArray(value)) {
149
+ return value.map((item) => sanitizeValue(item, depth + 1));
150
+ }
151
+ const sanitized = {};
152
+ for (const key in value) {
153
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
154
+ const val = value[key];
155
+ sanitized[key] = isSensitiveKey(key) ? "***REDACTED***" : sanitizeValue(val, depth + 1);
156
+ }
157
+ }
158
+ return sanitized;
159
+ };
160
+ var sanitizeEvent = (event) => {
161
+ const sanitizedPayload = sanitizeValue(event.payload, 0);
162
+ const sanitizedContext = sanitizeValue(event.context, 0);
163
+ const size = JSON.stringify(event).length;
164
+ if (size > MAX_SIZE) {
165
+ return {
166
+ ...event,
167
+ payload: { _truncated: true, _originalSize: size },
168
+ context: event.context
169
+ };
170
+ }
171
+ return {
172
+ ...event,
173
+ payload: sanitizedPayload,
174
+ context: sanitizedContext
175
+ };
176
+ };
177
+ var exportEventsAsJSONL = (events, mode) => {
178
+ const filtered = mode === "repro" ? getReproEvents(events) : events;
179
+ return filtered.map((event) => JSON.stringify(event)).join("\n");
180
+ };
181
+ var getReproEvents = (events) => {
182
+ if (events.length === 0) {
183
+ return [];
184
+ }
185
+ const lastEvent = events[events.length - 1];
186
+ if (lastEvent === void 0) {
187
+ return [];
188
+ }
189
+ const currentSessionId = lastEvent.sessionId;
190
+ const sessionIds = Array.from(new Set(events.map((e) => e.sessionId)));
191
+ const currentSessionIndex = sessionIds.indexOf(currentSessionId);
192
+ const previousSessionId = currentSessionIndex > 0 ? sessionIds[currentSessionIndex - 1] ?? null : null;
193
+ const errors = events.filter((e) => e.category === "error").slice(-10);
194
+ const sessionEvents = events.filter(
195
+ (e) => e.sessionId === currentSessionId || e.sessionId === previousSessionId
196
+ );
197
+ const combined = [...sessionEvents];
198
+ errors.forEach((error) => {
199
+ if (!combined.find((e) => e.eventId === error.eventId)) {
200
+ combined.push(error);
201
+ }
202
+ });
203
+ return combined.sort((a, b) => a.timestamp - b.timestamp);
204
+ };
205
+
206
+ // src/core/buffer.ts
207
+ var createRingBuffer = (maxSize) => {
208
+ return {
209
+ buffer: new Array(maxSize).fill(void 0),
210
+ writeIndex: 0,
211
+ size: 0,
212
+ maxSize
213
+ };
214
+ };
215
+ var pushEvent = (state, event) => {
216
+ const newBuffer = [...state.buffer];
217
+ newBuffer[state.writeIndex] = event;
218
+ return {
219
+ ...state,
220
+ buffer: newBuffer,
221
+ writeIndex: (state.writeIndex + 1) % state.maxSize,
222
+ size: Math.min(state.size + 1, state.maxSize)
223
+ };
224
+ };
225
+ var toArray = (state) => {
226
+ if (state.size < state.maxSize) {
227
+ return state.buffer.slice(0, state.size).filter((e) => e !== void 0);
228
+ }
229
+ const afterWrite = state.buffer.slice(state.writeIndex);
230
+ const beforeWrite = state.buffer.slice(0, state.writeIndex);
231
+ return [...afterWrite, ...beforeWrite].filter((e) => e !== void 0);
232
+ };
233
+ var clearBuffer = (state) => {
234
+ return createRingBuffer(state.maxSize);
235
+ };
236
+
237
+ // src/core/query.ts
238
+ var queryEvents = (events, query) => {
239
+ let filtered = events;
240
+ if (query.category && query.category.length > 0) {
241
+ filtered = filtered.filter((e) => {
242
+ const categories = query.category;
243
+ return categories ? categories.includes(e.category) : true;
244
+ });
245
+ }
246
+ if (query.timeRange) {
247
+ filtered = filtered.filter(
248
+ (e) => e.timestamp >= query.timeRange.start && e.timestamp <= query.timeRange.end
249
+ );
250
+ }
251
+ if (query.sessionId) {
252
+ filtered = filtered.filter((e) => e.sessionId === query.sessionId);
253
+ }
254
+ if (query.search) {
255
+ const searchLower = query.search.toLowerCase();
256
+ filtered = filtered.filter((e) => {
257
+ const payloadStr = JSON.stringify(e.payload).toLowerCase();
258
+ return payloadStr.includes(searchLower);
259
+ });
260
+ }
261
+ if (query.limit && query.limit > 0) {
262
+ filtered = filtered.slice(-query.limit);
263
+ }
264
+ return filtered;
265
+ };
266
+
267
+ // src/core/network.ts
268
+ var originalFetch = global.fetch;
269
+ var originalXMLHttpRequest = global.XMLHttpRequest;
270
+ var interceptFetch = (eventLog2, config) => {
271
+ if (!config.interceptFetch) return;
272
+ global.fetch = async (input, init) => {
273
+ const startTime = Date.now();
274
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
275
+ const method = (init?.method ?? "GET").toUpperCase();
276
+ try {
277
+ const response = await originalFetch(input, init);
278
+ const endTime = Date.now();
279
+ const duration = endTime - startTime;
280
+ const clone = response.clone();
281
+ let responseBody;
282
+ if (config.logResponseBody) {
283
+ try {
284
+ const text = await clone.text();
285
+ if (!config.maxBodySize || text.length <= config.maxBodySize) {
286
+ try {
287
+ responseBody = JSON.parse(text);
288
+ } catch {
289
+ responseBody = text;
290
+ }
291
+ } else {
292
+ responseBody = `[Body too large: ${text.length} bytes]`;
293
+ }
294
+ } catch {
295
+ responseBody = "[Failed to read response body]";
296
+ }
297
+ }
298
+ const payload = {
299
+ url,
300
+ method,
301
+ status: response.status,
302
+ duration,
303
+ responseHeaders: config.redactHeaders ? redactHeaders(response.headers, config.redactHeaders) : headersToObject(response.headers),
304
+ responseBody
305
+ };
306
+ eventLog2.network(payload);
307
+ return response;
308
+ } catch (error) {
309
+ const endTime = Date.now();
310
+ eventLog2.error(error, {
311
+ url,
312
+ method,
313
+ duration: endTime - startTime,
314
+ category: "network"
315
+ // Context hack
316
+ });
317
+ throw error;
318
+ }
319
+ };
320
+ };
321
+ var interceptXHR = (eventLog2, config) => {
322
+ if (config.interceptAxios === false) return;
323
+ const XHR = originalXMLHttpRequest;
324
+ global.XMLHttpRequest = function() {
325
+ const xhr = new XHR();
326
+ const startTime = Date.now();
327
+ let method = "GET";
328
+ let url = "";
329
+ const originalOpen = xhr.open;
330
+ const originalSend = xhr.send;
331
+ xhr.open = function(m, u, ...args) {
332
+ method = m.toUpperCase();
333
+ url = u;
334
+ return originalOpen.apply(this, [m, u, ...args]);
335
+ };
336
+ xhr.send = function(body) {
337
+ xhr.addEventListener("loadend", () => {
338
+ const duration = Date.now() - startTime;
339
+ const responseHeadersString = xhr.getAllResponseHeaders();
340
+ const responseHeaders = parseXHRHeaders(responseHeadersString);
341
+ let responseBody;
342
+ if (config.logResponseBody) {
343
+ try {
344
+ if (xhr.responseType === "" || xhr.responseType === "text") {
345
+ responseBody = xhr.responseText;
346
+ if (responseBody && typeof responseBody === "string" && (!config.maxBodySize || responseBody.length <= config.maxBodySize)) {
347
+ try {
348
+ responseBody = JSON.parse(responseBody);
349
+ } catch {
350
+ }
351
+ }
352
+ }
353
+ } catch {
354
+ }
355
+ }
356
+ const payload = {
357
+ url,
358
+ method,
359
+ status: xhr.status,
360
+ duration,
361
+ responseHeaders: config.redactHeaders ? redactHeaders(responseHeaders, config.redactHeaders) : responseHeaders,
362
+ responseBody
363
+ };
364
+ if (typeof eventLog2.network === "function") {
365
+ eventLog2.network(payload);
366
+ }
367
+ });
368
+ return originalSend.apply(this, [body]);
369
+ };
370
+ return xhr;
371
+ };
372
+ global.XMLHttpRequest.prototype = XHR.prototype;
373
+ Object.assign(global.XMLHttpRequest, XHR);
374
+ };
375
+ var headersToObject = (headers) => {
376
+ const obj = {};
377
+ headers.forEach((value, key) => {
378
+ obj[key] = value;
379
+ });
380
+ return obj;
381
+ };
382
+ var redactHeaders = (headers, keysToRedact) => {
383
+ const obj = headers instanceof Headers ? headersToObject(headers) : headers;
384
+ const redacted = { ...obj };
385
+ keysToRedact.forEach((key) => {
386
+ const lowerKey = key.toLowerCase();
387
+ Object.keys(redacted).forEach((h) => {
388
+ if (h.toLowerCase() === lowerKey) {
389
+ redacted[h] = "***REDACTED***";
390
+ }
391
+ });
392
+ });
393
+ return redacted;
394
+ };
395
+ var parseXHRHeaders = (headers) => {
396
+ if (!headers) return {};
397
+ const result = {};
398
+ const pairs = headers.trim().split(/[\r\n]+/);
399
+ pairs.forEach((line) => {
400
+ const parts = line.split(": ");
401
+ if (parts.length >= 2) {
402
+ const key = parts.shift();
403
+ const value = parts.join(": ");
404
+ result[key] = value;
405
+ }
406
+ });
407
+ return result;
408
+ };
409
+ var enableNetworkInterception = (eventLog2, config) => {
410
+ if (!config || false) return;
411
+ restoreNetworkInterception();
412
+ if (config.interceptFetch !== false) {
413
+ interceptFetch(eventLog2, config);
414
+ }
415
+ if (config.interceptAxios !== false) {
416
+ interceptXHR(eventLog2, config);
417
+ }
418
+ };
419
+ var restoreNetworkInterception = () => {
420
+ global.fetch = originalFetch;
421
+ global.XMLHttpRequest = originalXMLHttpRequest;
422
+ };
423
+
424
+ // src/core/errors.ts
425
+ var enableGlobalErrorLogging = (eventLog2, config) => {
426
+ if (!config || config.enabled === false) return;
427
+ try {
428
+ if (global.ErrorUtils) {
429
+ const originalHandler = global.ErrorUtils.getGlobalHandler();
430
+ global.ErrorUtils.setGlobalHandler((error, isFatal) => {
431
+ eventLog2.error(error, {
432
+ isFatal,
433
+ origin: "global_handler",
434
+ timestamp: Date.now()
435
+ });
436
+ if (originalHandler) {
437
+ originalHandler(error, isFatal);
438
+ }
439
+ });
440
+ }
441
+ } catch (e) {
442
+ console.warn("[EventLog] Failed to setup ErrorUtils handler", e);
443
+ }
444
+ const rejectionHandler = (event) => {
445
+ const reason = event?.reason;
446
+ if (reason) {
447
+ eventLog2.error(reason, {
448
+ origin: "unhandled_rejection",
449
+ isFatal: false
450
+ });
451
+ }
452
+ };
453
+ if (typeof window !== "undefined" && window.addEventListener) {
454
+ window.addEventListener("unhandledrejection", rejectionHandler);
455
+ }
456
+ };
457
+ var mmkv;
458
+ try {
459
+ const m = MMKVModule__namespace?.default ? MMKVModule__namespace.default : MMKVModule__namespace;
460
+ if (typeof m.createMMKV === "function") {
461
+ mmkv = m.createMMKV({ id: "eventlog-rn" });
462
+ } else if (typeof m.MMKV === "function") {
463
+ mmkv = new m.MMKV({ id: "eventlog-rn" });
464
+ } else if (typeof m === "function") {
465
+ mmkv = new m({ id: "eventlog-rn" });
466
+ } else {
467
+ throw new Error("MMKV module found but no valid constructor (createMMKV or class MMKV) found.");
468
+ }
469
+ } catch (error) {
470
+ const errorMessage = error instanceof Error ? error.message : String(error);
471
+ throw new Error(
472
+ `[EventLog] Failed to initialize MMKV: ${errorMessage}.`
473
+ );
474
+ }
475
+ var internalStorage = {
476
+ setItem: async (key, value) => {
477
+ mmkv.set(key, value);
478
+ },
479
+ getItem: async (key) => {
480
+ return mmkv.getString(key) ?? null;
481
+ },
482
+ removeItem: async (key) => {
483
+ if (typeof mmkv.remove === "function") {
484
+ mmkv.remove(key);
485
+ } else {
486
+ mmkv.delete(key);
487
+ }
488
+ }
489
+ };
490
+
491
+ // src/core/eventlog.ts
492
+ var STORAGE_KEYS = {
493
+ SESSION: "eventlog-rn/session",
494
+ EVENTS: "eventlog-rn/events"
495
+ };
496
+ var createEventLog = (config = {}) => {
497
+ let state = null;
498
+ let initPromise = null;
499
+ let preInitQueue = [];
500
+ const loadSession = async (storage) => {
501
+ try {
502
+ const data = await storage.getItem(STORAGE_KEYS.SESSION);
503
+ if (!data) {
504
+ return null;
505
+ }
506
+ return JSON.parse(data);
507
+ } catch {
508
+ return null;
509
+ }
510
+ };
511
+ const saveSession = async (storage, session) => {
512
+ try {
513
+ await storage.setItem(STORAGE_KEYS.SESSION, JSON.stringify(session));
514
+ } catch (error) {
515
+ console.warn("[EventLog] Failed to save session:", error);
516
+ }
517
+ };
518
+ const loadEvents = async (storage) => {
519
+ try {
520
+ const data = await storage.getItem(STORAGE_KEYS.EVENTS);
521
+ if (!data) {
522
+ return [];
523
+ }
524
+ return JSON.parse(data);
525
+ } catch {
526
+ return [];
527
+ }
528
+ };
529
+ const saveEventsImmediate = async (storage, events) => {
530
+ try {
531
+ await storage.setItem(STORAGE_KEYS.EVENTS, JSON.stringify(events));
532
+ } catch (error) {
533
+ console.warn("[EventLog] Failed to save events:", error);
534
+ }
535
+ };
536
+ const saveEvents = debounce(
537
+ (storage, events) => {
538
+ saveEventsImmediate(storage, events);
539
+ },
540
+ config.batchWriteDelayMs ?? 100
541
+ );
542
+ const autoDetectDeviceInfo = () => {
543
+ try {
544
+ const { Platform, Dimensions } = __require("react-native");
545
+ return {
546
+ platform: Platform.OS,
547
+ platformVersion: Platform.Version,
548
+ screenWidth: Dimensions.get("window").width,
549
+ screenHeight: Dimensions.get("window").height
550
+ };
551
+ } catch {
552
+ return {};
553
+ }
554
+ };
555
+ const init = async (runtimeConfig) => {
556
+ if (runtimeConfig) {
557
+ Object.assign(config, runtimeConfig);
558
+ }
559
+ if (state?.initialized) {
560
+ return { ok: true, value: void 0 };
561
+ }
562
+ if (initPromise) {
563
+ return initPromise;
564
+ }
565
+ initPromise = (async () => {
566
+ try {
567
+ const savedSession = await loadSession(internalStorage);
568
+ const now = Date.now();
569
+ const timeoutMs = (config.sessionTimeoutMinutes ?? 30) * 60 * 1e3;
570
+ const session = savedSession && now - savedSession.lastActivityTime < timeoutMs ? { ...savedSession, startType: "warm" } : createSession("cold");
571
+ const events = await loadEvents(internalStorage);
572
+ let buffer = createRingBuffer(config.maxEvents ?? 1e3);
573
+ const maxAge = (config.maxAgeDays ?? 7) * 24 * 60 * 60 * 1e3;
574
+ events.forEach((event) => {
575
+ if (now - event.timestamp < maxAge) {
576
+ buffer = pushEvent(buffer, event);
577
+ }
578
+ });
579
+ const deviceInfo = autoDetectDeviceInfo();
580
+ state = {
581
+ config,
582
+ session,
583
+ buffer,
584
+ context: { deviceInfo },
585
+ initialized: true
586
+ };
587
+ await saveSession(internalStorage, session);
588
+ if (preInitQueue.length > 0) {
589
+ preInitQueue.forEach((item) => {
590
+ logEvent(item.category, item.payload);
591
+ });
592
+ preInitQueue = [];
593
+ }
594
+ const networkConfig = config.features?.network ?? { enabled: true };
595
+ if (networkConfig.enabled !== false) {
596
+ const proxyConfig = {
597
+ ...networkConfig,
598
+ enabled: true
599
+ };
600
+ const networkProxy = {
601
+ log: (level, message, data) => logEvent("log", createLogPayload(level, message, data)),
602
+ error: (error, context) => logEvent("error", createErrorPayload(error, context)),
603
+ network: (payload) => logEvent("network", payload)
604
+ };
605
+ enableNetworkInterception(networkProxy, proxyConfig);
606
+ }
607
+ const globalErrorsConfig = config.features?.globalErrors ?? { enabled: true };
608
+ if (globalErrorsConfig.enabled !== false) {
609
+ const errorProxy = {
610
+ error: (error, context) => logEvent("error", createErrorPayload(error, context))
611
+ };
612
+ enableGlobalErrorLogging(errorProxy, { ...globalErrorsConfig, enabled: true });
613
+ }
614
+ return { ok: true, value: void 0 };
615
+ } catch (error) {
616
+ console.error("[EventLog] Initialization failed:", error);
617
+ return {
618
+ ok: false,
619
+ error: error instanceof Error ? error : new Error(String(error))
620
+ };
621
+ }
622
+ })();
623
+ return initPromise;
624
+ };
625
+ const isReady = () => {
626
+ return state?.initialized ?? false;
627
+ };
628
+ const logEvent = (category, payload) => {
629
+ if (!state?.initialized) {
630
+ preInitQueue.push({ category, payload });
631
+ return { ok: true, value: void 0 };
632
+ }
633
+ try {
634
+ let event = createEvent(state.session, state.context, category, payload);
635
+ event = sanitizeEvent(event);
636
+ if (state.config.sanitize) {
637
+ event = state.config.sanitize(event);
638
+ }
639
+ state.buffer = pushEvent(state.buffer, event);
640
+ state.session = incrementSeq(state.session);
641
+ state.session = updateSessionActivity(state.session);
642
+ const events = toArray(state.buffer);
643
+ saveEvents(internalStorage, events);
644
+ saveSession(internalStorage, state.session);
645
+ return { ok: true, value: void 0 };
646
+ } catch (error) {
647
+ return {
648
+ ok: false,
649
+ error: error instanceof Error ? error : new Error(String(error))
650
+ };
651
+ }
652
+ };
653
+ return {
654
+ init,
655
+ isReady,
656
+ screen: (name, params) => {
657
+ return logEvent("screen", createScreenPayload(name, params));
658
+ },
659
+ action: (name, data) => {
660
+ return logEvent("action", createActionPayload(name, data));
661
+ },
662
+ log: (level, message, data) => {
663
+ return logEvent("log", createLogPayload(level, message, data));
664
+ },
665
+ error: (error, context) => {
666
+ return logEvent("error", createErrorPayload(error, context));
667
+ },
668
+ setUser: (user) => {
669
+ if (state) {
670
+ state.context = { ...state.context, user };
671
+ }
672
+ },
673
+ setContext: (key, value) => {
674
+ if (state) {
675
+ state.context = { ...state.context, [key]: value };
676
+ }
677
+ },
678
+ setDeviceInfo: (info) => {
679
+ if (state) {
680
+ state.context = { ...state.context, deviceInfo: info };
681
+ }
682
+ },
683
+ export: async (options) => {
684
+ if (!state?.initialized) {
685
+ return {
686
+ ok: false,
687
+ error: new Error("[EventLog] Not initialized")
688
+ };
689
+ }
690
+ try {
691
+ const events = toArray(state.buffer);
692
+ await saveEventsImmediate(internalStorage, events);
693
+ const jsonl = exportEventsAsJSONL(events, options.mode);
694
+ return { ok: true, value: jsonl };
695
+ } catch (error) {
696
+ return {
697
+ ok: false,
698
+ error: error instanceof Error ? error : new Error(String(error))
699
+ };
700
+ }
701
+ },
702
+ clear: async () => {
703
+ if (!state?.initialized) {
704
+ return {
705
+ ok: false,
706
+ error: new Error("[EventLog] Not initialized")
707
+ };
708
+ }
709
+ try {
710
+ state.buffer = clearBuffer(state.buffer);
711
+ state.context = {};
712
+ await internalStorage.removeItem(STORAGE_KEYS.EVENTS);
713
+ return { ok: true, value: void 0 };
714
+ } catch (error) {
715
+ return {
716
+ ok: false,
717
+ error: error instanceof Error ? error : new Error(String(error))
718
+ };
719
+ }
720
+ },
721
+ getEvents: () => {
722
+ if (!state?.initialized) {
723
+ return {
724
+ ok: false,
725
+ error: new Error("[EventLog] Not initialized")
726
+ };
727
+ }
728
+ const events = toArray(state.buffer);
729
+ return { ok: true, value: events };
730
+ },
731
+ query: (query) => {
732
+ if (!state?.initialized) {
733
+ return {
734
+ ok: false,
735
+ error: new Error("[EventLog] Not initialized")
736
+ };
737
+ }
738
+ try {
739
+ const events = toArray(state.buffer);
740
+ const filtered = queryEvents(events, query);
741
+ return { ok: true, value: filtered };
742
+ } catch (error) {
743
+ return {
744
+ ok: false,
745
+ error: error instanceof Error ? error : new Error(String(error))
746
+ };
747
+ }
748
+ },
749
+ network: (payload) => {
750
+ return logEvent("network", payload);
751
+ }
752
+ };
753
+ };
754
+ var colors = {
755
+ background: "#f9fafb",
756
+ surface: "#fff",
757
+ border: "#e5e7eb",
758
+ text: {
759
+ primary: "#111827",
760
+ secondary: "#6b7280",
761
+ tertiary: "#9ca3af"
762
+ },
763
+ primary: "#3b82f6"
764
+ };
765
+ var styles = reactNative.StyleSheet.create({
766
+ container: {
767
+ flex: 1,
768
+ backgroundColor: colors.background
769
+ },
770
+ header: {
771
+ flexDirection: "row",
772
+ justifyContent: "space-between",
773
+ alignItems: "center",
774
+ padding: 16,
775
+ backgroundColor: colors.surface,
776
+ borderBottomWidth: 1,
777
+ borderBottomColor: colors.border
778
+ },
779
+ title: {
780
+ fontSize: 18,
781
+ fontWeight: "600",
782
+ color: colors.text.primary
783
+ },
784
+ headerButtons: {
785
+ flexDirection: "row",
786
+ gap: 8
787
+ },
788
+ button: {
789
+ paddingHorizontal: 12,
790
+ paddingVertical: 6,
791
+ backgroundColor: colors.primary,
792
+ borderRadius: 6
793
+ },
794
+ buttonText: {
795
+ color: "#fff",
796
+ fontSize: 14,
797
+ fontWeight: "500"
798
+ },
799
+ searchInput: {
800
+ margin: 16,
801
+ marginBottom: 8,
802
+ padding: 12,
803
+ backgroundColor: colors.surface,
804
+ borderRadius: 8,
805
+ borderWidth: 1,
806
+ borderColor: colors.border,
807
+ fontSize: 14,
808
+ color: colors.text.primary
809
+ },
810
+ filters: {
811
+ paddingHorizontal: 16,
812
+ paddingBottom: 12,
813
+ maxHeight: 50
814
+ },
815
+ filterChip: {
816
+ paddingHorizontal: 12,
817
+ paddingVertical: 6,
818
+ borderRadius: 16,
819
+ marginRight: 8,
820
+ justifyContent: "center",
821
+ alignItems: "center"
822
+ },
823
+ filterText: {
824
+ fontSize: 12,
825
+ fontWeight: "500",
826
+ textAlign: "center",
827
+ textAlignVertical: "center"
828
+ // Android
829
+ },
830
+ eventList: {
831
+ flex: 1
832
+ },
833
+ eventItem: {
834
+ backgroundColor: colors.surface,
835
+ marginHorizontal: 16,
836
+ marginBottom: 8,
837
+ padding: 12,
838
+ borderRadius: 8,
839
+ borderWidth: 1,
840
+ borderColor: colors.border
841
+ },
842
+ eventHeader: {
843
+ flexDirection: "row",
844
+ justifyContent: "space-between",
845
+ alignItems: "center",
846
+ marginBottom: 8
847
+ },
848
+ categoryBadge: {
849
+ paddingHorizontal: 8,
850
+ paddingVertical: 4,
851
+ borderRadius: 4,
852
+ justifyContent: "center",
853
+ alignItems: "center"
854
+ },
855
+ categoryText: {
856
+ color: "#fff",
857
+ fontSize: 12,
858
+ fontWeight: "600",
859
+ textTransform: "uppercase",
860
+ textAlign: "center",
861
+ textAlignVertical: "center"
862
+ // Android specific
863
+ },
864
+ timestamp: {
865
+ fontSize: 12,
866
+ color: colors.text.secondary
867
+ },
868
+ eventSummary: {
869
+ fontSize: 14,
870
+ color: colors.text.secondary,
871
+ fontFamily: "monospace"
872
+ },
873
+ eventDetails: {
874
+ marginTop: 12,
875
+ paddingTop: 12,
876
+ borderTopWidth: 1,
877
+ borderTopColor: colors.border
878
+ },
879
+ detailsLabel: {
880
+ fontSize: 12,
881
+ fontWeight: "600",
882
+ color: colors.text.secondary,
883
+ marginTop: 8,
884
+ marginBottom: 4
885
+ },
886
+ detailsText: {
887
+ fontSize: 12,
888
+ color: colors.text.secondary,
889
+ fontFamily: "monospace"
890
+ },
891
+ emptyState: {
892
+ padding: 32,
893
+ alignItems: "center"
894
+ },
895
+ emptyText: {
896
+ fontSize: 14,
897
+ color: colors.text.tertiary
898
+ }
899
+ });
900
+
901
+ // src/viewer/Header.tsx
902
+ var Header = ({ eventCount, onExport, onClear }) => {
903
+ return /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles.header }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.title }, "Event Log (", eventCount, ")"), /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles.headerButtons }, /* @__PURE__ */ React5__default.default.createElement(reactNative.TouchableOpacity, { onPress: onExport, style: styles.button }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.buttonText }, "Export")), /* @__PURE__ */ React5__default.default.createElement(reactNative.TouchableOpacity, { onPress: onClear, style: styles.button }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.buttonText }, "Clear"))));
904
+ };
905
+ var SearchBar = ({ value, onChange }) => {
906
+ return /* @__PURE__ */ React5__default.default.createElement(
907
+ reactNative.TextInput,
908
+ {
909
+ style: styles.searchInput,
910
+ placeholder: "Search events...",
911
+ value,
912
+ onChangeText: onChange,
913
+ placeholderTextColor: colors.text.tertiary
914
+ }
915
+ );
916
+ };
917
+
918
+ // src/viewer/types.ts
919
+ var CATEGORY_COLORS = {
920
+ screen: "#3b82f6",
921
+ action: "#10b981",
922
+ network: "#f59e0b",
923
+ error: "#ef4444",
924
+ log: "#6b7280"
925
+ };
926
+ var ALL_CATEGORIES = [
927
+ "screen",
928
+ "action",
929
+ "network",
930
+ "error",
931
+ "log"
932
+ ];
933
+ var FilterChip = ({ category, selected, onToggle }) => {
934
+ return /* @__PURE__ */ React5__default.default.createElement(
935
+ reactNative.TouchableOpacity,
936
+ {
937
+ onPress: onToggle,
938
+ style: [
939
+ styles.filterChip,
940
+ {
941
+ backgroundColor: selected ? CATEGORY_COLORS[category] : colors.border
942
+ }
943
+ ]
944
+ },
945
+ /* @__PURE__ */ React5__default.default.createElement(
946
+ reactNative.Text,
947
+ {
948
+ style: [
949
+ styles.filterText,
950
+ { color: selected ? "#fff" : colors.text.secondary }
951
+ ]
952
+ },
953
+ category
954
+ )
955
+ );
956
+ };
957
+
958
+ // src/viewer/FilterBar.tsx
959
+ var FilterBar = ({ selected, onToggle }) => {
960
+ return /* @__PURE__ */ React5__default.default.createElement(reactNative.ScrollView, { horizontal: true, style: styles.filters, showsHorizontalScrollIndicator: false }, ALL_CATEGORIES.map((category) => /* @__PURE__ */ React5__default.default.createElement(
961
+ FilterChip,
962
+ {
963
+ key: category,
964
+ category,
965
+ selected: selected.includes(category),
966
+ onToggle: () => onToggle(category)
967
+ }
968
+ )));
969
+ };
970
+ var EventItem = ({ event, expanded, onPress }) => {
971
+ return /* @__PURE__ */ React5__default.default.createElement(reactNative.TouchableOpacity, { onPress, style: styles.eventItem }, /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles.eventHeader }, /* @__PURE__ */ React5__default.default.createElement(
972
+ reactNative.View,
973
+ {
974
+ style: [
975
+ styles.categoryBadge,
976
+ { backgroundColor: CATEGORY_COLORS[event.category] }
977
+ ]
978
+ },
979
+ /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.categoryText }, event.category)
980
+ ), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.timestamp }, new Date(event.timestamp).toLocaleTimeString())), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.eventSummary, numberOfLines: expanded ? void 0 : 1 }, JSON.stringify(event.payload)), expanded && /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles.eventDetails }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsLabel }, "Session:"), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsText }, event.sessionId), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsLabel }, "Sequence:"), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsText }, event.seq), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsLabel }, "Timezone:"), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsText }, event.timezone), event.context && Object.keys(event.context).length > 0 && /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsLabel }, "Context:"), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsText }, JSON.stringify(event.context, null, 2))), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsLabel }, "Full Event:"), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.detailsText }, JSON.stringify(event, null, 2))));
981
+ };
982
+
983
+ // src/viewer/EventList.tsx
984
+ var EventList = ({ events, onEventPress, expandedId }) => {
985
+ const renderItem = ({ item }) => /* @__PURE__ */ React5__default.default.createElement(
986
+ EventItem,
987
+ {
988
+ event: item,
989
+ expanded: expandedId === item.eventId,
990
+ onPress: () => onEventPress(item.eventId)
991
+ }
992
+ );
993
+ const keyExtractor = (item) => item.eventId;
994
+ const renderEmpty = () => /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles.emptyState }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles.emptyText }, "No events"));
995
+ return /* @__PURE__ */ React5__default.default.createElement(
996
+ reactNative.FlatList,
997
+ {
998
+ data: events,
999
+ renderItem,
1000
+ keyExtractor,
1001
+ ListEmptyComponent: renderEmpty,
1002
+ style: styles.eventList,
1003
+ initialNumToRender: 20,
1004
+ maxToRenderPerBatch: 10,
1005
+ windowSize: 5
1006
+ }
1007
+ );
1008
+ };
1009
+ var useEvents = (eventLog2, maxEvents) => {
1010
+ const [events, setEvents] = React5.useState([]);
1011
+ const fetchEvents = React5.useCallback(() => {
1012
+ const result = eventLog2.getEvents();
1013
+ if (result.ok) {
1014
+ setEvents(result.value.slice(-maxEvents));
1015
+ }
1016
+ }, [eventLog2, maxEvents]);
1017
+ React5.useEffect(() => {
1018
+ fetchEvents();
1019
+ }, [fetchEvents]);
1020
+ return { events, refetch: fetchEvents };
1021
+ };
1022
+ var useEventFilter = (events) => {
1023
+ const [filter, setFilter] = React5.useState([]);
1024
+ const [search, setSearch] = React5.useState("");
1025
+ const toggleFilter = React5.useCallback((category) => {
1026
+ setFilter(
1027
+ (prev) => prev.includes(category) ? prev.filter((c) => c !== category) : [...prev, category]
1028
+ );
1029
+ }, []);
1030
+ const filteredEvents = events.filter((event) => {
1031
+ if (filter.length > 0 && !filter.includes(event.category)) {
1032
+ return false;
1033
+ }
1034
+ if (search) {
1035
+ const searchLower = search.toLowerCase();
1036
+ const payloadStr = JSON.stringify(event.payload).toLowerCase();
1037
+ return payloadStr.includes(searchLower);
1038
+ }
1039
+ return true;
1040
+ });
1041
+ return {
1042
+ filter,
1043
+ search,
1044
+ setSearch,
1045
+ toggleFilter,
1046
+ filteredEvents
1047
+ };
1048
+ };
1049
+ var useEventActions = (eventLog2, onClear) => {
1050
+ const handleClear = React5.useCallback(async () => {
1051
+ await eventLog2.clear();
1052
+ if (onClear) {
1053
+ onClear();
1054
+ }
1055
+ }, [eventLog2, onClear]);
1056
+ const handleExport = React5.useCallback(async () => {
1057
+ const result = await eventLog2.export({ mode: "full" });
1058
+ if (result.ok) {
1059
+ console.log("=== EXPORTED LOGS ===");
1060
+ console.log(result.value);
1061
+ console.log("=== END LOGS ===");
1062
+ }
1063
+ }, [eventLog2]);
1064
+ return { handleClear, handleExport };
1065
+ };
1066
+
1067
+ // src/instance.ts
1068
+ var eventLog = createEventLog();
1069
+
1070
+ // src/viewer/EventLogViewer.tsx
1071
+ var EventLogViewer = ({
1072
+ maxEvents = 100
1073
+ }) => {
1074
+ const [expandedId, setExpandedId] = React5.useState(null);
1075
+ const { events, refetch } = useEvents(eventLog, maxEvents);
1076
+ const { filter, search, setSearch, toggleFilter, filteredEvents } = useEventFilter(events);
1077
+ const { handleClear, handleExport } = useEventActions(eventLog, refetch);
1078
+ const handleEventPress = (eventId) => {
1079
+ setExpandedId(expandedId === eventId ? null : eventId);
1080
+ };
1081
+ return /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles.container }, /* @__PURE__ */ React5__default.default.createElement(
1082
+ Header,
1083
+ {
1084
+ eventCount: filteredEvents.length,
1085
+ onExport: handleExport,
1086
+ onClear: handleClear
1087
+ }
1088
+ ), /* @__PURE__ */ React5__default.default.createElement(SearchBar, { value: search, onChange: setSearch }), /* @__PURE__ */ React5__default.default.createElement(FilterBar, { selected: filter, onToggle: toggleFilter }), /* @__PURE__ */ React5__default.default.createElement(
1089
+ EventList,
1090
+ {
1091
+ events: filteredEvents,
1092
+ onEventPress: handleEventPress,
1093
+ expandedId
1094
+ }
1095
+ ));
1096
+ };
1097
+ var EventLogErrorBoundary = class extends React5.Component {
1098
+ constructor(props) {
1099
+ super(props);
1100
+ this.resetError = () => {
1101
+ this.setState({ hasError: false, error: null });
1102
+ };
1103
+ this.state = { hasError: false, error: null };
1104
+ }
1105
+ static getDerivedStateFromError(error) {
1106
+ return { hasError: true, error };
1107
+ }
1108
+ componentDidCatch(error, info) {
1109
+ eventLog.error(error, {
1110
+ origin: "react_error_boundary",
1111
+ componentStack: info.componentStack
1112
+ });
1113
+ if (this.props.onError) {
1114
+ this.props.onError(error, info);
1115
+ }
1116
+ }
1117
+ render() {
1118
+ if (this.state.hasError) {
1119
+ if (this.props.fallback) {
1120
+ return this.props.fallback;
1121
+ }
1122
+ return /* @__PURE__ */ React5__default.default.createElement(reactNative.View, { style: styles2.content }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles2.title }, "Something went wrong"), /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles2.message }, this.state.error?.message || "An unexpected error occurred."), /* @__PURE__ */ React5__default.default.createElement(reactNative.TouchableOpacity, { style: styles2.button, onPress: this.resetError }, /* @__PURE__ */ React5__default.default.createElement(reactNative.Text, { style: styles2.buttonText }, "Try Again")));
1123
+ }
1124
+ return this.props.children;
1125
+ }
1126
+ };
1127
+ var styles2 = reactNative.StyleSheet.create({
1128
+ container: {
1129
+ flex: 1,
1130
+ backgroundColor: "#fff",
1131
+ justifyContent: "center",
1132
+ alignItems: "center"
1133
+ },
1134
+ content: {
1135
+ padding: 20,
1136
+ flex: 1,
1137
+ backgroundColor: "#fff",
1138
+ justifyContent: "center",
1139
+ alignItems: "center"
1140
+ },
1141
+ title: {
1142
+ fontSize: 20,
1143
+ fontWeight: "bold",
1144
+ marginBottom: 10,
1145
+ color: "#333"
1146
+ },
1147
+ message: {
1148
+ fontSize: 14,
1149
+ color: "#666",
1150
+ textAlign: "center",
1151
+ marginBottom: 20
1152
+ },
1153
+ button: {
1154
+ backgroundColor: "#3b82f6",
1155
+ paddingHorizontal: 20,
1156
+ paddingVertical: 10,
1157
+ borderRadius: 8
1158
+ },
1159
+ buttonText: {
1160
+ color: "#fff",
1161
+ fontWeight: "600"
1162
+ }
1163
+ });
1164
+
1165
+ exports.EventLogErrorBoundary = EventLogErrorBoundary;
1166
+ exports.EventLogViewer = EventLogViewer;
1167
+ exports.createEventLog = createEventLog;
1168
+ exports.eventLog = eventLog;
1169
+ //# sourceMappingURL=index.js.map
1170
+ //# sourceMappingURL=index.js.map