@getlimelight/sdk 0.5.5 → 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";
@@ -284,8 +285,9 @@ var SENSITIVE_HEADERS = [
284
285
  ];
285
286
  var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
286
287
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
288
+ var LIMELIGHT_MCP_WS_URL = "ws://localhost:9229";
287
289
  var WS_PATH = "/limelight";
288
- var SDK_VERSION = true ? "0.5.5" : "test-version";
290
+ var SDK_VERSION = true ? "0.7.4" : "test-version";
289
291
  var RENDER_THRESHOLDS = {
290
292
  HOT_VELOCITY: 5,
291
293
  HIGH_RENDER_COUNT: 50,
@@ -539,6 +541,9 @@ var formatRequestName = (url) => {
539
541
  }
540
542
  };
541
543
 
544
+ // src/helpers/utils/environment.ts
545
+ var hasDOM = () => typeof window !== "undefined" && typeof document !== "undefined";
546
+
542
547
  // src/helpers/render/generateRenderId.ts
543
548
  var counter = 0;
544
549
  var generateRenderId = () => {
@@ -704,13 +709,31 @@ var generateRequestId = () => {
704
709
  return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
705
710
  };
706
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
+
707
728
  // src/limelight/interceptors/NetworkInterceptor.ts
708
729
  var NetworkInterceptor = class {
709
730
  constructor(sendMessage, getSessionId) {
710
731
  this.sendMessage = sendMessage;
711
732
  this.getSessionId = getSessionId;
712
733
  this.globalObject = detectGlobalObject();
713
- 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
+ }
714
737
  }
715
738
  originalFetch;
716
739
  config = null;
@@ -730,9 +753,18 @@ var NetworkInterceptor = class {
730
753
  }
731
754
  return;
732
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
+ }
733
764
  this.isSetup = true;
734
765
  this.config = config;
735
766
  const self2 = this;
767
+ const originalFetch = this.originalFetch;
736
768
  this.globalObject.fetch = async function(input, init = {}) {
737
769
  const requestId = generateRequestId();
738
770
  const startTime = Date.now();
@@ -750,6 +782,12 @@ var NetworkInterceptor = class {
750
782
  });
751
783
  }
752
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];
753
791
  modifiedInit.headers = new Headers(headers);
754
792
  let requestBodyToSerialize = init.body;
