@getlimelight/sdk 0.1.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,1244 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BodyFormat: () => BodyFormat,
24
+ ConsoleLevel: () => ConsoleLevel,
25
+ ConsoleSource: () => ConsoleSource,
26
+ ConsoleType: () => ConsoleType,
27
+ EventType: () => EventType,
28
+ GraphqlOprtation: () => GraphqlOprtation,
29
+ HttpMethod: () => HttpMethod,
30
+ HttpStatusClass: () => HttpStatusClass,
31
+ Limelight: () => Limelight,
32
+ NetworkPhase: () => NetworkPhase,
33
+ NetworkType: () => NetworkType
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/types/console.ts
38
+ var ConsoleLevel = /* @__PURE__ */ ((ConsoleLevel2) => {
39
+ ConsoleLevel2["LOG"] = "log";
40
+ ConsoleLevel2["WARN"] = "warn";
41
+ ConsoleLevel2["ERROR"] = "error";
42
+ ConsoleLevel2["INFO"] = "info";
43
+ ConsoleLevel2["DEBUG"] = "debug";
44
+ ConsoleLevel2["TRACE"] = "trace";
45
+ return ConsoleLevel2;
46
+ })(ConsoleLevel || {});
47
+ var ConsoleSource = /* @__PURE__ */ ((ConsoleSource2) => {
48
+ ConsoleSource2["APP"] = "app";
49
+ ConsoleSource2["LIBRARY"] = "library";
50
+ ConsoleSource2["REACT_NATIVE"] = "react-native";
51
+ ConsoleSource2["NATIVE"] = "native";
52
+ return ConsoleSource2;
53
+ })(ConsoleSource || {});
54
+ var ConsoleType = /* @__PURE__ */ ((ConsoleType2) => {
55
+ ConsoleType2["EXCEPTION"] = "exception";
56
+ ConsoleType2["WARNING"] = "warning";
57
+ ConsoleType2["NETWORK"] = "network";
58
+ ConsoleType2["PERFORMANCE"] = "performance";
59
+ ConsoleType2["GENERAL"] = "general";
60
+ return ConsoleType2;
61
+ })(ConsoleType || {});
62
+
63
+ // src/types/core.ts
64
+ var NetworkType = /* @__PURE__ */ ((NetworkType2) => {
65
+ NetworkType2["FETCH"] = "fetch";
66
+ NetworkType2["XHR"] = "xhr";
67
+ NetworkType2["GRAPHQL"] = "graphql";
68
+ return NetworkType2;
69
+ })(NetworkType || {});
70
+ var NetworkPhase = /* @__PURE__ */ ((NetworkPhase2) => {
71
+ NetworkPhase2["CONNECT"] = "CONNECT";
72
+ NetworkPhase2["REQUEST"] = "REQUEST";
73
+ NetworkPhase2["RESPONSE"] = "RESPONSE";
74
+ NetworkPhase2["ERROR"] = "ERROR";
75
+ return NetworkPhase2;
76
+ })(NetworkPhase || {});
77
+ var BodyFormat = /* @__PURE__ */ ((BodyFormat2) => {
78
+ BodyFormat2["TEXT"] = "TEXT";
79
+ BodyFormat2["JSON"] = "JSON";
80
+ BodyFormat2["FORM_DATA"] = "FORM_DATA";
81
+ BodyFormat2["BLOB"] = "BLOB";
82
+ BodyFormat2["ARRAY_BUFFER"] = "ARRAY_BUFFER";
83
+ BodyFormat2["NONE"] = "NONE";
84
+ BodyFormat2["UNSERIALIZABLE"] = "UNSERIALIZABLE";
85
+ return BodyFormat2;
86
+ })(BodyFormat || {});
87
+ var HttpMethod = /* @__PURE__ */ ((HttpMethod4) => {
88
+ HttpMethod4["GET"] = "GET";
89
+ HttpMethod4["POST"] = "POST";
90
+ HttpMethod4["PUT"] = "PUT";
91
+ HttpMethod4["PATCH"] = "PATCH";
92
+ HttpMethod4["DELETE"] = "DELETE";
93
+ HttpMethod4["HEAD"] = "HEAD";
94
+ HttpMethod4["OPTIONS"] = "OPTIONS";
95
+ HttpMethod4["TRACE"] = "TRACE";
96
+ HttpMethod4["CONNECT"] = "CONNECT";
97
+ return HttpMethod4;
98
+ })(HttpMethod || {});
99
+ var HttpStatusClass = /* @__PURE__ */ ((HttpStatusClass2) => {
100
+ HttpStatusClass2[HttpStatusClass2["INFORMATIONAL"] = 100] = "INFORMATIONAL";
101
+ HttpStatusClass2[HttpStatusClass2["SUCCESS"] = 200] = "SUCCESS";
102
+ HttpStatusClass2[HttpStatusClass2["REDIRECTION"] = 300] = "REDIRECTION";
103
+ HttpStatusClass2[HttpStatusClass2["CLIENT_ERROR"] = 400] = "CLIENT_ERROR";
104
+ HttpStatusClass2[HttpStatusClass2["SERVER_ERROR"] = 500] = "SERVER_ERROR";
105
+ return HttpStatusClass2;
106
+ })(HttpStatusClass || {});
107
+ var EventType = /* @__PURE__ */ ((EventType2) => {
108
+ EventType2["NETWORK"] = "NETWORK";
109
+ EventType2["CONSOLE"] = "CONSOLE";
110
+ return EventType2;
111
+ })(EventType || {});
112
+
113
+ // src/types/graphql.ts
114
+ var GraphqlOprtation = /* @__PURE__ */ ((GraphqlOprtation2) => {
115
+ GraphqlOprtation2["QUERY"] = "QUERY";
116
+ GraphqlOprtation2["MUTATION"] = "MUTATION";
117
+ GraphqlOprtation2["SUB"] = "SUBSCRIPTION";
118
+ return GraphqlOprtation2;
119
+ })(GraphqlOprtation || {});
120
+
121
+ // src/helpers/detection/detectConsoleType.ts
122
+ var detectConsoleType = (level, args) => {
123
+ const messageStr = args.map((arg) => {
124
+ try {
125
+ return typeof arg === "object" ? JSON.stringify(arg) : String(arg);
126
+ } catch {
127
+ return String(arg);
128
+ }
129
+ }).join(" ").toLowerCase();
130
+ if (level === "error") {
131
+ if (messageStr.includes("error:") || messageStr.includes("exception") || messageStr.includes("uncaught") || messageStr.includes("unhandled") || args.some((arg) => arg instanceof Error)) {
132
+ return "exception" /* EXCEPTION */;
133
+ }
134
+ }
135
+ if (level === "warn") {
136
+ return "warning" /* WARNING */;
137
+ }
138
+ if (messageStr.includes("network") || messageStr.includes("fetch") || messageStr.includes("request") || messageStr.includes("response") || messageStr.includes("http") || messageStr.includes("api") || messageStr.includes("graphql") || messageStr.includes("xhr")) {
139
+ return "network" /* NETWORK */;
140
+ }
141
+ if (messageStr.includes("performance") || messageStr.includes("slow") || messageStr.includes("render") || messageStr.includes("fps") || messageStr.includes("memory") || messageStr.includes("optimization") || messageStr.includes("bottleneck")) {
142
+ return "performance" /* PERFORMANCE */;
143
+ }
144
+ return "general" /* GENERAL */;
145
+ };
146
+
147
+ // src/helpers/detection/detectSource.ts
148
+ var detectLogSource = () => {
149
+ try {
150
+ const stack = new Error().stack;
151
+ if (!stack) return "app" /* APP */;
152
+ const stackLines = stack.split("\n");
153
+ for (let i = 3; i < stackLines.length; i++) {
154
+ const line = stackLines[i];
155
+ if (line === void 0) return "app" /* APP */;
156
+ if (line.includes("node_modules/react-native/") || line.includes("react-native/Libraries/") || line.includes("MessageQueue.js") || line.includes("BatchedBridge")) {
157
+ return "react-native" /* REACT_NATIVE */;
158
+ }
159
+ if (line.includes("[native code]") || line.includes("NativeModules")) {
160
+ return "native" /* NATIVE */;
161
+ }
162
+ if (!line.includes("node_modules/")) {
163
+ return "app" /* APP */;
164
+ }
165
+ if (!line.includes("node_modules/")) {
166
+ return "app" /* APP */;
167
+ }
168
+ }
169
+ return "app" /* APP */;
170
+ } catch {
171
+ return "app" /* APP */;
172
+ }
173
+ };
174
+
175
+ // src/helpers/detection/getInitiator.ts
176
+ var getInitiator = () => {
177
+ try {
178
+ const stack = new Error().stack;
179
+ if (!stack) return "unknown";
180
+ const lines = stack.split("\n");
181
+ const callerLine = lines[4] || lines[3];
182
+ if (!callerLine) return "unknown";
183
+ const match = callerLine.match(/at (.+) \((.+):(\d+):(\d+)\)/);
184
+ if (match) {
185
+ const [, functionName, filePath, line] = match;
186
+ const fileName = filePath?.split("/").pop();
187
+ return `${functionName} (${fileName}:${line})`;
188
+ }
189
+ return callerLine.trim();
190
+ } catch {
191
+ return "unknown";
192
+ }
193
+ };
194
+
195
+ // src/helpers/graphql/detectGraphQlOperationType.ts
196
+ var detectGraphQlOperationType = (query) => {
197
+ if (!query) return null;
198
+ if (query.trim().startsWith("mutation")) return "MUTATION" /* MUTATION */;
199
+ if (query.trim().startsWith("subscription")) return "SUBSCRIPTION" /* SUB */;
200
+ return "QUERY" /* QUERY */;
201
+ };
202
+
203
+ // src/helpers/graphql/isGraphQLRequest.ts
204
+ var isGraphQLRequest = (url, body) => {
205
+ const isGraphqlUrl = url.toLowerCase().includes("graphql");
206
+ const rawBody = typeof body === "object" && body !== null ? body.raw : body;
207
+ if (typeof rawBody !== "string") return isGraphqlUrl;
208
+ try {
209
+ if (rawBody.includes('"query"') || rawBody.includes('"operationName"')) {
210
+ return true;
211
+ }
212
+ } catch {
213
+ }
214
+ return isGraphqlUrl;
215
+ };
216
+
217
+ // src/helpers/graphql/parseGraphQL.ts
218
+ var parseGraphQL = (body) => {
219
+ try {
220
+ const parsed = typeof body === "string" ? JSON.parse(body) : body;
221
+ if (!parsed || typeof parsed !== "object") {
222
+ return null;
223
+ }
224
+ if (!parsed.query && !parsed.operationName) {
225
+ return null;
226
+ }
227
+ return {
228
+ operationName: parsed.operationName || void 0,
229
+ operationType: detectGraphQlOperationType(parsed.query),
230
+ variables: parsed.variables || void 0,
231
+ query: parsed.query || void 0
232
+ };
233
+ } catch {
234
+ return null;
235
+ }
236
+ };
237
+
238
+ // src/constants/index.ts
239
+ var SENSITIVE_HEADERS = [
240
+ "authorization",
241
+ "cookie",
242
+ "set-cookie",
243
+ "x-api-key",
244
+ "x-auth-token",
245
+ "x-access-token",
246
+ "api-key",
247
+ "apikey",
248
+ "proxy-authorization",
249
+ "x-csrf-token",
250
+ "x-xsrf-token",
251
+ "x-auth",
252
+ "auth-token",
253
+ "access-token",
254
+ "secret",
255
+ "x-secret",
256
+ "bearer"
257
+ ];
258
+ var DEFAULT_PORT = 9090;
259
+ var WS_PATH = "/limelight";
260
+
261
+ // src/helpers/safety/redactSensitiveHeaders.ts
262
+ var redactSensitiveHeaders = (headers) => {
263
+ const redacted = { ...headers };
264
+ Object.keys(redacted).forEach((key) => {
265
+ if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
266
+ redacted[key] = "[REDACTED]";
267
+ }
268
+ });
269
+ return redacted;
270
+ };
271
+
272
+ // src/helpers/safety/safeStringify.ts
273
+ var safeStringify = (value, maxDepth = 10, pretty = false) => {
274
+ const seen = /* @__PURE__ */ new WeakMap();
275
+ const process2 = (val, currentDepth) => {
276
+ if (val === null) return null;
277
+ if (val === void 0) return "[undefined]";
278
+ if (typeof val === "bigint") return `${val}n`;
279
+ if (typeof val === "symbol") return val.toString();
280
+ if (typeof val === "function") {
281
+ return `[Function: ${val.name || "anonymous"}]`;
282
+ }
283
+ if (typeof val !== "object") return val;
284
+ if (currentDepth >= maxDepth) {
285
+ return "[Max Depth]";
286
+ }
287
+ if (seen.has(val)) {
288
+ return "[Circular]";
289
+ }
290
+ seen.set(val, true);
291
+ if (val instanceof Error) {
292
+ return {
293
+ __type: "Error",
294
+ name: val.name,
295
+ message: val.message,
296
+ stack: val.stack
297
+ };
298
+ }
299
+ if (val instanceof Date) {
300
+ return val.toISOString();
301
+ }
302
+ if (val instanceof RegExp) {
303
+ return val.toString();
304
+ }
305
+ if (val instanceof Map) {
306
+ const obj = {};
307
+ val.forEach((v, k) => {
308
+ const key = typeof k === "string" ? k : String(k);
309
+ obj[key] = process2(v, currentDepth + 1);
310
+ });
311
+ return obj;
312
+ }
313
+ if (val instanceof Set) {
314
+ return Array.from(val).map((v) => process2(v, currentDepth + 1));
315
+ }
316
+ if (ArrayBuffer.isView(val)) {
317
+ return `[${val.constructor.name}(${val.length})]`;
318
+ }
319
+ if (Array.isArray(val)) {
320
+ return val.map((item) => process2(item, currentDepth + 1));
321
+ }
322
+ const result = {};
323
+ for (const key in val) {
324
+ if (Object.prototype.hasOwnProperty.call(val, key)) {
325
+ result[key] = process2(val[key], currentDepth + 1);
326
+ }
327
+ }
328
+ return result;
329
+ };
330
+ try {
331
+ const processed = process2(value, 0);
332
+ return JSON.stringify(processed, null, pretty ? 2 : 0);
333
+ } catch (error) {
334
+ return JSON.stringify({
335
+ __error: "Stringification failed",
336
+ message: error instanceof Error ? error.message : String(error)
337
+ });
338
+ }
339
+ };
340
+
341
+ // src/helpers/utils/serializeBody.ts
342
+ var serializeBody = (input, disableBodyCapture) => {
343
+ if (disableBodyCapture) {
344
+ return { format: "NONE" /* NONE */, size: 0, preview: "" };
345
+ }
346
+ if (!input) {
347
+ return {
348
+ format: "NONE" /* NONE */,
349
+ size: 0,
350
+ preview: ""
351
+ };
352
+ }
353
+ try {
354
+ if (typeof input === "object") {
355
+ const json = JSON.stringify(input);
356
+ return {
357
+ format: "JSON" /* JSON */,
358
+ size: json.length,
359
+ preview: json.slice(0, 200),
360
+ raw: json
361
+ };
362
+ }
363
+ if (typeof input === "string") {
364
+ return {
365
+ format: "TEXT" /* TEXT */,
366
+ size: input.length,
367
+ preview: input.slice(0, 200),
368
+ raw: input
369
+ };
370
+ }
371
+ if (typeof FormData !== "undefined" && input instanceof FormData) {
372
+ return {
373
+ format: "FORM_DATA" /* FORM_DATA */,
374
+ size: 0,
375
+ preview: "[FormData]"
376
+ };
377
+ }
378
+ if (typeof Blob !== "undefined" && input instanceof Blob) {
379
+ return {
380
+ format: "BLOB" /* BLOB */,
381
+ size: input.size,
382
+ preview: "[Blob]"
383
+ };
384
+ }
385
+ if (input instanceof ArrayBuffer) {
386
+ return {
387
+ format: "ARRAY_BUFFER" /* ARRAY_BUFFER */,
388
+ size: input.byteLength,
389
+ preview: "[ArrayBuffer]"
390
+ };
391
+ }
392
+ return {
393
+ format: "UNSERIALIZABLE" /* UNSERIALIZABLE */,
394
+ size: 0,
395
+ preview: String(input)
396
+ };
397
+ } catch {
398
+ return {
399
+ format: "UNSERIALIZABLE" /* UNSERIALIZABLE */,
400
+ size: 0,
401
+ preview: "[Unserializable]"
402
+ };
403
+ }
404
+ };
405
+
406
+ // src/helpers/utils/isDevelopment.ts
407
+ var isDevelopment = () => {
408
+ try {
409
+ const g = globalThis;
410
+ if (typeof g.__DEV__ !== "undefined") return !!g.__DEV__;
411
+ if (typeof process !== "undefined" && process.env?.NODE_ENV) {
412
+ return process.env.NODE_ENV !== "production";
413
+ }
414
+ const importMeta = g.import?.meta;
415
+ if (importMeta?.env?.DEV) {
416
+ return true;
417
+ }
418
+ if (typeof g.import !== "undefined" && g.import.meta?.env?.DEV) {
419
+ return true;
420
+ }
421
+ } catch (e) {
422
+ return true;
423
+ }
424
+ return true;
425
+ };
426
+
427
+ // src/helpers/utils/formatRequestName.ts
428
+ var formatRequestName = (url) => {
429
+ try {
430
+ const urlObj = new URL(url);
431
+ const path = urlObj.pathname;
432
+ const segments = path.split("/").filter(Boolean);
433
+ return segments[segments.length - 1] || path || url;
434
+ } catch {
435
+ return url;
436
+ }
437
+ };
438
+
439
+ // src/limelight/interceptors/ConsoleInterceptor.ts
440
+ var ConsoleInterceptor = class {
441
+ constructor(sendMessage, getSessionId) {
442
+ this.sendMessage = sendMessage;
443
+ this.getSessionId = getSessionId;
444
+ }
445
+ originalConsole = {};
446
+ counter = 0;
447
+ isSetup = false;
448
+ isInternalLog = false;
449
+ config = null;
450
+ /**
451
+ * Sets up console interception by wrapping console methods.
452
+ * Intercepts log, warn, error, info, debug, trace methods to capture console output.
453
+ * Prevents double setup and infinite loops from internal logging.
454
+ * @param {LimelightConfig} config - Configuration object for Limelight
455
+ * @returns {void}
456
+ */
457
+ setup(config) {
458
+ if (this.isSetup) {
459
+ console.warn("[Limelight] Console interceptor already set up");
460
+ return;
461
+ }
462
+ this.isSetup = true;
463
+ this.config = config;
464
+ const self = this;
465
+ const methods = [
466
+ "log" /* LOG */,
467
+ "warn" /* WARN */,
468
+ "error" /* ERROR */,
469
+ "info" /* INFO */,
470
+ "debug" /* DEBUG */,
471
+ "trace" /* TRACE */
472
+ ];
473
+ methods.forEach((level) => {
474
+ const original = console[level];
475
+ this.originalConsole[level] = original;
476
+ console[level] = function(...args) {
477
+ if (self.isInternalLog) {
478
+ return original.apply(console, args);
479
+ }
480
+ self.isInternalLog = true;
481
+ try {
482
+ const source = detectLogSource();
483
+ const consoleType = detectConsoleType(level, args);
484
+ const stackTrace = self.captureStackTrace();
485
+ let consoleEvent = {
486
+ id: `${self.getSessionId()}-${Date.now()}-${self.counter++}`,
487
+ phase: "CONSOLE",
488
+ type: "CONSOLE" /* CONSOLE */,
489
+ level,
490
+ timestamp: Date.now(),
491
+ sessionId: self.getSessionId(),
492
+ source,
493
+ consoleType,
494
+ args: args.map((arg) => safeStringify(arg)),
495
+ stackTrace
496
+ };
497
+ if (self.config?.beforeSend) {
498
+ const modifiedEvent = self.config.beforeSend(consoleEvent);
499
+ if (!modifiedEvent) {
500
+ return original.apply(console, args);
501
+ }
502
+ if (modifiedEvent.phase !== "CONSOLE") {
503
+ console.error(
504
+ "[Limelight] beforeSend must return same event type"
505
+ );
506
+ return original.apply(console, args);
507
+ }
508
+ consoleEvent = modifiedEvent;
509
+ }
510
+ self.sendMessage(consoleEvent);
511
+ } catch (error) {
512
+ } finally {
513
+ self.isInternalLog = false;
514
+ }
515
+ return original.apply(console, args);
516
+ };
517
+ });
518
+ }
519
+ /**
520
+ * Captures the current stack trace, filtering out internal frames.
521
+ * @private
522
+ * @returns {string | undefined} Formatted stack trace or undefined if unavailable
523
+ */
524
+ captureStackTrace() {
525
+ try {
526
+ const error = new Error();
527
+ const stack = error.stack;
528
+ if (!stack) return void 0;
529
+ const relevantLines = stack.split("\n").slice(3).filter(
530
+ (line) => !line.includes("ConsoleInterceptor") && !line.includes("limelight")
531
+ );
532
+ return relevantLines.length > 0 ? relevantLines.join("\n") : void 0;
533
+ } catch {
534
+ return void 0;
535
+ }
536
+ }
537
+ /**
538
+ * Restores original console methods and removes all interception.
539
+ * @returns {void}
540
+ */
541
+ cleanup() {
542
+ if (!this.isSetup) {
543
+ console.warn("[Limelight] Console interceptor not set up");
544
+ return;
545
+ }
546
+ this.isSetup = false;
547
+ Object.entries(this.originalConsole).forEach(([method, fn]) => {
548
+ if (fn) {
549
+ console[method] = fn;
550
+ }
551
+ });
552
+ this.originalConsole = {};
553
+ }
554
+ };
555
+
556
+ // src/protocol/protocol.ts
557
+ var createSessionId = () => {
558
+ return `${Date.now()}-${Math.random().toString(36).substring(7)}`;
559
+ };
560
+ var generateRequestId = () => {
561
+ return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
562
+ };
563
+
564
+ // src/limelight/interceptors/NetworkInterceptor.ts
565
+ var NetworkInterceptor = class {
566
+ constructor(sendMessage, getSessionId) {
567
+ this.sendMessage = sendMessage;
568
+ this.getSessionId = getSessionId;
569
+ this.originalFetch = global.fetch;
570
+ }
571
+ originalFetch;
572
+ config = null;
573
+ isSetup = false;
574
+ /**
575
+ * Sets up fetch interception by wrapping the global fetch function.
576
+ * Intercepts all fetch requests to capture network events.
577
+ * Prevents double setup to avoid losing original fetch reference.
578
+ * @param {LimelightConfig} config - Configuration object for Limelight
579
+ * @returns {void}
580
+ */
581
+ setup(config) {
582
+ if (this.isSetup) {
583
+ console.warn("[Limelight] Network interceptor already set up");
584
+ return;
585
+ }
586
+ this.isSetup = true;
587
+ this.config = config;
588
+ const self = this;
589
+ global.fetch = async function(input, init = {}) {
590
+ const requestId = generateRequestId();
591
+ const startTime = Date.now();
592
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
593
+ const method = init.method || "GET";
594
+ const modifiedInit = { ...init };
595
+ const headers = {};
596
+ if (modifiedInit.headers instanceof Headers) {
597
+ modifiedInit.headers.forEach((value, key) => {
598
+ headers[key.toLowerCase()] = value;
599
+ });
600
+ } else if (modifiedInit.headers) {
601
+ Object.entries(modifiedInit.headers).forEach(([key, value]) => {
602
+ headers[key.toLowerCase()] = value;
603
+ });
604
+ }
605
+ headers["x-limelight-intercepted"] = "fetch";
606
+ modifiedInit.headers = new Headers(headers);
607
+ let requestBodyToSerialize = init.body;
608
+ if (input instanceof Request && !requestBodyToSerialize) {
609
+ try {
610
+ const clonedRequest = input.clone();
611
+ const contentType = clonedRequest.headers.get("content-type") || "";
612
+ if (contentType.includes("application/json") || contentType.includes("text/")) {
613
+ requestBodyToSerialize = await clonedRequest.text();
614
+ } else {
615
+ requestBodyToSerialize = await clonedRequest.blob();
616
+ }
617
+ } catch {
618
+ requestBodyToSerialize = void 0;
619
+ console.warn(
620
+ "[Limelight] Failed to read request body from Request object"
621
+ );
622
+ }
623
+ }
624
+ const requestBody = serializeBody(
625
+ requestBodyToSerialize,
626
+ self.config?.disableBodyCapture
627
+ );
628
+ let graphqlData = void 0;
629
+ if (self.config?.enableGraphQL && isGraphQLRequest(url, requestBody)) {
630
+ const rawBody = requestBody?.raw;
631
+ if (rawBody) {
632
+ graphqlData = parseGraphQL(rawBody) ?? void 0;
633
+ }
634
+ }
635
+ let requestEvent = {
636
+ id: requestId,
637
+ sessionId: self.getSessionId(),
638
+ timestamp: startTime,
639
+ phase: "REQUEST" /* REQUEST */,
640
+ networkType: "fetch" /* FETCH */,
641
+ url,
642
+ method,
643
+ headers: redactSensitiveHeaders(headers),
644
+ body: requestBody,
645
+ name: formatRequestName(url),
646
+ initiator: getInitiator(),
647
+ requestSize: requestBody?.size ?? 0,
648
+ graphql: graphqlData
649
+ };
650
+ if (self.config?.beforeSend) {
651
+ const modifiedEvent = self.config.beforeSend(requestEvent);
652
+ if (!modifiedEvent) {
653
+ return self.originalFetch(input, modifiedInit);
654
+ }
655
+ if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
656
+ console.error("[Limelight] beforeSend must return same event type");
657
+ return self.originalFetch(input, modifiedInit);
658
+ }
659
+ requestEvent = modifiedEvent;
660
+ }
661
+ self.sendMessage(requestEvent);
662
+ try {
663
+ const response = await self.originalFetch(input, modifiedInit);
664
+ const clone = response.clone();
665
+ const endTime = Date.now();
666
+ const duration = endTime - startTime;
667
+ const responseHeaders = {};
668
+ response.headers.forEach((value, key) => {
669
+ responseHeaders[key] = value;
670
+ });
671
+ let responseText;
672
+ try {
673
+ responseText = await clone.text();
674
+ } catch (cloneError) {
675
+ responseText = void 0;
676
+ }
677
+ const responseBody = serializeBody(
678
+ responseText,
679
+ self.config?.disableBodyCapture
680
+ );
681
+ let responseEvent = {
682
+ id: requestId,
683
+ sessionId: self.getSessionId(),
684
+ timestamp: endTime,
685
+ phase: "RESPONSE" /* RESPONSE */,
686
+ networkType: "fetch" /* FETCH */,
687
+ status: response.status,
688
+ statusText: response.statusText,
689
+ headers: redactSensitiveHeaders(responseHeaders),
690
+ body: responseBody,
691
+ duration,
692
+ responseSize: responseBody?.size ?? 0,
693
+ redirected: response.redirected,
694
+ ok: response.ok
695
+ };
696
+ if (self.config?.beforeSend) {
697
+ const modifiedEvent = self.config.beforeSend(responseEvent);
698
+ if (!modifiedEvent) {
699
+ return response;
700
+ }
701
+ if (modifiedEvent.phase !== "RESPONSE" /* RESPONSE */) {
702
+ console.error("[Limelight] beforeSend must return same event type");
703
+ return response;
704
+ }
705
+ responseEvent = modifiedEvent;
706
+ }
707
+ self.sendMessage(responseEvent);
708
+ return response;
709
+ } catch (err) {
710
+ const errorMessage = err instanceof Error ? err.message : String(err);
711
+ const errorStack = err instanceof Error ? err.stack : void 0;
712
+ let errorEvent = {
713
+ id: requestId,
714
+ sessionId: self.getSessionId(),
715
+ timestamp: Date.now(),
716
+ phase: "ERROR" /* ERROR */,
717
+ networkType: "fetch" /* FETCH */,
718
+ errorMessage,
719
+ stack: errorStack
720
+ };
721
+ if (self.config?.beforeSend) {
722
+ const modifiedEvent = self.config.beforeSend(errorEvent);
723
+ if (modifiedEvent && modifiedEvent.phase === "ERROR" /* ERROR */) {
724
+ errorEvent = modifiedEvent;
725
+ }
726
+ }
727
+ self.sendMessage(errorEvent);
728
+ throw err;
729
+ }
730
+ };
731
+ }
732
+ /**
733
+ * Restores the original fetch function and removes all interception.
734
+ * @returns {void}
735
+ */
736
+ cleanup() {
737
+ if (!this.isSetup) {
738
+ console.warn("[Limelight] Network interceptor not set up");
739
+ return;
740
+ }
741
+ this.isSetup = false;
742
+ global.fetch = this.originalFetch;
743
+ }
744
+ };
745
+
746
+ // src/limelight/interceptors/XHRInterceptor.ts
747
+ var XHRInterceptor = class {
748
+ constructor(sendMessage, getSessionId) {
749
+ this.sendMessage = sendMessage;
750
+ this.getSessionId = getSessionId;
751
+ this.originalXHROpen = XMLHttpRequest.prototype.open;
752
+ this.originalXHRSend = XMLHttpRequest.prototype.send;
753
+ this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
754
+ }
755
+ originalXHROpen;
756
+ originalXHRSend;
757
+ originalXHRSetRequestHeader;
758
+ isSetup = false;
759
+ config = null;
760
+ /**
761
+ * Sets up XHR interception by wrapping XMLHttpRequest methods.
762
+ * Intercepts open, setRequestHeader, and send to capture network events.
763
+ * Prevents double setup to avoid losing original method references.
764
+ * @param {LimelightConfig} config - Configuration object for Limelight
765
+ * @returns {void}
766
+ */
767
+ setup(config) {
768
+ if (this.isSetup) {
769
+ console.warn("[Limelight] XHR interceptor already set up");
770
+ return;
771
+ }
772
+ this.isSetup = true;
773
+ this.config = config;
774
+ const self = this;
775
+ XMLHttpRequest.prototype.open = function(method, url) {
776
+ this._limelightData = {
777
+ id: generateRequestId(),
778
+ method,
779
+ url,
780
+ headers: {},
781
+ startTime: Date.now(),
782
+ listeners: /* @__PURE__ */ new Map()
783
+ };
784
+ return self.originalXHROpen.apply(
785
+ this,
786
+ arguments
787
+ );
788
+ };
789
+ XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
790
+ if (this._limelightData) {
791
+ this._limelightData.headers[header] = value;
792
+ if (header.toLowerCase() === "x-limelight-intercepted" && value === "fetch") {
793
+ this._limelightData.skipIntercept = true;
794
+ }
795
+ }
796
+ return self.originalXHRSetRequestHeader.apply(this, arguments);
797
+ };
798
+ XMLHttpRequest.prototype.send = function(body) {
799
+ const data = this._limelightData;
800
+ if (data?.skipIntercept) {
801
+ return self.originalXHRSend.apply(this, arguments);
802
+ }
803
+ if (data) {
804
+ const requestBody = serializeBody(
805
+ body,
806
+ self.config?.disableBodyCapture
807
+ );
808
+ let requestEvent = {
809
+ id: data.id,
810
+ sessionId: self.getSessionId(),
811
+ timestamp: data.startTime,
812
+ phase: "REQUEST" /* REQUEST */,
813
+ networkType: "xhr" /* XHR */,
814
+ url: data.url,
815
+ method: data.method,
816
+ headers: redactSensitiveHeaders(data.headers),
817
+ body: requestBody,
818
+ name: formatRequestName(data.url),
819
+ initiator: getInitiator(),
820
+ requestSize: requestBody?.size ?? 0
821
+ };
822
+ if (self.config?.beforeSend) {
823
+ const modifiedEvent = self.config.beforeSend(requestEvent);
824
+ if (!modifiedEvent) {
825
+ return self.originalXHRSend.apply(this, arguments);
826
+ }
827
+ if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
828
+ console.error("[Limelight] beforeSend must return same event type");
829
+ return self.originalXHRSend.apply(this, arguments);
830
+ }
831
+ requestEvent = modifiedEvent;
832
+ }
833
+ self.sendMessage(requestEvent);
834
+ let responseSent = false;
835
+ const cleanup = () => {
836
+ if (data.listeners) {
837
+ data.listeners.forEach((listener, event) => {
838
+ this.removeEventListener(event, listener);
839
+ });
840
+ data.listeners.clear();
841
+ }
842
+ delete this._limelightData;
843
+ };
844
+ const sendResponse = () => {
845
+ if (responseSent) return;
846
+ responseSent = true;
847
+ const endTime = Date.now();
848
+ const duration = endTime - data.startTime;
849
+ const responseHeaders = self.parseResponseHeaders(
850
+ this.getAllResponseHeaders()
851
+ );
852
+ const responseBody = serializeBody(
853
+ this.responseText || this.response,
854
+ self.config?.disableBodyCapture
855
+ );
856
+ let responseEvent = {
857
+ id: data.id,
858
+ sessionId: self.getSessionId(),
859
+ timestamp: endTime,
860
+ phase: "RESPONSE" /* RESPONSE */,
861
+ networkType: "xhr" /* XHR */,
862
+ status: this.status,
863
+ statusText: this.statusText,
864
+ headers: redactSensitiveHeaders(responseHeaders),
865
+ body: responseBody,
866
+ duration,
867
+ responseSize: responseBody?.size ?? 0,
868
+ redirected: false,
869
+ ok: this.status >= 200 && this.status < 300
870
+ };
871
+ if (self.config?.beforeSend) {
872
+ const modifiedEvent = self.config.beforeSend(responseEvent);
873
+ if (!modifiedEvent) {
874
+ return;
875
+ }
876
+ if (modifiedEvent.phase !== "RESPONSE" /* RESPONSE */) {
877
+ console.error(
878
+ "[Limelight] beforeSend must return same event type"
879
+ );
880
+ return;
881
+ }
882
+ responseEvent = modifiedEvent;
883
+ }
884
+ self.sendMessage(responseEvent);
885
+ cleanup.call(this);
886
+ };
887
+ const sendError = (errorMessage) => {
888
+ if (responseSent) return;
889
+ responseSent = true;
890
+ let errorEvent = {
891
+ id: data.id,
892
+ sessionId: self.getSessionId(),
893
+ timestamp: Date.now(),
894
+ phase: "ERROR" /* ERROR */,
895
+ networkType: "xhr" /* XHR */,
896
+ errorMessage
897
+ };
898
+ if (self.config?.beforeSend) {
899
+ const modifiedEvent = self.config.beforeSend(errorEvent);
900
+ if (modifiedEvent && modifiedEvent.phase === "ERROR" /* ERROR */) {
901
+ errorEvent = modifiedEvent;
902
+ }
903
+ }
904
+ self.sendMessage(errorEvent);
905
+ cleanup.call(this);
906
+ };
907
+ const readyStateChangeHandler = function() {
908
+ if ((this.readyState === 3 || this.readyState === 4) && this.status !== 0) {
909
+ sendResponse.call(this);
910
+ }
911
+ };
912
+ const loadHandler = function() {
913
+ sendResponse.call(this);
914
+ };
915
+ const errorHandler = function() {
916
+ sendError("Network request failed");
917
+ cleanup.call(this);
918
+ };
919
+ const abortHandler = function() {
920
+ sendError("Request aborted");
921
+ cleanup.call(this);
922
+ };
923
+ const timeoutHandler = function() {
924
+ sendError("Request timeout");
925
+ cleanup.call(this);
926
+ };
927
+ const loadEndHandler = function() {
928
+ cleanup.call(this);
929
+ };
930
+ this.addEventListener("readystatechange", readyStateChangeHandler);
931
+ this.addEventListener("load", loadHandler);
932
+ this.addEventListener("error", errorHandler);
933
+ this.addEventListener("abort", abortHandler);
934
+ this.addEventListener("timeout", timeoutHandler);
935
+ this.addEventListener("loadend", loadEndHandler);
936
+ data.listeners.set("readystatechange", readyStateChangeHandler);
937
+ data.listeners.set("load", loadHandler);
938
+ data.listeners.set("error", errorHandler);
939
+ data.listeners.set("abort", abortHandler);
940
+ data.listeners.set("timeout", timeoutHandler);
941
+ data.listeners.set("loadend", loadEndHandler);
942
+ }
943
+ return self.originalXHRSend.apply(this, arguments);
944
+ };
945
+ }
946
+ /**
947
+ * Parses raw HTTP header string into a key-value object.
948
+ * @private
949
+ * @param {string} headerString - Raw header string from getAllResponseHeaders()
950
+ * @returns {Record<string, string>} Parsed headers object
951
+ */
952
+ parseResponseHeaders(headerString) {
953
+ const headers = {};
954
+ headerString.split("\r\n").forEach((line) => {
955
+ const colonIndex = line.indexOf(": ");
956
+ if (colonIndex > 0) {
957
+ const key = line.substring(0, colonIndex);
958
+ const value = line.substring(colonIndex + 2);
959
+ headers[key] = value;
960
+ }
961
+ });
962
+ return headers;
963
+ }
964
+ /**
965
+ * Restores original XMLHttpRequest methods and removes all interception.
966
+ * @returns {void}
967
+ */
968
+ cleanup() {
969
+ if (!this.isSetup) {
970
+ console.warn("[Limelight] XHR interceptor not set up");
971
+ return;
972
+ }
973
+ this.isSetup = false;
974
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
975
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
976
+ XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
977
+ }
978
+ };
979
+
980
+ // src/limelight/LimelightClient.ts
981
+ var LimelightClient = class {
982
+ ws = null;
983
+ config = null;
984
+ sessionId = "";
985
+ reconnectAttempts = 0;
986
+ maxReconnectAttempts = 5;
987
+ reconnectDelay = 1e3;
988
+ reconnectTimer = null;
989
+ messageQueue = [];
990
+ maxQueueSize = 100;
991
+ networkInterceptor;
992
+ xhrInterceptor;
993
+ consoleInterceptor;
994
+ constructor() {
995
+ this.networkInterceptor = new NetworkInterceptor(
996
+ this.sendMessage.bind(this),
997
+ () => this.sessionId
998
+ );
999
+ this.xhrInterceptor = new XHRInterceptor(
1000
+ this.sendMessage.bind(this),
1001
+ () => this.sessionId
1002
+ );
1003
+ this.consoleInterceptor = new ConsoleInterceptor(
1004
+ this.sendMessage.bind(this),
1005
+ () => this.sessionId
1006
+ );
1007
+ }
1008
+ /**
1009
+ * Configures the Limelight client with the provided settings.
1010
+ * Sets up network, XHR, and console interceptors based on the configuration.
1011
+ * @internal
1012
+ * @private
1013
+ * @param {LimelightConfig} config - Configuration object for Limelight
1014
+ * @returns {void}
1015
+ */
1016
+ configure(config) {
1017
+ const isEnabled = config.enabled ?? isDevelopment();
1018
+ this.config = {
1019
+ appName: "Limelight App",
1020
+ serverUrl: `ws://localhost:${DEFAULT_PORT}${WS_PATH}`,
1021
+ enabled: isEnabled,
1022
+ enableNetworkInspector: true,
1023
+ enableConsole: true,
1024
+ enableGraphQL: true,
1025
+ ...config
1026
+ };
1027
+ if (!this.config.enabled) {
1028
+ return;
1029
+ }
1030
+ this.sessionId = createSessionId();
1031
+ try {
1032
+ if (this.config.enableNetworkInspector) {
1033
+ this.networkInterceptor.setup(this.config);
1034
+ this.xhrInterceptor.setup(this.config);
1035
+ }
1036
+ if (this.config.enableConsole) {
1037
+ this.consoleInterceptor.setup(this.config);
1038
+ }
1039
+ } catch (error) {
1040
+ console.error("[Limelight] Failed to setup interceptors:", error);
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Establishes a WebSocket connection to the Limelight server.
1045
+ * If a config object is provided, it will configure the client before connecting.
1046
+ *
1047
+ * If no config is provided and the client hasn't been configured, it will use default settings.
1048
+ *
1049
+ * Prevents multiple simultaneous connections and handles reconnection logic.
1050
+ *
1051
+ * @param {LimelightConfig} [config] - Optional configuration object.
1052
+ * @returns {void}
1053
+ */
1054
+ connect(config) {
1055
+ if (config) {
1056
+ this.configure(config);
1057
+ } else if (!this.config) {
1058
+ this.configure({});
1059
+ }
1060
+ if (!this.config?.enabled) {
1061
+ return;
1062
+ }
1063
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1064
+ console.warn("[Limelight] Already connected. Call disconnect() first.");
1065
+ return;
1066
+ }
1067
+ if (this.ws) {
1068
+ const oldWs = this.ws;
1069
+ oldWs.onclose = null;
1070
+ oldWs.onerror = null;
1071
+ oldWs.onopen = null;
1072
+ if (oldWs.readyState === 1) {
1073
+ oldWs.close();
1074
+ }
1075
+ this.ws = null;
1076
+ }
1077
+ const { serverUrl, appName, platform } = this.config;
1078
+ if (!serverUrl) {
1079
+ console.error("[Limelight] serverUrl missing in configuration.");
1080
+ return;
1081
+ }
1082
+ try {
1083
+ this.ws = new WebSocket(serverUrl);
1084
+ const message = {
1085
+ phase: "CONNECT",
1086
+ sessionId: this.sessionId,
1087
+ timestamp: Date.now(),
1088
+ data: {
1089
+ appName,
1090
+ platform: platform || "react-native"
1091
+ }
1092
+ };
1093
+ this.ws.onopen = () => {
1094
+ this.reconnectAttempts = 0;
1095
+ this.flushMessageQueue();
1096
+ this.sendMessage(message);
1097
+ };
1098
+ this.ws.onerror = (error) => {
1099
+ console.error("[Limelight] WebSocket error:", error);
1100
+ };
1101
+ this.ws.onclose = () => {
1102
+ this.attemptReconnect();
1103
+ };
1104
+ } catch (error) {
1105
+ console.error("[Limelight] Failed to connect:", error);
1106
+ this.attemptReconnect();
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Attempts to reconnect to the Limelight server using exponential backoff.
1111
+ * Will retry up to maxReconnectAttempts times with increasing delays.
1112
+ * Maximum delay is capped at 30 seconds.
1113
+ * @private
1114
+ * @returns {void}
1115
+ */
1116
+ attemptReconnect() {
1117
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1118
+ return;
1119
+ }
1120
+ if (this.reconnectTimer) {
1121
+ clearTimeout(this.reconnectTimer);
1122
+ this.reconnectTimer = null;
1123
+ }
1124
+ this.reconnectAttempts++;
1125
+ const delay = Math.min(
1126
+ this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
1127
+ 3e4
1128
+ );
1129
+ this.reconnectTimer = setTimeout(() => {
1130
+ this.reconnectTimer = null;
1131
+ this.connect();
1132
+ }, delay);
1133
+ }
1134
+ /**
1135
+ * Sends all queued messages to the server.
1136
+ * Only executes if the WebSocket connection is open.
1137
+ * @private
1138
+ * @returns {void}
1139
+ */
1140
+ flushMessageQueue() {
1141
+ if (this.ws?.readyState !== WebSocket.OPEN) return;
1142
+ while (this.messageQueue.length > 0) {
1143
+ const message = this.messageQueue.shift();
1144
+ try {
1145
+ this.ws.send(safeStringify(message));
1146
+ } catch (error) {
1147
+ console.error("[Limelight] Failed to send queued message:", error);
1148
+ }
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Sends a message to the Limelight server or queues it if not connected.
1153
+ * Messages are automatically queued when the connection is not open.
1154
+ * If the queue is full, the oldest message will be dropped.
1155
+ * @private
1156
+ * @param {LimelightMessage} message - The message to send
1157
+ * @returns {void}
1158
+ */
1159
+ sendMessage(message) {
1160
+ if (this.ws?.readyState === WebSocket.OPEN) {
1161
+ this.flushMessageQueue();
1162
+ if (this.ws?.readyState === WebSocket.OPEN) {
1163
+ try {
1164
+ this.ws.send(safeStringify(message));
1165
+ } catch (error) {
1166
+ console.error("[Limelight] Failed to send message:", error);
1167
+ this.messageQueue.push(message);
1168
+ }
1169
+ } else {
1170
+ this.messageQueue.push(message);
1171
+ }
1172
+ } else {
1173
+ if (this.messageQueue.length >= this.maxQueueSize) {
1174
+ console.warn("[Limelight] Message queue full, dropping oldest message");
1175
+ this.messageQueue.shift();
1176
+ }
1177
+ this.messageQueue.push(message);
1178
+ }
1179
+ }
1180
+ /**
1181
+ * Disconnects from the Limelight server and cleans up resources.
1182
+ * Closes the WebSocket connection, removes all interceptors, and resets connection state.
1183
+ * Preserves configuration and session ID for potential reconnection.
1184
+ * @returns {void}
1185
+ */
1186
+ disconnect() {
1187
+ if (this.ws) {
1188
+ this.ws.onopen = null;
1189
+ this.ws.onerror = null;
1190
+ this.ws.onclose = null;
1191
+ try {
1192
+ if (this.ws.readyState === 0 || this.ws.readyState === 1) {
1193
+ if ("terminate" in this.ws && typeof this.ws.terminate === "function") {
1194
+ if (this.ws._socket) {
1195
+ this.ws.terminate();
1196
+ } else {
1197
+ this.ws.readyState = 3;
1198
+ }
1199
+ } else {
1200
+ this.ws.close();
1201
+ }
1202
+ }
1203
+ } catch (e) {
1204
+ }
1205
+ this.ws = null;
1206
+ }
1207
+ if (this.reconnectTimer) {
1208
+ clearTimeout(this.reconnectTimer);
1209
+ this.reconnectTimer = null;
1210
+ }
1211
+ this.networkInterceptor.cleanup();
1212
+ this.xhrInterceptor.cleanup();
1213
+ this.consoleInterceptor.cleanup();
1214
+ this.reconnectAttempts = 0;
1215
+ this.messageQueue = [];
1216
+ }
1217
+ /**
1218
+ * Performs a complete reset of the Limelight client.
1219
+ * Disconnects from the server and clears all configuration and session data.
1220
+ * After calling reset(), connect() must be called again.
1221
+ * @returns {void}
1222
+ */
1223
+ reset() {
1224
+ this.disconnect();
1225
+ this.config = null;
1226
+ this.sessionId = "";
1227
+ }
1228
+ };
1229
+ var Limelight = new LimelightClient();
1230
+ // Annotate the CommonJS export names for ESM import in node:
1231
+ 0 && (module.exports = {
1232
+ BodyFormat,
1233
+ ConsoleLevel,
1234
+ ConsoleSource,
1235
+ ConsoleType,
1236
+ EventType,
1237
+ GraphqlOprtation,
1238
+ HttpMethod,
1239
+ HttpStatusClass,
1240
+ Limelight,
1241
+ NetworkPhase,
1242
+ NetworkType
1243
+ });
1244
+ //# sourceMappingURL=index.js.map