@allstak/react 0.2.1 → 0.3.1
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.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
AllStak: () => AllStak,
|
|
34
34
|
AllStakClient: () => AllStakClient,
|
|
35
35
|
AllStakErrorBoundary: () => AllStakErrorBoundary,
|
|
36
|
+
HttpRequestModule: () => HttpRequestModule,
|
|
36
37
|
INGEST_HOST: () => INGEST_HOST,
|
|
37
38
|
ReplayRecorder: () => ReplayRecorder,
|
|
38
39
|
SDK_NAME: () => SDK_NAME,
|
|
@@ -191,12 +192,12 @@ function instrumentFetch(addBreadcrumb, ownBaseUrl) {
|
|
|
191
192
|
else if (input && typeof input.url === "string") url = input.url;
|
|
192
193
|
else url = String(input);
|
|
193
194
|
const safePath = url.split("?")[0];
|
|
194
|
-
const
|
|
195
|
+
const isOwnIngest2 = !!(ownBaseUrl && url.startsWith(ownBaseUrl));
|
|
195
196
|
const start = Date.now();
|
|
196
197
|
try {
|
|
197
198
|
const response = await originalFetch.call(this, input, init);
|
|
198
199
|
const durationMs = Date.now() - start;
|
|
199
|
-
if (!
|
|
200
|
+
if (!isOwnIngest2) {
|
|
200
201
|
addBreadcrumb(
|
|
201
202
|
"http",
|
|
202
203
|
`${method} ${safePath} -> ${response.status}`,
|
|
@@ -207,7 +208,7 @@ function instrumentFetch(addBreadcrumb, ownBaseUrl) {
|
|
|
207
208
|
return response;
|
|
208
209
|
} catch (err) {
|
|
209
210
|
const durationMs = Date.now() - start;
|
|
210
|
-
if (!
|
|
211
|
+
if (!isOwnIngest2) {
|
|
211
212
|
addBreadcrumb("http", `${method} ${safePath} -> failed`, "error", {
|
|
212
213
|
method,
|
|
213
214
|
url: safePath,
|
|
@@ -743,10 +744,534 @@ function resolveDebugId(filename) {
|
|
|
743
744
|
return id2;
|
|
744
745
|
}
|
|
745
746
|
|
|
747
|
+
// src/http-requests.ts
|
|
748
|
+
var INGEST_PATH = "/ingest/v1/http-requests";
|
|
749
|
+
var FLUSH_INTERVAL_MS3 = 5e3;
|
|
750
|
+
var BATCH_SIZE_THRESHOLD2 = 20;
|
|
751
|
+
var RECENT_FAILED_BUFFER_SIZE = 10;
|
|
752
|
+
function genTraceId() {
|
|
753
|
+
const hex = (n) => Math.floor(Math.random() * n).toString(16).padStart(1, "0");
|
|
754
|
+
const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
|
|
755
|
+
return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
|
|
756
|
+
}
|
|
757
|
+
function splitHostPath(url) {
|
|
758
|
+
try {
|
|
759
|
+
const u = new URL(url);
|
|
760
|
+
return { host: u.host, path: u.pathname || "/" };
|
|
761
|
+
} catch {
|
|
762
|
+
return { host: "", path: url.split("?")[0] };
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
var HttpRequestModule = class {
|
|
766
|
+
constructor(transport) {
|
|
767
|
+
this.transport = transport;
|
|
768
|
+
this.queue = [];
|
|
769
|
+
this.recentFailed = [];
|
|
770
|
+
this.flushTimer = null;
|
|
771
|
+
this.destroyed = false;
|
|
772
|
+
this.defaults = {};
|
|
773
|
+
}
|
|
774
|
+
setDefaults(defaults) {
|
|
775
|
+
this.defaults = { ...this.defaults, ...defaults };
|
|
776
|
+
}
|
|
777
|
+
capture(ev) {
|
|
778
|
+
if (this.destroyed) return;
|
|
779
|
+
const { host, path } = splitHostPath(ev.url);
|
|
780
|
+
const item = {
|
|
781
|
+
type: "http_request",
|
|
782
|
+
traceId: ev.traceId ?? genTraceId(),
|
|
783
|
+
direction: "outbound",
|
|
784
|
+
method: (ev.method || "GET").toUpperCase(),
|
|
785
|
+
host,
|
|
786
|
+
path,
|
|
787
|
+
url: ev.url,
|
|
788
|
+
statusCode: ev.statusCode ?? 0,
|
|
789
|
+
durationMs: Math.max(0, Math.floor(ev.durationMs)),
|
|
790
|
+
requestSize: ev.requestSize,
|
|
791
|
+
responseSize: ev.responseSize,
|
|
792
|
+
requestBody: ev.requestBody,
|
|
793
|
+
responseBody: ev.responseBody,
|
|
794
|
+
requestHeaders: ev.requestHeaders,
|
|
795
|
+
responseHeaders: ev.responseHeaders,
|
|
796
|
+
error: ev.error,
|
|
797
|
+
environment: this.defaults.environment,
|
|
798
|
+
release: this.defaults.release,
|
|
799
|
+
dist: this.defaults.dist,
|
|
800
|
+
platform: this.defaults.platform,
|
|
801
|
+
"sdk.name": this.defaults.sdkName,
|
|
802
|
+
"sdk.version": this.defaults.sdkVersion,
|
|
803
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
804
|
+
};
|
|
805
|
+
this.queue.push(item);
|
|
806
|
+
const isFailed = item.statusCode >= 400 || !!item.error;
|
|
807
|
+
if (isFailed) {
|
|
808
|
+
this.recentFailed.push(item);
|
|
809
|
+
if (this.recentFailed.length > RECENT_FAILED_BUFFER_SIZE) this.recentFailed.shift();
|
|
810
|
+
}
|
|
811
|
+
if (this.queue.length >= BATCH_SIZE_THRESHOLD2) {
|
|
812
|
+
this.flush();
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (!this.flushTimer) this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS3);
|
|
816
|
+
}
|
|
817
|
+
/** Snapshot of the last failed requests for error-linking. Newest last. */
|
|
818
|
+
getRecentFailed() {
|
|
819
|
+
return this.recentFailed;
|
|
820
|
+
}
|
|
821
|
+
flush() {
|
|
822
|
+
if (this.queue.length === 0) return;
|
|
823
|
+
const batch = this.queue;
|
|
824
|
+
this.queue = [];
|
|
825
|
+
const payload = { requests: batch };
|
|
826
|
+
this.transport.send(INGEST_PATH, payload);
|
|
827
|
+
}
|
|
828
|
+
destroy() {
|
|
829
|
+
this.destroyed = true;
|
|
830
|
+
if (this.flushTimer) {
|
|
831
|
+
clearInterval(this.flushTimer);
|
|
832
|
+
this.flushTimer = null;
|
|
833
|
+
}
|
|
834
|
+
this.flush();
|
|
835
|
+
this.recentFailed = [];
|
|
836
|
+
}
|
|
837
|
+
/** @internal — for tests. */
|
|
838
|
+
getQueueSize() {
|
|
839
|
+
return this.queue.length;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// src/http-redact.ts
|
|
844
|
+
var ALWAYS_REDACT_HEADERS = /* @__PURE__ */ new Set([
|
|
845
|
+
"authorization",
|
|
846
|
+
"cookie",
|
|
847
|
+
"set-cookie",
|
|
848
|
+
"x-api-key",
|
|
849
|
+
"x-auth-token",
|
|
850
|
+
"proxy-authorization"
|
|
851
|
+
]);
|
|
852
|
+
var ALWAYS_REDACT_QUERY = /* @__PURE__ */ new Set([
|
|
853
|
+
"token",
|
|
854
|
+
"password",
|
|
855
|
+
"api_key",
|
|
856
|
+
"apikey",
|
|
857
|
+
"authorization",
|
|
858
|
+
"auth",
|
|
859
|
+
"secret",
|
|
860
|
+
"access_token",
|
|
861
|
+
"refresh_token",
|
|
862
|
+
"session",
|
|
863
|
+
"sessionid",
|
|
864
|
+
"jwt"
|
|
865
|
+
]);
|
|
866
|
+
var REDACTED = "[REDACTED]";
|
|
867
|
+
function shouldCaptureUrl(url, opts) {
|
|
868
|
+
if (!url) return false;
|
|
869
|
+
const lower = url.toLowerCase();
|
|
870
|
+
if (opts.allowedUrls && opts.allowedUrls.length > 0) {
|
|
871
|
+
return opts.allowedUrls.some((p) => matches(p, url, lower));
|
|
872
|
+
}
|
|
873
|
+
if (opts.ignoredUrls && opts.ignoredUrls.length > 0) {
|
|
874
|
+
return !opts.ignoredUrls.some((p) => matches(p, url, lower));
|
|
875
|
+
}
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
function matches(pattern, url, lower) {
|
|
879
|
+
if (pattern instanceof RegExp) return pattern.test(url);
|
|
880
|
+
if (typeof pattern !== "string") return false;
|
|
881
|
+
return lower.includes(pattern.toLowerCase());
|
|
882
|
+
}
|
|
883
|
+
function redactUrl(url, opts) {
|
|
884
|
+
if (!url) return url;
|
|
885
|
+
const extra = (opts.redactQueryParams ?? []).map((s) => s.toLowerCase());
|
|
886
|
+
const redactSet = /* @__PURE__ */ new Set([...ALWAYS_REDACT_QUERY, ...extra]);
|
|
887
|
+
let parsed = null;
|
|
888
|
+
try {
|
|
889
|
+
parsed = new URL(url);
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
if (parsed) {
|
|
893
|
+
const params = parsed.searchParams;
|
|
894
|
+
let mutated = false;
|
|
895
|
+
const keysToRedact = [];
|
|
896
|
+
params.forEach((_v, k) => {
|
|
897
|
+
if (redactSet.has(k.toLowerCase())) keysToRedact.push(k);
|
|
898
|
+
});
|
|
899
|
+
for (const k of keysToRedact) {
|
|
900
|
+
params.set(k, REDACTED);
|
|
901
|
+
mutated = true;
|
|
902
|
+
}
|
|
903
|
+
if (mutated) parsed.search = params.toString();
|
|
904
|
+
return parsed.toString();
|
|
905
|
+
}
|
|
906
|
+
const qIdx = url.indexOf("?");
|
|
907
|
+
if (qIdx < 0) return url;
|
|
908
|
+
const head = url.slice(0, qIdx);
|
|
909
|
+
const queryAndHash = url.slice(qIdx + 1);
|
|
910
|
+
const hashIdx = queryAndHash.indexOf("#");
|
|
911
|
+
const query = hashIdx < 0 ? queryAndHash : queryAndHash.slice(0, hashIdx);
|
|
912
|
+
const hash = hashIdx < 0 ? "" : queryAndHash.slice(hashIdx);
|
|
913
|
+
const parts = query.split("&").map((pair) => {
|
|
914
|
+
const eq = pair.indexOf("=");
|
|
915
|
+
if (eq < 0) return pair;
|
|
916
|
+
const key = pair.slice(0, eq);
|
|
917
|
+
return redactSet.has(key.toLowerCase()) ? `${key}=${REDACTED}` : pair;
|
|
918
|
+
});
|
|
919
|
+
return `${head}?${parts.join("&")}${hash ? "#" + hash : ""}`;
|
|
920
|
+
}
|
|
921
|
+
function sanitizeHeaders(headers, opts) {
|
|
922
|
+
if (!opts.captureHeaders) return void 0;
|
|
923
|
+
if (!headers) return {};
|
|
924
|
+
const extra = (opts.redactHeaders ?? []).map((s) => s.toLowerCase());
|
|
925
|
+
const redactSet = /* @__PURE__ */ new Set([...ALWAYS_REDACT_HEADERS, ...extra]);
|
|
926
|
+
const out = {};
|
|
927
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
928
|
+
if (v == null) continue;
|
|
929
|
+
const lower = k.toLowerCase();
|
|
930
|
+
out[lower] = redactSet.has(lower) ? REDACTED : Array.isArray(v) ? v.join(", ") : String(v);
|
|
931
|
+
}
|
|
932
|
+
return out;
|
|
933
|
+
}
|
|
934
|
+
function captureBody(body, enabled, maxBodyBytes) {
|
|
935
|
+
if (!enabled) return void 0;
|
|
936
|
+
if (body == null) return void 0;
|
|
937
|
+
let str;
|
|
938
|
+
if (typeof body === "string") str = body;
|
|
939
|
+
else if (typeof body === "number" || typeof body === "boolean") str = String(body);
|
|
940
|
+
else if (typeof body === "object") {
|
|
941
|
+
const tag = Object.prototype.toString.call(body);
|
|
942
|
+
if (tag !== "[object Object]" && tag !== "[object Array]") return "<binary>";
|
|
943
|
+
try {
|
|
944
|
+
str = JSON.stringify(body);
|
|
945
|
+
} catch {
|
|
946
|
+
return "<unserializable>";
|
|
947
|
+
}
|
|
948
|
+
} else {
|
|
949
|
+
return "<binary>";
|
|
950
|
+
}
|
|
951
|
+
if (str.length > maxBodyBytes) return str.slice(0, maxBodyBytes) + "\u2026[truncated]";
|
|
952
|
+
return str;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// src/http-instrumentation.ts
|
|
956
|
+
var FETCH_FLAG2 = "__allstak_http_fetch_patched__";
|
|
957
|
+
var XHR_FLAG = "__allstak_http_xhr_patched__";
|
|
958
|
+
var AXIOS_FLAG = /* @__PURE__ */ Symbol.for("allstak.http.axios.instrumented");
|
|
959
|
+
var DEFAULT_MAX_BODY = 4096;
|
|
960
|
+
var _currentModule = null;
|
|
961
|
+
var _currentOpts = null;
|
|
962
|
+
function currentModule() {
|
|
963
|
+
return _currentModule;
|
|
964
|
+
}
|
|
965
|
+
function currentOpts() {
|
|
966
|
+
return _currentOpts;
|
|
967
|
+
}
|
|
968
|
+
function safeCapture(ev) {
|
|
969
|
+
try {
|
|
970
|
+
currentModule()?.capture(ev);
|
|
971
|
+
} catch {
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function bind(opts, ownIngestHost) {
|
|
975
|
+
return {
|
|
976
|
+
captureRequestBody: opts.captureRequestBody ?? false,
|
|
977
|
+
captureResponseBody: opts.captureResponseBody ?? false,
|
|
978
|
+
captureHeaders: opts.captureHeaders ?? false,
|
|
979
|
+
redactHeaders: opts.redactHeaders ?? [],
|
|
980
|
+
redactQueryParams: opts.redactQueryParams ?? [],
|
|
981
|
+
ignoredUrls: opts.ignoredUrls ?? [],
|
|
982
|
+
allowedUrls: opts.allowedUrls ?? [],
|
|
983
|
+
maxBodyBytes: opts.maxBodyBytes ?? DEFAULT_MAX_BODY,
|
|
984
|
+
ownIngestPrefix: ownIngestHost.replace(/\/$/, "")
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function isOwnIngest(url, prefix) {
|
|
988
|
+
return !!prefix && url.startsWith(prefix);
|
|
989
|
+
}
|
|
990
|
+
function urlString(input) {
|
|
991
|
+
if (typeof input === "string") return input;
|
|
992
|
+
if (input && typeof input.href === "string") return input.href;
|
|
993
|
+
if (input && typeof input.url === "string") return input.url;
|
|
994
|
+
return String(input);
|
|
995
|
+
}
|
|
996
|
+
function safeByteLength(s) {
|
|
997
|
+
if (s == null) return void 0;
|
|
998
|
+
if (typeof s === "string") {
|
|
999
|
+
let n = 0;
|
|
1000
|
+
for (let i = 0; i < s.length; i++) {
|
|
1001
|
+
const c = s.charCodeAt(i);
|
|
1002
|
+
if (c < 128) n += 1;
|
|
1003
|
+
else if (c < 2048) n += 2;
|
|
1004
|
+
else if (c >= 55296 && c <= 56319) {
|
|
1005
|
+
n += 4;
|
|
1006
|
+
i += 1;
|
|
1007
|
+
} else n += 3;
|
|
1008
|
+
}
|
|
1009
|
+
return n;
|
|
1010
|
+
}
|
|
1011
|
+
return void 0;
|
|
1012
|
+
}
|
|
1013
|
+
function headersToObject(h) {
|
|
1014
|
+
if (!h) return {};
|
|
1015
|
+
if (typeof h.entries === "function") {
|
|
1016
|
+
const out = {};
|
|
1017
|
+
for (const [k, v] of h.entries()) out[String(k).toLowerCase()] = String(v);
|
|
1018
|
+
return out;
|
|
1019
|
+
}
|
|
1020
|
+
if (typeof h === "object") {
|
|
1021
|
+
const out = {};
|
|
1022
|
+
for (const [k, v] of Object.entries(h)) {
|
|
1023
|
+
if (v == null) continue;
|
|
1024
|
+
out[String(k).toLowerCase()] = Array.isArray(v) ? v.join(", ") : String(v);
|
|
1025
|
+
}
|
|
1026
|
+
return out;
|
|
1027
|
+
}
|
|
1028
|
+
return {};
|
|
1029
|
+
}
|
|
1030
|
+
function patchFetch() {
|
|
1031
|
+
const g = globalThis;
|
|
1032
|
+
if (typeof g.fetch !== "function") return;
|
|
1033
|
+
if (g.fetch[FETCH_FLAG2]) return;
|
|
1034
|
+
const original = g.fetch;
|
|
1035
|
+
const wrapped = async function(input, init) {
|
|
1036
|
+
const opts = currentOpts();
|
|
1037
|
+
if (!opts || !currentModule()) {
|
|
1038
|
+
return original.call(this, input, init);
|
|
1039
|
+
}
|
|
1040
|
+
const rawUrl = urlString(input);
|
|
1041
|
+
const sanitizedUrl = redactUrl(rawUrl, opts);
|
|
1042
|
+
const method = (init?.method || input && typeof input === "object" && input.method || "GET").toUpperCase();
|
|
1043
|
+
if (isOwnIngest(rawUrl, opts.ownIngestPrefix) || !shouldCaptureUrl(rawUrl, opts)) {
|
|
1044
|
+
return original.call(this, input, init);
|
|
1045
|
+
}
|
|
1046
|
+
const start = Date.now();
|
|
1047
|
+
const reqHeaders = sanitizeHeaders(headersToObject(init?.headers ?? (input && input.headers)), opts);
|
|
1048
|
+
const reqBody = captureBody(init?.body, opts.captureRequestBody, opts.maxBodyBytes);
|
|
1049
|
+
const reqSize = safeByteLength(typeof init?.body === "string" ? init.body : void 0);
|
|
1050
|
+
let response;
|
|
1051
|
+
try {
|
|
1052
|
+
response = await original.call(this, input, init);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
safeCapture({
|
|
1055
|
+
type: "http_request",
|
|
1056
|
+
method,
|
|
1057
|
+
url: sanitizedUrl,
|
|
1058
|
+
statusCode: 0,
|
|
1059
|
+
durationMs: Date.now() - start,
|
|
1060
|
+
requestBody: reqBody,
|
|
1061
|
+
requestHeaders: reqHeaders,
|
|
1062
|
+
requestSize: reqSize,
|
|
1063
|
+
error: String(err?.message ?? err)
|
|
1064
|
+
});
|
|
1065
|
+
throw err;
|
|
1066
|
+
}
|
|
1067
|
+
const durationMs = Date.now() - start;
|
|
1068
|
+
let respBody;
|
|
1069
|
+
let respSize;
|
|
1070
|
+
let respHeaders;
|
|
1071
|
+
try {
|
|
1072
|
+
respHeaders = sanitizeHeaders(headersToObject(response.headers), opts);
|
|
1073
|
+
const lenHeader = typeof response.headers?.get === "function" ? response.headers.get("content-length") : null;
|
|
1074
|
+
if (lenHeader) respSize = parseInt(lenHeader, 10) || void 0;
|
|
1075
|
+
if (opts.captureResponseBody && typeof response.clone === "function") {
|
|
1076
|
+
try {
|
|
1077
|
+
const cloned = response.clone();
|
|
1078
|
+
const text = await cloned.text();
|
|
1079
|
+
respBody = captureBody(text, true, opts.maxBodyBytes);
|
|
1080
|
+
if (respSize == null) respSize = safeByteLength(text);
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
safeCapture({
|
|
1087
|
+
type: "http_request",
|
|
1088
|
+
method,
|
|
1089
|
+
url: sanitizedUrl,
|
|
1090
|
+
statusCode: response.status,
|
|
1091
|
+
durationMs,
|
|
1092
|
+
requestBody: reqBody,
|
|
1093
|
+
requestHeaders: reqHeaders,
|
|
1094
|
+
requestSize: reqSize,
|
|
1095
|
+
responseBody: respBody,
|
|
1096
|
+
responseHeaders: respHeaders,
|
|
1097
|
+
responseSize: respSize
|
|
1098
|
+
});
|
|
1099
|
+
return response;
|
|
1100
|
+
};
|
|
1101
|
+
wrapped[FETCH_FLAG2] = true;
|
|
1102
|
+
g.fetch = wrapped;
|
|
1103
|
+
}
|
|
1104
|
+
function patchXhr() {
|
|
1105
|
+
const g = globalThis;
|
|
1106
|
+
const X = g.XMLHttpRequest;
|
|
1107
|
+
if (!X || X.prototype[XHR_FLAG]) return;
|
|
1108
|
+
const origOpen = X.prototype.open;
|
|
1109
|
+
const origSend = X.prototype.send;
|
|
1110
|
+
const origSetRequestHeader = X.prototype.setRequestHeader;
|
|
1111
|
+
X.prototype.open = function(method, url, ...rest) {
|
|
1112
|
+
this.__allstak_method__ = method;
|
|
1113
|
+
this.__allstak_url__ = url;
|
|
1114
|
+
this.__allstak_headers__ = {};
|
|
1115
|
+
return origOpen.call(this, method, url, ...rest);
|
|
1116
|
+
};
|
|
1117
|
+
X.prototype.setRequestHeader = function(name, value) {
|
|
1118
|
+
try {
|
|
1119
|
+
this.__allstak_headers__[String(name).toLowerCase()] = String(value);
|
|
1120
|
+
} catch {
|
|
1121
|
+
}
|
|
1122
|
+
return origSetRequestHeader.call(this, name, value);
|
|
1123
|
+
};
|
|
1124
|
+
X.prototype.send = function(body) {
|
|
1125
|
+
const opts = currentOpts();
|
|
1126
|
+
if (!opts || !currentModule()) return origSend.call(this, body);
|
|
1127
|
+
const start = Date.now();
|
|
1128
|
+
const method = String(this.__allstak_method__ || "GET").toUpperCase();
|
|
1129
|
+
const rawUrl = String(this.__allstak_url__ || "");
|
|
1130
|
+
const sanitizedUrl = redactUrl(rawUrl, opts);
|
|
1131
|
+
if (isOwnIngest(rawUrl, opts.ownIngestPrefix) || !shouldCaptureUrl(rawUrl, opts)) {
|
|
1132
|
+
return origSend.call(this, body);
|
|
1133
|
+
}
|
|
1134
|
+
const reqHeaders = sanitizeHeaders(this.__allstak_headers__, opts);
|
|
1135
|
+
const reqBody = captureBody(body, opts.captureRequestBody, opts.maxBodyBytes);
|
|
1136
|
+
const reqSize = safeByteLength(typeof body === "string" ? body : void 0);
|
|
1137
|
+
const finish = (statusCode, error) => {
|
|
1138
|
+
const durationMs = Date.now() - start;
|
|
1139
|
+
let respHeaders;
|
|
1140
|
+
let respBody;
|
|
1141
|
+
let respSize;
|
|
1142
|
+
try {
|
|
1143
|
+
const liveOpts = currentOpts() ?? opts;
|
|
1144
|
+
if (liveOpts.captureHeaders && typeof this.getAllResponseHeaders === "function") {
|
|
1145
|
+
const raw = this.getAllResponseHeaders() || "";
|
|
1146
|
+
const dict = {};
|
|
1147
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
1148
|
+
const idx = line.indexOf(":");
|
|
1149
|
+
if (idx > 0) dict[line.slice(0, idx).trim().toLowerCase()] = line.slice(idx + 1).trim();
|
|
1150
|
+
}
|
|
1151
|
+
respHeaders = sanitizeHeaders(dict, liveOpts);
|
|
1152
|
+
}
|
|
1153
|
+
if (liveOpts.captureResponseBody) {
|
|
1154
|
+
const text = this.responseText;
|
|
1155
|
+
if (typeof text === "string") {
|
|
1156
|
+
respBody = captureBody(text, true, liveOpts.maxBodyBytes);
|
|
1157
|
+
respSize = safeByteLength(text);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
safeCapture({
|
|
1163
|
+
type: "http_request",
|
|
1164
|
+
method,
|
|
1165
|
+
url: sanitizedUrl,
|
|
1166
|
+
statusCode,
|
|
1167
|
+
durationMs,
|
|
1168
|
+
requestBody: reqBody,
|
|
1169
|
+
requestHeaders: reqHeaders,
|
|
1170
|
+
requestSize: reqSize,
|
|
1171
|
+
responseBody: respBody,
|
|
1172
|
+
responseHeaders: respHeaders,
|
|
1173
|
+
responseSize: respSize,
|
|
1174
|
+
error
|
|
1175
|
+
});
|
|
1176
|
+
};
|
|
1177
|
+
this.addEventListener?.("load", () => finish(this.status || 0));
|
|
1178
|
+
this.addEventListener?.("error", () => finish(0, "network"));
|
|
1179
|
+
this.addEventListener?.("abort", () => finish(0, "abort"));
|
|
1180
|
+
this.addEventListener?.("timeout", () => finish(0, "timeout"));
|
|
1181
|
+
return origSend.call(this, body);
|
|
1182
|
+
};
|
|
1183
|
+
X.prototype[XHR_FLAG] = true;
|
|
1184
|
+
}
|
|
1185
|
+
function instrumentAxiosInstance(axiosInstance, module2, opts) {
|
|
1186
|
+
if (!axiosInstance || typeof axiosInstance.interceptors !== "object") return axiosInstance;
|
|
1187
|
+
if (axiosInstance[AXIOS_FLAG]) return axiosInstance;
|
|
1188
|
+
axiosInstance[AXIOS_FLAG] = true;
|
|
1189
|
+
const reqStarts = /* @__PURE__ */ new WeakMap();
|
|
1190
|
+
axiosInstance.interceptors.request.use((config) => {
|
|
1191
|
+
try {
|
|
1192
|
+
const rawUrl = (config.baseURL ? config.baseURL.replace(/\/$/, "") : "") + (config.url || "");
|
|
1193
|
+
reqStarts.set(config, {
|
|
1194
|
+
start: Date.now(),
|
|
1195
|
+
method: String(config.method || "GET").toUpperCase(),
|
|
1196
|
+
rawUrl
|
|
1197
|
+
});
|
|
1198
|
+
} catch {
|
|
1199
|
+
}
|
|
1200
|
+
return config;
|
|
1201
|
+
});
|
|
1202
|
+
const finalize = (cfg, statusCode, response, error) => {
|
|
1203
|
+
const meta = reqStarts.get(cfg);
|
|
1204
|
+
if (!meta) return;
|
|
1205
|
+
reqStarts.delete(cfg);
|
|
1206
|
+
if (isOwnIngest(meta.rawUrl, opts.ownIngestPrefix)) return;
|
|
1207
|
+
if (!shouldCaptureUrl(meta.rawUrl, opts)) return;
|
|
1208
|
+
const sanitizedUrl = redactUrl(meta.rawUrl, opts);
|
|
1209
|
+
const reqHeaders = sanitizeHeaders(headersToObject(cfg.headers), opts);
|
|
1210
|
+
const reqBody = captureBody(cfg.data, opts.captureRequestBody, opts.maxBodyBytes);
|
|
1211
|
+
const respHeaders = sanitizeHeaders(headersToObject(response?.headers), opts);
|
|
1212
|
+
const respBody = captureBody(response?.data, opts.captureResponseBody, opts.maxBodyBytes);
|
|
1213
|
+
try {
|
|
1214
|
+
module2.capture({
|
|
1215
|
+
type: "http_request",
|
|
1216
|
+
method: meta.method,
|
|
1217
|
+
url: sanitizedUrl,
|
|
1218
|
+
statusCode,
|
|
1219
|
+
durationMs: Date.now() - meta.start,
|
|
1220
|
+
requestBody: reqBody,
|
|
1221
|
+
requestHeaders: reqHeaders,
|
|
1222
|
+
responseBody: respBody,
|
|
1223
|
+
responseHeaders: respHeaders,
|
|
1224
|
+
error
|
|
1225
|
+
});
|
|
1226
|
+
} catch {
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
axiosInstance.interceptors.response.use(
|
|
1230
|
+
(response) => {
|
|
1231
|
+
finalize(response.config, response.status);
|
|
1232
|
+
return response;
|
|
1233
|
+
},
|
|
1234
|
+
(err) => {
|
|
1235
|
+
const cfg = err?.config;
|
|
1236
|
+
const status = err?.response?.status ?? 0;
|
|
1237
|
+
finalize(cfg, status, err?.response, String(err?.message ?? err));
|
|
1238
|
+
throw err;
|
|
1239
|
+
}
|
|
1240
|
+
);
|
|
1241
|
+
return axiosInstance;
|
|
1242
|
+
}
|
|
1243
|
+
function tryAutoInstrumentAxios(module2, opts) {
|
|
1244
|
+
try {
|
|
1245
|
+
const axios = globalThis.require?.("axios") ?? null;
|
|
1246
|
+
if (axios) instrumentAxiosInstance(axios.default ?? axios, module2, opts);
|
|
1247
|
+
} catch {
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function installHttpInstrumentation(module2, options, ownIngestHost) {
|
|
1251
|
+
const bound = bind(options, ownIngestHost);
|
|
1252
|
+
_currentModule = module2;
|
|
1253
|
+
_currentOpts = bound;
|
|
1254
|
+
try {
|
|
1255
|
+
patchFetch();
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1258
|
+
try {
|
|
1259
|
+
patchXhr();
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
try {
|
|
1263
|
+
tryAutoInstrumentAxios(module2, bound);
|
|
1264
|
+
} catch {
|
|
1265
|
+
}
|
|
1266
|
+
return {
|
|
1267
|
+
instrumentAxios: (axios) => instrumentAxiosInstance(axios, module2, bound)
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
|
|
746
1271
|
// src/client.ts
|
|
747
1272
|
var INGEST_HOST = "https://api.allstak.sa";
|
|
748
1273
|
var SDK_NAME = "allstak-react";
|
|
749
|
-
var SDK_VERSION = "0.
|
|
1274
|
+
var SDK_VERSION = "0.3.0";
|
|
750
1275
|
var ERRORS_PATH = "/ingest/v1/errors";
|
|
751
1276
|
var LOGS_PATH = "/ingest/v1/logs";
|
|
752
1277
|
var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
|
|
@@ -776,6 +1301,8 @@ var AllStakClient = class {
|
|
|
776
1301
|
this.breadcrumbs = [];
|
|
777
1302
|
this.scopeStack = [];
|
|
778
1303
|
this.replay = null;
|
|
1304
|
+
this.httpRequests = null;
|
|
1305
|
+
this._instrumentAxios = null;
|
|
779
1306
|
this.onErrorHandler = null;
|
|
780
1307
|
this.onRejectionHandler = null;
|
|
781
1308
|
if (!config.apiKey) throw new Error("AllStak: config.apiKey is required");
|
|
@@ -821,6 +1348,35 @@ var AllStakClient = class {
|
|
|
821
1348
|
} catch {
|
|
822
1349
|
}
|
|
823
1350
|
}
|
|
1351
|
+
if (config.enableHttpTracking) {
|
|
1352
|
+
try {
|
|
1353
|
+
this.httpRequests = new HttpRequestModule(this.transport);
|
|
1354
|
+
this.httpRequests.setDefaults({
|
|
1355
|
+
environment: this.config.environment,
|
|
1356
|
+
release: this.config.release,
|
|
1357
|
+
dist: this.config.dist,
|
|
1358
|
+
platform: this.config.platform,
|
|
1359
|
+
sdkName: this.config.sdkName,
|
|
1360
|
+
sdkVersion: this.config.sdkVersion
|
|
1361
|
+
});
|
|
1362
|
+
const { instrumentAxios } = installHttpInstrumentation(
|
|
1363
|
+
this.httpRequests,
|
|
1364
|
+
config.httpTracking ?? {},
|
|
1365
|
+
baseUrl
|
|
1366
|
+
);
|
|
1367
|
+
this._instrumentAxios = instrumentAxios;
|
|
1368
|
+
} catch {
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
/** Manually instrument an axios instance. No-op when HTTP tracking is off. */
|
|
1373
|
+
instrumentAxios(axios) {
|
|
1374
|
+
if (!this._instrumentAxios) return axios;
|
|
1375
|
+
return this._instrumentAxios(axios);
|
|
1376
|
+
}
|
|
1377
|
+
/** Snapshot of recent failed HTTP requests for error-linking. */
|
|
1378
|
+
getRecentFailedHttp() {
|
|
1379
|
+
return this.httpRequests?.getRecentFailed() ?? [];
|
|
824
1380
|
}
|
|
825
1381
|
captureException(error, context) {
|
|
826
1382
|
if (!this.passesSampleRate()) return;
|
|
@@ -839,6 +1395,16 @@ var AllStakClient = class {
|
|
|
839
1395
|
if (traceId) traceContext.traceId = traceId;
|
|
840
1396
|
const spanId = this.tracing.getCurrentSpanId();
|
|
841
1397
|
if (spanId) traceContext.spanId = spanId;
|
|
1398
|
+
const recentFailed = this.httpRequests?.getRecentFailed() ?? [];
|
|
1399
|
+
if (recentFailed.length > 0) {
|
|
1400
|
+
traceContext["http.recentFailed"] = recentFailed.map((r) => ({
|
|
1401
|
+
method: r.method,
|
|
1402
|
+
url: r.url,
|
|
1403
|
+
statusCode: r.statusCode,
|
|
1404
|
+
durationMs: r.durationMs,
|
|
1405
|
+
error: r.error
|
|
1406
|
+
}));
|
|
1407
|
+
}
|
|
842
1408
|
const payload = {
|
|
843
1409
|
exceptionClass,
|
|
844
1410
|
message: error.message,
|
|
@@ -995,6 +1561,11 @@ var AllStakClient = class {
|
|
|
995
1561
|
this.replay.destroy();
|
|
996
1562
|
this.replay = null;
|
|
997
1563
|
}
|
|
1564
|
+
if (this.httpRequests) {
|
|
1565
|
+
this.httpRequests.destroy();
|
|
1566
|
+
this.httpRequests = null;
|
|
1567
|
+
}
|
|
1568
|
+
this._instrumentAxios = null;
|
|
998
1569
|
this.breadcrumbs = [];
|
|
999
1570
|
}
|
|
1000
1571
|
installBrowserHandlers() {
|
|
@@ -1203,6 +1774,10 @@ var AllStak = {
|
|
|
1203
1774
|
resetTrace() {
|
|
1204
1775
|
ensureInit().resetTrace();
|
|
1205
1776
|
},
|
|
1777
|
+
/** Manually instrument an axios instance. No-op when HTTP tracking is off. */
|
|
1778
|
+
instrumentAxios(axios) {
|
|
1779
|
+
return ensureInit().instrumentAxios(axios);
|
|
1780
|
+
},
|
|
1206
1781
|
getSessionId() {
|
|
1207
1782
|
return ensureInit().getSessionId();
|
|
1208
1783
|
},
|