@allstak/react 0.2.1 → 0.3.0
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 +43 -0
- package/dist/index.d.mts +156 -36
- package/dist/index.d.ts +156 -36
- package/dist/index.js +579 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +579 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -143,12 +143,12 @@ function instrumentFetch(addBreadcrumb, ownBaseUrl) {
|
|
|
143
143
|
else if (input && typeof input.url === "string") url = input.url;
|
|
144
144
|
else url = String(input);
|
|
145
145
|
const safePath = url.split("?")[0];
|
|
146
|
-
const
|
|
146
|
+
const isOwnIngest2 = !!(ownBaseUrl && url.startsWith(ownBaseUrl));
|
|
147
147
|
const start = Date.now();
|
|
148
148
|
try {
|
|
149
149
|
const response = await originalFetch.call(this, input, init);
|
|
150
150
|
const durationMs = Date.now() - start;
|
|
151
|
-
if (!
|
|
151
|
+
if (!isOwnIngest2) {
|
|
152
152
|
addBreadcrumb(
|
|
153
153
|
"http",
|
|
154
154
|
`${method} ${safePath} -> ${response.status}`,
|
|
@@ -159,7 +159,7 @@ function instrumentFetch(addBreadcrumb, ownBaseUrl) {
|
|
|
159
159
|
return response;
|
|
160
160
|
} catch (err) {
|
|
161
161
|
const durationMs = Date.now() - start;
|
|
162
|
-
if (!
|
|
162
|
+
if (!isOwnIngest2) {
|
|
163
163
|
addBreadcrumb("http", `${method} ${safePath} -> failed`, "error", {
|
|
164
164
|
method,
|
|
165
165
|
url: safePath,
|
|
@@ -695,10 +695,534 @@ function resolveDebugId(filename) {
|
|
|
695
695
|
return id2;
|
|
696
696
|
}
|
|
697
697
|
|
|
698
|
+
// src/http-requests.ts
|
|
699
|
+
var INGEST_PATH = "/ingest/v1/http-requests";
|
|
700
|
+
var FLUSH_INTERVAL_MS3 = 5e3;
|
|
701
|
+
var BATCH_SIZE_THRESHOLD2 = 20;
|
|
702
|
+
var RECENT_FAILED_BUFFER_SIZE = 10;
|
|
703
|
+
function genTraceId() {
|
|
704
|
+
const hex = (n) => Math.floor(Math.random() * n).toString(16).padStart(1, "0");
|
|
705
|
+
const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
|
|
706
|
+
return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
|
|
707
|
+
}
|
|
708
|
+
function splitHostPath(url) {
|
|
709
|
+
try {
|
|
710
|
+
const u = new URL(url);
|
|
711
|
+
return { host: u.host, path: u.pathname || "/" };
|
|
712
|
+
} catch {
|
|
713
|
+
return { host: "", path: url.split("?")[0] };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
var HttpRequestModule = class {
|
|
717
|
+
constructor(transport) {
|
|
718
|
+
this.transport = transport;
|
|
719
|
+
this.queue = [];
|
|
720
|
+
this.recentFailed = [];
|
|
721
|
+
this.flushTimer = null;
|
|
722
|
+
this.destroyed = false;
|
|
723
|
+
this.defaults = {};
|
|
724
|
+
}
|
|
725
|
+
setDefaults(defaults) {
|
|
726
|
+
this.defaults = { ...this.defaults, ...defaults };
|
|
727
|
+
}
|
|
728
|
+
capture(ev) {
|
|
729
|
+
if (this.destroyed) return;
|
|
730
|
+
const { host, path } = splitHostPath(ev.url);
|
|
731
|
+
const item = {
|
|
732
|
+
type: "http_request",
|
|
733
|
+
traceId: ev.traceId ?? genTraceId(),
|
|
734
|
+
direction: "outbound",
|
|
735
|
+
method: (ev.method || "GET").toUpperCase(),
|
|
736
|
+
host,
|
|
737
|
+
path,
|
|
738
|
+
url: ev.url,
|
|
739
|
+
statusCode: ev.statusCode ?? 0,
|
|
740
|
+
durationMs: Math.max(0, Math.floor(ev.durationMs)),
|
|
741
|
+
requestSize: ev.requestSize,
|
|
742
|
+
responseSize: ev.responseSize,
|
|
743
|
+
requestBody: ev.requestBody,
|
|
744
|
+
responseBody: ev.responseBody,
|
|
745
|
+
requestHeaders: ev.requestHeaders,
|
|
746
|
+
responseHeaders: ev.responseHeaders,
|
|
747
|
+
error: ev.error,
|
|
748
|
+
environment: this.defaults.environment,
|
|
749
|
+
release: this.defaults.release,
|
|
750
|
+
dist: this.defaults.dist,
|
|
751
|
+
platform: this.defaults.platform,
|
|
752
|
+
"sdk.name": this.defaults.sdkName,
|
|
753
|
+
"sdk.version": this.defaults.sdkVersion,
|
|
754
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
755
|
+
};
|
|
756
|
+
this.queue.push(item);
|
|
757
|
+
const isFailed = item.statusCode >= 400 || !!item.error;
|
|
758
|
+
if (isFailed) {
|
|
759
|
+
this.recentFailed.push(item);
|
|
760
|
+
if (this.recentFailed.length > RECENT_FAILED_BUFFER_SIZE) this.recentFailed.shift();
|
|
761
|
+
}
|
|
762
|
+
if (this.queue.length >= BATCH_SIZE_THRESHOLD2) {
|
|
763
|
+
this.flush();
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (!this.flushTimer) this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS3);
|
|
767
|
+
}
|
|
768
|
+
/** Snapshot of the last failed requests for error-linking. Newest last. */
|
|
769
|
+
getRecentFailed() {
|
|
770
|
+
return this.recentFailed;
|
|
771
|
+
}
|
|
772
|
+
flush() {
|
|
773
|
+
if (this.queue.length === 0) return;
|
|
774
|
+
const batch = this.queue;
|
|
775
|
+
this.queue = [];
|
|
776
|
+
const payload = { requests: batch };
|
|
777
|
+
this.transport.send(INGEST_PATH, payload);
|
|
778
|
+
}
|
|
779
|
+
destroy() {
|
|
780
|
+
this.destroyed = true;
|
|
781
|
+
if (this.flushTimer) {
|
|
782
|
+
clearInterval(this.flushTimer);
|
|
783
|
+
this.flushTimer = null;
|
|
784
|
+
}
|
|
785
|
+
this.flush();
|
|
786
|
+
this.recentFailed = [];
|
|
787
|
+
}
|
|
788
|
+
/** @internal — for tests. */
|
|
789
|
+
getQueueSize() {
|
|
790
|
+
return this.queue.length;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// src/http-redact.ts
|
|
795
|
+
var ALWAYS_REDACT_HEADERS = /* @__PURE__ */ new Set([
|
|
796
|
+
"authorization",
|
|
797
|
+
"cookie",
|
|
798
|
+
"set-cookie",
|
|
799
|
+
"x-api-key",
|
|
800
|
+
"x-auth-token",
|
|
801
|
+
"proxy-authorization"
|
|
802
|
+
]);
|
|
803
|
+
var ALWAYS_REDACT_QUERY = /* @__PURE__ */ new Set([
|
|
804
|
+
"token",
|
|
805
|
+
"password",
|
|
806
|
+
"api_key",
|
|
807
|
+
"apikey",
|
|
808
|
+
"authorization",
|
|
809
|
+
"auth",
|
|
810
|
+
"secret",
|
|
811
|
+
"access_token",
|
|
812
|
+
"refresh_token",
|
|
813
|
+
"session",
|
|
814
|
+
"sessionid",
|
|
815
|
+
"jwt"
|
|
816
|
+
]);
|
|
817
|
+
var REDACTED = "[REDACTED]";
|
|
818
|
+
function shouldCaptureUrl(url, opts) {
|
|
819
|
+
if (!url) return false;
|
|
820
|
+
const lower = url.toLowerCase();
|
|
821
|
+
if (opts.allowedUrls && opts.allowedUrls.length > 0) {
|
|
822
|
+
return opts.allowedUrls.some((p) => matches(p, url, lower));
|
|
823
|
+
}
|
|
824
|
+
if (opts.ignoredUrls && opts.ignoredUrls.length > 0) {
|
|
825
|
+
return !opts.ignoredUrls.some((p) => matches(p, url, lower));
|
|
826
|
+
}
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
function matches(pattern, url, lower) {
|
|
830
|
+
if (pattern instanceof RegExp) return pattern.test(url);
|
|
831
|
+
if (typeof pattern !== "string") return false;
|
|
832
|
+
return lower.includes(pattern.toLowerCase());
|
|
833
|
+
}
|
|
834
|
+
function redactUrl(url, opts) {
|
|
835
|
+
if (!url) return url;
|
|
836
|
+
const extra = (opts.redactQueryParams ?? []).map((s) => s.toLowerCase());
|
|
837
|
+
const redactSet = /* @__PURE__ */ new Set([...ALWAYS_REDACT_QUERY, ...extra]);
|
|
838
|
+
let parsed = null;
|
|
839
|
+
try {
|
|
840
|
+
parsed = new URL(url);
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
if (parsed) {
|
|
844
|
+
const params = parsed.searchParams;
|
|
845
|
+
let mutated = false;
|
|
846
|
+
const keysToRedact = [];
|
|
847
|
+
params.forEach((_v, k) => {
|
|
848
|
+
if (redactSet.has(k.toLowerCase())) keysToRedact.push(k);
|
|
849
|
+
});
|
|
850
|
+
for (const k of keysToRedact) {
|
|
851
|
+
params.set(k, REDACTED);
|
|
852
|
+
mutated = true;
|
|
853
|
+
}
|
|
854
|
+
if (mutated) parsed.search = params.toString();
|
|
855
|
+
return parsed.toString();
|
|
856
|
+
}
|
|
857
|
+
const qIdx = url.indexOf("?");
|
|
858
|
+
if (qIdx < 0) return url;
|
|
859
|
+
const head = url.slice(0, qIdx);
|
|
860
|
+
const queryAndHash = url.slice(qIdx + 1);
|
|
861
|
+
const hashIdx = queryAndHash.indexOf("#");
|
|
862
|
+
const query = hashIdx < 0 ? queryAndHash : queryAndHash.slice(0, hashIdx);
|
|
863
|
+
const hash = hashIdx < 0 ? "" : queryAndHash.slice(hashIdx);
|
|
864
|
+
const parts = query.split("&").map((pair) => {
|
|
865
|
+
const eq = pair.indexOf("=");
|
|
866
|
+
if (eq < 0) return pair;
|
|
867
|
+
const key = pair.slice(0, eq);
|
|
868
|
+
return redactSet.has(key.toLowerCase()) ? `${key}=${REDACTED}` : pair;
|
|
869
|
+
});
|
|
870
|
+
return `${head}?${parts.join("&")}${hash ? "#" + hash : ""}`;
|
|
871
|
+
}
|
|
872
|
+
function sanitizeHeaders(headers, opts) {
|
|
873
|
+
if (!opts.captureHeaders) return void 0;
|
|
874
|
+
if (!headers) return {};
|
|
875
|
+
const extra = (opts.redactHeaders ?? []).map((s) => s.toLowerCase());
|
|
876
|
+
const redactSet = /* @__PURE__ */ new Set([...ALWAYS_REDACT_HEADERS, ...extra]);
|
|
877
|
+
const out = {};
|
|
878
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
879
|
+
if (v == null) continue;
|
|
880
|
+
const lower = k.toLowerCase();
|
|
881
|
+
out[lower] = redactSet.has(lower) ? REDACTED : Array.isArray(v) ? v.join(", ") : String(v);
|
|
882
|
+
}
|
|
883
|
+
return out;
|
|
884
|
+
}
|
|
885
|
+
function captureBody(body, enabled, maxBodyBytes) {
|
|
886
|
+
if (!enabled) return void 0;
|
|
887
|
+
if (body == null) return void 0;
|
|
888
|
+
let str;
|
|
889
|
+
if (typeof body === "string") str = body;
|
|
890
|
+
else if (typeof body === "number" || typeof body === "boolean") str = String(body);
|
|
891
|
+
else if (typeof body === "object") {
|
|
892
|
+
const tag = Object.prototype.toString.call(body);
|
|
893
|
+
if (tag !== "[object Object]" && tag !== "[object Array]") return "<binary>";
|
|
894
|
+
try {
|
|
895
|
+
str = JSON.stringify(body);
|
|
896
|
+
} catch {
|
|
897
|
+
return "<unserializable>";
|
|
898
|
+
}
|
|
899
|
+
} else {
|
|
900
|
+
return "<binary>";
|
|
901
|
+
}
|
|
902
|
+
if (str.length > maxBodyBytes) return str.slice(0, maxBodyBytes) + "\u2026[truncated]";
|
|
903
|
+
return str;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// src/http-instrumentation.ts
|
|
907
|
+
var FETCH_FLAG2 = "__allstak_http_fetch_patched__";
|
|
908
|
+
var XHR_FLAG = "__allstak_http_xhr_patched__";
|
|
909
|
+
var AXIOS_FLAG = /* @__PURE__ */ Symbol.for("allstak.http.axios.instrumented");
|
|
910
|
+
var DEFAULT_MAX_BODY = 4096;
|
|
911
|
+
var _currentModule = null;
|
|
912
|
+
var _currentOpts = null;
|
|
913
|
+
function currentModule() {
|
|
914
|
+
return _currentModule;
|
|
915
|
+
}
|
|
916
|
+
function currentOpts() {
|
|
917
|
+
return _currentOpts;
|
|
918
|
+
}
|
|
919
|
+
function safeCapture(ev) {
|
|
920
|
+
try {
|
|
921
|
+
currentModule()?.capture(ev);
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function bind(opts, ownIngestHost) {
|
|
926
|
+
return {
|
|
927
|
+
captureRequestBody: opts.captureRequestBody ?? false,
|
|
928
|
+
captureResponseBody: opts.captureResponseBody ?? false,
|
|
929
|
+
captureHeaders: opts.captureHeaders ?? false,
|
|
930
|
+
redactHeaders: opts.redactHeaders ?? [],
|
|
931
|
+
redactQueryParams: opts.redactQueryParams ?? [],
|
|
932
|
+
ignoredUrls: opts.ignoredUrls ?? [],
|
|
933
|
+
allowedUrls: opts.allowedUrls ?? [],
|
|
934
|
+
maxBodyBytes: opts.maxBodyBytes ?? DEFAULT_MAX_BODY,
|
|
935
|
+
ownIngestPrefix: ownIngestHost.replace(/\/$/, "")
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function isOwnIngest(url, prefix) {
|
|
939
|
+
return !!prefix && url.startsWith(prefix);
|
|
940
|
+
}
|
|
941
|
+
function urlString(input) {
|
|
942
|
+
if (typeof input === "string") return input;
|
|
943
|
+
if (input && typeof input.href === "string") return input.href;
|
|
944
|
+
if (input && typeof input.url === "string") return input.url;
|
|
945
|
+
return String(input);
|
|
946
|
+
}
|
|
947
|
+
function safeByteLength(s) {
|
|
948
|
+
if (s == null) return void 0;
|
|
949
|
+
if (typeof s === "string") {
|
|
950
|
+
let n = 0;
|
|
951
|
+
for (let i = 0; i < s.length; i++) {
|
|
952
|
+
const c = s.charCodeAt(i);
|
|
953
|
+
if (c < 128) n += 1;
|
|
954
|
+
else if (c < 2048) n += 2;
|
|
955
|
+
else if (c >= 55296 && c <= 56319) {
|
|
956
|
+
n += 4;
|
|
957
|
+
i += 1;
|
|
958
|
+
} else n += 3;
|
|
959
|
+
}
|
|
960
|
+
return n;
|
|
961
|
+
}
|
|
962
|
+
return void 0;
|
|
963
|
+
}
|
|
964
|
+
function headersToObject(h) {
|
|
965
|
+
if (!h) return {};
|
|
966
|
+
if (typeof h.entries === "function") {
|
|
967
|
+
const out = {};
|
|
968
|
+
for (const [k, v] of h.entries()) out[String(k).toLowerCase()] = String(v);
|
|
969
|
+
return out;
|
|
970
|
+
}
|
|
971
|
+
if (typeof h === "object") {
|
|
972
|
+
const out = {};
|
|
973
|
+
for (const [k, v] of Object.entries(h)) {
|
|
974
|
+
if (v == null) continue;
|
|
975
|
+
out[String(k).toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
976
|
+
}
|
|
977
|
+
return out;
|
|
978
|
+
}
|
|
979
|
+
return {};
|
|
980
|
+
}
|
|
981
|
+
function patchFetch() {
|
|
982
|
+
const g = globalThis;
|
|
983
|
+
if (typeof g.fetch !== "function") return;
|
|
984
|
+
if (g.fetch[FETCH_FLAG2]) return;
|
|
985
|
+
const original = g.fetch;
|
|
986
|
+
const wrapped = async function(input, init) {
|
|
987
|
+
const opts = currentOpts();
|
|
988
|
+
if (!opts || !currentModule()) {
|
|
989
|
+
return original.call(this, input, init);
|
|
990
|
+
}
|
|
991
|
+
const rawUrl = urlString(input);
|
|
992
|
+
const sanitizedUrl = redactUrl(rawUrl, opts);
|
|
993
|
+
const method = (init?.method || input && typeof input === "object" && input.method || "GET").toUpperCase();
|
|
994
|
+
if (isOwnIngest(rawUrl, opts.ownIngestPrefix) || !shouldCaptureUrl(rawUrl, opts)) {
|
|
995
|
+
return original.call(this, input, init);
|
|
996
|
+
}
|
|
997
|
+
const start = Date.now();
|
|
998
|
+
const reqHeaders = sanitizeHeaders(headersToObject(init?.headers ?? (input && input.headers)), opts);
|
|
999
|
+
const reqBody = captureBody(init?.body, opts.captureRequestBody, opts.maxBodyBytes);
|
|
1000
|
+
const reqSize = safeByteLength(typeof init?.body === "string" ? init.body : void 0);
|
|
1001
|
+
let response;
|
|
1002
|
+
try {
|
|
1003
|
+
response = await original.call(this, input, init);
|
|
1004
|
+
} catch (err) {
|
|
1005
|
+
safeCapture({
|
|
1006
|
+
type: "http_request",
|
|
1007
|
+
method,
|
|
1008
|
+
url: sanitizedUrl,
|
|
1009
|
+
statusCode: 0,
|
|
1010
|
+
durationMs: Date.now() - start,
|
|
1011
|
+
requestBody: reqBody,
|
|
1012
|
+
requestHeaders: reqHeaders,
|
|
1013
|
+
requestSize: reqSize,
|
|
1014
|
+
error: String(err?.message ?? err)
|
|
1015
|
+
});
|
|
1016
|
+
throw err;
|
|
1017
|
+
}
|
|
1018
|
+
const durationMs = Date.now() - start;
|
|
1019
|
+
let respBody;
|
|
1020
|
+
let respSize;
|
|
1021
|
+
let respHeaders;
|
|
1022
|
+
try {
|
|
1023
|
+
respHeaders = sanitizeHeaders(headersToObject(response.headers), opts);
|
|
1024
|
+
const lenHeader = typeof response.headers?.get === "function" ? response.headers.get("content-length") : null;
|
|
1025
|
+
if (lenHeader) respSize = parseInt(lenHeader, 10) || void 0;
|
|
1026
|
+
if (opts.captureResponseBody && typeof response.clone === "function") {
|
|
1027
|
+
try {
|
|
1028
|
+
const cloned = response.clone();
|
|
1029
|
+
const text = await cloned.text();
|
|
1030
|
+
respBody = captureBody(text, true, opts.maxBodyBytes);
|
|
1031
|
+
if (respSize == null) respSize = safeByteLength(text);
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
} catch {
|
|
1036
|
+
}
|
|
1037
|
+
safeCapture({
|
|
1038
|
+
type: "http_request",
|
|
1039
|
+
method,
|
|
1040
|
+
url: sanitizedUrl,
|
|
1041
|
+
statusCode: response.status,
|
|
1042
|
+
durationMs,
|
|
1043
|
+
requestBody: reqBody,
|
|
1044
|
+
requestHeaders: reqHeaders,
|
|
1045
|
+
requestSize: reqSize,
|
|
1046
|
+
responseBody: respBody,
|
|
1047
|
+
responseHeaders: respHeaders,
|
|
1048
|
+
responseSize: respSize
|
|
1049
|
+
});
|
|
1050
|
+
return response;
|
|
1051
|
+
};
|
|
1052
|
+
wrapped[FETCH_FLAG2] = true;
|
|
1053
|
+
g.fetch = wrapped;
|
|
1054
|
+
}
|
|
1055
|
+
function patchXhr() {
|
|
1056
|
+
const g = globalThis;
|
|
1057
|
+
const X = g.XMLHttpRequest;
|
|
1058
|
+
if (!X || X.prototype[XHR_FLAG]) return;
|
|
1059
|
+
const origOpen = X.prototype.open;
|
|
1060
|
+
const origSend = X.prototype.send;
|
|
1061
|
+
const origSetRequestHeader = X.prototype.setRequestHeader;
|
|
1062
|
+
X.prototype.open = function(method, url, ...rest) {
|
|
1063
|
+
this.__allstak_method__ = method;
|
|
1064
|
+
this.__allstak_url__ = url;
|
|
1065
|
+
this.__allstak_headers__ = {};
|
|
1066
|
+
return origOpen.call(this, method, url, ...rest);
|
|
1067
|
+
};
|
|
1068
|
+
X.prototype.setRequestHeader = function(name, value) {
|
|
1069
|
+
try {
|
|
1070
|
+
this.__allstak_headers__[String(name).toLowerCase()] = String(value);
|
|
1071
|
+
} catch {
|
|
1072
|
+
}
|
|
1073
|
+
return origSetRequestHeader.call(this, name, value);
|
|
1074
|
+
};
|
|
1075
|
+
X.prototype.send = function(body) {
|
|
1076
|
+
const opts = currentOpts();
|
|
1077
|
+
if (!opts || !currentModule()) return origSend.call(this, body);
|
|
1078
|
+
const start = Date.now();
|
|
1079
|
+
const method = String(this.__allstak_method__ || "GET").toUpperCase();
|
|
1080
|
+
const rawUrl = String(this.__allstak_url__ || "");
|
|
1081
|
+
const sanitizedUrl = redactUrl(rawUrl, opts);
|
|
1082
|
+
if (isOwnIngest(rawUrl, opts.ownIngestPrefix) || !shouldCaptureUrl(rawUrl, opts)) {
|
|
1083
|
+
return origSend.call(this, body);
|
|
1084
|
+
}
|
|
1085
|
+
const reqHeaders = sanitizeHeaders(this.__allstak_headers__, opts);
|
|
1086
|
+
const reqBody = captureBody(body, opts.captureRequestBody, opts.maxBodyBytes);
|
|
1087
|
+
const reqSize = safeByteLength(typeof body === "string" ? body : void 0);
|
|
1088
|
+
const finish = (statusCode, error) => {
|
|
1089
|
+
const durationMs = Date.now() - start;
|
|
1090
|
+
let respHeaders;
|
|
1091
|
+
let respBody;
|
|
1092
|
+
let respSize;
|
|
1093
|
+
try {
|
|
1094
|
+
const liveOpts = currentOpts() ?? opts;
|
|
1095
|
+
if (liveOpts.captureHeaders && typeof this.getAllResponseHeaders === "function") {
|
|
1096
|
+
const raw = this.getAllResponseHeaders() || "";
|
|
1097
|
+
const dict = {};
|
|
1098
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
1099
|
+
const idx = line.indexOf(":");
|
|
1100
|
+
if (idx > 0) dict[line.slice(0, idx).trim().toLowerCase()] = line.slice(idx + 1).trim();
|
|
1101
|
+
}
|
|
1102
|
+
respHeaders = sanitizeHeaders(dict, liveOpts);
|
|
1103
|
+
}
|
|
1104
|
+
if (liveOpts.captureResponseBody) {
|
|
1105
|
+
const text = this.responseText;
|
|
1106
|
+
if (typeof text === "string") {
|
|
1107
|
+
respBody = captureBody(text, true, liveOpts.maxBodyBytes);
|
|
1108
|
+
respSize = safeByteLength(text);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
safeCapture({
|
|
1114
|
+
type: "http_request",
|
|
1115
|
+
method,
|
|
1116
|
+
url: sanitizedUrl,
|
|
1117
|
+
statusCode,
|
|
1118
|
+
durationMs,
|
|
1119
|
+
requestBody: reqBody,
|
|
1120
|
+
requestHeaders: reqHeaders,
|
|
1121
|
+
requestSize: reqSize,
|
|
1122
|
+
responseBody: respBody,
|
|
1123
|
+
responseHeaders: respHeaders,
|
|
1124
|
+
responseSize: respSize,
|
|
1125
|
+
error
|
|
1126
|
+
});
|
|
1127
|
+
};
|
|
1128
|
+
this.addEventListener?.("load", () => finish(this.status || 0));
|
|
1129
|
+
this.addEventListener?.("error", () => finish(0, "network"));
|
|
1130
|
+
this.addEventListener?.("abort", () => finish(0, "abort"));
|
|
1131
|
+
this.addEventListener?.("timeout", () => finish(0, "timeout"));
|
|
1132
|
+
return origSend.call(this, body);
|
|
1133
|
+
};
|
|
1134
|
+
X.prototype[XHR_FLAG] = true;
|
|
1135
|
+
}
|
|
1136
|
+
function instrumentAxiosInstance(axiosInstance, module, opts) {
|
|
1137
|
+
if (!axiosInstance || typeof axiosInstance.interceptors !== "object") return axiosInstance;
|
|
1138
|
+
if (axiosInstance[AXIOS_FLAG]) return axiosInstance;
|
|
1139
|
+
axiosInstance[AXIOS_FLAG] = true;
|
|
1140
|
+
const reqStarts = /* @__PURE__ */ new WeakMap();
|
|
1141
|
+
axiosInstance.interceptors.request.use((config) => {
|
|
1142
|
+
try {
|
|
1143
|
+
const rawUrl = (config.baseURL ? config.baseURL.replace(/\/$/, "") : "") + (config.url || "");
|
|
1144
|
+
reqStarts.set(config, {
|
|
1145
|
+
start: Date.now(),
|
|
1146
|
+
method: String(config.method || "GET").toUpperCase(),
|
|
1147
|
+
rawUrl
|
|
1148
|
+
});
|
|
1149
|
+
} catch {
|
|
1150
|
+
}
|
|
1151
|
+
return config;
|
|
1152
|
+
});
|
|
1153
|
+
const finalize = (cfg, statusCode, response, error) => {
|
|
1154
|
+
const meta = reqStarts.get(cfg);
|
|
1155
|
+
if (!meta) return;
|
|
1156
|
+
reqStarts.delete(cfg);
|
|
1157
|
+
if (isOwnIngest(meta.rawUrl, opts.ownIngestPrefix)) return;
|
|
1158
|
+
if (!shouldCaptureUrl(meta.rawUrl, opts)) return;
|
|
1159
|
+
const sanitizedUrl = redactUrl(meta.rawUrl, opts);
|
|
1160
|
+
const reqHeaders = sanitizeHeaders(headersToObject(cfg.headers), opts);
|
|
1161
|
+
const reqBody = captureBody(cfg.data, opts.captureRequestBody, opts.maxBodyBytes);
|
|
1162
|
+
const respHeaders = sanitizeHeaders(headersToObject(response?.headers), opts);
|
|
1163
|
+
const respBody = captureBody(response?.data, opts.captureResponseBody, opts.maxBodyBytes);
|
|
1164
|
+
try {
|
|
1165
|
+
module.capture({
|
|
1166
|
+
type: "http_request",
|
|
1167
|
+
method: meta.method,
|
|
1168
|
+
url: sanitizedUrl,
|
|
1169
|
+
statusCode,
|
|
1170
|
+
durationMs: Date.now() - meta.start,
|
|
1171
|
+
requestBody: reqBody,
|
|
1172
|
+
requestHeaders: reqHeaders,
|
|
1173
|
+
responseBody: respBody,
|
|
1174
|
+
responseHeaders: respHeaders,
|
|
1175
|
+
error
|
|
1176
|
+
});
|
|
1177
|
+
} catch {
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
axiosInstance.interceptors.response.use(
|
|
1181
|
+
(response) => {
|
|
1182
|
+
finalize(response.config, response.status);
|
|
1183
|
+
return response;
|
|
1184
|
+
},
|
|
1185
|
+
(err) => {
|
|
1186
|
+
const cfg = err?.config;
|
|
1187
|
+
const status = err?.response?.status ?? 0;
|
|
1188
|
+
finalize(cfg, status, err?.response, String(err?.message ?? err));
|
|
1189
|
+
throw err;
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
return axiosInstance;
|
|
1193
|
+
}
|
|
1194
|
+
function tryAutoInstrumentAxios(module, opts) {
|
|
1195
|
+
try {
|
|
1196
|
+
const axios = globalThis.require?.("axios") ?? null;
|
|
1197
|
+
if (axios) instrumentAxiosInstance(axios.default ?? axios, module, opts);
|
|
1198
|
+
} catch {
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function installHttpInstrumentation(module, options, ownIngestHost) {
|
|
1202
|
+
const bound = bind(options, ownIngestHost);
|
|
1203
|
+
_currentModule = module;
|
|
1204
|
+
_currentOpts = bound;
|
|
1205
|
+
try {
|
|
1206
|
+
patchFetch();
|
|
1207
|
+
} catch {
|
|
1208
|
+
}
|
|
1209
|
+
try {
|
|
1210
|
+
patchXhr();
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
try {
|
|
1214
|
+
tryAutoInstrumentAxios(module, bound);
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
instrumentAxios: (axios) => instrumentAxiosInstance(axios, module, bound)
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
698
1222
|
// src/client.ts
|
|
699
1223
|
var INGEST_HOST = "https://api.allstak.sa";
|
|
700
1224
|
var SDK_NAME = "allstak-react";
|
|
701
|
-
var SDK_VERSION = "0.
|
|
1225
|
+
var SDK_VERSION = "0.3.0";
|
|
702
1226
|
var ERRORS_PATH = "/ingest/v1/errors";
|
|
703
1227
|
var LOGS_PATH = "/ingest/v1/logs";
|
|
704
1228
|
var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
|
|
@@ -728,6 +1252,8 @@ var AllStakClient = class {
|
|
|
728
1252
|
this.breadcrumbs = [];
|
|
729
1253
|
this.scopeStack = [];
|
|
730
1254
|
this.replay = null;
|
|
1255
|
+
this.httpRequests = null;
|
|
1256
|
+
this._instrumentAxios = null;
|
|
731
1257
|
this.onErrorHandler = null;
|
|
732
1258
|
this.onRejectionHandler = null;
|
|
733
1259
|
if (!config.apiKey) throw new Error("AllStak: config.apiKey is required");
|
|
@@ -773,6 +1299,35 @@ var AllStakClient = class {
|
|
|
773
1299
|
} catch {
|
|
774
1300
|
}
|
|
775
1301
|
}
|
|
1302
|
+
if (config.enableHttpTracking) {
|
|
1303
|
+
try {
|
|
1304
|
+
this.httpRequests = new HttpRequestModule(this.transport);
|
|
1305
|
+
this.httpRequests.setDefaults({
|
|
1306
|
+
environment: this.config.environment,
|
|
1307
|
+
release: this.config.release,
|
|
1308
|
+
dist: this.config.dist,
|
|
1309
|
+
platform: this.config.platform,
|
|
1310
|
+
sdkName: this.config.sdkName,
|
|
1311
|
+
sdkVersion: this.config.sdkVersion
|
|
1312
|
+
});
|
|
1313
|
+
const { instrumentAxios } = installHttpInstrumentation(
|
|
1314
|
+
this.httpRequests,
|
|
1315
|
+
config.httpTracking ?? {},
|
|
1316
|
+
baseUrl
|
|
1317
|
+
);
|
|
1318
|
+
this._instrumentAxios = instrumentAxios;
|
|
1319
|
+
} catch {
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/** Manually instrument an axios instance. No-op when HTTP tracking is off. */
|
|
1324
|
+
instrumentAxios(axios) {
|
|
1325
|
+
if (!this._instrumentAxios) return axios;
|
|
1326
|
+
return this._instrumentAxios(axios);
|
|
1327
|
+
}
|
|
1328
|
+
/** Snapshot of recent failed HTTP requests for error-linking. */
|
|
1329
|
+
getRecentFailedHttp() {
|
|
1330
|
+
return this.httpRequests?.getRecentFailed() ?? [];
|
|
776
1331
|
}
|
|
777
1332
|
captureException(error, context) {
|
|
778
1333
|
if (!this.passesSampleRate()) return;
|
|
@@ -791,6 +1346,16 @@ var AllStakClient = class {
|
|
|
791
1346
|
if (traceId) traceContext.traceId = traceId;
|
|
792
1347
|
const spanId = this.tracing.getCurrentSpanId();
|
|
793
1348
|
if (spanId) traceContext.spanId = spanId;
|
|
1349
|
+
const recentFailed = this.httpRequests?.getRecentFailed() ?? [];
|
|
1350
|
+
if (recentFailed.length > 0) {
|
|
1351
|
+
traceContext["http.recentFailed"] = recentFailed.map((r) => ({
|
|
1352
|
+
method: r.method,
|
|
1353
|
+
url: r.url,
|
|
1354
|
+
statusCode: r.statusCode,
|
|
1355
|
+
durationMs: r.durationMs,
|
|
1356
|
+
error: r.error
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
794
1359
|
const payload = {
|
|
795
1360
|
exceptionClass,
|
|
796
1361
|
message: error.message,
|
|
@@ -947,6 +1512,11 @@ var AllStakClient = class {
|
|
|
947
1512
|
this.replay.destroy();
|
|
948
1513
|
this.replay = null;
|
|
949
1514
|
}
|
|
1515
|
+
if (this.httpRequests) {
|
|
1516
|
+
this.httpRequests.destroy();
|
|
1517
|
+
this.httpRequests = null;
|
|
1518
|
+
}
|
|
1519
|
+
this._instrumentAxios = null;
|
|
950
1520
|
this.breadcrumbs = [];
|
|
951
1521
|
}
|
|
952
1522
|
installBrowserHandlers() {
|
|
@@ -1155,6 +1725,10 @@ var AllStak = {
|
|
|
1155
1725
|
resetTrace() {
|
|
1156
1726
|
ensureInit().resetTrace();
|
|
1157
1727
|
},
|
|
1728
|
+
/** Manually instrument an axios instance. No-op when HTTP tracking is off. */
|
|
1729
|
+
instrumentAxios(axios) {
|
|
1730
|
+
return ensureInit().instrumentAxios(axios);
|
|
1731
|
+
},
|
|
1158
1732
|
getSessionId() {
|
|
1159
1733
|
return ensureInit().getSessionId();
|
|
1160
1734
|
},
|
|
@@ -1242,6 +1816,7 @@ export {
|
|
|
1242
1816
|
AllStak,
|
|
1243
1817
|
AllStakClient,
|
|
1244
1818
|
AllStakErrorBoundary,
|
|
1819
|
+
HttpRequestModule,
|
|
1245
1820
|
INGEST_HOST,
|
|
1246
1821
|
ReplayRecorder,
|
|
1247
1822
|
SDK_NAME,
|