@obsrviq/tracker 0.6.0 → 0.7.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
@@ -792,6 +792,83 @@ function tokenBucket(rate2, burst) {
792
792
  };
793
793
  }
794
794
 
795
+ // src/trace.ts
796
+ function randomHex(bytes) {
797
+ const arr = new Uint8Array(bytes);
798
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
799
+ crypto.getRandomValues(arr);
800
+ } else {
801
+ for (let i = 0; i < bytes; i++) arr[i] = Math.random() * 256 | 0;
802
+ }
803
+ let s = "";
804
+ for (let i = 0; i < bytes; i++) s += arr[i].toString(16).padStart(2, "0");
805
+ return s;
806
+ }
807
+ function newTrace() {
808
+ const traceId = randomHex(16);
809
+ const spanId = randomHex(8);
810
+ return { traceId, spanId, header: `00-${traceId}-${spanId}-01` };
811
+ }
812
+ function traceTarget(rawUrl, cfg) {
813
+ if (!cfg.tracePropagation) return false;
814
+ let u;
815
+ try {
816
+ u = new URL(rawUrl, typeof location !== "undefined" ? location.href : void 0);
817
+ } catch {
818
+ return false;
819
+ }
820
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
821
+ if (typeof location !== "undefined" && u.origin === location.origin) return true;
822
+ return hostAllowed(u.hostname, cfg.traceHosts);
823
+ }
824
+ function hostAllowed(hostname, hosts) {
825
+ const h = hostname.toLowerCase();
826
+ for (const raw of hosts) {
827
+ let entry = String(raw).trim().toLowerCase();
828
+ if (!entry) continue;
829
+ if (entry.includes("://")) {
830
+ try {
831
+ entry = new URL(entry).hostname;
832
+ } catch {
833
+ }
834
+ }
835
+ entry = entry.replace(/^\*?\./, "");
836
+ if (h === entry || h.endsWith("." + entry)) return true;
837
+ }
838
+ return false;
839
+ }
840
+ function traceIdOf(header) {
841
+ if (!header) return null;
842
+ const parts = header.split("-");
843
+ return parts.length >= 2 && /^[0-9a-f]{32}$/.test(parts[1]) ? parts[1] : null;
844
+ }
845
+ function existingFetchTraceparent(input, init) {
846
+ const hasInitHeaders = !!init && "headers" in init && init.headers != null;
847
+ const src = hasInitHeaders ? init.headers : input instanceof Request ? input.headers : void 0;
848
+ if (!src) return null;
849
+ try {
850
+ return new Headers(src).get("traceparent");
851
+ } catch {
852
+ return null;
853
+ }
854
+ }
855
+ function applyFetchTrace(input, init) {
856
+ const existing = existingFetchTraceparent(input, init);
857
+ if (existing) {
858
+ return { input, init, traceId: traceIdOf(existing) ?? "" };
859
+ }
860
+ const tr = newTrace();
861
+ const hasInitHeaders = !!init && "headers" in init && init.headers != null;
862
+ if (input instanceof Request && !hasInitHeaders) {
863
+ const h2 = new Headers(input.headers);
864
+ h2.set("traceparent", tr.header);
865
+ return { input: new Request(input, { headers: h2 }), init, traceId: tr.traceId };
866
+ }
867
+ const h = new Headers(init?.headers ?? void 0);
868
+ h.set("traceparent", tr.header);
869
+ return { input, init: { ...init || {}, headers: h }, traceId: tr.traceId };
870
+ }
871
+
795
872
  // src/network.ts
796
873
  var BODY_CAP = 4 * 1024;
797
874
  var NET_RATE = 50;
@@ -807,6 +884,7 @@ var HEADER_ALLOW = /* @__PURE__ */ new Set([
807
884
  "content-encoding",
808
885
  "x-request-id",
809
886
  "x-correlation-id",
887
+ "traceparent",
810
888
  "server",
811
889
  "vary"
812
890
  ]);
@@ -1041,6 +1119,16 @@ function wrapFetch(ctx, tr, emitNet) {
1041
1119
  const perfStart = perfNow();
1042
1120
  const method = (init?.method || (input instanceof Request ? input.method : "GET")).toUpperCase();
1043
1121
  const url = redactUrl(rawUrl);
1122
+ let traceId;
1123
+ if (traceTarget(rawUrl, ctx.config)) {
1124
+ try {
1125
+ const inj = applyFetchTrace(input, init);
1126
+ input = inj.input;
1127
+ init = inj.init;
1128
+ traceId = inj.traceId || void 0;
1129
+ } catch {
1130
+ }
1131
+ }
1044
1132
  const reqHeaders = pickHeaders(
1045
1133
  ctx.config.redactHeaders,
1046
1134
  headerEntries(init?.headers ?? (input instanceof Request ? input.headers : void 0))
@@ -1063,6 +1151,7 @@ function wrapFetch(ctx, tr, emitNet) {
1063
1151
  ok: false,
1064
1152
  resourceType: "fetch",
1065
1153
  requestHeaders: reqHeaders,
1154
+ traceId,
1066
1155
  timing,
1067
1156
  ...partial
1068
1157
  };
@@ -1144,6 +1233,19 @@ function wrapXhr(ctx, tr, emitNet) {
1144
1233
  meta.startT = ctx.clock.now();
1145
1234
  meta.perfStart = perfNow();
1146
1235
  if (ctx.config.captureNetworkBodies) meta.body = stringifyBody(body);
1236
+ if (traceTarget(meta.rawUrl, ctx.config)) {
1237
+ try {
1238
+ const appSet = meta.reqHeaders["traceparent"];
1239
+ if (appSet) {
1240
+ meta.traceId = traceIdOf(appSet) ?? void 0;
1241
+ } else {
1242
+ const tr2 = newTrace();
1243
+ this.setRequestHeader("traceparent", tr2.header);
1244
+ meta.traceId = tr2.traceId;
1245
+ }
1246
+ } catch {
1247
+ }
1248
+ }
1147
1249
  const finish = (error) => {
1148
1250
  const endT = ctx.clock.now();
1149
1251
  const base = {
@@ -1178,6 +1280,7 @@ function wrapXhr(ctx, tr, emitNet) {
1178
1280
  ok: status >= 200 && status < 400,
1179
1281
  resourceType: "xhr",
1180
1282
  requestHeaders: meta.reqHeaders,
1283
+ traceId: meta.traceId,
1181
1284
  responseHeaders: respHeaders,
1182
1285
  responseSize,
1183
1286
  requestBody: ctx.config.captureNetworkBodies ? bodyPreview(meta.body, void 0) : void 0,
@@ -2179,6 +2282,8 @@ var DEFAULTS = {
2179
2282
  noPrivacy: false,
2180
2283
  captureNetworkBodies: false,
2181
2284
  redactHeaders: ["authorization", "cookie", "set-cookie"],
2285
+ tracePropagation: false,
2286
+ traceHosts: [],
2182
2287
  sampleRate: 1,
2183
2288
  recordCanvas: false,
2184
2289
  endpoint: "https://in.lumera.app",