@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.mjs CHANGED
@@ -1,3 +1,8 @@
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 }); }
5
+
1
6
  // src/types/console.ts
2
7
  var ConsoleLevel = /* @__PURE__ */ ((ConsoleLevel2) => {
3
8
  ConsoleLevel2["LOG"] = "log";
@@ -29,6 +34,8 @@ var NetworkType = /* @__PURE__ */ ((NetworkType2) => {
29
34
  NetworkType2["FETCH"] = "fetch";
30
35
  NetworkType2["XHR"] = "xhr";
31
36
  NetworkType2["GRAPHQL"] = "graphql";
37
+ NetworkType2["INCOMING"] = "incoming";
38
+ NetworkType2["HTTP"] = "http";
32
39
  return NetworkType2;
33
40
  })(NetworkType || {});
34
41
  var NetworkPhase = /* @__PURE__ */ ((NetworkPhase2) => {
@@ -49,17 +56,17 @@ var BodyFormat = /* @__PURE__ */ ((BodyFormat2) => {
49
56
  BodyFormat2["UNSERIALIZABLE"] = "UNSERIALIZABLE";
50
57
  return BodyFormat2;
51
58
  })(BodyFormat || {});
52
- var HttpMethod = /* @__PURE__ */ ((HttpMethod5) => {
53
- HttpMethod5["GET"] = "GET";
54
- HttpMethod5["POST"] = "POST";
55
- HttpMethod5["PUT"] = "PUT";
56
- HttpMethod5["PATCH"] = "PATCH";
57
- HttpMethod5["DELETE"] = "DELETE";
58
- HttpMethod5["HEAD"] = "HEAD";
59
- HttpMethod5["OPTIONS"] = "OPTIONS";
60
- HttpMethod5["TRACE"] = "TRACE";
61
- HttpMethod5["CONNECT"] = "CONNECT";
62
- return HttpMethod5;
59
+ var HttpMethod = /* @__PURE__ */ ((HttpMethod7) => {
60
+ HttpMethod7["GET"] = "GET";
61
+ HttpMethod7["POST"] = "POST";
62
+ HttpMethod7["PUT"] = "PUT";
63
+ HttpMethod7["PATCH"] = "PATCH";
64
+ HttpMethod7["DELETE"] = "DELETE";
65
+ HttpMethod7["HEAD"] = "HEAD";
66
+ HttpMethod7["OPTIONS"] = "OPTIONS";
67
+ HttpMethod7["TRACE"] = "TRACE";
68
+ HttpMethod7["CONNECT"] = "CONNECT";
69
+ return HttpMethod7;
63
70
  })(HttpMethod || {});
64
71
  var HttpStatusClass = /* @__PURE__ */ ((HttpStatusClass2) => {
65
72
  HttpStatusClass2[HttpStatusClass2["INFORMATIONAL"] = 100] = "INFORMATIONAL";
@@ -249,7 +256,7 @@ var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
249
256
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
250
257
  var LIMELIGHT_MCP_WS_URL = "ws://localhost:9229";
251
258
  var WS_PATH = "/limelight";
252
- var SDK_VERSION = true ? "0.6.1" : "test-version";
259
+ var SDK_VERSION = true ? "0.7.8" : "test-version";
253
260
  var RENDER_THRESHOLDS = {
254
261
  HOT_VELOCITY: 5,
255
262
  HIGH_RENDER_COUNT: 50,
@@ -263,6 +270,16 @@ var RENDER_THRESHOLDS = {
263
270
  TOP_PROPS_TO_REPORT: 5
264
271
  // Only report top N changed props
265
272
  };
273
+ var BINARY_CONTENT_TYPES = [
274
+ "image/",
275
+ "audio/",
276
+ "video/",
277
+ "application/octet-stream",
278
+ "application/pdf",
279
+ "application/zip",
280
+ "application/gzip"
281
+ ];
282
+ var MAX_BODY_SIZE = 1024 * 1024;
266
283
 
267
284
  // src/helpers/safety/redactSensitiveHeaders.ts
268
285
  var redactSensitiveHeaders = (headers) => {
@@ -503,6 +520,9 @@ var formatRequestName = (url) => {
503
520
  }
504
521
  };
505
522
 
523
+ // src/helpers/utils/environment.ts
524
+ var hasDOM = () => typeof window !== "undefined" && typeof document !== "undefined";
525
+
506
526
  // src/helpers/render/generateRenderId.ts
507
527
  var counter = 0;
508
528
  var generateRenderId = () => {
@@ -539,6 +559,21 @@ var getCurrentTransactionId = () => {
539
559
  return globalGetTransactionId?.() ?? null;
540
560
  };
541
561
 
562
+ // src/helpers/http/resolveUrl.ts
563
+ var resolveUrl = (protocol, options) => {
564
+ const host = options.hostname || options.host || "localhost";
565
+ const port = options.port ? `:${options.port}` : "";
566
+ const path = options.path || "/";
567
+ return `${protocol}//${host}${port}${path}`;
568
+ };
569
+
570
+ // src/helpers/http/isBinaryContentType.ts
571
+ var isBinaryContentType = (contentType) => {
572
+ return BINARY_CONTENT_TYPES.some(
573
+ (type) => contentType.toLowerCase().includes(type)
574
+ );
575
+ };
576
+
542
577
  // src/limelight/interceptors/ConsoleInterceptor.ts
543
578
  var ConsoleInterceptor = class {
544
579
  constructor(sendMessage, getSessionId) {
@@ -668,13 +703,31 @@ var generateRequestId = () => {
668
703
  return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
669
704
  };
670
705
 
706
+ // src/limelight/context/traceContext.ts
707
+ var _resolved = false;
708
+ var _traceContext;
709
+ var getTraceContext = () => {
710
+ if (!_resolved) {
711
+ _resolved = true;
712
+ try {
713
+ const _require = globalThis["require"];
714
+ const { AsyncLocalStorage } = _require("node:async_hooks");
715
+ _traceContext = new AsyncLocalStorage();
716
+ } catch {
717
+ }
718
+ }
719
+ return _traceContext;
720
+ };
721
+
671
722
  // src/limelight/interceptors/NetworkInterceptor.ts
672
723
  var NetworkInterceptor = class {
673
724
  constructor(sendMessage, getSessionId) {
674
725
  this.sendMessage = sendMessage;
675
726
  this.getSessionId = getSessionId;
676
727
  this.globalObject = detectGlobalObject();
677
- this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
728
+ if (typeof this.globalObject.fetch === "function") {
729
+ this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
730
+ }
678
731
  }
679
732
  originalFetch;
680
733
  config = null;
@@ -694,9 +747,18 @@ var NetworkInterceptor = class {
694
747
  }
695
748
  return;
696
749
  }
750
+ if (!this.originalFetch) {
751
+ if (config?.enableInternalLogging) {
752
+ console.warn(
753
+ "[Limelight] fetch is not available in this environment, skipping network interception"
754
+ );
755
+ }
756
+ return;
757
+ }
697
758
  this.isSetup = true;
698
759
  this.config = config;
699
760
  const self2 = this;
761
+ const originalFetch = this.originalFetch;
700
762
  this.globalObject.fetch = async function(input, init = {}) {
701
763
  const requestId = generateRequestId();
702
764
  const startTime = Date.now();
@@ -714,6 +776,12 @@ var NetworkInterceptor = class {
714
776
  });
715
777
  }
716
778
  headers["x-limelight-intercepted"] = "fetch";
779
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
780
+ if (!headers[traceHeaderName]) {
781
+ const existingTraceId = getTraceContext()?.getStore()?.traceId;
782
+ headers[traceHeaderName] = existingTraceId || generateRequestId();
783
+ }
784
+ const traceId = headers[traceHeaderName];
717
785
  modifiedInit.headers = new Headers(headers);
718
786
  let requestBodyToSerialize = init.body;
719
787
  if (input instanceof Request && !requestBodyToSerialize) {
@@ -747,6 +815,7 @@ var NetworkInterceptor = class {
747
815
  }
748
816
  let requestEvent = {
749
817
  id: requestId,
818
+ traceId,
750
819
  sessionId: self2.getSessionId(),
751
820
  timestamp: startTime,
752
821
  phase: "REQUEST" /* REQUEST */,
@@ -763,17 +832,17 @@ var NetworkInterceptor = class {
763
832
  if (self2.config?.beforeSend) {
764
833
  const modifiedEvent = self2.config.beforeSend(requestEvent);
765
834
  if (!modifiedEvent) {
766
- return self2.originalFetch(input, modifiedInit);
835
+ return originalFetch(input, modifiedInit);
767
836
  }
768
837
  if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
769
838
  console.error("[Limelight] beforeSend must return same event type");
770
- return self2.originalFetch(input, modifiedInit);
839
+ return originalFetch(input, modifiedInit);
771
840
  }
772
841
  requestEvent = modifiedEvent;
773
842
  }
774
843
  self2.sendMessage(requestEvent);
775
844
  try {
776
- const response = await self2.originalFetch(input, modifiedInit);
845
+ const response = await originalFetch(input, modifiedInit);
777
846
  const clone = response.clone();
778
847
  const endTime = Date.now();
779
848
  const duration = endTime - startTime;
@@ -793,6 +862,7 @@ var NetworkInterceptor = class {
793
862
  );
794
863
  let responseEvent = {
795
864
  id: requestId,
865
+ traceId,
796
866
  sessionId: self2.getSessionId(),
797
867
  timestamp: endTime,
798
868
  phase: "RESPONSE" /* RESPONSE */,
@@ -825,6 +895,7 @@ var NetworkInterceptor = class {
825
895
  const errorStack = err instanceof Error ? err.stack : void 0;
826
896
  let errorEvent = {
827
897
  id: requestId,
898
+ traceId,
828
899
  sessionId: self2.getSessionId(),
829
900
  timestamp: Date.now(),
830
901
  phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
@@ -855,7 +926,9 @@ var NetworkInterceptor = class {
855
926
  return;
856
927
  }
857
928
  this.isSetup = false;
858
- this.globalObject.fetch = this.originalFetch;
929
+ if (this.originalFetch) {
930
+ this.globalObject.fetch = this.originalFetch;
931
+ }
859
932
  }
860
933
  };
861
934
 
@@ -864,9 +937,11 @@ var XHRInterceptor = class {
864
937
  constructor(sendMessage, getSessionId) {
865
938
  this.sendMessage = sendMessage;
866
939
  this.getSessionId = getSessionId;
867
- this.originalXHROpen = XMLHttpRequest.prototype.open;
868
- this.originalXHRSend = XMLHttpRequest.prototype.send;
869
- this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
940
+ if (typeof XMLHttpRequest !== "undefined") {
941
+ this.originalXHROpen = XMLHttpRequest.prototype.open;
942
+ this.originalXHRSend = XMLHttpRequest.prototype.send;
943
+ this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
944
+ }
870
945
  }
871
946
  originalXHROpen;
872
947
  originalXHRSend;
@@ -881,6 +956,9 @@ var XHRInterceptor = class {
881
956
  * @returns {void}
882
957
  */
883
958
  setup(config) {
959
+ if (typeof XMLHttpRequest === "undefined") {
960
+ return;
961
+ }
884
962
  if (this.isSetup) {
885
963
  if (this.config?.enableInternalLogging) {
886
964
  console.warn("[Limelight] XHR interceptor already set up");
@@ -919,12 +997,24 @@ var XHRInterceptor = class {
919
997
  return self2.originalXHRSend.apply(this, arguments);
920
998
  }
921
999
  if (data) {
1000
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
1001
+ if (!data.headers[traceHeaderName]) {
1002
+ data.traceId = generateRequestId();
1003
+ self2.originalXHRSetRequestHeader.call(
1004
+ this,
1005
+ traceHeaderName,
1006
+ data.traceId
1007
+ );
1008
+ } else {
1009
+ data.traceId = data.headers[traceHeaderName];
1010
+ }
922
1011
  const requestBody = serializeBody(
923
1012
  body,
924
1013
  self2.config?.disableBodyCapture
925
1014
  );
926
1015
  let requestEvent = {
927
1016
  id: data.id,
1017
+ traceId: data.traceId,
928
1018
  sessionId: self2.getSessionId(),
929
1019
  timestamp: data.startTime,
930
1020
  phase: "REQUEST" /* REQUEST */,
@@ -984,6 +1074,7 @@ var XHRInterceptor = class {
984
1074
  );
985
1075
  let responseEvent = {
986
1076
  id: data.id,
1077
+ traceId: data.traceId,
987
1078
  sessionId: self2.getSessionId(),
988
1079
  timestamp: endTime,
989
1080
  phase: "RESPONSE" /* RESPONSE */,
@@ -1018,6 +1109,7 @@ var XHRInterceptor = class {
1018
1109
  responseSent = true;
1019
1110
  let errorEvent = {
1020
1111
  id: data.id,
1112
+ traceId: data.traceId,
1021
1113
  sessionId: self2.getSessionId(),
1022
1114
  timestamp: Date.now(),
1023
1115
  phase,
@@ -1094,17 +1186,370 @@ var XHRInterceptor = class {
1094
1186
  * Restores original XMLHttpRequest methods and removes all interception.
1095
1187
  * @returns {void}
1096
1188
  */
1189
+ cleanup() {
1190
+ if (!this.isSetup) {
1191
+ return;
1192
+ }
1193
+ this.isSetup = false;
1194
+ if (typeof XMLHttpRequest !== "undefined") {
1195
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
1196
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
1197
+ XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1198
+ }
1199
+ }
1200
+ };
1201
+
1202
+ // src/limelight/interceptors/HttpInterceptor.ts
1203
+ var HttpInterceptor = class {
1204
+ constructor(sendMessage, getSessionId) {
1205
+ this.sendMessage = sendMessage;
1206
+ this.getSessionId = getSessionId;
1207
+ try {
1208
+ const _require = globalThis["require"];
1209
+ this.httpModule = _require("http");
1210
+ this.httpsModule = _require("https");
1211
+ this.originalHttpRequest = this.httpModule.request;
1212
+ this.originalHttpGet = this.httpModule.get;
1213
+ this.originalHttpsRequest = this.httpsModule.request;
1214
+ this.originalHttpsGet = this.httpsModule.get;
1215
+ } catch {
1216
+ }
1217
+ }
1218
+ originalHttpRequest = null;
1219
+ originalHttpGet = null;
1220
+ originalHttpsRequest = null;
1221
+ originalHttpsGet = null;
1222
+ httpModule = null;
1223
+ httpsModule = null;
1224
+ config = null;
1225
+ isSetup = false;
1226
+ setup(config) {
1227
+ if (this.isSetup) {
1228
+ if (this.config?.enableInternalLogging) {
1229
+ console.warn("[Limelight] HTTP interceptor already set up");
1230
+ }
1231
+ return;
1232
+ }
1233
+ if (!this.httpModule || !this.httpsModule) {
1234
+ if (config?.enableInternalLogging) {
1235
+ console.warn(
1236
+ "[Limelight] Node http module not available, skipping HTTP interception"
1237
+ );
1238
+ }
1239
+ return;
1240
+ }
1241
+ this.isSetup = true;
1242
+ this.config = config;
1243
+ const self2 = this;
1244
+ const httpMod = this.httpModule;
1245
+ const httpsMod = this.httpsModule;
1246
+ httpMod.request = (...args) => {
1247
+ return self2.interceptRequest(
1248
+ "http:",
1249
+ self2.originalHttpRequest,
1250
+ httpMod,
1251
+ args
1252
+ );
1253
+ };
1254
+ httpMod.get = (...args) => {
1255
+ const req = self2.interceptRequest(
1256
+ "http:",
1257
+ self2.originalHttpRequest,
1258
+ httpMod,
1259
+ args
1260
+ );
1261
+ req.end();
1262
+ return req;
1263
+ };
1264
+ httpsMod.request = (...args) => {
1265
+ return self2.interceptRequest(
1266
+ "https:",
1267
+ self2.originalHttpsRequest,
1268
+ httpsMod,
1269
+ args
1270
+ );
1271
+ };
1272
+ httpsMod.get = (...args) => {
1273
+ const req = self2.interceptRequest(
1274
+ "https:",
1275
+ self2.originalHttpsRequest,
1276
+ httpsMod,
1277
+ args
1278
+ );
1279
+ req.end();
1280
+ return req;
1281
+ };
1282
+ }
1283
+ interceptRequest(protocol, originalMethod, module, args) {
1284
+ let url;
1285
+ let options;
1286
+ let callback;
1287
+ if (typeof args[0] === "string" || args[0] instanceof URL) {
1288
+ url = args[0];
1289
+ if (typeof args[1] === "function") {
1290
+ options = {};
1291
+ callback = args[1];
1292
+ } else {
1293
+ options = args[1] || {};
1294
+ callback = args[2];
1295
+ }
1296
+ } else {
1297
+ options = args[0] || {};
1298
+ callback = args[1];
1299
+ }
1300
+ let resolvedUrl;
1301
+ if (url) {
1302
+ resolvedUrl = url.toString();
1303
+ } else {
1304
+ resolvedUrl = resolveUrl(protocol, options);
1305
+ }
1306
+ const limelightServerUrl = this.config?.serverUrl || "";
1307
+ if (limelightServerUrl && resolvedUrl.includes(limelightServerUrl)) {
1308
+ return originalMethod.apply(module, args);
1309
+ }
1310
+ const self2 = this;
1311
+ const requestId = generateRequestId();
1312
+ const startTime = Date.now();
1313
+ const initiator = getInitiator();
1314
+ const traceHeaderName = this.config?.traceHeaderName ?? "x-limelight-trace-id";
1315
+ const existingTraceId = getTraceContext()?.getStore()?.traceId;
1316
+ const traceId = existingTraceId || generateRequestId();
1317
+ if (!options.headers) {
1318
+ options.headers = {};
1319
+ }
1320
+ options.headers[traceHeaderName] = traceId;
1321
+ const method = (options.method || "GET").toUpperCase();
1322
+ let patchedArgs;
1323
+ if (url) {
1324
+ patchedArgs = callback ? [url, options, callback] : [url, options];
1325
+ } else {
1326
+ patchedArgs = callback ? [options, callback] : [options];
1327
+ }
1328
+ const req = originalMethod.apply(
1329
+ module,
1330
+ patchedArgs
1331
+ );
1332
+ const bodyChunks = [];
1333
+ let totalBodySize = 0;
1334
+ let requestEventSent = false;
1335
+ const originalWrite = req.write.bind(req);
1336
+ const originalEnd = req.end.bind(req);
1337
+ req.write = (chunk, encodingOrCallback, callback2) => {
1338
+ if (chunk && totalBodySize < MAX_BODY_SIZE) {
1339
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1340
+ bodyChunks.push(buf);
1341
+ totalBodySize += buf.length;
1342
+ }
1343
+ return originalWrite(chunk, encodingOrCallback, callback2);
1344
+ };
1345
+ req.end = (chunk, encodingOrCallback, callback2) => {
1346
+ if (chunk && typeof chunk !== "function" && totalBodySize < MAX_BODY_SIZE) {
1347
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1348
+ bodyChunks.push(buf);
1349
+ totalBodySize += buf.length;
1350
+ }
1351
+ if (!requestEventSent) {
1352
+ requestEventSent = true;
1353
+ const fullBody = bodyChunks.length > 0 ? Buffer.concat(bodyChunks).toString("utf-8") : void 0;
1354
+ const headers = {};
1355
+ const rawHeaders = req.getHeaders();
1356
+ for (const [key, value] of Object.entries(rawHeaders)) {
1357
+ if (value !== void 0) {
1358
+ headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
1359
+ }
1360
+ }
1361
+ const requestBody = serializeBody(
1362
+ fullBody,
1363
+ self2.config?.disableBodyCapture
1364
+ );
1365
+ let requestEvent = {
1366
+ id: requestId,
1367
+ traceId,
1368
+ sessionId: self2.getSessionId(),
1369
+ timestamp: startTime,
1370
+ phase: "REQUEST" /* REQUEST */,
1371
+ networkType: "http" /* HTTP */,
1372
+ url: resolvedUrl,
1373
+ method,
1374
+ headers: redactSensitiveHeaders(headers),
1375
+ body: requestBody,
1376
+ name: formatRequestName(resolvedUrl),
1377
+ initiator,
1378
+ requestSize: requestBody?.size ?? 0
1379
+ };
1380
+ if (self2.config?.beforeSend) {
1381
+ const modifiedEvent = self2.config.beforeSend(requestEvent);
1382
+ if (!modifiedEvent) {
1383
+ return originalEnd(
1384
+ chunk,
1385
+ encodingOrCallback,
1386
+ callback2
1387
+ );
1388
+ }
1389
+ if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
1390
+ console.error("[Limelight] beforeSend must return same event type");
1391
+ return originalEnd(
1392
+ chunk,
1393
+ encodingOrCallback,
1394
+ callback2
1395
+ );
1396
+ }
1397
+ requestEvent = modifiedEvent;
1398
+ }
1399
+ self2.sendMessage(requestEvent);
1400
+ }
1401
+ return originalEnd(chunk, encodingOrCallback, callback2);
1402
+ };
1403
+ let responseSent = false;
1404
+ req.on("response", (res) => {
1405
+ const responseChunks = [];
1406
+ let responseSize = 0;
1407
+ res.on("data", (chunk) => {
1408
+ if (responseSize < MAX_BODY_SIZE) {
1409
+ responseChunks.push(chunk);
1410
+ responseSize += chunk.length;
1411
+ }
1412
+ });
1413
+ res.on("end", () => {
1414
+ if (responseSent) return;
1415
+ responseSent = true;
1416
+ const endTime = Date.now();
1417
+ const duration = endTime - startTime;
1418
+ const responseHeaders = {};
1419
+ if (res.headers) {
1420
+ for (const [key, value] of Object.entries(res.headers)) {
1421
+ if (value) {
1422
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
1423
+ }
1424
+ }
1425
+ }
1426
+ const contentType = responseHeaders["content-type"] || "";
1427
+ let responseBodyStr;
1428
+ if (responseChunks.length > 0) {
1429
+ if (isBinaryContentType(contentType)) {
1430
+ responseBodyStr = `[Binary Data: ${contentType}]`;
1431
+ } else {
1432
+ const full = Buffer.concat(responseChunks);
1433
+ responseBodyStr = full.length > MAX_BODY_SIZE ? full.toString("utf-8", 0, MAX_BODY_SIZE) + "...[truncated]" : full.toString("utf-8");
1434
+ }
1435
+ }
1436
+ const responseBody = serializeBody(
1437
+ responseBodyStr,
1438
+ self2.config?.disableBodyCapture
1439
+ );
1440
+ const statusCode = res.statusCode ?? 0;
1441
+ let responseEvent = {
1442
+ id: requestId,
1443
+ traceId,
1444
+ sessionId: self2.getSessionId(),
1445
+ timestamp: endTime,
1446
+ phase: "RESPONSE" /* RESPONSE */,
1447
+ networkType: "http" /* HTTP */,
1448
+ status: statusCode,
1449
+ statusText: res.statusMessage || "",
1450
+ headers: redactSensitiveHeaders(responseHeaders),
1451
+ body: responseBody,
1452
+ duration,
1453
+ responseSize: responseBody?.size ?? 0,
1454
+ redirected: false,
1455
+ ok: statusCode >= 200 && statusCode < 300
1456
+ };
1457
+ if (self2.config?.beforeSend) {
1458
+ const modifiedEvent = self2.config.beforeSend(responseEvent);
1459
+ if (!modifiedEvent) return;
1460
+ if (modifiedEvent.phase !== "RESPONSE" /* RESPONSE */) {
1461
+ console.error("[Limelight] beforeSend must return same event type");
1462
+ return;
1463
+ }
1464
+ responseEvent = modifiedEvent;
1465
+ }
1466
+ self2.sendMessage(responseEvent);
1467
+ });
1468
+ });
1469
+ req.on("error", (err) => {
1470
+ if (responseSent) return;
1471
+ responseSent = true;
1472
+ let errorEvent = {
1473
+ id: requestId,
1474
+ traceId,
1475
+ sessionId: self2.getSessionId(),
1476
+ timestamp: Date.now(),
1477
+ phase: "ERROR" /* ERROR */,
1478
+ networkType: "http" /* HTTP */,
1479
+ errorMessage: err.message || "Network request failed",
1480
+ stack: err.stack
1481
+ };
1482
+ if (self2.config?.beforeSend) {
1483
+ const modifiedEvent = self2.config.beforeSend(errorEvent);
1484
+ if (modifiedEvent && (modifiedEvent.phase === "ERROR" /* ERROR */ || modifiedEvent.phase === "ABORT" /* ABORT */)) {
1485
+ errorEvent = modifiedEvent;
1486
+ }
1487
+ }
1488
+ self2.sendMessage(errorEvent);
1489
+ });
1490
+ req.on("timeout", () => {
1491
+ if (responseSent) return;
1492
+ responseSent = true;
1493
+ let errorEvent = {
1494
+ id: requestId,
1495
+ traceId,
1496
+ sessionId: self2.getSessionId(),
1497
+ timestamp: Date.now(),
1498
+ phase: "ERROR" /* ERROR */,
1499
+ networkType: "http" /* HTTP */,
1500
+ errorMessage: "Request timeout"
1501
+ };
1502
+ if (self2.config?.beforeSend) {
1503
+ const modifiedEvent = self2.config.beforeSend(errorEvent);
1504
+ if (modifiedEvent && (modifiedEvent.phase === "ERROR" /* ERROR */ || modifiedEvent.phase === "ABORT" /* ABORT */)) {
1505
+ errorEvent = modifiedEvent;
1506
+ }
1507
+ }
1508
+ self2.sendMessage(errorEvent);
1509
+ });
1510
+ req.on("close", () => {
1511
+ if (responseSent) return;
1512
+ if (!req.destroyed) return;
1513
+ responseSent = true;
1514
+ let errorEvent = {
1515
+ id: requestId,
1516
+ traceId,
1517
+ sessionId: self2.getSessionId(),
1518
+ timestamp: Date.now(),
1519
+ phase: "ABORT" /* ABORT */,
1520
+ networkType: "http" /* HTTP */,
1521
+ errorMessage: "Request aborted"
1522
+ };
1523
+ if (self2.config?.beforeSend) {
1524
+ const modifiedEvent = self2.config.beforeSend(errorEvent);
1525
+ if (modifiedEvent && (modifiedEvent.phase === "ERROR" /* ERROR */ || modifiedEvent.phase === "ABORT" /* ABORT */)) {
1526
+ errorEvent = modifiedEvent;
1527
+ }
1528
+ }
1529
+ self2.sendMessage(errorEvent);
1530
+ });
1531
+ return req;
1532
+ }
1097
1533
  cleanup() {
1098
1534
  if (!this.isSetup) {
1099
1535
  if (this.config?.enableInternalLogging) {
1100
- console.warn("[Limelight] XHR interceptor not set up");
1536
+ console.warn("[Limelight] HTTP interceptor not set up");
1101
1537
  }
1102
1538
  return;
1103
1539
  }
1104
1540
  this.isSetup = false;
1105
- XMLHttpRequest.prototype.open = this.originalXHROpen;
1106
- XMLHttpRequest.prototype.send = this.originalXHRSend;
1107
- XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1541
+ if (this.httpModule && this.originalHttpRequest) {
1542
+ this.httpModule.request = this.originalHttpRequest;
1543
+ }
1544
+ if (this.httpModule && this.originalHttpGet) {
1545
+ this.httpModule.get = this.originalHttpGet;
1546
+ }
1547
+ if (this.httpsModule && this.originalHttpsRequest) {
1548
+ this.httpsModule.request = this.originalHttpsRequest;
1549
+ }
1550
+ if (this.httpsModule && this.originalHttpsGet) {
1551
+ this.httpsModule.get = this.originalHttpsGet;
1552
+ }
1108
1553
  }
1109
1554
  };
1110
1555
 
@@ -1757,6 +2202,83 @@ var RenderInterceptor = class {
1757
2202
  }
1758
2203
  };
1759
2204
 
2205
+ // src/limelight/interceptors/ErrorInterceptor.ts
2206
+ var ErrorInterceptor = class {
2207
+ constructor(sendMessage, getSessionId) {
2208
+ this.sendMessage = sendMessage;
2209
+ this.getSessionId = getSessionId;
2210
+ }
2211
+ isSetup = false;
2212
+ config = null;
2213
+ counter = 0;
2214
+ uncaughtExceptionHandler = null;
2215
+ unhandledRejectionHandler = null;
2216
+ setup(config) {
2217
+ if (this.isSetup) {
2218
+ if (this.config?.enableInternalLogging) {
2219
+ console.warn("[Limelight] Error interceptor already set up");
2220
+ }
2221
+ return;
2222
+ }
2223
+ if (typeof process === "undefined" || !process.on) {
2224
+ return;
2225
+ }
2226
+ this.isSetup = true;
2227
+ this.config = config;
2228
+ this.uncaughtExceptionHandler = (error) => {
2229
+ this.sendErrorEvent(error, "uncaughtException");
2230
+ setTimeout(() => {
2231
+ process.exit(1);
2232
+ }, 200);
2233
+ };
2234
+ this.unhandledRejectionHandler = (reason) => {
2235
+ const error = reason instanceof Error ? reason : new Error(String(reason));
2236
+ this.sendErrorEvent(error, "unhandledRejection");
2237
+ };
2238
+ process.on("uncaughtException", this.uncaughtExceptionHandler);
2239
+ process.on("unhandledRejection", this.unhandledRejectionHandler);
2240
+ }
2241
+ cleanup() {
2242
+ if (!this.isSetup) return;
2243
+ this.isSetup = false;
2244
+ if (this.uncaughtExceptionHandler) {
2245
+ process.removeListener(
2246
+ "uncaughtException",
2247
+ this.uncaughtExceptionHandler
2248
+ );
2249
+ this.uncaughtExceptionHandler = null;
2250
+ }
2251
+ if (this.unhandledRejectionHandler) {
2252
+ process.removeListener(
2253
+ "unhandledRejection",
2254
+ this.unhandledRejectionHandler
2255
+ );
2256
+ this.unhandledRejectionHandler = null;
2257
+ }
2258
+ this.config = null;
2259
+ }
2260
+ sendErrorEvent(error, source) {
2261
+ const sessionId = this.getSessionId();
2262
+ const event = {
2263
+ id: `${sessionId}-${Date.now()}-${this.counter++}`,
2264
+ phase: "CONSOLE",
2265
+ type: "CONSOLE" /* CONSOLE */,
2266
+ level: "error" /* ERROR */,
2267
+ timestamp: Date.now(),
2268
+ sessionId,
2269
+ source: "app" /* APP */,
2270
+ consoleType: "exception" /* EXCEPTION */,
2271
+ args: [safeStringify(`[${source}] ${error.message}`)],
2272
+ stackTrace: error.stack
2273
+ };
2274
+ if (this.config?.beforeSend) {
2275
+ const modified = this.config.beforeSend(event);
2276
+ if (!modified) return;
2277
+ }
2278
+ this.sendMessage(event);
2279
+ }
2280
+ };
2281
+
1760
2282
  // src/limelight/interceptors/StateInterceptor.ts
1761
2283
  var StateInterceptor = class {
1762
2284
  sendMessage;
@@ -2240,6 +2762,153 @@ var CommandHandler = class {
2240
2762
  }
2241
2763
  };
2242
2764
 
2765
+ // src/limelight/middleware/httpMiddleware.ts
2766
+ var DEFAULT_MAX_BODY_SIZE = 64 * 1024;
2767
+ var captureRequest = (req, res, sendMessage, getSessionId, config, options) => {
2768
+ const requestId = generateRequestId();
2769
+ const startTime = Date.now();
2770
+ const maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
2771
+ const traceHeaderName = config?.traceHeaderName ?? "x-limelight-trace-id";
2772
+ const incomingTraceId = req.headers[traceHeaderName];
2773
+ const traceId = incomingTraceId || generateRequestId();
2774
+ req.limelightTraceId = traceId;
2775
+ const url = req.url || "/";
2776
+ const method = (req.method || "GET").toUpperCase();
2777
+ const headers = {};
2778
+ for (const [key, value] of Object.entries(req.headers)) {
2779
+ if (value) {
2780
+ headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
2781
+ }
2782
+ }
2783
+ let requestBody = serializeBody(req.body, config?.disableBodyCapture);
2784
+ if (requestBody?.raw && requestBody.raw.length > maxBodySize) {
2785
+ requestBody = {
2786
+ ...requestBody,
2787
+ raw: requestBody.raw.slice(0, maxBodySize) + "...[truncated]",
2788
+ size: requestBody.size
2789
+ };
2790
+ }
2791
+ let requestEvent = {
2792
+ id: requestId,
2793
+ traceId,
2794
+ sessionId: getSessionId(),
2795
+ timestamp: startTime,
2796
+ phase: "REQUEST" /* REQUEST */,
2797
+ networkType: "incoming" /* INCOMING */,
2798
+ url,
2799
+ method,
2800
+ headers: redactSensitiveHeaders(headers),
2801
+ body: requestBody,
2802
+ name: url.split("?")[0]?.split("/").filter(Boolean).pop() ?? "/",
2803
+ initiator: "incoming",
2804
+ requestSize: requestBody?.size ?? 0
2805
+ };
2806
+ if (config?.beforeSend) {
2807
+ const modified = config.beforeSend(requestEvent);
2808
+ if (!modified) return;
2809
+ if (modified.phase !== "REQUEST" /* REQUEST */) {
2810
+ console.error("[Limelight] beforeSend must return same event type");
2811
+ return;
2812
+ }
2813
+ requestEvent = modified;
2814
+ }
2815
+ sendMessage(requestEvent);
2816
+ const chunks = [];
2817
+ let totalSize = 0;
2818
+ const originalWrite = res.write;
2819
+ const originalEnd = res.end;
2820
+ res.write = (chunk, ...args) => {
2821
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2822
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2823
+ chunks.push(buf);
2824
+ totalSize += buf.length;
2825
+ }
2826
+ return originalWrite.apply(res, [chunk, ...args]);
2827
+ };
2828
+ res.end = (chunk, ...args) => {
2829
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2830
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2831
+ chunks.push(buf);
2832
+ totalSize += buf.length;
2833
+ }
2834
+ return originalEnd.apply(res, [chunk, ...args]);
2835
+ };
2836
+ res.on("finish", () => {
2837
+ const endTime = Date.now();
2838
+ const duration = endTime - startTime;
2839
+ const responseHeaders = {};
2840
+ const rawHeaders = res.getHeaders();
2841
+ for (const [key, value] of Object.entries(rawHeaders)) {
2842
+ if (value) {
2843
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
2844
+ }
2845
+ }
2846
+ let responseBodyStr;
2847
+ if (chunks.length > 0 && !config?.disableBodyCapture) {
2848
+ const full = Buffer.concat(chunks);
2849
+ const fullStr = full.toString("utf-8");
2850
+ responseBodyStr = fullStr.length > maxBodySize ? fullStr.slice(0, maxBodySize) + "...[truncated]" : fullStr;
2851
+ }
2852
+ const responseBody = serializeBody(
2853
+ responseBodyStr,
2854
+ config?.disableBodyCapture
2855
+ );
2856
+ let responseEvent = {
2857
+ id: requestId,
2858
+ traceId,
2859
+ sessionId: getSessionId(),
2860
+ timestamp: endTime,
2861
+ phase: "RESPONSE" /* RESPONSE */,
2862
+ networkType: "incoming" /* INCOMING */,
2863
+ status: res.statusCode,
2864
+ statusText: res.statusMessage || "",
2865
+ headers: redactSensitiveHeaders(responseHeaders),
2866
+ body: responseBody,
2867
+ duration,
2868
+ responseSize: responseBody?.size ?? 0,
2869
+ redirected: false,
2870
+ ok: res.statusCode >= 200 && res.statusCode < 300
2871
+ };
2872
+ if (config?.beforeSend) {
2873
+ const modified = config.beforeSend(responseEvent);
2874
+ if (!modified) return;
2875
+ if (modified.phase !== "RESPONSE" /* RESPONSE */) {
2876
+ console.error("[Limelight] beforeSend must return same event type");
2877
+ return;
2878
+ }
2879
+ responseEvent = modified;
2880
+ }
2881
+ sendMessage(responseEvent);
2882
+ });
2883
+ };
2884
+ var createHttpMiddleware = (sendMessage, getSessionId, getConfig, options) => {
2885
+ return (req, res, next) => {
2886
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2887
+ const traceId = req.limelightTraceId;
2888
+ const ctx = getTraceContext();
2889
+ if (ctx && traceId) {
2890
+ ctx.run({ traceId }, next);
2891
+ } else {
2892
+ next();
2893
+ }
2894
+ };
2895
+ };
2896
+
2897
+ // src/limelight/middleware/withLimelight.ts
2898
+ var createWithLimelight = (sendMessage, getSessionId, getConfig, options) => {
2899
+ return (handler) => {
2900
+ return (req, res) => {
2901
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2902
+ const traceId = req.limelightTraceId;
2903
+ const ctx = getTraceContext();
2904
+ if (ctx && traceId) {
2905
+ return ctx.run({ traceId }, () => handler(req, res));
2906
+ }
2907
+ return handler(req, res);
2908
+ };
2909
+ };
2910
+ };
2911
+
2243
2912
  // src/limelight/LimelightClient.ts
2244
2913
  var LimelightClient = class {
2245
2914
  ws = null;
@@ -2253,9 +2922,11 @@ var LimelightClient = class {
2253
2922
  maxQueueSize = 100;
2254
2923
  networkInterceptor;
2255
2924
  xhrInterceptor;
2925
+ httpInterceptor;
2256
2926
  consoleInterceptor;
2257
2927
  renderInterceptor;
2258
2928
  stateInterceptor;
2929
+ errorInterceptor;
2259
2930
  requestBridge;
2260
2931
  commandHandler = null;
2261
2932
  constructor() {
@@ -2267,6 +2938,10 @@ var LimelightClient = class {
2267
2938
  this.sendMessage.bind(this),
2268
2939
  () => this.sessionId
2269
2940
  );
2941
+ this.httpInterceptor = new HttpInterceptor(
2942
+ this.sendMessage.bind(this),
2943
+ () => this.sessionId
2944
+ );
2270
2945
  this.consoleInterceptor = new ConsoleInterceptor(
2271
2946
  this.sendMessage.bind(this),
2272
2947
  () => this.sessionId
@@ -2279,6 +2954,10 @@ var LimelightClient = class {
2279
2954
  this.sendMessage.bind(this),
2280
2955
  () => this.sessionId
2281
2956
  );
2957
+ this.errorInterceptor = new ErrorInterceptor(
2958
+ this.sendMessage.bind(this),
2959
+ () => this.sessionId
2960
+ );
2282
2961
  this.requestBridge = new RequestBridge(
2283
2962
  this.sendMessage.bind(this),
2284
2963
  () => this.sessionId
@@ -2320,12 +2999,20 @@ var LimelightClient = class {
2320
2999
  try {
2321
3000
  if (this.config.enableNetworkInspector) {
2322
3001
  this.networkInterceptor.setup(this.config);
2323
- this.xhrInterceptor.setup(this.config);
3002
+ if (typeof XMLHttpRequest !== "undefined") {
3003
+ this.xhrInterceptor.setup(this.config);
3004
+ }
3005
+ if (!hasDOM()) {
3006
+ this.httpInterceptor.setup(this.config);
3007
+ }
2324
3008
  }
2325
3009
  if (this.config.enableConsole) {
2326
3010
  this.consoleInterceptor.setup(this.config);
3011
+ if (!hasDOM()) {
3012
+ this.errorInterceptor.setup(this.config);
3013
+ }
2327
3014
  }
2328
- if (this.config.enableRenderInspector) {
3015
+ if (this.config.enableRenderInspector && hasDOM()) {
2329
3016
  this.renderInterceptor.setup(this.config);
2330
3017
  }
2331
3018
  if (this.config.stores && this.config.enableStateInspector) {
@@ -2353,7 +3040,7 @@ var LimelightClient = class {
2353
3040
  if (!this.config?.enabled) {
2354
3041
  return;
2355
3042
  }
2356
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
3043
+ if (this.ws && this.ws.readyState === 1) {
2357
3044
  if (this.config?.enableInternalLogging) {
2358
3045
  console.warn("[Limelight] Already connected. Call disconnect() first.");
2359
3046
  }
@@ -2376,15 +3063,24 @@ var LimelightClient = class {
2376
3063
  }
2377
3064
  return;
2378
3065
  }
3066
+ const WsConstructor = this.config.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
3067
+ if (!WsConstructor) {
3068
+ if (this.config?.enableInternalLogging) {
3069
+ console.error(
3070
+ "[Limelight] WebSocket is not available. Pass webSocketImpl in config (e.g. ws package)."
3071
+ );
3072
+ }
3073
+ return;
3074
+ }
2379
3075
  try {
2380
- this.ws = new WebSocket(serverUrl);
3076
+ this.ws = new WsConstructor(serverUrl);
2381
3077
  const message = {
2382
3078
  phase: "CONNECT",
2383
3079
  sessionId: this.sessionId,
2384
3080
  timestamp: Date.now(),
2385
3081
  data: {
2386
3082
  appName,
2387
- platform: platform || (typeof window !== "undefined" ? "web" : "react-native"),
3083
+ platform: platform || (hasDOM() ? "web" : typeof process !== "undefined" ? "node" : "react-native"),
2388
3084
  projectKey: this.config.projectKey || "",
2389
3085
  sdkVersion: SDK_VERSION
2390
3086
  }
@@ -2451,10 +3147,13 @@ var LimelightClient = class {
2451
3147
  * @returns {void}
2452
3148
  */
2453
3149
  flushMessageQueue() {
2454
- if (this.ws?.readyState !== WebSocket.OPEN) return;
3150
+ if (this.ws?.readyState !== 1) return;
2455
3151
  while (this.messageQueue.length > 0) {
2456
3152
  const message = this.messageQueue.shift();
2457
3153
  try {
3154
+ if (message && "sessionId" in message && !message.sessionId) {
3155
+ message.sessionId = this.sessionId;
3156
+ }
2458
3157
  this.ws.send(safeStringify(message));
2459
3158
  } catch (error) {
2460
3159
  if (this.config?.enableInternalLogging) {
@@ -2472,9 +3171,9 @@ var LimelightClient = class {
2472
3171
  * @returns {void}
2473
3172
  */
2474
3173
  sendMessage(message) {
2475
- if (this.ws?.readyState === WebSocket.OPEN) {
3174
+ if (this.ws?.readyState === 1) {
2476
3175
  this.flushMessageQueue();
2477
- if (this.ws?.readyState === WebSocket.OPEN) {
3176
+ if (this.ws?.readyState === 1) {
2478
3177
  try {
2479
3178
  this.ws.send(safeStringify(message));
2480
3179
  } catch (error) {
@@ -2531,7 +3230,9 @@ var LimelightClient = class {
2531
3230
  }
2532
3231
  this.networkInterceptor.cleanup();
2533
3232
  this.xhrInterceptor.cleanup();
3233
+ this.httpInterceptor.cleanup();
2534
3234
  this.consoleInterceptor.cleanup();
3235
+ this.errorInterceptor.cleanup();
2535
3236
  this.renderInterceptor.cleanup();
2536
3237
  this.stateInterceptor.cleanup();
2537
3238
  this.requestBridge.cleanup();
@@ -2578,6 +3279,46 @@ var LimelightClient = class {
2578
3279
  failRequest(requestId, error) {
2579
3280
  this.requestBridge.failRequest(requestId, error);
2580
3281
  }
3282
+ /**
3283
+ * Returns an Express/Connect-compatible middleware that captures incoming
3284
+ * HTTP requests and responses.
3285
+ *
3286
+ * Place after body-parser middleware (express.json(), etc.) for request body capture.
3287
+ *
3288
+ * @example
3289
+ * ```ts
3290
+ * app.use(express.json());
3291
+ * app.use(Limelight.middleware());
3292
+ * ```
3293
+ */
3294
+ middleware(options) {
3295
+ return createHttpMiddleware(
3296
+ this.sendMessage.bind(this),
3297
+ () => this.sessionId,
3298
+ () => this.config,
3299
+ options
3300
+ );
3301
+ }
3302
+ /**
3303
+ * Wraps a Next.js Pages API route handler with request/response capture.
3304
+ * Works with Pages Router (`pages/api/`), not App Router (`app/api/`).
3305
+ *
3306
+ * @example
3307
+ * ```ts
3308
+ * // pages/api/users.ts
3309
+ * export default Limelight.withLimelight((req, res) => {
3310
+ * res.json({ ok: true });
3311
+ * });
3312
+ * ```
3313
+ */
3314
+ withLimelight(handler) {
3315
+ const wrapper = createWithLimelight(
3316
+ this.sendMessage.bind(this),
3317
+ () => this.sessionId,
3318
+ () => this.config
3319
+ );
3320
+ return wrapper(handler);
3321
+ }
2581
3322
  };
2582
3323
  var Limelight = new LimelightClient();
2583
3324
  export {