@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/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 isOwnIngest = !!(ownBaseUrl && url.startsWith(ownBaseUrl));
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 (!isOwnIngest) {
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 (!isOwnIngest) {
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.2.1";
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
  },