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