@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/README.md +50 -13
- package/dist/index.d.mts +100 -27
- package/dist/index.d.ts +100 -27
- package/dist/index.js +381 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +381 -34
- 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";
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
|
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
|
|
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 !==
|
|
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 ===
|
|
2818
|
+
if (this.ws?.readyState === 1) {
|
|
2513
2819
|
this.flushMessageQueue();
|
|
2514
|
-
if (this.ws?.readyState ===
|
|
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:
|