@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.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__ */ ((
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
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";
|
|
@@ -247,8 +248,9 @@ var SENSITIVE_HEADERS = [
|
|
|
247
248
|
];
|
|
248
249
|
var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
|
|
249
250
|
var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
|
|
251
|
+
var LIMELIGHT_MCP_WS_URL = "ws://localhost:9229";
|
|
250
252
|
var WS_PATH = "/limelight";
|
|
251
|
-
var SDK_VERSION = true ? "0.
|
|
253
|
+
var SDK_VERSION = true ? "0.7.4" : "test-version";
|
|
252
254
|
var RENDER_THRESHOLDS = {
|
|
253
255
|
HOT_VELOCITY: 5,
|
|
254
256
|
HIGH_RENDER_COUNT: 50,
|
|
@@ -502,6 +504,9 @@ var formatRequestName = (url) => {
|
|
|
502
504
|
}
|
|
503
505
|
};
|
|
504
506
|
|
|
507
|
+
// src/helpers/utils/environment.ts
|
|
508
|
+
var hasDOM = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
509
|
+
|
|
505
510
|
// src/helpers/render/generateRenderId.ts
|
|
506
511
|
var counter = 0;
|
|
507
512
|
var generateRenderId = () => {
|
|
@@ -667,13 +672,31 @@ var generateRequestId = () => {
|
|
|
667
672
|
return `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
668
673
|
};
|
|
669
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
|
+
|
|
670
691
|
// src/limelight/interceptors/NetworkInterceptor.ts
|
|
671
692
|
var NetworkInterceptor = class {
|
|
672
693
|
constructor(sendMessage, getSessionId) {
|
|
673
694
|
this.sendMessage = sendMessage;
|
|
674
695
|
this.getSessionId = getSessionId;
|
|
675
696
|
this.globalObject = detectGlobalObject();
|
|
676
|
-
|
|
697
|
+
if (typeof this.globalObject.fetch === "function") {
|
|
698
|
+
this.originalFetch = this.globalObject.fetch.bind(this.globalObject);
|
|
699
|
+
}
|
|
677
700
|
}
|
|
678
701
|
originalFetch;
|
|
679
702
|
config = null;
|
|
@@ -693,9 +716,18 @@ var NetworkInterceptor = class {
|
|
|
693
716
|
}
|
|
694
717
|
return;
|
|
695
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
|
+
}
|
|
696
727
|
this.isSetup = true;
|
|
697
728
|
this.config = config;
|
|
698
729
|
const self2 = this;
|
|
730
|
+
const originalFetch = this.originalFetch;
|
|
699
731
|
this.globalObject.fetch = async function(input, init = {}) {
|
|
700
732
|
const requestId = generateRequestId();
|
|
701
733
|
const startTime = Date.now();
|
|
@@ -713,6 +745,12 @@ var NetworkInterceptor = class {
|
|
|
713
745
|
});
|
|
714
746
|
}
|
|
715
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];
|
|
716
754
|
modifiedInit.headers = new Headers(headers);
|
|
717
755
|
let requestBodyToSerialize = init.body;
|
|
718
756
|
if (input instanceof Request && !requestBodyToSerialize) {
|
|
@@ -746,6 +784,7 @@ var NetworkInterceptor = class {
|
|
|
746
784
|
}
|
|
747
785
|
let requestEvent = {
|
|
748
786
|
id: requestId,
|
|
787
|
+
traceId,
|
|
749
788
|
sessionId: self2.getSessionId(),
|
|
750
789
|
timestamp: startTime,
|
|
751
790
|
phase: "REQUEST" /* REQUEST */,
|
|
@@ -762,17 +801,17 @@ var NetworkInterceptor = class {
|
|
|
762
801
|
if (self2.config?.beforeSend) {
|
|
763
802
|
const modifiedEvent = self2.config.beforeSend(requestEvent);
|
|
764
803
|
if (!modifiedEvent) {
|
|
765
|
-
return
|
|
804
|
+
return originalFetch(input, modifiedInit);
|
|
766
805
|
}
|
|
767
806
|
if (modifiedEvent.phase !== "REQUEST" /* REQUEST */) {
|
|
768
807
|
console.error("[Limelight] beforeSend must return same event type");
|
|
769
|
-
return
|
|
808
|
+
return originalFetch(input, modifiedInit);
|
|
770
809
|
}
|
|
771
810
|
requestEvent = modifiedEvent;
|
|
772
811
|
}
|
|
773
812
|
self2.sendMessage(requestEvent);
|
|
774
813
|
try {
|
|
775
|
-
const response = await
|
|
814
|
+
const response = await originalFetch(input, modifiedInit);
|
|
776
815
|
const clone = response.clone();
|
|
777
816
|
const endTime = Date.now();
|
|
778
817
|
const duration = endTime - startTime;
|
|
@@ -792,6 +831,7 @@ var NetworkInterceptor = class {
|
|
|
792
831
|
);
|
|
793
832
|
let responseEvent = {
|
|
794
833
|
id: requestId,
|
|
834
|
+
traceId,
|
|
795
835
|
sessionId: self2.getSessionId(),
|
|
796
836
|
timestamp: endTime,
|
|
797
837
|
phase: "RESPONSE" /* RESPONSE */,
|
|
@@ -824,6 +864,7 @@ var NetworkInterceptor = class {
|
|
|
824
864
|
const errorStack = err instanceof Error ? err.stack : void 0;
|
|
825
865
|
let errorEvent = {
|
|
826
866
|
id: requestId,
|
|
867
|
+
traceId,
|
|
827
868
|
sessionId: self2.getSessionId(),
|
|
828
869
|
timestamp: Date.now(),
|
|
829
870
|
phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
|
|
@@ -854,7 +895,9 @@ var NetworkInterceptor = class {
|
|
|
854
895
|
return;
|
|
855
896
|
}
|
|
856
897
|
this.isSetup = false;
|
|
857
|
-
|
|
898
|
+
if (this.originalFetch) {
|
|
899
|
+
this.globalObject.fetch = this.originalFetch;
|
|
900
|
+
}
|
|
858
901
|
}
|
|
859
902
|
};
|
|
860
903
|
|
|
@@ -863,9 +906,11 @@ var XHRInterceptor = class {
|
|
|
863
906
|
constructor(sendMessage, getSessionId) {
|
|
864
907
|
this.sendMessage = sendMessage;
|
|
865
908
|
this.getSessionId = getSessionId;
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
909
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
910
|
+
this.originalXHROpen = XMLHttpRequest.prototype.open;
|
|
911
|
+
this.originalXHRSend = XMLHttpRequest.prototype.send;
|
|
912
|
+
this.originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
913
|
+
}
|
|
869
914
|
}
|
|
870
915
|
originalXHROpen;
|
|
871
916
|
originalXHRSend;
|
|
@@ -880,6 +925,9 @@ var XHRInterceptor = class {
|
|
|
880
925
|
* @returns {void}
|
|
881
926
|
*/
|
|
882
927
|
setup(config) {
|
|
928
|
+
if (typeof XMLHttpRequest === "undefined") {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
883
931
|
if (this.isSetup) {
|
|
884
932
|
if (this.config?.enableInternalLogging) {
|
|
885
933
|
console.warn("[Limelight] XHR interceptor already set up");
|
|
@@ -918,12 +966,24 @@ var XHRInterceptor = class {
|
|
|
918
966
|
return self2.originalXHRSend.apply(this, arguments);
|
|
919
967
|
}
|
|
920
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
|
+
}
|
|
921
980
|
const requestBody = serializeBody(
|
|
922
981
|
body,
|
|
923
982
|
self2.config?.disableBodyCapture
|
|
924
983
|
);
|
|
925
984
|
let requestEvent = {
|
|
926
985
|
id: data.id,
|
|
986
|
+
traceId: data.traceId,
|
|
927
987
|
sessionId: self2.getSessionId(),
|
|
928
988
|
timestamp: data.startTime,
|
|
929
989
|
phase: "REQUEST" /* REQUEST */,
|
|
@@ -983,6 +1043,7 @@ var XHRInterceptor = class {
|
|
|
983
1043
|
);
|
|
984
1044
|
let responseEvent = {
|
|
985
1045
|
id: data.id,
|
|
1046
|
+
traceId: data.traceId,
|
|
986
1047
|
sessionId: self2.getSessionId(),
|
|
987
1048
|
timestamp: endTime,
|
|
988
1049
|
phase: "RESPONSE" /* RESPONSE */,
|
|
@@ -1017,6 +1078,7 @@ var XHRInterceptor = class {
|
|
|
1017
1078
|
responseSent = true;
|
|
1018
1079
|
let errorEvent = {
|
|
1019
1080
|
id: data.id,
|
|
1081
|
+
traceId: data.traceId,
|
|
1020
1082
|
sessionId: self2.getSessionId(),
|
|
1021
1083
|
timestamp: Date.now(),
|
|
1022
1084
|
phase,
|
|
@@ -1095,15 +1157,14 @@ var XHRInterceptor = class {
|
|
|
1095
1157
|
*/
|
|
1096
1158
|
cleanup() {
|
|
1097
1159
|
if (!this.isSetup) {
|
|
1098
|
-
if (this.config?.enableInternalLogging) {
|
|
1099
|
-
console.warn("[Limelight] XHR interceptor not set up");
|
|
1100
|
-
}
|
|
1101
1160
|
return;
|
|
1102
1161
|
}
|
|
1103
1162
|
this.isSetup = false;
|
|
1104
|
-
XMLHttpRequest
|
|
1105
|
-
|
|
1106
|
-
|
|
1163
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
1164
|
+
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
1165
|
+
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
1166
|
+
XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
|
|
1167
|
+
}
|
|
1107
1168
|
}
|
|
1108
1169
|
};
|
|
1109
1170
|
|
|
@@ -1756,6 +1817,83 @@ var RenderInterceptor = class {
|
|
|
1756
1817
|
}
|
|
1757
1818
|
};
|
|
1758
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
|
+
|
|
1759
1897
|
// src/limelight/interceptors/StateInterceptor.ts
|
|
1760
1898
|
var StateInterceptor = class {
|
|
1761
1899
|
sendMessage;
|
|
@@ -2239,6 +2377,153 @@ var CommandHandler = class {
|
|
|
2239
2377
|
}
|
|
2240
2378
|
};
|
|
2241
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
|
+
|
|
2242
2527
|
// src/limelight/LimelightClient.ts
|
|
2243
2528
|
var LimelightClient = class {
|
|
2244
2529
|
ws = null;
|
|
@@ -2255,6 +2540,7 @@ var LimelightClient = class {
|
|
|
2255
2540
|
consoleInterceptor;
|
|
2256
2541
|
renderInterceptor;
|
|
2257
2542
|
stateInterceptor;
|
|
2543
|
+
errorInterceptor;
|
|
2258
2544
|
requestBridge;
|
|
2259
2545
|
commandHandler = null;
|
|
2260
2546
|
constructor() {
|
|
@@ -2278,6 +2564,10 @@ var LimelightClient = class {
|
|
|
2278
2564
|
this.sendMessage.bind(this),
|
|
2279
2565
|
() => this.sessionId
|
|
2280
2566
|
);
|
|
2567
|
+
this.errorInterceptor = new ErrorInterceptor(
|
|
2568
|
+
this.sendMessage.bind(this),
|
|
2569
|
+
() => this.sessionId
|
|
2570
|
+
);
|
|
2281
2571
|
this.requestBridge = new RequestBridge(
|
|
2282
2572
|
this.sendMessage.bind(this),
|
|
2283
2573
|
() => this.sessionId
|
|
@@ -2298,7 +2588,7 @@ var LimelightClient = class {
|
|
|
2298
2588
|
*/
|
|
2299
2589
|
configure(config) {
|
|
2300
2590
|
const isEnabled = config?.enabled ?? isDevelopment();
|
|
2301
|
-
const configServerUrl = config?.serverUrl ? config.serverUrl : config?.projectKey ? `${LIMELIGHT_WEB_WSS_URL}${WS_PATH}` : `${LIMELIGHT_DESKTOP_WSS_URL}${WS_PATH}`;
|
|
2591
|
+
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}`;
|
|
2302
2592
|
this.config = {
|
|
2303
2593
|
...config,
|
|
2304
2594
|
appName: config?.appName ?? "Limelight App",
|
|
@@ -2319,12 +2609,17 @@ var LimelightClient = class {
|
|
|
2319
2609
|
try {
|
|
2320
2610
|
if (this.config.enableNetworkInspector) {
|
|
2321
2611
|
this.networkInterceptor.setup(this.config);
|
|
2322
|
-
|
|
2612
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
2613
|
+
this.xhrInterceptor.setup(this.config);
|
|
2614
|
+
}
|
|
2323
2615
|
}
|
|
2324
2616
|
if (this.config.enableConsole) {
|
|
2325
2617
|
this.consoleInterceptor.setup(this.config);
|
|
2618
|
+
if (!hasDOM()) {
|
|
2619
|
+
this.errorInterceptor.setup(this.config);
|
|
2620
|
+
}
|
|
2326
2621
|
}
|
|
2327
|
-
if (this.config.enableRenderInspector) {
|
|
2622
|
+
if (this.config.enableRenderInspector && hasDOM()) {
|
|
2328
2623
|
this.renderInterceptor.setup(this.config);
|
|
2329
2624
|
}
|
|
2330
2625
|
if (this.config.stores && this.config.enableStateInspector) {
|
|
@@ -2352,7 +2647,7 @@ var LimelightClient = class {
|
|
|
2352
2647
|
if (!this.config?.enabled) {
|
|
2353
2648
|
return;
|
|
2354
2649
|
}
|
|
2355
|
-
if (this.ws && this.ws.readyState ===
|
|
2650
|
+
if (this.ws && this.ws.readyState === 1) {
|
|
2356
2651
|
if (this.config?.enableInternalLogging) {
|
|
2357
2652
|
console.warn("[Limelight] Already connected. Call disconnect() first.");
|
|
2358
2653
|
}
|
|
@@ -2375,15 +2670,24 @@ var LimelightClient = class {
|
|
|
2375
2670
|
}
|
|
2376
2671
|
return;
|
|
2377
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
|
+
}
|
|
2378
2682
|
try {
|
|
2379
|
-
this.ws = new
|
|
2683
|
+
this.ws = new WsConstructor(serverUrl);
|
|
2380
2684
|
const message = {
|
|
2381
2685
|
phase: "CONNECT",
|
|
2382
2686
|
sessionId: this.sessionId,
|
|
2383
2687
|
timestamp: Date.now(),
|
|
2384
2688
|
data: {
|
|
2385
2689
|
appName,
|
|
2386
|
-
platform: platform || (typeof
|
|
2690
|
+
platform: platform || (hasDOM() ? "web" : typeof process !== "undefined" ? "node" : "react-native"),
|
|
2387
2691
|
projectKey: this.config.projectKey || "",
|
|
2388
2692
|
sdkVersion: SDK_VERSION
|
|
2389
2693
|
}
|
|
@@ -2450,10 +2754,13 @@ var LimelightClient = class {
|
|
|
2450
2754
|
* @returns {void}
|
|
2451
2755
|
*/
|
|
2452
2756
|
flushMessageQueue() {
|
|
2453
|
-
if (this.ws?.readyState !==
|
|
2757
|
+
if (this.ws?.readyState !== 1) return;
|
|
2454
2758
|
while (this.messageQueue.length > 0) {
|
|
2455
2759
|
const message = this.messageQueue.shift();
|
|
2456
2760
|
try {
|
|
2761
|
+
if (message && "sessionId" in message && !message.sessionId) {
|
|
2762
|
+
message.sessionId = this.sessionId;
|
|
2763
|
+
}
|
|
2457
2764
|
this.ws.send(safeStringify(message));
|
|
2458
2765
|
} catch (error) {
|
|
2459
2766
|
if (this.config?.enableInternalLogging) {
|
|
@@ -2471,9 +2778,9 @@ var LimelightClient = class {
|
|
|
2471
2778
|
* @returns {void}
|
|
2472
2779
|
*/
|
|
2473
2780
|
sendMessage(message) {
|
|
2474
|
-
if (this.ws?.readyState ===
|
|
2781
|
+
if (this.ws?.readyState === 1) {
|
|
2475
2782
|
this.flushMessageQueue();
|
|
2476
|
-
if (this.ws?.readyState ===
|
|
2783
|
+
if (this.ws?.readyState === 1) {
|
|
2477
2784
|
try {
|
|
2478
2785
|
this.ws.send(safeStringify(message));
|
|
2479
2786
|
} catch (error) {
|
|
@@ -2531,6 +2838,7 @@ var LimelightClient = class {
|
|
|
2531
2838
|
this.networkInterceptor.cleanup();
|
|
2532
2839
|
this.xhrInterceptor.cleanup();
|
|
2533
2840
|
this.consoleInterceptor.cleanup();
|
|
2841
|
+
this.errorInterceptor.cleanup();
|
|
2534
2842
|
this.renderInterceptor.cleanup();
|
|
2535
2843
|
this.stateInterceptor.cleanup();
|
|
2536
2844
|
this.requestBridge.cleanup();
|
|
@@ -2577,6 +2885,46 @@ var LimelightClient = class {
|
|
|
2577
2885
|
failRequest(requestId, error) {
|
|
2578
2886
|
this.requestBridge.failRequest(requestId, error);
|
|
2579
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
|
+
}
|
|
2580
2928
|
};
|
|
2581
2929
|
var Limelight = new LimelightClient();
|
|
2582
2930
|
export {
|