@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.js CHANGED
@@ -66,6 +66,7 @@ var NetworkType = /* @__PURE__ */ ((NetworkType2) => {
66
66
  NetworkType2["FETCH"] = "fetch";
67
67
  NetworkType2["XHR"] = "xhr";
68
68
  NetworkType2["GRAPHQL"] = "graphql";
69
+ NetworkType2["INCOMING"] = "incoming";
69
70
  return NetworkType2;
70
71
  })(NetworkType || {});
71
72
  var NetworkPhase = /* @__PURE__ */ ((NetworkPhase2) => {
@@ -86,17 +87,17 @@ var BodyFormat = /* @__PURE__ */ ((BodyFormat2) => {
86
87
  BodyFormat2["UNSERIALIZABLE"] = "UNSERIALIZABLE";
87
88
  return BodyFormat2;
88
89
  })(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;
90
+ var HttpMethod = /* @__PURE__ */ ((HttpMethod6) => {
91
+ HttpMethod6["GET"] = "GET";
92
+ HttpMethod6["POST"] = "POST";
93
+ HttpMethod6["PUT"] = "PUT";
94
+ HttpMethod6["PATCH"] = "PATCH";
95
+ HttpMethod6["DELETE"] = "DELETE";
96
+ HttpMethod6["HEAD"] = "HEAD";
97
+ HttpMethod6["OPTIONS"] = "OPTIONS";
98
+ HttpMethod6["TRACE"] = "TRACE";
99
+ HttpMethod6["CONNECT"] = "CONNECT";
100
+ return HttpMethod6;
100
101
  })(HttpMethod || {});
101
102
  var HttpStatusClass = /* @__PURE__ */ ((HttpStatusClass2) => {
102
103
  HttpStatusClass2[HttpStatusClass2["INFORMATIONAL"] = 100] = "INFORMATIONAL";
@@ -286,7 +287,7 @@ var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
286
287
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
287
288
  var LIMELIGHT_MCP_WS_URL = "ws://localhost:9229";
288
289
  var WS_PATH = "/limelight";
289
- var SDK_VERSION = true ? "0.6.1" : "test-version";
290
+ var SDK_VERSION = true ? "0.7.4" : "test-version";
290
291
  var RENDER_THRESHOLDS = {
291
292
  HOT_VELOCITY: 5,
292
293
  HIGH_RENDER_COUNT: 50,
@@ -540,6 +541,9 @@ var formatRequestName = (url) => {
540
541
  }
541
542
  };
542
543
 
544
+ // src/helpers/utils/environment.ts
545
+ var hasDOM = () => typeof window !== "undefined" && typeof document !== "undefined";
546
+
543
547
  // src/helpers/render/generateRenderId.ts
544
548
  var counter = 0;
545
549
  var generateRenderId = () => {
@@ -705,13 +709,31 @@ var generateRequestId = () => {
705
709
  return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
706
710
  };
707
711
 
712
+ // src/limelight/context/traceContext.ts
713
+ var _resolved = false;
714
+ var _traceContext;
715
+ var getTraceContext = () => {
716
+ if (!_resolved) {
717
+ _resolved = true;
718
+ try {
719
+ const _require = globalThis["require"];
720
+ const { AsyncLocalStorage } = _require("node:async_hooks");
721
+ _traceContext = new AsyncLocalStorage();
722
+ } catch {
723
+ }
724
+ }
725
+ return _traceContext;
726
+ };
727
+
708
728
  // src/limelight/interceptors/NetworkInterceptor.ts
709
729
  var NetworkInterceptor = class {
710
730
  constructor(sendMessage, getSessionId) {
711
731
  this.sendMessage = sendMessage;
712
732
  this.getSessionId = getSessionId;
713
733
  this.globalObject = detectGlobalObject();
714
- this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
734
+ if (typeof this.globalObject.fetch === "function") {
735
+ this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
736
+ }
715
737
  }
716
738
  originalFetch;
717
739
  config = null;
@@ -731,9 +753,18 @@ var NetworkInterceptor = class {
731
753
  }
732
754
  return;
733
755
  }
756
+ if (!this.originalFetch) {
757
+ if (config?.enableInternalLogging) {
758
+ console.warn(
759
+ "[Limelight] fetch is not available in this environment, skipping network interception"
760
+ );
761
+ }
762
+ return;
763
+ }
734
764
  this.isSetup = true;
735
765
  this.config = config;
736
766
  const self2 = this;
767
+ const originalFetch = this.originalFetch;
737
768
  this.globalObject.fetch = async function(input, init = {}) {
738
769
  const requestId = generateRequestId();
739
770
  const startTime = Date.now();
@@ -751,6 +782,12 @@ var NetworkInterceptor = class {
751
782
  });
752
783
  }
753
784
  headers["x-limelight-intercepted"] = "fetch";
785
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
786
+ if (!headers[traceHeaderName]) {
787
+ const existingTraceId = getTraceContext()?.getStore()?.traceId;
788
+ headers[traceHeaderName] = existingTraceId || generateRequestId();
789
+ }
790
+ const traceId = headers[traceHeaderName];
754
791
  modifiedInit.headers = new Headers(headers);
755
792
  let requestBodyToSerialize = init.body;
756
793
  if (input instanceof Request && !requestBodyToSerialize) {
@@ -784,6 +821,7 @@ var NetworkInterceptor = class {
784
821
  }
785
822
  let requestEvent = {
786
823
  id: requestId,
824
+ traceId,
787
825
  sessionId: self2.getSessionId(),
788
826
  timestamp: startTime,
789
827
  phase: "REQUEST" /* REQUEST */,
@@ -800,17 +838,17 @@ var NetworkInterceptor = class {
800
838
  if (self2.config?.beforeSend) {
801
839
  const modifiedEvent = self2.config.beforeSend(requestEvent);
802
840
  if (!modifiedEvent) {
803
- return self2.originalFetch(input, modifiedInit);
841
+ return originalFetch(input, modifiedInit);
804
842
  }
805
843
  if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
806
844
  console.error("[Limelight] beforeSend must return same event type");
807
- return self2.originalFetch(input, modifiedInit);
845
+ return originalFetch(input, modifiedInit);
808
846
  }
809
847
  requestEvent = modifiedEvent;
810
848
  }
811
849
  self2.sendMessage(requestEvent);
812
850
  try {
813
- const response = await self2.originalFetch(input, modifiedInit);
851
+ const response = await originalFetch(input, modifiedInit);
814
852
  const clone = response.clone();
815
853
  const endTime = Date.now();
816
854
  const duration = endTime - startTime;
@@ -830,6 +868,7 @@ var NetworkInterceptor = class {
830
868
  );
831
869
  let responseEvent = {
832
870
  id: requestId,
871
+ traceId,
833
872
  sessionId: self2.getSessionId(),
834
873
  timestamp: endTime,
835
874
  phase: "RESPONSE" /* RESPONSE */,
@@ -862,6 +901,7 @@ var NetworkInterceptor = class {
862
901
  const errorStack = err instanceof Error ? err.stack : void 0;
863
902
  let errorEvent = {
864
903
  id: requestId,
904
+ traceId,
865
905
  sessionId: self2.getSessionId(),
866
906
  timestamp: Date.now(),
867
907
  phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
@@ -892,7 +932,9 @@ var NetworkInterceptor = class {
892
932
  return;
893
933
  }
894
934
  this.isSetup = false;
895
- this.globalObject.fetch = this.originalFetch;
935
+ if (this.originalFetch) {
936
+ this.globalObject.fetch = this.originalFetch;
937
+ }
896
938
  }
897
939
  };
898
940
 
@@ -901,9 +943,11 @@ var XHRInterceptor = class {
901
943
  constructor(sendMessage, getSessionId) {
902
944
  this.sendMessage = sendMessage;
903
945
  this.getSessionId = getSessionId;
904
- this.originalXHROpen = XMLHttpRequest.prototype.open;
905
- this.originalXHRSend = XMLHttpRequest.prototype.send;
906
- this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
946
+ if (typeof XMLHttpRequest !== "undefined") {
947
+ this.originalXHROpen = XMLHttpRequest.prototype.open;
948
+ this.originalXHRSend = XMLHttpRequest.prototype.send;
949
+ this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
950
+ }
907
951
  }
908
952
  originalXHROpen;
909
953
  originalXHRSend;
@@ -918,6 +962,9 @@ var XHRInterceptor = class {
918
962
  * @returns {void}
919
963
  */
920
964
  setup(config) {
965
+ if (typeof XMLHttpRequest === "undefined") {
966
+ return;
967
+ }
921
968
  if (this.isSetup) {
922
969
  if (this.config?.enableInternalLogging) {
923
970
  console.warn("[Limelight] XHR interceptor already set up");
@@ -956,12 +1003,24 @@ var XHRInterceptor = class {
956
1003
  return self2.originalXHRSend.apply(this, arguments);
957
1004
  }
958
1005
  if (data) {
1006
+ const traceHeaderName = self2.config?.traceHeaderName ?? "x-limelight-trace-id";
1007
+ if (!data.headers[traceHeaderName]) {
1008
+ data.traceId = generateRequestId();
1009
+ self2.originalXHRSetRequestHeader.call(
1010
+ this,
1011
+ traceHeaderName,
1012
+ data.traceId
1013
+ );
1014
+ } else {
1015
+ data.traceId = data.headers[traceHeaderName];
1016
+ }
959
1017
  const requestBody = serializeBody(
960
1018
  body,
961
1019
  self2.config?.disableBodyCapture
962
1020
  );
963
1021
  let requestEvent = {
964
1022
  id: data.id,
1023
+ traceId: data.traceId,
965
1024
  sessionId: self2.getSessionId(),
966
1025
  timestamp: data.startTime,
967
1026
  phase: "REQUEST" /* REQUEST */,
@@ -1021,6 +1080,7 @@ var XHRInterceptor = class {
1021
1080
  );
1022
1081
  let responseEvent = {
1023
1082
  id: data.id,
1083
+ traceId: data.traceId,
1024
1084
  sessionId: self2.getSessionId(),
1025
1085
  timestamp: endTime,
1026
1086
  phase: "RESPONSE" /* RESPONSE */,
@@ -1055,6 +1115,7 @@ var XHRInterceptor = class {
1055
1115
  responseSent = true;
1056
1116
  let errorEvent = {
1057
1117
  id: data.id,
1118
+ traceId: data.traceId,
1058
1119
  sessionId: self2.getSessionId(),
1059
1120
  timestamp: Date.now(),
1060
1121
  phase,
@@ -1133,15 +1194,14 @@ var XHRInterceptor = class {
1133
1194
  */
1134
1195
  cleanup() {
1135
1196
  if (!this.isSetup) {
1136
- if (this.config?.enableInternalLogging) {
1137
- console.warn("[Limelight] XHR interceptor not set up");
1138
- }
1139
1197
  return;
1140
1198
  }
1141
1199
  this.isSetup = false;
1142
- XMLHttpRequest.prototype.open = this.originalXHROpen;
1143
- XMLHttpRequest.prototype.send = this.originalXHRSend;
1144
- XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1200
+ if (typeof XMLHttpRequest !== "undefined") {
1201
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
1202
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
1203
+ XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
1204
+ }
1145
1205
  }
1146
1206
  };
1147
1207
 
@@ -1794,6 +1854,83 @@ var RenderInterceptor = class {
1794
1854
  }
1795
1855
  };
1796
1856
 
1857
+ // src/limelight/interceptors/ErrorInterceptor.ts
1858
+ var ErrorInterceptor = class {
1859
+ constructor(sendMessage, getSessionId) {
1860
+ this.sendMessage = sendMessage;
1861
+ this.getSessionId = getSessionId;
1862
+ }
1863
+ isSetup = false;
1864
+ config = null;
1865
+ counter = 0;
1866
+ uncaughtExceptionHandler = null;
1867
+ unhandledRejectionHandler = null;
1868
+ setup(config) {
1869
+ if (this.isSetup) {
1870
+ if (this.config?.enableInternalLogging) {
1871
+ console.warn("[Limelight] Error interceptor already set up");
1872
+ }
1873
+ return;
1874
+ }
1875
+ if (typeof process === "undefined" || !process.on) {
1876
+ return;
1877
+ }
1878
+ this.isSetup = true;
1879
+ this.config = config;
1880
+ this.uncaughtExceptionHandler = (error) => {
1881
+ this.sendErrorEvent(error, "uncaughtException");
1882
+ setTimeout(() => {
1883
+ process.exit(1);
1884
+ }, 200);
1885
+ };
1886
+ this.unhandledRejectionHandler = (reason) => {
1887
+ const error = reason instanceof Error ? reason : new Error(String(reason));
1888
+ this.sendErrorEvent(error, "unhandledRejection");
1889
+ };
1890
+ process.on("uncaughtException", this.uncaughtExceptionHandler);
1891
+ process.on("unhandledRejection", this.unhandledRejectionHandler);
1892
+ }
1893
+ cleanup() {
1894
+ if (!this.isSetup) return;
1895
+ this.isSetup = false;
1896
+ if (this.uncaughtExceptionHandler) {
1897
+ process.removeListener(
1898
+ "uncaughtException",
1899
+ this.uncaughtExceptionHandler
1900
+ );
1901
+ this.uncaughtExceptionHandler = null;
1902
+ }
1903
+ if (this.unhandledRejectionHandler) {
1904
+ process.removeListener(
1905
+ "unhandledRejection",
1906
+ this.unhandledRejectionHandler
1907
+ );
1908
+ this.unhandledRejectionHandler = null;
1909
+ }
1910
+ this.config = null;
1911
+ }
1912
+ sendErrorEvent(error, source) {
1913
+ const sessionId = this.getSessionId();
1914
+ const event = {
1915
+ id: `${sessionId}-${Date.now()}-${this.counter++}`,
1916
+ phase: "CONSOLE",
1917
+ type: "CONSOLE" /* CONSOLE */,
1918
+ level: "error" /* ERROR */,
1919
+ timestamp: Date.now(),
1920
+ sessionId,
1921
+ source: "app" /* APP */,
1922
+ consoleType: "exception" /* EXCEPTION */,
1923
+ args: [safeStringify(`[${source}] ${error.message}`)],
1924
+ stackTrace: error.stack
1925
+ };
1926
+ if (this.config?.beforeSend) {
1927
+ const modified = this.config.beforeSend(event);
1928
+ if (!modified) return;
1929
+ }
1930
+ this.sendMessage(event);
1931
+ }
1932
+ };
1933
+
1797
1934
  // src/limelight/interceptors/StateInterceptor.ts
1798
1935
  var StateInterceptor = class {
1799
1936
  sendMessage;
@@ -2277,6 +2414,153 @@ var CommandHandler = class {
2277
2414
  }
2278
2415
  };
2279
2416
 
2417
+ // src/limelight/middleware/httpMiddleware.ts
2418
+ var DEFAULT_MAX_BODY_SIZE = 64 * 1024;
2419
+ var captureRequest = (req, res, sendMessage, getSessionId, config, options) => {
2420
+ const requestId = generateRequestId();
2421
+ const startTime = Date.now();
2422
+ const maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
2423
+ const traceHeaderName = config?.traceHeaderName ?? "x-limelight-trace-id";
2424
+ const incomingTraceId = req.headers[traceHeaderName];
2425
+ const traceId = incomingTraceId || generateRequestId();
2426
+ req.limelightTraceId = traceId;
2427
+ const url = req.url || "/";
2428
+ const method = (req.method || "GET").toUpperCase();
2429
+ const headers = {};
2430
+ for (const [key, value] of Object.entries(req.headers)) {
2431
+ if (value) {
2432
+ headers[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
2433
+ }
2434
+ }
2435
+ let requestBody = serializeBody(req.body, config?.disableBodyCapture);
2436
+ if (requestBody?.raw && requestBody.raw.length > maxBodySize) {
2437
+ requestBody = {
2438
+ ...requestBody,
2439
+ raw: requestBody.raw.slice(0, maxBodySize) + "...[truncated]",
2440
+ size: requestBody.size
2441
+ };
2442
+ }
2443
+ let requestEvent = {
2444
+ id: requestId,
2445
+ traceId,
2446
+ sessionId: getSessionId(),
2447
+ timestamp: startTime,
2448
+ phase: "REQUEST" /* REQUEST */,
2449
+ networkType: "incoming" /* INCOMING */,
2450
+ url,
2451
+ method,
2452
+ headers: redactSensitiveHeaders(headers),
2453
+ body: requestBody,
2454
+ name: url.split("?")[0]?.split("/").filter(Boolean).pop() ?? "/",
2455
+ initiator: "incoming",
2456
+ requestSize: requestBody?.size ?? 0
2457
+ };
2458
+ if (config?.beforeSend) {
2459
+ const modified = config.beforeSend(requestEvent);
2460
+ if (!modified) return;
2461
+ if (modified.phase !== "REQUEST" /* REQUEST */) {
2462
+ console.error("[Limelight] beforeSend must return same event type");
2463
+ return;
2464
+ }
2465
+ requestEvent = modified;
2466
+ }
2467
+ sendMessage(requestEvent);
2468
+ const chunks = [];
2469
+ let totalSize = 0;
2470
+ const originalWrite = res.write;
2471
+ const originalEnd = res.end;
2472
+ res.write = (chunk, ...args) => {
2473
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2474
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2475
+ chunks.push(buf);
2476
+ totalSize += buf.length;
2477
+ }
2478
+ return originalWrite.apply(res, [chunk, ...args]);
2479
+ };
2480
+ res.end = (chunk, ...args) => {
2481
+ if (chunk && typeof chunk !== "function" && totalSize < maxBodySize) {
2482
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2483
+ chunks.push(buf);
2484
+ totalSize += buf.length;
2485
+ }
2486
+ return originalEnd.apply(res, [chunk, ...args]);
2487
+ };
2488
+ res.on("finish", () => {
2489
+ const endTime = Date.now();
2490
+ const duration = endTime - startTime;
2491
+ const responseHeaders = {};
2492
+ const rawHeaders = res.getHeaders();
2493
+ for (const [key, value] of Object.entries(rawHeaders)) {
2494
+ if (value) {
2495
+ responseHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
2496
+ }
2497
+ }
2498
+ let responseBodyStr;
2499
+ if (chunks.length > 0 && !config?.disableBodyCapture) {
2500
+ const full = Buffer.concat(chunks);
2501
+ const fullStr = full.toString("utf-8");
2502
+ responseBodyStr = fullStr.length > maxBodySize ? fullStr.slice(0, maxBodySize) + "...[truncated]" : fullStr;
2503
+ }
2504
+ const responseBody = serializeBody(
2505
+ responseBodyStr,
2506
+ config?.disableBodyCapture
2507
+ );
2508
+ let responseEvent = {
2509
+ id: requestId,
2510
+ traceId,
2511
+ sessionId: getSessionId(),
2512
+ timestamp: endTime,
2513
+ phase: "RESPONSE" /* RESPONSE */,
2514
+ networkType: "incoming" /* INCOMING */,
2515
+ status: res.statusCode,
2516
+ statusText: res.statusMessage || "",
2517
+ headers: redactSensitiveHeaders(responseHeaders),
2518
+ body: responseBody,
2519
+ duration,
2520
+ responseSize: responseBody?.size ?? 0,
2521
+ redirected: false,
2522
+ ok: res.statusCode >= 200 && res.statusCode < 300
2523
+ };
2524
+ if (config?.beforeSend) {
2525
+ const modified = config.beforeSend(responseEvent);
2526
+ if (!modified) return;
2527
+ if (modified.phase !== "RESPONSE" /* RESPONSE */) {
2528
+ console.error("[Limelight] beforeSend must return same event type");
2529
+ return;
2530
+ }
2531
+ responseEvent = modified;
2532
+ }
2533
+ sendMessage(responseEvent);
2534
+ });
2535
+ };
2536
+ var createHttpMiddleware = (sendMessage, getSessionId, getConfig, options) => {
2537
+ return (req, res, next) => {
2538
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2539
+ const traceId = req.limelightTraceId;
2540
+ const ctx = getTraceContext();
2541
+ if (ctx && traceId) {
2542
+ ctx.run({ traceId }, next);
2543
+ } else {
2544
+ next();
2545
+ }
2546
+ };
2547
+ };
2548
+
2549
+ // src/limelight/middleware/withLimelight.ts
2550
+ var createWithLimelight = (sendMessage, getSessionId, getConfig, options) => {
2551
+ return (handler) => {
2552
+ return (req, res) => {
2553
+ captureRequest(req, res, sendMessage, getSessionId, getConfig(), options);
2554
+ const traceId = req.limelightTraceId;
2555
+ const ctx = getTraceContext();
2556
+ if (ctx && traceId) {
2557
+ return ctx.run({ traceId }, () => handler(req, res));
2558
+ }
2559
+ return handler(req, res);
2560
+ };
2561
+ };
2562
+ };
2563
+
2280
2564
  // src/limelight/LimelightClient.ts
2281
2565
  var LimelightClient = class {
2282
2566
  ws = null;
@@ -2293,6 +2577,7 @@ var LimelightClient = class {
2293
2577
  consoleInterceptor;
2294
2578
  renderInterceptor;
2295
2579
  stateInterceptor;
2580
+ errorInterceptor;
2296
2581
  requestBridge;
2297
2582
  commandHandler = null;
2298
2583
  constructor() {
@@ -2316,6 +2601,10 @@ var LimelightClient = class {
2316
2601
  this.sendMessage.bind(this),
2317
2602
  () => this.sessionId
2318
2603
  );
2604
+ this.errorInterceptor = new ErrorInterceptor(
2605
+ this.sendMessage.bind(this),
2606
+ () => this.sessionId
2607
+ );
2319
2608
  this.requestBridge = new RequestBridge(
2320
2609
  this.sendMessage.bind(this),
2321
2610
  () => this.sessionId
@@ -2357,12 +2646,17 @@ var LimelightClient = class {
2357
2646
  try {
2358
2647
  if (this.config.enableNetworkInspector) {
2359
2648
  this.networkInterceptor.setup(this.config);
2360
- this.xhrInterceptor.setup(this.config);
2649
+ if (typeof XMLHttpRequest !== "undefined") {
2650
+ this.xhrInterceptor.setup(this.config);
2651
+ }
2361
2652
  }
2362
2653
  if (this.config.enableConsole) {
2363
2654
  this.consoleInterceptor.setup(this.config);
2655
+ if (!hasDOM()) {
2656
+ this.errorInterceptor.setup(this.config);
2657
+ }
2364
2658
  }
2365
- if (this.config.enableRenderInspector) {
2659
+ if (this.config.enableRenderInspector && hasDOM()) {
2366
2660
  this.renderInterceptor.setup(this.config);
2367
2661
  }
2368
2662
  if (this.config.stores && this.config.enableStateInspector) {
@@ -2390,7 +2684,7 @@ var LimelightClient = class {
2390
2684
  if (!this.config?.enabled) {
2391
2685
  return;
2392
2686
  }
2393
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
2687
+ if (this.ws && this.ws.readyState === 1) {
2394
2688
  if (this.config?.enableInternalLogging) {
2395
2689
  console.warn("[Limelight] Already connected. Call disconnect() first.");
2396
2690
  }
@@ -2413,15 +2707,24 @@ var LimelightClient = class {
2413
2707
  }
2414
2708
  return;
2415
2709
  }
2710
+ const WsConstructor = this.config.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
2711
+ if (!WsConstructor) {
2712
+ if (this.config?.enableInternalLogging) {
2713
+ console.error(
2714
+ "[Limelight] WebSocket is not available. Pass webSocketImpl in config (e.g. ws package)."
2715
+ );
2716
+ }
2717
+ return;
2718
+ }
2416
2719
  try {
2417
- this.ws = new WebSocket(serverUrl);
2720
+ this.ws = new WsConstructor(serverUrl);
2418
2721
  const message = {
2419
2722
  phase: "CONNECT",
2420
2723
  sessionId: this.sessionId,
2421
2724
  timestamp: Date.now(),
2422
2725
  data: {
2423
2726
  appName,
2424
- platform: platform || (typeof window !== "undefined" ? "web" : "react-native"),
2727
+ platform: platform || (hasDOM() ? "web" : typeof process !== "undefined" ? "node" : "react-native"),
2425
2728
  projectKey: this.config.projectKey || "",
2426
2729
  sdkVersion: SDK_VERSION
2427
2730
  }
@@ -2488,10 +2791,13 @@ var LimelightClient = class {
2488
2791
  * @returns {void}
2489
2792
  */
2490
2793
  flushMessageQueue() {
2491
- if (this.ws?.readyState !== WebSocket.OPEN) return;
2794
+ if (this.ws?.readyState !== 1) return;
2492
2795
  while (this.messageQueue.length > 0) {
2493
2796
  const message = this.messageQueue.shift();
2494
2797
  try {
2798
+ if (message && "sessionId" in message && !message.sessionId) {
2799
+ message.sessionId = this.sessionId;
2800
+ }
2495
2801
  this.ws.send(safeStringify(message));
2496
2802
  } catch (error) {
2497
2803
  if (this.config?.enableInternalLogging) {
@@ -2509,9 +2815,9 @@ var LimelightClient = class {
2509
2815
  * @returns {void}
2510
2816
  */
2511
2817
  sendMessage(message) {
2512
- if (this.ws?.readyState === WebSocket.OPEN) {
2818
+ if (this.ws?.readyState === 1) {
2513
2819
  this.flushMessageQueue();
2514
- if (this.ws?.readyState === WebSocket.OPEN) {
2820
+ if (this.ws?.readyState === 1) {
2515
2821
  try {
2516
2822
  this.ws.send(safeStringify(message));
2517
2823
  } catch (error) {
@@ -2569,6 +2875,7 @@ var LimelightClient = class {
2569
2875
  this.networkInterceptor.cleanup();
2570
2876
  this.xhrInterceptor.cleanup();
2571
2877
  this.consoleInterceptor.cleanup();
2878
+ this.errorInterceptor.cleanup();
2572
2879
  this.renderInterceptor.cleanup();
2573
2880
  this.stateInterceptor.cleanup();
2574
2881
  this.requestBridge.cleanup();
@@ -2615,6 +2922,46 @@ var LimelightClient = class {
2615
2922
  failRequest(requestId, error) {
2616
2923
  this.requestBridge.failRequest(requestId, error);
2617
2924
  }
2925
+ /**
2926
+ * Returns an Express/Connect-compatible middleware that captures incoming
2927
+ * HTTP requests and responses.
2928
+ *
2929
+ * Place after body-parser middleware (express.json(), etc.) for request body capture.
2930
+ *
2931
+ * @example
2932
+ * ```ts
2933
+ * app.use(express.json());
2934
+ * app.use(Limelight.middleware());
2935
+ * ```
2936
+ */
2937
+ middleware(options) {
2938
+ return createHttpMiddleware(
2939
+ this.sendMessage.bind(this),
2940
+ () => this.sessionId,
2941
+ () => this.config,
2942
+ options
2943
+ );
2944
+ }
2945
+ /**
2946
+ * Wraps a Next.js Pages API route handler with request/response capture.
2947
+ * Works with Pages Router (`pages/api/`), not App Router (`app/api/`).
2948
+ *
2949
+ * @example
2950
+ * ```ts
2951
+ * // pages/api/users.ts
2952
+ * export default Limelight.withLimelight((req, res) => {
2953
+ * res.json({ ok: true });
2954
+ * });
2955
+ * ```
2956
+ */
2957
+ withLimelight(handler) {
2958
+ const wrapper = createWithLimelight(
2959
+ this.sendMessage.bind(this),
2960
+ () => this.sessionId,
2961
+ () => this.config
2962
+ );
2963
+ return wrapper(handler);
2964
+ }
2618
2965
  };
2619
2966
  var Limelight = new LimelightClient();
2620
2967
  // Annotate the CommonJS export names for ESM import in node: