@getlimelight/sdk 0.6.1 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ // Bridge Node CJS require to globalThis so runtime dynamic requires work
2
+ // in environments like ts-node where require is module-scoped only.
3
+ // In ESM or browser contexts typeof require is 'undefined', so this is a no-op.
4
+ if (typeof require === "function" && typeof globalThis !== "undefined" && typeof globalThis.require === "undefined" && typeof process !== "undefined" && process.versions && process.versions.node) { Object.defineProperty(globalThis, "require", { value: require, configurable: true, writable: true, enumerable: false }); }
1
5
  "use strict";
2
6
  var __defProp = Object.defineProperty;
3
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -66,6 +70,8 @@ var NetworkType = /* @__PURE__ */ ((NetworkType2) => {
66
70
  NetworkType2["FETCH"] = "fetch";
67
71
  NetworkType2["XHR"] = "xhr";
68
72
  NetworkType2["GRAPHQL"] = "graphql";
73
+ NetworkType2["INCOMING"] = "incoming";
74
+ NetworkType2["HTTP"] = "http";
69
75
  return NetworkType2;
70
76
  })(NetworkType || {});
71
77
  var NetworkPhase = /* @__PURE__ */ ((NetworkPhase2) => {
@@ -86,17 +92,17 @@ var BodyFormat = /* @__PURE__ */ ((BodyFormat2) => {
86
92
  BodyFormat2["UNSERIALIZABLE"] = "UNSERIALIZABLE";
87
93
  return BodyFormat2;
88
94
  })(BodyFormat || {});
89
- var HttpMethod = /* @__PURE__ */ ((HttpMethod5) => {
90
- HttpMethod5["GET"] = "GET";
91
- HttpMethod5["POST"] = "POST";
92
- HttpMethod5["PUT"] = "PUT";
93
- HttpMethod5["PATCH"] = "PATCH";
94
- HttpMethod5["DELETE"] = "DELETE";
95
- HttpMethod5["HEAD"] = "HEAD";
96
- HttpMethod5["OPTIONS"] = "OPTIONS";
97
- HttpMethod5["TRACE"] = "TRACE";
98
- HttpMethod5["CONNECT"] = "CONNECT";
99
- return HttpMethod5;
95
+ var HttpMethod = /* @__PURE__ */ ((HttpMethod7) => {
96
+ HttpMethod7["GET"] = "GET";
97
+ HttpMethod7["POST"] = "POST";
98
+ HttpMethod7["PUT"] = "PUT";
99
+ HttpMethod7["PATCH"] = "PATCH";
100
+ HttpMethod7["DELETE"] = "DELETE";
101
+ HttpMethod7["HEAD"] = "HEAD";
102
+ HttpMethod7["OPTIONS"] = "OPTIONS";
103
+ HttpMethod7["TRACE"] = "TRACE";
104
+ HttpMethod7["CONNECT"] = "CONNECT";
105
+ return HttpMethod7;
100
106
  })(HttpMethod || {});
101
107
  var HttpStatusClass = /* @__PURE__ */ ((HttpStatusClass2) => {
102
108
  HttpStatusClass2[HttpStatusClass2["INFORMATIONAL"] = 100] = "INFORMATIONAL";
@@ -286,7 +292,7 @@ var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
286
292
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
287
293
  var LIMELIGHT_MCP_WS_URL = "ws://localhost:9229";
288
294
  var WS_PATH = "/limelight";
289
- var SDK_VERSION = true ? "0.6.1" : "test-version";
295
+ var SDK_VERSION = true ? "0.7.8" : "test-version";
290
296
  var RENDER_THRESHOLDS = {
291
297
  HOT_VELOCITY: 5,
292
298
  HIGH_RENDER_COUNT: 50,
@@ -300,6 +306,16 @@ var RENDER_THRESHOLDS = {
300
306
  TOP_PROPS_TO_REPORT: 5
301
307
  // Only report top N changed props
302
308
  };
309
+ var BINARY_CONTENT_TYPES = [
310
+ "image/",
311
+ "audio/",
312
+ "video/",
313
+ "application/octet-stream",
314
+ "application/pdf",
315
+ "application/zip",
316
+ "application/gzip"
317
+ ];
318
+ var MAX_BODY_SIZE = 1024 * 1024;
303
319
 
304
320
  // src/helpers/safety/redactSensitiveHeaders.ts
305
321
  var redactSensitiveHeaders = (headers) => {
@@ -540,6 +556,9 @@ var formatRequestName = (url) => {
540
556
  }
541
557
  };
542
558
 
559
+ // src/helpers/utils/environment.ts
560
+ var hasDOM = () => typeof window !== "undefined" && typeof document !== "undefined";
561
+
543
562
  // src/helpers/render/generateRenderId.ts
544
563
  var counter = 0;
545
564
  var generateRenderId = () => {
@@ -576,6 +595,21 @@ var getCurrentTransactionId = () => {
576
595
  return globalGetTransactionId?.() ?? null;
577
596
  };
578
597
 
598
+ // src/helpers/http/resolveUrl.ts
599
+ var resolveUrl = (protocol, options) => {
600
+ const host = options.hostname || options.host || "localhost";
601
+ const port = options.port ? `:${options.port}` : "";
602
+ const path = options.path || "/";
603
+ return `${protocol}//${host}${port}${path}`;
604
+ };
605
+
606
+ // src/helpers/http/isBinaryContentType.ts
607
+ var isBinaryContentType = (contentType) => {
608
+ return BINARY_CONTENT_TYPES.some(
609
+ (type) => contentType.toLowerCase().includes(type)
610
+ );
611
+ };
612
+
579
613
  // src/limelight/interceptors/ConsoleInterceptor.ts
580
614
  var ConsoleInterceptor = class {
581
615
  constructor(sendMessage, getSessionId) {
@@ -705,13 +739,31 @@ var generateRequestId = () => {
705
739
  return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
706
740
  };
707
741
 
742
+ // src/limelight/context/traceContext.ts
743
+ var _resolved = false;
744
+ var _traceContext;
745
+ var getTraceContext = () => {
746
+ if (!_resolved) {
747
+ _resolved = true;
748
+ try {
749
+ const _require = globalThis["require"];
750
+ const { AsyncLocalStorage } = _require("node:async_hooks");
751
+ _traceContext = new AsyncLocalStorage();
752
+ } catch {
753
+ }
754
+ }
755
+ return _traceContext;
756
+ };
757
+
708
758
  // src/limelight/interceptors/NetworkInterceptor.ts
709
759
  var NetworkInterceptor = class {
710
760
  constructor(sendMessage, getSessionId) {
711
761
  this.sendMessage = sendMessage;
712
762
  this.getSessionId = getSessionId;
713
763
  this.globalObject = detectGlobalObject();
714
- this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
764
+ if (typeof this.globalObject.fetch === "function") {
765
+ this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
766
+ }
715
767
  }
716
768
  originalFetch;
717
769
  config = null;
@@ -731,9 +783,18 @@ var NetworkInterceptor = class {
731
783
  }
732
784
  return;
733
785
  }
786
+ if (!this.originalFetch) {
787
+ if (config?.enableInternalLogging) {
788
+ console.warn(
789
+ "[Limelight] fetch is not available in this environment, skipping network interception"
790
+ );
791
+ }
792
+ return;
793
+ }
734
794
  this.isSetup = true;
735
795
  this.config = config;
736
796
  const self2 = this;
797
+ const originalFetch = this.originalFetch;
737
798
  this.globalObject.fetch = async function(input, init = {}) {
738
799
  const requestId = generateRequestId();
739
800
  const startTime = Date.now();
@@ -751,6 +812,12 @@ var NetworkInterceptor = class {
751
812
  });
752
813
  }
753
814
  headers["x-limelight-intercepted"] = "fetch";
815
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
816
+ if (!headers[traceHeaderName]) {
817
+ const existingTraceId = getTraceContext()?.getStore()?.traceId;
818
+ headers[traceHeaderName] = existingTraceId || generateRequestId();
819
+ }
820
+ const traceId = headers[traceHeaderName];
754
821
  modifiedInit.headers = new Headers(headers);
755
822
  let requestBodyToSerialize = init.body;
756
823
  if (input instanceof Request && !requestBodyToSerialize) {
@@ -784,6 +851,7 @@ var NetworkInterceptor = class {
784
851
  }
785
852
  let requestEvent = {
786
853
  id: requestId,
854
+ traceId,
787
855
  sessionId: self2.getSessionId(),
788
856
  timestamp: startTime,
789
857
  phase: "REQUEST" /* REQUEST */,
@@ -800,17 +868,17 @@ var NetworkInterceptor = class {
800
868
  if (self2.config?.beforeSend) {
801
869
  const modifiedEvent = self2.config.beforeSend(requestEvent);
802
870
  if (!modifiedEvent) {
803
- return self2.originalFetch(input, modifiedInit);
871
+ return originalFetch(input, modifiedInit);
804
872
  }
805
873
  if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
806
874
  console.error("[Limelight] beforeSend must return same event type");
807
- return self2.originalFetch(input, modifiedInit);
875
+ return originalFetch(input, modifiedInit);
808
876
  }
809
877
  requestEvent = modifiedEvent;
810
878
  }
811
879
  self2.sendMessage(requestEvent);
812
880
  try {
813
- const response = await self2.originalFetch(input, modifiedInit);
881
+ const response = await originalFetch(input, modifiedInit);
814
882
  const clone = response.clone();
815
883
  const endTime = Date.now();
816
884
  const duration = endTime - startTime;
@@ -830,6 +898,7 @@ var NetworkInterceptor = class {
830
898
  );
831
899
  let responseEvent = {
832
900
  id: requestId,
901
+ traceId,
833
902
  sessionId: self2.getSessionId(),
834
903
  timestamp: endTime,
835
904
  phase: "RESPONSE" /* RESPONSE */,
@@ -862,6 +931,7 @@ var NetworkInterceptor = class {
862
931
  const errorStack = err instanceof Error ? err.stack : void 0;
863
932
  let errorEvent = {
864
933
  id: requestId,
934
+ traceId,
865
935
  sessionId: self2.getSessionId(),
866
936
  timestamp: Date.now(),
867
937
  phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
@@ -892,7 +962,9 @@ var NetworkInterceptor = class {
892
962
  return;
893
963
  }
894
964
  this.isSetup = false;
895
- this.globalObject.fetch = this.originalFetch;
965
+ if (this.originalFetch) {
966
+ this.globalObject.fetch = this.originalFetch;
967
+ }
896
968
  }
897
969
  };
898
970
 
@@ -901,9 +973,11 @@ var XHRInterceptor = class {
901
973
  constructor(sendMessage, getSessionId) {
902
974
  this.sendMessage = sendMessage;
903
975
  this.getSessionId = getSessionId;
904
- this.originalXHROpen = XMLHttpRequest.prototype.open;
905
- this.originalXHRSend = XMLHttpRequest.prototype.send;
906
- this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
976
+ if (typeof XMLHttpRequest !== "undefined") {
977
+ this.originalXHROpen = XMLHttpRequest.prototype.open;
978
+ this.originalXHRSend = XMLHttpRequest.prototype.send;
979
+ this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
980
+ }
907
981
  }
908
982
  originalXHROpen;
909
983
  originalXHRSend;
@@ -918,6 +992,9 @@ var XHRInterceptor = class {
918
992
  * @returns {void}
919
993
  */
920
994
  setup(config) {
995
+ if (typeof XMLHttpRequest === "undefined") {
996
+ return;
997
+ }
921
998
  if (this.isSetup) {
922
999
  if (this.config?.enableInternalLogging) {
923
1000
  console.warn("[Limelight] XHR interceptor already set up");
@@ -956,12 +1033,24 @@ var XHRInterceptor = class {
956
1033
  return self2.originalXHRSend.apply(this, arguments);
957
1034
  }
958
1035
  if (data) {
1036
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
1037
+ if (!data.headers[traceHeaderName]) {
1038
+ data.traceId = generateRequestId();
1039
+ self2.originalXHRSetRequestHeader.call(
1040
+ this,
1041
+ traceHeaderName,
1042
+ data.traceId
1043
+ );
1044
+ } else {
1045
+ data.traceId = data.headers[traceHeaderName];
1046
+ }
959
1047
  const requestBody = serializeBody(
960
1048
  body,
961
1049
  self2.config?.disableBodyCapture
962
1050
  );
963
1051
  let requestEvent = {
964
1052
  id: data.id,
1053
+ traceId: data.traceId,
965
1054
  sessionId: self2.getSessionId(),
966
1055
  timestamp: data.startTime,
967
1056
  phase: "REQUEST" /* REQUEST */,
@@ -1021,6 +1110,7 @@ var XHRInterceptor = class {
1021
1110
  );
1022
1111
  let responseEvent = {
1023
1112
  id: data.id,
1113
+ traceId: data.traceId,
1024
1114
  sessionId: self2.getSessionId(),
1025
1115
  timestamp: endTime,
1026
1116
  phase: "RESPONSE" /* RESPONSE */,
@@ -1055,6 +1145,7 @@ var XHRInterceptor = class {
1055
1145
  responseSent = true;
1056
1146
  let errorEvent = {
1057
1147
  id: data.id,
1148
+ traceId: data.traceId,
1058
1149
  sessionId: self2.getSessionId(),
1059
1150
  timestamp: Date.now(),
1060
1151
  phase,
@@ -1133,15 +1224,368 @@ var XHRInterceptor = class {
1133
1224
  */
1134
1225
  cleanup() {
1135
1226
  if (!this.isSetup) {
1227
+ return;
1228
+ }
1229
+ this.isSetup = false;
1230
+ if (typeof XMLHttpRequest !== "undefined") {
1231
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
1232
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
1233
+ XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1234
+ }
1235
+ }
1236
+ };
1237
+
1238
+ // src/limelight/interceptors/HttpInterceptor.ts
1239
+ var HttpInterceptor = class {
1240
+ constructor(sendMessage, getSessionId) {
1241
+ this.sendMessage = sendMessage;
1242
+ this.getSessionId = getSessionId;
1243
+ try {
1244
+ const _require = globalThis["require"];
1245
+ this.httpModule = _require("http");
1246
+ this.httpsModule = _require("https");
1247
+ this.originalHttpRequest = this.httpModule.request;
1248
+ this.originalHttpGet = this.httpModule.get;
1249
+ this.originalHttpsRequest = this.httpsModule.request;
1250
+ this.originalHttpsGet = this.httpsModule.get;
1251
+ } catch {
1252
+ }
1253
+ }
1254
+ originalHttpRequest = null;
1255
+ originalHttpGet = null;
1256
+ originalHttpsRequest = null;
1257
+ originalHttpsGet = null;
1258
+ httpModule = null;
1259
+ httpsModule = null;
1260
+ config = null;
1261
+ isSetup = false;
1262
+ setup(config) {
1263
+ if (this.isSetup) {
1136
1264
  if (this.config?.enableInternalLogging) {
1137
- console.warn("[Limelight] XHR interceptor not set up");
1265
+ console.warn("[Limelight] HTTP interceptor already set up");
1266
+ }
1267
+ return;
1268
+ }
1269
+ if (!this.httpModule || !this.httpsModule) {
1270
+ if (config?.enableInternalLogging) {
1271
+ console.warn(
1272
+ "[Limelight] Node http module not available, skipping HTTP interception"
1273
+ );
1274
+ }
1275
+ return;
1276
+ }
1277
+ this.isSetup = true;
1278
+ this.config = config;
1279
+ const self2 = this;
1280
+ const httpMod = this.httpModule;
1281
+ const httpsMod = this.httpsModule;
1282
+ httpMod.request = (...args) => {
1283
+ return self2.interceptRequest(
1284
+ "http:",
1285
+ self2.originalHttpRequest,
1286
+ httpMod,
1287
+ args
1288
+ );
1289
+ };
1290
+ httpMod.get = (...args) => {
1291
+ const req = self2.interceptRequest(
1292
+ "http:",
1293
+ self2.originalHttpRequest,
1294
+ httpMod,
1295
+ args
1296
+ );
1297
+ req.end();
1298
+ return req;
1299
+ };
1300
+ httpsMod.request = (...args) => {
1301
+ return self2.interceptRequest(
1302
+ "https:",
1303
+ self2.originalHttpsRequest,
1304
+ httpsMod,
1305
+ args
1306
+ );
1307
+ };
1308
+ httpsMod.get = (...args) => {
1309
+ const req = self2.interceptRequest(
1310
+ "https:",
1311
+ self2.originalHttpsRequest,
1312
+ httpsMod,
1313
+ args
1314
+ );
1315
+ req.end();
1316
+ return req;
1317
+ };
1318
+ }
1319
+ interceptRequest(protocol, originalMethod, module2, args) {
1320
+ let url;
1321
+ let options;
1322
+ let callback;
1323
+ if (typeof args[0] === "string" || args[0] instanceof URL) {
1324
+ url = args[0];
1325
+ if (typeof args[1] === "function") {
1326
+ options = {};
1327
+ callback = args[1];
1328
+ } else {
1329
+ options = args[1] || {};
1330
+ callback = args[2];
1331
+ }
1332
+ } else {
1333
+ options = args[0] || {};
1334
+ callback = args[1];
1335
+ }
1336
+ let resolvedUrl;
1337
+ if (url) {
1338
+ resolvedUrl = url.toString();
1339
+ } else {
1340
+ resolvedUrl = resolveUrl(protocol, options);
1341
+ }
1342
+ const limelightServerUrl = this.config?.serverUrl || "";
1343
+ if (limelightServerUrl && resolvedUrl.includes(limelightServerUrl)) {
1344
+ return originalMethod.apply(module2, args);
1345
+ }
1346
+ const self2 = this;
1347
+ const requestId = generateRequestId();
1348
+ const startTime = Date.now();
1349
+ const initiator = getInitiator();
1350
+ const traceHeaderName = this.config?.traceHeaderName ?? "x-limelight-trace-id";
1351
+ const existingTraceId = getTraceContext()?.getStore()?.traceId;
1352
+ const traceId = existingTraceId || generateRequestId();
1353
+ if (!options.headers) {
1354
+ options.headers = {};
1355
+ }
1356
+ options.headers[traceHeaderName] = traceId;
1357
+ const method = (options.method || "GET").toUpperCase();
1358
+ let patchedArgs;
1359
+ if (url) {
1360
+ patchedArgs = callback ? [url, options, callback] : [url, options];
1361
+ } else {
1362
+ patchedArgs = callback ? [options, callback] : [options];
1363
+ }
1364
+ const req = originalMethod.apply(
1365
+ module2,
1366
+ patchedArgs
1367
+ );
1368
+ const bodyChunks = [];
1369
+ let totalBodySize = 0;
1370
+ let requestEventSent = false;
1371
+ const originalWrite = req.write.bind(req);
1372
+ const originalEnd = req.end.bind(req);
1373
+ req.write = (chunk, encodingOrCallback, callback2) => {
1374
+ if (chunk && totalBodySize < MAX_BODY_SIZE) {
1375
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1376
+ bodyChunks.push(buf);
1377
+ totalBodySize += buf.length;
1378
+ }
1379
+ return originalWrite(chunk, encodingOrCallback, callback2);
1380
+ };
1381
+ req.end = (chunk, encodingOrCallback, callback2) => {
1382
+ if (chunk && typeof chunk !== "function" && totalBodySize < MAX_BODY_SIZE) {
1383
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1384
+ bodyChunks.push(buf);
1385
+ totalBodySize += buf.length;
1386
+ }
1387
+ if (!requestEventSent) {
1388
+ requestEventSent = true;
1389
+ const fullBody = bodyChunks.length > 0 ? Buffer.concat(bodyChunks).toString("utf-8") : void 0;
1390
+ const headers = {};
1391
+ const rawHeaders = req.getHeaders();
1392
+ for (const [key, value] of Object.entries(rawHeaders)) {
1393
+ if (value !== void 0) {
1394
+ headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
1395
+ }
1396
+ }
1397
+ const requestBody = serializeBody(
1398
+ fullBody,
1399
+ self2.config?.disableBodyCapture
1400
+ );
1401
+ let requestEvent = {
1402
+ id: requestId,
1403
+ traceId,
1404
+ sessionId: self2.getSessionId(),
1405
+ timestamp: startTime,
1406
+ phase: "REQUEST" /* REQUEST */,
1407
+ networkType: "http" /* HTTP */,
1408
+ url: resolvedUrl,
1409
+ method,
1410
+ headers: redactSensitiveHeaders(headers),
1411
+ body: requestBody,
1412
+ name: formatRequestName(resolvedUrl),
1413
+ initiator,
1414
+ requestSize: requestBody?.size ?? 0
1415
+ };
1416
+ if (self2.config?.beforeSend) {
1417
+ const modifiedEvent = self2.config.beforeSend(requestEvent);
1418
+ if (!modifiedEvent) {
1419
+ return originalEnd(
1420
+ chunk,
1421
+ encodingOrCallback,
1422
+ callback2
1423
+ );
1424
+ }
1425
+ if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
1426
+ console.error("[Limelight] beforeSend must return same event type");
1427
+ return originalEnd(
1428
+ chunk,
1429
+ encodingOrCallback,
1430
+ callback2
1431
+ );
1432
+ }
1433
+ requestEvent = modifiedEvent;
1434
+ }
1435
+ self2.sendMessage(requestEvent);
1436
+ }
1437
+ return originalEnd(chunk, encodingOrCallback, callback2);
1438
+ };
1439
+ let responseSent = false;
1440
+ req.on("response", (res) => {
1441
+ const responseChunks = [];
1442
+ let responseSize = 0;
1443
+ res.on("data", (chunk) => {
1444
+ if (responseSize < MAX_BODY_SIZE) {
1445
+ responseChunks.push(chunk);
1446
+ responseSize += chunk.length;
1447
+ }
1448
+ });
1449
+ res.on("end", () => {
1450
+ if (responseSent) return;
1451
+ responseSent = true;
1452
+ const endTime = Date.now();
1453
+ const duration = endTime - startTime;
1454
+ const responseHeaders = {};
1455
+ if (res.headers) {
1456
+ for (const [key, value] of Object.entries(res.headers)) {
1457
+ if (value) {
1458
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
1459
+ }
1460
+ }
1461
+ }
1462
+ const contentType = responseHeaders["content-type"] || "";
1463
+ let responseBodyStr;
1464
+ if (responseChunks.length > 0) {
1465
+ if (isBinaryContentType(contentType)) {
1466
+ responseBodyStr = `[Binary Data: ${contentType}]`;
1467
+ } else {
1468
+ const full = Buffer.concat(responseChunks);
1469
+ responseBodyStr = full.length > MAX_BODY_SIZE ? full.toString("utf-8", 0, MAX_BODY_SIZE) + "...[truncated]" : full.toString("utf-8");
1470
+ }
1471
+ }
1472
+ const responseBody = serializeBody(
1473
+ responseBodyStr,
1474
+ self2.config?.disableBodyCapture
1475
+ );
1476
+ const statusCode = res.statusCode ?? 0;
1477
+ let responseEvent = {
1478
+ id: requestId,
1479
+ traceId,
1480
+ sessionId: self2.getSessionId(),
1481
+ timestamp: endTime,
1482
+ phase: "RESPONSE" /* RESPONSE */,
1483
+ networkType: "http" /* HTTP */,
1484
+ status: statusCode,
1485
+ statusText: res.statusMessage || "",
1486
+ headers: redactSensitiveHeaders(responseHeaders),
1487
+ body: responseBody,
1488
+ duration,
1489
+ responseSize: responseBody?.size ?? 0,
1490
+ redirected: false,
1491
+ ok: statusCode >= 200 && statusCode < 300
1492
+ };
1493
+ if (self2.config?.beforeSend) {
1494
+ const modifiedEvent = self2.config.beforeSend(responseEvent);
1495
+ if (!modifiedEvent) return;
1496
+ if (modifiedEvent.phase !== "RESPONSE" /* RESPONSE */) {
1497
+ console.error("[Limelight] beforeSend must return same event type");
1498
+ return;
1499
+ }
1500
+ responseEvent = modifiedEvent;
1501
+ }
1502
+ self2.sendMessage(responseEvent);
1503
+ });
1504
+ });
1505
+ req.on("error", (err) => {
1506
+ if (responseSent) return;
1507
+ responseSent = true;
1508
+ let errorEvent = {
1509
+ id: requestId,
1510
+ traceId,
1511
+ sessionId: self2.getSessionId(),
1512
+ timestamp: Date.now(),
1513
+ phase: "ERROR" /* ERROR */,
1514
+ networkType: "http" /* HTTP */,
1515
+ errorMessage: err.message || "Network request failed",
1516
+ stack: err.stack
1517
+ };
1518
+ if (self2.config?.beforeSend) {
1519
+ const modifiedEvent = self2.config.beforeSend(errorEvent);
1520
+ if (modifiedEvent && (modifiedEvent.phase === "ERROR" /* ERROR */ || modifiedEvent.phase === "ABORT" /* ABORT */)) {
1521
+ errorEvent = modifiedEvent;
1522
+ }
1523
+ }
1524
+ self2.sendMessage(errorEvent);
1525
+ });
1526
+ req.on("timeout", () => {
1527
+ if (responseSent) return;
1528
+ responseSent = true;
1529
+ let errorEvent = {
1530
+ id: requestId,
1531
+ traceId,
1532
+ sessionId: self2.getSessionId(),
1533
+ timestamp: Date.now(),
1534
+ phase: "ERROR" /* ERROR */,
1535
+ networkType: "http" /* HTTP */,
1536
+ errorMessage: "Request timeout"
1537
+ };
1538
+ if (self2.config?.beforeSend) {
1539
+ const modifiedEvent = self2.config.beforeSend(errorEvent);
1540
+ if (modifiedEvent && (modifiedEvent.phase === "ERROR" /* ERROR */ || modifiedEvent.phase === "ABORT" /* ABORT */)) {
1541
+ errorEvent = modifiedEvent;
1542
+ }
1543
+ }
1544
+ self2.sendMessage(errorEvent);
1545
+ });
1546
+ req.on("close", () => {
1547
+ if (responseSent) return;
1548
+ if (!req.destroyed) return;
1549
+ responseSent = true;
1550
+ let errorEvent = {
1551
+ id: requestId,
1552
+ traceId,
1553
+ sessionId: self2.getSessionId(),
1554
+ timestamp: Date.now(),
1555
+ phase: "ABORT" /* ABORT */,
1556
+ networkType: "http" /* HTTP */,
1557
+ errorMessage: "Request aborted"
1558
+ };
1559
+ if (self2.config?.beforeSend) {
1560
+ const modifiedEvent = self2.config.beforeSend(errorEvent);
1561
+ if (modifiedEvent && (modifiedEvent.phase === "ERROR" /* ERROR */ || modifiedEvent.phase === "ABORT" /* ABORT */)) {
1562
+ errorEvent = modifiedEvent;
1563
+ }
1564
+ }
1565
+ self2.sendMessage(errorEvent);
1566
+ });
1567
+ return req;
1568
+ }
1569
+ cleanup() {
1570
+ if (!this.isSetup) {
1571
+ if (this.config?.enableInternalLogging) {
1572
+ console.warn("[Limelight] HTTP interceptor not set up");
1138
1573
  }
1139
1574
  return;
1140
1575
  }
1141
1576
  this.isSetup = false;
1142
- XMLHttpRequest.prototype.open = this.originalXHROpen;
1143
- XMLHttpRequest.prototype.send = this.originalXHRSend;
1144
- XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1577
+ if (this.httpModule && this.originalHttpRequest) {
1578
+ this.httpModule.request = this.originalHttpRequest;
1579
+ }
1580
+ if (this.httpModule && this.originalHttpGet) {
1581
+ this.httpModule.get = this.originalHttpGet;
1582
+ }
1583
+ if (this.httpsModule && this.originalHttpsRequest) {
1584
+ this.httpsModule.request = this.originalHttpsRequest;
1585
+ }
1586
+ if (this.httpsModule && this.originalHttpsGet) {
1587
+ this.httpsModule.get = this.originalHttpsGet;
1588
+ }
1145
1589
  }
1146
1590
  };
1147
1591
 
@@ -1794,6 +2238,83 @@ var RenderInterceptor = class {
1794
2238
  }
1795
2239
  };
1796
2240
 
2241
+ // src/limelight/interceptors/ErrorInterceptor.ts
2242
+ var ErrorInterceptor = class {
2243
+ constructor(sendMessage, getSessionId) {
2244
+ this.sendMessage = sendMessage;
2245
+ this.getSessionId = getSessionId;
2246
+ }
2247
+ isSetup = false;
2248
+ config = null;
2249
+ counter = 0;
2250
+ uncaughtExceptionHandler = null;
2251
+ unhandledRejectionHandler = null;
2252
+ setup(config) {
2253
+ if (this.isSetup) {
2254
+ if (this.config?.enableInternalLogging) {
2255
+ console.warn("[Limelight] Error interceptor already set up");
2256
+ }
2257
+ return;
2258
+ }
2259
+ if (typeof process === "undefined" || !process.on) {
2260
+ return;
2261
+ }
2262
+ this.isSetup = true;
2263
+ this.config = config;
2264
+ this.uncaughtExceptionHandler = (error) => {
2265
+ this.sendErrorEvent(error, "uncaughtException");
2266
+ setTimeout(() => {
2267
+ process.exit(1);
2268
+ }, 200);
2269
+ };
2270
+ this.unhandledRejectionHandler = (reason) => {
2271
+ const error = reason instanceof Error ? reason : new Error(String(reason));
2272
+ this.sendErrorEvent(error, "unhandledRejection");
2273
+ };
2274
+ process.on("uncaughtException", this.uncaughtExceptionHandler);
2275
+ process.on("unhandledRejection", this.unhandledRejectionHandler);
2276
+ }
2277
+ cleanup() {
2278
+ if (!this.isSetup) return;
2279
+ this.isSetup = false;
2280
+ if (this.uncaughtExceptionHandler) {
2281
+ process.removeListener(
2282
+ "uncaughtException",
2283
+ this.uncaughtExceptionHandler
2284
+ );
2285
+ this.uncaughtExceptionHandler = null;
2286
+ }
2287
+ if (this.unhandledRejectionHandler) {
2288
+ process.removeListener(
2289
+ "unhandledRejection",
2290
+ this.unhandledRejectionHandler
2291
+ );
2292
+ this.unhandledRejectionHandler = null;
2293
+ }
2294
+ this.config = null;
2295
+ }
2296
+ sendErrorEvent(error, source) {
2297
+ const sessionId = this.getSessionId();
2298
+ const event = {
2299
+ id: `${sessionId}-${Date.now()}-${this.counter++}`,
2300
+ phase: "CONSOLE",
2301
+ type: "CONSOLE" /* CONSOLE */,
2302
+ level: "error" /* ERROR */,
2303
+ timestamp: Date.now(),
2304
+ sessionId,
2305
+ source: "app" /* APP */,
2306
+ consoleType: "exception" /* EXCEPTION */,
2307
+ args: [safeStringify(`[${source}] ${error.message}`)],
2308
+ stackTrace: error.stack
2309
+ };
2310
+ if (this.config?.beforeSend) {
2311
+ const modified = this.config.beforeSend(event);
2312
+ if (!modified) return;
2313
+ }
2314
+ this.sendMessage(event);
2315
+ }
2316
+ };
2317
+
1797
2318
  // src/limelight/interceptors/StateInterceptor.ts
1798
2319
  var StateInterceptor = class {
1799
2320
  sendMessage;
@@ -2277,6 +2798,153 @@ var CommandHandler = class {
2277
2798
  }
2278
2799
  };
2279
2800
 
2801
+ // src/limelight/middleware/httpMiddleware.ts
2802
+ var DEFAULT_MAX_BODY_SIZE = 64 * 1024;
2803
+ var captureRequest = (req, res, sendMessage, getSessionId, config, options) => {
2804
+ const requestId = generateRequestId();
2805
+ const startTime = Date.now();
2806
+ const maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
2807
+ const traceHeaderName = config?.traceHeaderName ?? "x-limelight-trace-id";
2808
+ const incomingTraceId = req.headers[traceHeaderName];
2809
+ const traceId = incomingTraceId || generateRequestId();
2810
+ req.limelightTraceId = traceId;
2811
+ const url = req.url || "/";
2812
+ const method = (req.method || "GET").toUpperCase();
2813
+ const headers = {};
2814
+ for (const [key, value] of Object.entries(req.headers)) {
2815
+ if (value) {
2816
+ headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
2817
+ }
2818
+ }
2819
+ let requestBody = serializeBody(req.body, config?.disableBodyCapture);
2820
+ if (requestBody?.raw && requestBody.raw.length > maxBodySize) {
2821
+ requestBody = {
2822
+ ...requestBody,
2823
+ raw: requestBody.raw.slice(0, maxBodySize) + "...[truncated]",
2824
+ size: requestBody.size
2825
+ };
2826
+ }
2827
+ let requestEvent = {
2828
+ id: requestId,
2829
+ traceId,
2830
+ sessionId: getSessionId(),
2831
+ timestamp: startTime,
2832
+ phase: "REQUEST" /* REQUEST */,
2833
+ networkType: "incoming" /* INCOMING */,
2834
+ url,
2835
+ method,
2836
+ headers: redactSensitiveHeaders(headers),
2837
+ body: requestBody,
2838
+ name: url.split("?")[0]?.split("/").filter(Boolean).pop() ?? "/",
2839
+ initiator: "incoming",
2840
+ requestSize: requestBody?.size ?? 0
2841
+ };
2842
+ if (config?.beforeSend) {
2843
+ const modified = config.beforeSend(requestEvent);
2844
+ if (!modified) return;
2845
+ if (modified.phase !== "REQUEST" /* REQUEST */) {
2846
+ console.error("[Limelight] beforeSend must return same event type");
2847
+ return;
2848
+ }
2849
+ requestEvent = modified;
2850
+ }
2851
+ sendMessage(requestEvent);
2852
+ const chunks = [];
2853
+ let totalSize = 0;
2854
+ const originalWrite = res.write;
2855
+ const originalEnd = res.end;
2856
+ res.write = (chunk, ...args) => {
2857
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2858
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2859
+ chunks.push(buf);
2860
+ totalSize += buf.length;
2861
+ }
2862
+ return originalWrite.apply(res, [chunk, ...args]);
2863
+ };
2864
+ res.end = (chunk, ...args) => {
2865
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2866
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2867
+ chunks.push(buf);
2868
+ totalSize += buf.length;
2869
+ }
2870
+ return originalEnd.apply(res, [chunk, ...args]);
2871
+ };
2872
+ res.on("finish", () => {
2873
+ const endTime = Date.now();
2874
+ const duration = endTime - startTime;
2875
+ const responseHeaders = {};
2876
+ const rawHeaders = res.getHeaders();
2877
+ for (const [key, value] of Object.entries(rawHeaders)) {
2878
+ if (value) {
2879
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
2880
+ }
2881
+ }
2882
+ let responseBodyStr;
2883
+ if (chunks.length > 0 && !config?.disableBodyCapture) {
2884
+ const full = Buffer.concat(chunks);
2885
+ const fullStr = full.toString("utf-8");
2886
+ responseBodyStr = fullStr.length > maxBodySize ? fullStr.slice(0, maxBodySize) + "...[truncated]" : fullStr;
2887
+ }
2888
+ const responseBody = serializeBody(
2889
+ responseBodyStr,
2890
+ config?.disableBodyCapture
2891
+ );
2892
+ let responseEvent = {
2893
+ id: requestId,
2894
+ traceId,
2895
+ sessionId: getSessionId(),
2896
+ timestamp: endTime,
2897
+ phase: "RESPONSE" /* RESPONSE */,
2898
+ networkType: "incoming" /* INCOMING */,
2899
+ status: res.statusCode,
2900
+ statusText: res.statusMessage || "",
2901
+ headers: redactSensitiveHeaders(responseHeaders),
2902
+ body: responseBody,
2903
+ duration,
2904
+ responseSize: responseBody?.size ?? 0,
2905
+ redirected: false,
2906
+ ok: res.statusCode >= 200 && res.statusCode < 300
2907
+ };
2908
+ if (config?.beforeSend) {
2909
+ const modified = config.beforeSend(responseEvent);
2910
+ if (!modified) return;
2911
+ if (modified.phase !== "RESPONSE" /* RESPONSE */) {
2912
+ console.error("[Limelight] beforeSend must return same event type");
2913
+ return;
2914
+ }
2915
+ responseEvent = modified;
2916
+ }
2917
+ sendMessage(responseEvent);
2918
+ });
2919
+ };
2920
+ var createHttpMiddleware = (sendMessage, getSessionId, getConfig, options) => {
2921
+ return (req, res, next) => {
2922
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2923
+ const traceId = req.limelightTraceId;
2924
+ const ctx = getTraceContext();
2925
+ if (ctx && traceId) {
2926
+ ctx.run({ traceId }, next);
2927
+ } else {
2928
+ next();
2929
+ }
2930
+ };
2931
+ };
2932
+
2933
+ // src/limelight/middleware/withLimelight.ts
2934
+ var createWithLimelight = (sendMessage, getSessionId, getConfig, options) => {
2935
+ return (handler) => {
2936
+ return (req, res) => {
2937
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2938
+ const traceId = req.limelightTraceId;
2939
+ const ctx = getTraceContext();
2940
+ if (ctx && traceId) {
2941
+ return ctx.run({ traceId }, () => handler(req, res));
2942
+ }
2943
+ return handler(req, res);
2944
+ };
2945
+ };
2946
+ };
2947
+
2280
2948
  // src/limelight/LimelightClient.ts
2281
2949
  var LimelightClient = class {
2282
2950
  ws = null;
@@ -2290,9 +2958,11 @@ var LimelightClient = class {
2290
2958
  maxQueueSize = 100;
2291
2959
  networkInterceptor;
2292
2960
  xhrInterceptor;
2961
+ httpInterceptor;
2293
2962
  consoleInterceptor;
2294
2963
  renderInterceptor;
2295
2964
  stateInterceptor;
2965
+ errorInterceptor;
2296
2966
  requestBridge;
2297
2967
  commandHandler = null;
2298
2968
  constructor() {
@@ -2304,6 +2974,10 @@ var LimelightClient = class {
2304
2974
  this.sendMessage.bind(this),
2305
2975
  () => this.sessionId
2306
2976
  );
2977
+ this.httpInterceptor = new HttpInterceptor(
2978
+ this.sendMessage.bind(this),
2979
+ () => this.sessionId
2980
+ );
2307
2981
  this.consoleInterceptor = new ConsoleInterceptor(
2308
2982
  this.sendMessage.bind(this),
2309
2983
  () => this.sessionId
@@ -2316,6 +2990,10 @@ var LimelightClient = class {
2316
2990
  this.sendMessage.bind(this),
2317
2991
  () => this.sessionId
2318
2992
  );
2993
+ this.errorInterceptor = new ErrorInterceptor(
2994
+ this.sendMessage.bind(this),
2995
+ () => this.sessionId
2996
+ );
2319
2997
  this.requestBridge = new RequestBridge(
2320
2998
  this.sendMessage.bind(this),
2321
2999
  () => this.sessionId
@@ -2357,12 +3035,20 @@ var LimelightClient = class {
2357
3035
  try {
2358
3036
  if (this.config.enableNetworkInspector) {
2359
3037
  this.networkInterceptor.setup(this.config);
2360
- this.xhrInterceptor.setup(this.config);
3038
+ if (typeof XMLHttpRequest !== "undefined") {
3039
+ this.xhrInterceptor.setup(this.config);
3040
+ }
3041
+ if (!hasDOM()) {
3042
+ this.httpInterceptor.setup(this.config);
3043
+ }
2361
3044
  }
2362
3045
  if (this.config.enableConsole) {
2363
3046
  this.consoleInterceptor.setup(this.config);
3047
+ if (!hasDOM()) {
3048
+ this.errorInterceptor.setup(this.config);
3049
+ }
2364
3050
  }
2365
- if (this.config.enableRenderInspector) {
3051
+ if (this.config.enableRenderInspector && hasDOM()) {
2366
3052
  this.renderInterceptor.setup(this.config);
2367
3053
  }
2368
3054
  if (this.config.stores && this.config.enableStateInspector) {
@@ -2390,7 +3076,7 @@ var LimelightClient = class {
2390
3076
  if (!this.config?.enabled) {
2391
3077
  return;
2392
3078
  }
2393
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
3079
+ if (this.ws && this.ws.readyState === 1) {
2394
3080
  if (this.config?.enableInternalLogging) {
2395
3081
  console.warn("[Limelight] Already connected. Call disconnect() first.");
2396
3082
  }
@@ -2413,15 +3099,24 @@ var LimelightClient = class {
2413
3099
  }
2414
3100
  return;
2415
3101
  }
3102
+ const WsConstructor = this.config.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
3103
+ if (!WsConstructor) {
3104
+ if (this.config?.enableInternalLogging) {
3105
+ console.error(
3106
+ "[Limelight] WebSocket is not available. Pass webSocketImpl in config (e.g. ws package)."
3107
+ );
3108
+ }
3109
+ return;
3110
+ }
2416
3111
  try {
2417
- this.ws = new WebSocket(serverUrl);
3112
+ this.ws = new WsConstructor(serverUrl);
2418
3113
  const message = {
2419
3114
  phase: "CONNECT",
2420
3115
  sessionId: this.sessionId,
2421
3116
  timestamp: Date.now(),
2422
3117
  data: {
2423
3118
  appName,
2424
- platform: platform || (typeof window !== "undefined" ? "web" : "react-native"),
3119
+ platform: platform || (hasDOM() ? "web" : typeof process !== "undefined" ? "node" : "react-native"),
2425
3120
  projectKey: this.config.projectKey || "",
2426
3121
  sdkVersion: SDK_VERSION
2427
3122
  }
@@ -2488,10 +3183,13 @@ var LimelightClient = class {
2488
3183
  * @returns {void}
2489
3184
  */
2490
3185
  flushMessageQueue() {
2491
- if (this.ws?.readyState !== WebSocket.OPEN) return;
3186
+ if (this.ws?.readyState !== 1) return;
2492
3187
  while (this.messageQueue.length > 0) {
2493
3188
  const message = this.messageQueue.shift();
2494
3189
  try {
3190
+ if (message && "sessionId" in message && !message.sessionId) {
3191
+ message.sessionId = this.sessionId;
3192
+ }
2495
3193
  this.ws.send(safeStringify(message));
2496
3194
  } catch (error) {
2497
3195
  if (this.config?.enableInternalLogging) {
@@ -2509,9 +3207,9 @@ var LimelightClient = class {
2509
3207
  * @returns {void}
2510
3208
  */
2511
3209
  sendMessage(message) {
2512
- if (this.ws?.readyState === WebSocket.OPEN) {
3210
+ if (this.ws?.readyState === 1) {
2513
3211
  this.flushMessageQueue();
2514
- if (this.ws?.readyState === WebSocket.OPEN) {
3212
+ if (this.ws?.readyState === 1) {
2515
3213
  try {
2516
3214
  this.ws.send(safeStringify(message));
2517
3215
  } catch (error) {
@@ -2568,7 +3266,9 @@ var LimelightClient = class {
2568
3266
  }
2569
3267
  this.networkInterceptor.cleanup();
2570
3268
  this.xhrInterceptor.cleanup();
3269
+ this.httpInterceptor.cleanup();
2571
3270
  this.consoleInterceptor.cleanup();
3271
+ this.errorInterceptor.cleanup();
2572
3272
  this.renderInterceptor.cleanup();
2573
3273
  this.stateInterceptor.cleanup();
2574
3274
  this.requestBridge.cleanup();
@@ -2615,6 +3315,46 @@ var LimelightClient = class {
2615
3315
  failRequest(requestId, error) {
2616
3316
  this.requestBridge.failRequest(requestId, error);
2617
3317
  }
3318
+ /**
3319
+ * Returns an Express/Connect-compatible middleware that captures incoming
3320
+ * HTTP requests and responses.
3321
+ *
3322
+ * Place after body-parser middleware (express.json(), etc.) for request body capture.
3323
+ *
3324
+ * @example
3325
+ * ```ts
3326
+ * app.use(express.json());
3327
+ * app.use(Limelight.middleware());
3328
+ * ```
3329
+ */
3330
+ middleware(options) {
3331
+ return createHttpMiddleware(
3332
+ this.sendMessage.bind(this),
3333
+ () => this.sessionId,
3334
+ () => this.config,
3335
+ options
3336
+ );
3337
+ }
3338
+ /**
3339
+ * Wraps a Next.js Pages API route handler with request/response capture.
3340
+ * Works with Pages Router (`pages/api/`), not App Router (`app/api/`).
3341
+ *
3342
+ * @example
3343
+ * ```ts
3344
+ * // pages/api/users.ts
3345
+ * export default Limelight.withLimelight((req, res) => {
3346
+ * res.json({ ok: true });
3347
+ * });
3348
+ * ```
3349
+ */
3350
+ withLimelight(handler) {
3351
+ const wrapper = createWithLimelight(
3352
+ this.sendMessage.bind(this),
3353
+ () => this.sessionId,
3354
+ () => this.config
3355
+ );
3356
+ return wrapper(handler);
3357
+ }
2618
3358
  };
2619
3359
  var Limelight = new LimelightClient();
2620
3360
  // Annotate the CommonJS export names for ESM import in node: