@getlimelight/sdk 0.6.1 → 0.7.4

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
@@ -29,6 +29,7 @@ var NetworkType = /* @__PURE__ */ ((NetworkType2) => {
29
29
  NetworkType2["FETCH"] = "fetch";
30
30
  NetworkType2["XHR"] = "xhr";
31
31
  NetworkType2["GRAPHQL"] = "graphql";
32
+ NetworkType2["INCOMING"] = "incoming";
32
33
  return NetworkType2;
33
34
  })(NetworkType || {});
34
35
  var NetworkPhase = /* @__PURE__ */ ((NetworkPhase2) => {
@@ -49,17 +50,17 @@ var BodyFormat = /* @__PURE__ */ ((BodyFormat2) => {
49
50
  BodyFormat2["UNSERIALIZABLE"] = "UNSERIALIZABLE";
50
51
  return BodyFormat2;
51
52
  })(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;
53
+ var HttpMethod = /* @__PURE__ */ ((HttpMethod6) => {
54
+ HttpMethod6["GET"] = "GET";
55
+ HttpMethod6["POST"] = "POST";
56
+ HttpMethod6["PUT"] = "PUT";
57
+ HttpMethod6["PATCH"] = "PATCH";
58
+ HttpMethod6["DELETE"] = "DELETE";
59
+ HttpMethod6["HEAD"] = "HEAD";
60
+ HttpMethod6["OPTIONS"] = "OPTIONS";
61
+ HttpMethod6["TRACE"] = "TRACE";
62
+ HttpMethod6["CONNECT"] = "CONNECT";
63
+ return HttpMethod6;
63
64
  })(HttpMethod || {});
64
65
  var HttpStatusClass = /* @__PURE__ */ ((HttpStatusClass2) => {
65
66
  HttpStatusClass2[HttpStatusClass2["INFORMATIONAL"] = 100] = "INFORMATIONAL";
@@ -249,7 +250,7 @@ var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
249
250
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
250
251
  var LIMELIGHT_MCP_WS_URL = "ws://localhost:9229";
251
252
  var WS_PATH = "/limelight";
252
- var SDK_VERSION = true ? "0.6.1" : "test-version";
253
+ var SDK_VERSION = true ? "0.7.4" : "test-version";
253
254
  var RENDER_THRESHOLDS = {
254
255
  HOT_VELOCITY: 5,
255
256
  HIGH_RENDER_COUNT: 50,
@@ -503,6 +504,9 @@ var formatRequestName = (url) => {
503
504
  }
504
505
  };
505
506
 
507
+ // src/helpers/utils/environment.ts
508
+ var hasDOM = () => typeof window !== "undefined" && typeof document !== "undefined";
509
+
506
510
  // src/helpers/render/generateRenderId.ts
507
511
  var counter = 0;
508
512
  var generateRenderId = () => {
@@ -668,13 +672,31 @@ var generateRequestId = () => {
668
672
  return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
669
673
  };
670
674
 
675
+ // src/limelight/context/traceContext.ts
676
+ var _resolved = false;
677
+ var _traceContext;
678
+ var getTraceContext = () => {
679
+ if (!_resolved) {
680
+ _resolved = true;
681
+ try {
682
+ const _require = globalThis["require"];
683
+ const { AsyncLocalStorage } = _require("node:async_hooks");
684
+ _traceContext = new AsyncLocalStorage();
685
+ } catch {
686
+ }
687
+ }
688
+ return _traceContext;
689
+ };
690
+
671
691
  // src/limelight/interceptors/NetworkInterceptor.ts
672
692
  var NetworkInterceptor = class {
673
693
  constructor(sendMessage, getSessionId) {
674
694
  this.sendMessage = sendMessage;
675
695
  this.getSessionId = getSessionId;
676
696
  this.globalObject = detectGlobalObject();
677
- this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
697
+ if (typeof this.globalObject.fetch === "function") {
698
+ this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
699
+ }
678
700
  }
679
701
  originalFetch;
680
702
  config = null;
@@ -694,9 +716,18 @@ var NetworkInterceptor = class {
694
716
  }
695
717
  return;
696
718
  }
719
+ if (!this.originalFetch) {
720
+ if (config?.enableInternalLogging) {
721
+ console.warn(
722
+ "[Limelight] fetch is not available in this environment, skipping network interception"
723
+ );
724
+ }
725
+ return;
726
+ }
697
727
  this.isSetup = true;
698
728
  this.config = config;
699
729
  const self2 = this;
730
+ const originalFetch = this.originalFetch;
700
731
  this.globalObject.fetch = async function(input, init = {}) {
701
732
  const requestId = generateRequestId();
702
733
  const startTime = Date.now();
@@ -714,6 +745,12 @@ var NetworkInterceptor = class {
714
745
  });
715
746
  }
716
747
  headers["x-limelight-intercepted"] = "fetch";
748
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
749
+ if (!headers[traceHeaderName]) {
750
+ const existingTraceId = getTraceContext()?.getStore()?.traceId;
751
+ headers[traceHeaderName] = existingTraceId || generateRequestId();
752
+ }
753
+ const traceId = headers[traceHeaderName];
717
754
  modifiedInit.headers = new Headers(headers);
718
755
  let requestBodyToSerialize = init.body;
719
756
  if (input instanceof Request && !requestBodyToSerialize) {
@@ -747,6 +784,7 @@ var NetworkInterceptor = class {
747
784
  }
748
785
  let requestEvent = {
749
786
  id: requestId,
787
+ traceId,
750
788
  sessionId: self2.getSessionId(),
751
789
  timestamp: startTime,
752
790
  phase: "REQUEST" /* REQUEST */,
@@ -763,17 +801,17 @@ var NetworkInterceptor = class {
763
801
  if (self2.config?.beforeSend) {
764
802
  const modifiedEvent = self2.config.beforeSend(requestEvent);
765
803
  if (!modifiedEvent) {
766
- return self2.originalFetch(input, modifiedInit);
804
+ return originalFetch(input, modifiedInit);
767
805
  }
768
806
  if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
769
807
  console.error("[Limelight] beforeSend must return same event type");
770
- return self2.originalFetch(input, modifiedInit);
808
+ return originalFetch(input, modifiedInit);
771
809
  }
772
810
  requestEvent = modifiedEvent;
773
811
  }
774
812
  self2.sendMessage(requestEvent);
775
813
  try {
776
- const response = await self2.originalFetch(input, modifiedInit);
814
+ const response = await originalFetch(input, modifiedInit);
777
815
  const clone = response.clone();
778
816
  const endTime = Date.now();
779
817
  const duration = endTime - startTime;
@@ -793,6 +831,7 @@ var NetworkInterceptor = class {
793
831
  );
794
832
  let responseEvent = {
795
833
  id: requestId,
834
+ traceId,
796
835
  sessionId: self2.getSessionId(),
797
836
  timestamp: endTime,
798
837
  phase: "RESPONSE" /* RESPONSE */,
@@ -825,6 +864,7 @@ var NetworkInterceptor = class {
825
864
  const errorStack = err instanceof Error ? err.stack : void 0;
826
865
  let errorEvent = {
827
866
  id: requestId,
867
+ traceId,
828
868
  sessionId: self2.getSessionId(),
829
869
  timestamp: Date.now(),
830
870
  phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
@@ -855,7 +895,9 @@ var NetworkInterceptor = class {
855
895
  return;
856
896
  }
857
897
  this.isSetup = false;
858
- this.globalObject.fetch = this.originalFetch;
898
+ if (this.originalFetch) {
899
+ this.globalObject.fetch = this.originalFetch;
900
+ }
859
901
  }
860
902
  };
861
903
 
@@ -864,9 +906,11 @@ var XHRInterceptor = class {
864
906
  constructor(sendMessage, getSessionId) {
865
907
  this.sendMessage = sendMessage;
866
908
  this.getSessionId = getSessionId;
867
- this.originalXHROpen = XMLHttpRequest.prototype.open;
868
- this.originalXHRSend = XMLHttpRequest.prototype.send;
869
- this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
909
+ if (typeof XMLHttpRequest !== "undefined") {
910
+ this.originalXHROpen = XMLHttpRequest.prototype.open;
911
+ this.originalXHRSend = XMLHttpRequest.prototype.send;
912
+ this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
913
+ }
870
914
  }
871
915
  originalXHROpen;
872
916
  originalXHRSend;
@@ -881,6 +925,9 @@ var XHRInterceptor = class {
881
925
  * @returns {void}
882
926
  */
883
927
  setup(config) {
928
+ if (typeof XMLHttpRequest === "undefined") {
929
+ return;
930
+ }
884
931
  if (this.isSetup) {
885
932
  if (this.config?.enableInternalLogging) {
886
933
  console.warn("[Limelight] XHR interceptor already set up");
@@ -919,12 +966,24 @@ var XHRInterceptor = class {
919
966
  return self2.originalXHRSend.apply(this, arguments);
920
967
  }
921
968
  if (data) {
969
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
970
+ if (!data.headers[traceHeaderName]) {
971
+ data.traceId = generateRequestId();
972
+ self2.originalXHRSetRequestHeader.call(
973
+ this,
974
+ traceHeaderName,
975
+ data.traceId
976
+ );
977
+ } else {
978
+ data.traceId = data.headers[traceHeaderName];
979
+ }
922
980
  const requestBody = serializeBody(
923
981
  body,
924
982
  self2.config?.disableBodyCapture
925
983
  );
926
984
  let requestEvent = {
927
985
  id: data.id,
986
+ traceId: data.traceId,
928
987
  sessionId: self2.getSessionId(),
929
988
  timestamp: data.startTime,
930
989
  phase: "REQUEST" /* REQUEST */,
@@ -984,6 +1043,7 @@ var XHRInterceptor = class {
984
1043
  );
985
1044
  let responseEvent = {
986
1045
  id: data.id,
1046
+ traceId: data.traceId,
987
1047
  sessionId: self2.getSessionId(),
988
1048
  timestamp: endTime,
989
1049
  phase: "RESPONSE" /* RESPONSE */,
@@ -1018,6 +1078,7 @@ var XHRInterceptor = class {
1018
1078
  responseSent = true;
1019
1079
  let errorEvent = {
1020
1080
  id: data.id,
1081
+ traceId: data.traceId,
1021
1082
  sessionId: self2.getSessionId(),
1022
1083
  timestamp: Date.now(),
1023
1084
  phase,
@@ -1096,15 +1157,14 @@ var XHRInterceptor = class {
1096
1157
  */
1097
1158
  cleanup() {
1098
1159
  if (!this.isSetup) {
1099
- if (this.config?.enableInternalLogging) {
1100
- console.warn("[Limelight] XHR interceptor not set up");
1101
- }
1102
1160
  return;
1103
1161
  }
1104
1162
  this.isSetup = false;
1105
- XMLHttpRequest.prototype.open = this.originalXHROpen;
1106
- XMLHttpRequest.prototype.send = this.originalXHRSend;
1107
- XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1163
+ if (typeof XMLHttpRequest !== "undefined") {
1164
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
1165
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
1166
+ XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1167
+ }
1108
1168
  }
1109
1169
  };
1110
1170
 
@@ -1757,6 +1817,83 @@ var RenderInterceptor = class {
1757
1817
  }
1758
1818
  };
1759
1819
 
1820
+ // src/limelight/interceptors/ErrorInterceptor.ts
1821
+ var ErrorInterceptor = class {
1822
+ constructor(sendMessage, getSessionId) {
1823
+ this.sendMessage = sendMessage;
1824
+ this.getSessionId = getSessionId;
1825
+ }
1826
+ isSetup = false;
1827
+ config = null;
1828
+ counter = 0;
1829
+ uncaughtExceptionHandler = null;
1830
+ unhandledRejectionHandler = null;
1831
+ setup(config) {
1832
+ if (this.isSetup) {
1833
+ if (this.config?.enableInternalLogging) {
1834
+ console.warn("[Limelight] Error interceptor already set up");
1835
+ }
1836
+ return;
1837
+ }
1838
+ if (typeof process === "undefined" || !process.on) {
1839
+ return;
1840
+ }
1841
+ this.isSetup = true;
1842
+ this.config = config;
1843
+ this.uncaughtExceptionHandler = (error) => {
1844
+ this.sendErrorEvent(error, "uncaughtException");
1845
+ setTimeout(() => {
1846
+ process.exit(1);
1847
+ }, 200);
1848
+ };
1849
+ this.unhandledRejectionHandler = (reason) => {
1850
+ const error = reason instanceof Error ? reason : new Error(String(reason));
1851
+ this.sendErrorEvent(error, "unhandledRejection");
1852
+ };
1853
+ process.on("uncaughtException", this.uncaughtExceptionHandler);
1854
+ process.on("unhandledRejection", this.unhandledRejectionHandler);
1855
+ }
1856
+ cleanup() {
1857
+ if (!this.isSetup) return;
1858
+ this.isSetup = false;
1859
+ if (this.uncaughtExceptionHandler) {
1860
+ process.removeListener(
1861
+ "uncaughtException",
1862
+ this.uncaughtExceptionHandler
1863
+ );
1864
+ this.uncaughtExceptionHandler = null;
1865
+ }
1866
+ if (this.unhandledRejectionHandler) {
1867
+ process.removeListener(
1868
+ "unhandledRejection",
1869
+ this.unhandledRejectionHandler
1870
+ );
1871
+ this.unhandledRejectionHandler = null;
1872
+ }
1873
+ this.config = null;
1874
+ }
1875
+ sendErrorEvent(error, source) {
1876
+ const sessionId = this.getSessionId();
1877
+ const event = {
1878
+ id: `${sessionId}-${Date.now()}-${this.counter++}`,
1879
+ phase: "CONSOLE",
1880
+ type: "CONSOLE" /* CONSOLE */,
1881
+ level: "error" /* ERROR */,
1882
+ timestamp: Date.now(),
1883
+ sessionId,
1884
+ source: "app" /* APP */,
1885
+ consoleType: "exception" /* EXCEPTION */,
1886
+ args: [safeStringify(`[${source}] ${error.message}`)],
1887
+ stackTrace: error.stack
1888
+ };
1889
+ if (this.config?.beforeSend) {
1890
+ const modified = this.config.beforeSend(event);
1891
+ if (!modified) return;
1892
+ }
1893
+ this.sendMessage(event);
1894
+ }
1895
+ };
1896
+
1760
1897
  // src/limelight/interceptors/StateInterceptor.ts
1761
1898
  var StateInterceptor = class {
1762
1899
  sendMessage;
@@ -2240,6 +2377,153 @@ var CommandHandler = class {
2240
2377
  }
2241
2378
  };
2242
2379
 
2380
+ // src/limelight/middleware/httpMiddleware.ts
2381
+ var DEFAULT_MAX_BODY_SIZE = 64 * 1024;
2382
+ var captureRequest = (req, res, sendMessage, getSessionId, config, options) => {
2383
+ const requestId = generateRequestId();
2384
+ const startTime = Date.now();
2385
+ const maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
2386
+ const traceHeaderName = config?.traceHeaderName ?? "x-limelight-trace-id";
2387
+ const incomingTraceId = req.headers[traceHeaderName];
2388
+ const traceId = incomingTraceId || generateRequestId();
2389
+ req.limelightTraceId = traceId;
2390
+ const url = req.url || "/";
2391
+ const method = (req.method || "GET").toUpperCase();
2392
+ const headers = {};
2393
+ for (const [key, value] of Object.entries(req.headers)) {
2394
+ if (value) {
2395
+ headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
2396
+ }
2397
+ }
2398
+ let requestBody = serializeBody(req.body, config?.disableBodyCapture);
2399
+ if (requestBody?.raw && requestBody.raw.length > maxBodySize) {
2400
+ requestBody = {
2401
+ ...requestBody,
2402
+ raw: requestBody.raw.slice(0, maxBodySize) + "...[truncated]",
2403
+ size: requestBody.size
2404
+ };
2405
+ }
2406
+ let requestEvent = {
2407
+ id: requestId,
2408
+ traceId,
2409
+ sessionId: getSessionId(),
2410
+ timestamp: startTime,
2411
+ phase: "REQUEST" /* REQUEST */,
2412
+ networkType: "incoming" /* INCOMING */,
2413
+ url,
2414
+ method,
2415
+ headers: redactSensitiveHeaders(headers),
2416
+ body: requestBody,
2417
+ name: url.split("?")[0]?.split("/").filter(Boolean).pop() ?? "/",
2418
+ initiator: "incoming",
2419
+ requestSize: requestBody?.size ?? 0
2420
+ };
2421
+ if (config?.beforeSend) {
2422
+ const modified = config.beforeSend(requestEvent);
2423
+ if (!modified) return;
2424
+ if (modified.phase !== "REQUEST" /* REQUEST */) {
2425
+ console.error("[Limelight] beforeSend must return same event type");
2426
+ return;
2427
+ }
2428
+ requestEvent = modified;
2429
+ }
2430
+ sendMessage(requestEvent);
2431
+ const chunks = [];
2432
+ let totalSize = 0;
2433
+ const originalWrite = res.write;
2434
+ const originalEnd = res.end;
2435
+ res.write = (chunk, ...args) => {
2436
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2437
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2438
+ chunks.push(buf);
2439
+ totalSize += buf.length;
2440
+ }
2441
+ return originalWrite.apply(res, [chunk, ...args]);
2442
+ };
2443
+ res.end = (chunk, ...args) => {
2444
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2445
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2446
+ chunks.push(buf);
2447
+ totalSize += buf.length;
2448
+ }
2449
+ return originalEnd.apply(res, [chunk, ...args]);
2450
+ };
2451
+ res.on("finish", () => {
2452
+ const endTime = Date.now();
2453
+ const duration = endTime - startTime;
2454
+ const responseHeaders = {};
2455
+ const rawHeaders = res.getHeaders();
2456
+ for (const [key, value] of Object.entries(rawHeaders)) {
2457
+ if (value) {
2458
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
2459
+ }
2460
+ }
2461
+ let responseBodyStr;
2462
+ if (chunks.length > 0 && !config?.disableBodyCapture) {
2463
+ const full = Buffer.concat(chunks);
2464
+ const fullStr = full.toString("utf-8");
2465
+ responseBodyStr = fullStr.length > maxBodySize ? fullStr.slice(0, maxBodySize) + "...[truncated]" : fullStr;
2466
+ }
2467
+ const responseBody = serializeBody(
2468
+ responseBodyStr,
2469
+ config?.disableBodyCapture
2470
+ );
2471
+ let responseEvent = {
2472
+ id: requestId,
2473
+ traceId,
2474
+ sessionId: getSessionId(),
2475
+ timestamp: endTime,
2476
+ phase: "RESPONSE" /* RESPONSE */,
2477
+ networkType: "incoming" /* INCOMING */,
2478
+ status: res.statusCode,
2479
+ statusText: res.statusMessage || "",
2480
+ headers: redactSensitiveHeaders(responseHeaders),
2481
+ body: responseBody,
2482
+ duration,
2483
+ responseSize: responseBody?.size ?? 0,
2484
+ redirected: false,
2485
+ ok: res.statusCode >= 200 && res.statusCode < 300
2486
+ };
2487
+ if (config?.beforeSend) {
2488
+ const modified = config.beforeSend(responseEvent);
2489
+ if (!modified) return;
2490
+ if (modified.phase !== "RESPONSE" /* RESPONSE */) {
2491
+ console.error("[Limelight] beforeSend must return same event type");
2492
+ return;
2493
+ }
2494
+ responseEvent = modified;
2495
+ }
2496
+ sendMessage(responseEvent);
2497
+ });
2498
+ };
2499
+ var createHttpMiddleware = (sendMessage, getSessionId, getConfig, options) => {
2500
+ return (req, res, next) => {
2501
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2502
+ const traceId = req.limelightTraceId;
2503
+ const ctx = getTraceContext();
2504
+ if (ctx && traceId) {
2505
+ ctx.run({ traceId }, next);
2506
+ } else {
2507
+ next();
2508
+ }
2509
+ };
2510
+ };
2511
+
2512
+ // src/limelight/middleware/withLimelight.ts
2513
+ var createWithLimelight = (sendMessage, getSessionId, getConfig, options) => {
2514
+ return (handler) => {
2515
+ return (req, res) => {
2516
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2517
+ const traceId = req.limelightTraceId;
2518
+ const ctx = getTraceContext();
2519
+ if (ctx && traceId) {
2520
+ return ctx.run({ traceId }, () => handler(req, res));
2521
+ }
2522
+ return handler(req, res);
2523
+ };
2524
+ };
2525
+ };
2526
+
2243
2527
  // src/limelight/LimelightClient.ts
2244
2528
  var LimelightClient = class {
2245
2529
  ws = null;
@@ -2256,6 +2540,7 @@ var LimelightClient = class {
2256
2540
  consoleInterceptor;
2257
2541
  renderInterceptor;
2258
2542
  stateInterceptor;
2543
+ errorInterceptor;
2259
2544
  requestBridge;
2260
2545
  commandHandler = null;
2261
2546
  constructor() {
@@ -2279,6 +2564,10 @@ var LimelightClient = class {
2279
2564
  this.sendMessage.bind(this),
2280
2565
  () => this.sessionId
2281
2566
  );
2567
+ this.errorInterceptor = new ErrorInterceptor(
2568
+ this.sendMessage.bind(this),
2569
+ () => this.sessionId
2570
+ );
2282
2571
  this.requestBridge = new RequestBridge(
2283
2572
  this.sendMessage.bind(this),
2284
2573
  () => this.sessionId
@@ -2320,12 +2609,17 @@ var LimelightClient = class {
2320
2609
  try {
2321
2610
  if (this.config.enableNetworkInspector) {
2322
2611
  this.networkInterceptor.setup(this.config);
2323
- this.xhrInterceptor.setup(this.config);
2612
+ if (typeof XMLHttpRequest !== "undefined") {
2613
+ this.xhrInterceptor.setup(this.config);
2614
+ }
2324
2615
  }
2325
2616
  if (this.config.enableConsole) {
2326
2617
  this.consoleInterceptor.setup(this.config);
2618
+ if (!hasDOM()) {
2619
+ this.errorInterceptor.setup(this.config);
2620
+ }
2327
2621
  }
2328
- if (this.config.enableRenderInspector) {
2622
+ if (this.config.enableRenderInspector && hasDOM()) {
2329
2623
  this.renderInterceptor.setup(this.config);
2330
2624
  }
2331
2625
  if (this.config.stores && this.config.enableStateInspector) {
@@ -2353,7 +2647,7 @@ var LimelightClient = class {
2353
2647
  if (!this.config?.enabled) {
2354
2648
  return;
2355
2649
  }
2356
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
2650
+ if (this.ws && this.ws.readyState === 1) {
2357
2651
  if (this.config?.enableInternalLogging) {
2358
2652
  console.warn("[Limelight] Already connected. Call disconnect() first.");
2359
2653
  }
@@ -2376,15 +2670,24 @@ var LimelightClient = class {
2376
2670
  }
2377
2671
  return;
2378
2672
  }
2673
+ const WsConstructor = this.config.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
2674
+ if (!WsConstructor) {
2675
+ if (this.config?.enableInternalLogging) {
2676
+ console.error(
2677
+ "[Limelight] WebSocket is not available. Pass webSocketImpl in config (e.g. ws package)."
2678
+ );
2679
+ }
2680
+ return;
2681
+ }
2379
2682
  try {
2380
- this.ws = new WebSocket(serverUrl);
2683
+ this.ws = new WsConstructor(serverUrl);
2381
2684
  const message = {
2382
2685
  phase: "CONNECT",
2383
2686
  sessionId: this.sessionId,
2384
2687
  timestamp: Date.now(),
2385
2688
  data: {
2386
2689
  appName,
2387
- platform: platform || (typeof window !== "undefined" ? "web" : "react-native"),
2690
+ platform: platform || (hasDOM() ? "web" : typeof process !== "undefined" ? "node" : "react-native"),
2388
2691
  projectKey: this.config.projectKey || "",
2389
2692
  sdkVersion: SDK_VERSION
2390
2693
  }
@@ -2451,10 +2754,13 @@ var LimelightClient = class {
2451
2754
  * @returns {void}
2452
2755
  */
2453
2756
  flushMessageQueue() {
2454
- if (this.ws?.readyState !== WebSocket.OPEN) return;
2757
+ if (this.ws?.readyState !== 1) return;
2455
2758
  while (this.messageQueue.length > 0) {
2456
2759
  const message = this.messageQueue.shift();
2457
2760
  try {
2761
+ if (message && "sessionId" in message && !message.sessionId) {
2762
+ message.sessionId = this.sessionId;
2763
+ }
2458
2764
  this.ws.send(safeStringify(message));
2459
2765
  } catch (error) {
2460
2766
  if (this.config?.enableInternalLogging) {
@@ -2472,9 +2778,9 @@ var LimelightClient = class {
2472
2778
  * @returns {void}
2473
2779
  */
2474
2780
  sendMessage(message) {
2475
- if (this.ws?.readyState === WebSocket.OPEN) {
2781
+ if (this.ws?.readyState === 1) {
2476
2782
  this.flushMessageQueue();
2477
- if (this.ws?.readyState === WebSocket.OPEN) {
2783
+ if (this.ws?.readyState === 1) {
2478
2784
  try {
2479
2785
  this.ws.send(safeStringify(message));
2480
2786
  } catch (error) {
@@ -2532,6 +2838,7 @@ var LimelightClient = class {
2532
2838
  this.networkInterceptor.cleanup();
2533
2839
  this.xhrInterceptor.cleanup();
2534
2840
  this.consoleInterceptor.cleanup();
2841
+ this.errorInterceptor.cleanup();
2535
2842
  this.renderInterceptor.cleanup();
2536
2843
  this.stateInterceptor.cleanup();
2537
2844
  this.requestBridge.cleanup();
@@ -2578,6 +2885,46 @@ var LimelightClient = class {
2578
2885
  failRequest(requestId, error) {
2579
2886
  this.requestBridge.failRequest(requestId, error);
2580
2887
  }
2888
+ /**
2889
+ * Returns an Express/Connect-compatible middleware that captures incoming
2890
+ * HTTP requests and responses.
2891
+ *
2892
+ * Place after body-parser middleware (express.json(), etc.) for request body capture.
2893
+ *
2894
+ * @example
2895
+ * ```ts
2896
+ * app.use(express.json());
2897
+ * app.use(Limelight.middleware());
2898
+ * ```
2899
+ */
2900
+ middleware(options) {
2901
+ return createHttpMiddleware(
2902
+ this.sendMessage.bind(this),
2903
+ () => this.sessionId,
2904
+ () => this.config,
2905
+ options
2906
+ );
2907
+ }
2908
+ /**
2909
+ * Wraps a Next.js Pages API route handler with request/response capture.
2910
+ * Works with Pages Router (`pages/api/`), not App Router (`app/api/`).
2911
+ *
2912
+ * @example
2913
+ * ```ts
2914
+ * // pages/api/users.ts
2915
+ * export default Limelight.withLimelight((req, res) => {
2916
+ * res.json({ ok: true });
2917
+ * });
2918
+ * ```
2919
+ */
2920
+ withLimelight(handler) {
2921
+ const wrapper = createWithLimelight(
2922
+ this.sendMessage.bind(this),
2923
+ () => this.sessionId,
2924
+ () => this.config
2925
+ );
2926
+ return wrapper(handler);
2927
+ }
2581
2928
  };
2582
2929
  var Limelight = new LimelightClient();
2583
2930
  export {