755
793
  if (input instanceof Request && !requestBodyToSerialize) {
@@ -783,6 +821,7 @@ var NetworkInterceptor = class {
783
821
  }
784
822
  let requestEvent = {
785
823
  id: requestId,
824
+ traceId,
786
825
  sessionId: self2.getSessionId(),
787
826
  timestamp: startTime,
788
827
  phase: "REQUEST" /* REQUEST */,
@@ -799,17 +838,17 @@ var NetworkInterceptor = class {
799
838
  if (self2.config?.beforeSend) {
800
839
  const modifiedEvent = self2.config.beforeSend(requestEvent);
801
840
  if (!modifiedEvent) {
802
- return self2.originalFetch(input, modifiedInit);
841
+ return originalFetch(input, modifiedInit);
803
842
  }
804
843
  if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
805
844
  console.error("[Limelight] beforeSend must return same event type");
806
- return self2.originalFetch(input, modifiedInit);
845
+ return originalFetch(input, modifiedInit);
807
846
  }
808
847
  requestEvent = modifiedEvent;
809
848
  }
810
849
  self2.sendMessage(requestEvent);
811
850
  try {
812
- const response = await self2.originalFetch(input, modifiedInit);
851
+ const response = await originalFetch(input, modifiedInit);
813
852
  const clone = response.clone();
814
853
  const endTime = Date.now();
815
854
  const duration = endTime - startTime;
@@ -829,6 +868,7 @@ var NetworkInterceptor = class {
829
868
  );
830
869
  let responseEvent = {
831
870
  id: requestId,
871
+ traceId,
832
872
  sessionId: self2.getSessionId(),
833
873
  timestamp: endTime,
834
874
  phase: "RESPONSE" /* RESPONSE */,
@@ -861,6 +901,7 @@ var NetworkInterceptor = class {
861
901
  const errorStack = err instanceof Error ? err.stack : void 0;
862
902
  let errorEvent = {
863
903
  id: requestId,
904
+ traceId,
864
905
  sessionId: self2.getSessionId(),
865
906
  timestamp: Date.now(),
866
907
  phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
@@ -891,7 +932,9 @@ var NetworkInterceptor = class {
891
932
  return;
892
933
  }
893
934
  this.isSetup = false;
894
- this.globalObject.fetch = this.originalFetch;
935
+ if (this.originalFetch) {
936
+ this.globalObject.fetch = this.originalFetch;
937
+ }
895
938
  }
896
939
  };
897
940
 
@@ -900,9 +943,11 @@ var XHRInterceptor = class {
900
943
  constructor(sendMessage, getSessionId) {
901
944
  this.sendMessage = sendMessage;
902
945
  this.getSessionId = getSessionId;
903
- this.originalXHROpen = XMLHttpRequest.prototype.open;
904
- this.originalXHRSend = XMLHttpRequest.prototype.send;
905
- 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
+ }
906
951
  }
907
952
  originalXHROpen;
908
953
  originalXHRSend;
@@ -917,6 +962,9 @@ var XHRInterceptor = class {
917
962
  * @returns {void}
918
963
  */
919
964
  setup(config) {
965
+ if (typeof XMLHttpRequest === "undefined") {
966
+ return;
967
+ }
920
968
  if (this.isSetup) {
921
969
  if (this.config?.enableInternalLogging) {
922
970
  console.warn("[Limelight] XHR interceptor already set up");
@@ -955,12 +1003,24 @@ var XHRInterceptor = class {
955
1003
  return self2.originalXHRSend.apply(this, arguments);
956
1004
  }
957
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
+ }
958
1017
  const requestBody = serializeBody(
959
1018
  body,
960
1019
  self2.config?.disableBodyCapture
961
1020
  );
962
1021
  let requestEvent = {
963
1022
  id: data.id,
1023
+ traceId: data.traceId,
964
1024
  sessionId: self2.getSessionId(),
965
1025
  timestamp: data.startTime,
966
1026
  phase: "REQUEST" /* REQUEST */,
@@ -1020,6 +1080,7 @@ var XHRInterceptor = class {
1020
1080
  );
1021
1081
  let responseEvent = {
1022
1082
  id: data.id,
1083
+ traceId: data.traceId,
1023
1084
  sessionId: self2.getSessionId(),
1024
1085
  timestamp: endTime,
1025
1086
  phase: "RESPONSE" /* RESPONSE */,
@@ -1054,6 +1115,7 @@ var XHRInterceptor = class {
1054
1115
  responseSent = true;
1055
1116
  let errorEvent = {
1056
1117
  id: data.id,
1118
+ traceId: data.traceId,
1057
1119
  sessionId: self2.getSessionId(),
1058
1120
  timestamp: Date.now(),
1059
1121
  phase,
@@ -1132,15 +1194,14 @@ var XHRInterceptor = class {
1132
1194
  */
1133
1195
  cleanup() {
1134
1196
  if (!this.isSetup) {
1135
- if (this.config?.enableInternalLogging) {
1136
- console.warn("[Limelight] XHR interceptor not set up");
1137
- }
1138
1197
  return;
1139
1198
  }
1140
1199
  this.isSetup = false;
1141
- XMLHttpRequest.prototype.open = this.originalXHROpen;
1142
- XMLHttpRequest.prototype.send = this.originalXHRSend;
1143
- 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
+ }
1144
1205
  }
1145
1206
  };
1146
1207
 
@@ -1793,6 +1854,83 @@ var RenderInterceptor = class {
1793
1854
  }
1794
1855
  };
1795
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
+
1796
1934
  // src/limelight/interceptors/StateInterceptor.ts
1797
1935
  var StateInterceptor = class {
1798
1936
  sendMessage;
@@ -2276,6 +2414,153 @@ var CommandHandler = class {
2276
2414
  }
2277
2415
  };
2278
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
+
2279
2564
  // src/limelight/LimelightClient.ts
2280
2565
  var LimelightClient = class {
2281
2566
  ws = null;
@@ -2292,6 +2577,7 @@ var LimelightClient = class {
2292
2577
  consoleInterceptor;
2293
2578
  renderInterceptor;
2294
2579
  stateInterceptor;
2580
+ errorInterceptor;
2295
2581
  requestBridge;
2296
2582
  commandHandler = null;
2297
2583
  constructor() {
@@ -2315,6 +2601,10 @@ var LimelightClient = class {
2315
2601
  this.sendMessage.bind(this),
2316
2602
  () => this.sessionId
2317
2603
  );
2604
+ this.errorInterceptor = new ErrorInterceptor(
2605
+ this.sendMessage.bind(this),
2606
+ () => this.sessionId
2607
+ );
2318
2608
  this.requestBridge = new RequestBridge(
2319
2609
  this.sendMessage.bind(this),
2320
2610
  () => this.sessionId
@@ -2335,7 +2625,7 @@ var LimelightClient = class {
2335
2625
  */
2336
2626
  configure(config) {
2337
2627
  const isEnabled = config?.enabled ?? isDevelopment();
2338
- const configServerUrl = config?.serverUrl ? config.serverUrl : config?.projectKey ? `${LIMELIGHT_WEB_WSS_URL}${WS_PATH}` : `${LIMELIGHT_DESKTOP_WSS_URL}${WS_PATH}`;
2628
+ const configServerUrl = config?.serverUrl ? config.serverUrl : config?.target === "mcp" ? LIMELIGHT_MCP_WS_URL : config?.projectKey ? `${LIMELIGHT_WEB_WSS_URL}${WS_PATH}` : `${LIMELIGHT_DESKTOP_WSS_URL}${WS_PATH}`;
2339
2629
  this.config = {
2340
2630
  ...config,
2341
2631
  appName: config?.appName ?? "Limelight App",
@@ -2356,12 +2646,17 @@ var LimelightClient = class {
2356
2646
  try {
2357
2647
  if (this.config.enableNetworkInspector) {
2358
2648
  this.networkInterceptor.setup(this.config);
2359
- this.xhrInterceptor.setup(this.config);
2649
+ if (typeof XMLHttpRequest !== "undefined") {
2650
+ this.xhrInterceptor.setup(this.config);
2651
+ }
2360
2652
  }
2361
2653
  if (this.config.enableConsole) {
2362
2654
  this.consoleInterceptor.setup(this.config);
2655
+ if (!hasDOM()) {
2656
+ this.errorInterceptor.setup(this.config);
2657
+ }
2363
2658
  }
2364
- if (this.config.enableRenderInspector) {
2659
+ if (this.config.enableRenderInspector && hasDOM()) {
2365
2660
  this.renderInterceptor.setup(this.config);
2366
2661
  }
2367
2662
  if (this.config.stores && this.config.enableStateInspector) {
@@ -2389,7 +2684,7 @@ var LimelightClient = class {
2389
2684
  if (!this.config?.enabled) {
2390
2685
  return;
2391
2686
  }
2392
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
2687
+ if (this.ws && this.ws.readyState === 1) {
2393
2688
  if (this.config?.enableInternalLogging) {
2394
2689
  console.warn("[Limelight] Already connected. Call disconnect() first.");
2395
2690
  }
@@ -2412,15 +2707,24 @@ var LimelightClient = class {
2412
2707
  }
2413
2708
  return;
2414
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
+ }
2415
2719
  try {
2416
- this.ws = new WebSocket(serverUrl);
2720
+ this.ws = new WsConstructor(serverUrl);
2417
2721
  const message = {
2418
2722
  phase: "CONNECT",
2419
2723
  sessionId: this.sessionId,
2420
2724
  timestamp: Date.now(),
2421
2725
  data: {
2422
2726
  appName,
2423
- platform: platform || (typeof window !== "undefined" ? "web" : "react-native"),
2727
+ platform: platform || (hasDOM() ? "web" : typeof process !== "undefined" ? "node" : "react-native"),
2424
2728
  projectKey: this.config.projectKey || "",
2425
2729
  sdkVersion: SDK_VERSION
2426
2730
  }
@@ -2487,10 +2791,13 @@ var LimelightClient = class {
2487
2791
  * @returns {void}
2488
2792
  */
2489
2793
  flushMessageQueue() {
2490
- if (this.ws?.readyState !== WebSocket.OPEN) return;
2794
+ if (this.ws?.readyState !== 1) return;
2491
2795
  while (this.messageQueue.length > 0) {
2492
2796
  const message = this.messageQueue.shift();
2493
2797
  try {
2798
+ if (message && "sessionId" in message && !message.sessionId) {
2799
+ message.sessionId = this.sessionId;
2800
+ }
2494
2801
  this.ws.send(safeStringify(message));
2495
2802
  } catch (error) {
2496
2803
  if (this.config?.enableInternalLogging) {
@@ -2508,9 +2815,9 @@ var LimelightClient = class {
2508
2815
  * @returns {void}
2509
2816
  */
2510
2817
  sendMessage(message) {
2511
- if (this.ws?.readyState === WebSocket.OPEN) {
2818
+ if (this.ws?.readyState === 1) {
2512
2819
  this.flushMessageQueue();
2513
- if (this.ws?.readyState === WebSocket.OPEN) {
2820
+ if (this.ws?.readyState === 1) {
2514
2821
  try {
2515
2822
  this.ws.send(safeStringify(message));
2516
2823
  } catch (error) {
@@ -2568,6 +2875,7 @@ var LimelightClient = class {
2568
2875
  this.networkInterceptor.cleanup();
2569
2876
  this.xhrInterceptor.cleanup();
2570
2877
  this.consoleInterceptor.cleanup();
2878
+ this.errorInterceptor.cleanup();
2571
2879
  this.renderInterceptor.cleanup();
2572
2880
  this.stateInterceptor.cleanup();
2573
2881
  this.requestBridge.cleanup();
@@ -2614,6 +2922,46 @@ var LimelightClient = class {
2614
2922
  failRequest(requestId, error) {
2615
2923
  this.requestBridge.failRequest(requestId, error);
2616
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
+ }
2617
2965
  };
2618
2966
  var Limelight = new LimelightClient();
2619
2967
  // Annotate the CommonJS export names for ESM import in node: