@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/README.md +50 -13
- package/dist/index.d.mts +104 -27
- package/dist/index.d.ts +104 -27
- package/dist/index.js +383 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +383 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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__ */ ((
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
|
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
|
|
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 !==
|
|
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 ===
|
|
2818
|
+
if (this.ws?.readyState === 1) {
|
|
2512
2819
|
this.flushMessageQueue();
|
|
2513
|
-
if (this.ws?.readyState ===
|
|
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:
|