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/README.md +64 -0
- package/dist/index.d.mts +206 -0
- package/dist/index.d.ts +206 -0
- package/dist/index.js +1170 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1142 -0
- package/dist/index.mjs.map +1 -0
- package/dist/viewer/index.d.mts +19 -0
- package/dist/viewer/index.d.ts +19 -0
- package/dist/viewer/index.js +1100 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/viewer/index.mjs +1075 -0
- package/dist/viewer/index.mjs.map +1 -0
- package/package.json +60 -0
- package/src/components/EventLogErrorBoundary.tsx +109 -0
- package/src/core/buffer.ts +66 -0
- package/src/core/errors.ts +79 -0
- package/src/core/eventlog.ts +463 -0
- package/src/core/events.ts +256 -0
- package/src/core/network.ts +209 -0
- package/src/core/query.ts +50 -0
- package/src/core/utils.ts +54 -0
- package/src/index.ts +39 -0
- package/src/instance.ts +7 -0
- package/src/storage/mmkv.ts +57 -0
- package/src/types.ts +159 -0
- package/src/viewer/EventItem.tsx +63 -0
- package/src/viewer/EventList.tsx +41 -0
- package/src/viewer/EventLogViewer.tsx +48 -0
- package/src/viewer/FilterBar.tsx +25 -0
- package/src/viewer/FilterChip.tsx +32 -0
- package/src/viewer/Header.tsx +24 -0
- package/src/viewer/SearchBar.tsx +20 -0
- package/src/viewer/hooks.ts +78 -0
- package/src/viewer/index.ts +6 -0
- package/src/viewer/styles.ts +151 -0
- package/src/viewer/types.ts +59 -0
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
|