@allstak/react-native 0.3.0 → 0.3.2

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.mjs CHANGED
@@ -3,28 +3,57 @@ import {
3
3
  } from "./chunk-BJTO5JO5.mjs";
4
4
 
5
5
  // src/transport.ts
6
- var REQUEST_TIMEOUT = 3e3;
6
+ var REQUEST_TIMEOUT = 2e3;
7
7
  var MAX_BUFFER = 100;
8
+ var FAILURE_THRESHOLD = 3;
9
+ var BACKOFF_BASE_MS = 500;
10
+ var BACKOFF_MAX_MS = 3e4;
8
11
  var HttpTransport = class {
9
- constructor(baseUrl, apiKey) {
12
+ constructor(baseUrl, apiKey, enabled = true) {
10
13
  this.baseUrl = baseUrl;
11
14
  this.apiKey = apiKey;
15
+ this.enabled = enabled;
12
16
  this.buffer = [];
13
17
  this.flushing = false;
18
+ this.consecutiveFailures = 0;
19
+ this.circuitOpenUntil = 0;
20
+ this.sent = 0;
21
+ this.failed = 0;
22
+ this.dropped = 0;
23
+ }
24
+ send(path, payload) {
25
+ if (!this.enabled) {
26
+ this.noteDropped();
27
+ return Promise.resolve();
28
+ }
29
+ this.enqueueOrDispatch({ path, payload });
30
+ return Promise.resolve();
31
+ }
32
+ enqueueOrDispatch(item) {
33
+ if (Date.now() < this.circuitOpenUntil) {
34
+ this.push(item);
35
+ return;
36
+ }
37
+ void this.dispatch(item).catch(() => void 0);
14
38
  }
15
- async send(path, payload) {
39
+ async dispatch(item) {
16
40
  try {
17
- await this.doFetch(path, payload);
18
- await this.flushBuffer();
19
- } catch {
20
- if (this.buffer.length >= MAX_BUFFER) this.buffer.shift();
21
- this.buffer.push({ path, payload });
41
+ await this.doFetch(item.path, item.payload);
42
+ this.sent++;
43
+ this.consecutiveFailures = 0;
44
+ this.circuitOpenUntil = 0;
45
+ this.scheduleFlush();
46
+ } catch (err) {
47
+ this.failed++;
48
+ this.recordFailure(err);
49
+ this.push(item);
22
50
  }
23
51
  }
24
52
  async doFetch(path, payload) {
25
53
  const url = `${this.baseUrl}${path}`;
26
54
  const controller = new AbortController();
27
55
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
56
+ const started = Date.now();
28
57
  try {
29
58
  const res = await fetch(url, {
30
59
  method: "POST",
@@ -38,26 +67,77 @@ var HttpTransport = class {
38
67
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
39
68
  } finally {
40
69
  clearTimeout(timeoutId);
70
+ this.lastTransportLatencyMs = Date.now() - started;
41
71
  }
42
72
  }
73
+ push(item) {
74
+ if (this.buffer.length >= MAX_BUFFER) {
75
+ this.buffer.shift();
76
+ this.dropped++;
77
+ }
78
+ this.buffer.push(item);
79
+ }
80
+ scheduleFlush() {
81
+ if (this.flushing || this.buffer.length === 0) return;
82
+ const delay = Math.max(0, this.circuitOpenUntil - Date.now());
83
+ const timer = setTimeout(() => {
84
+ void this.flushBuffer().catch(() => void 0);
85
+ }, delay);
86
+ if (typeof timer === "object" && typeof timer.unref === "function") timer.unref();
87
+ }
43
88
  async flushBuffer() {
44
89
  if (this.flushing || this.buffer.length === 0) return;
45
90
  this.flushing = true;
91
+ const started = Date.now();
46
92
  try {
47
93
  const items = this.buffer.splice(0, this.buffer.length);
48
94
  for (const item of items) {
95
+ if (Date.now() < this.circuitOpenUntil) {
96
+ this.push(item);
97
+ continue;
98
+ }
49
99
  try {
50
100
  await this.doFetch(item.path, item.payload);
51
- } catch {
101
+ this.sent++;
102
+ this.consecutiveFailures = 0;
103
+ this.circuitOpenUntil = 0;
104
+ } catch (err) {
105
+ this.failed++;
106
+ this.recordFailure(err);
107
+ this.push(item);
52
108
  }
53
109
  }
54
110
  } finally {
111
+ this.lastFlushDurationMs = Date.now() - started;
55
112
  this.flushing = false;
113
+ if (this.buffer.length > 0) this.scheduleFlush();
56
114
  }
57
115
  }
116
+ recordFailure(error) {
117
+ this.consecutiveFailures++;
118
+ if (this.consecutiveFailures < FAILURE_THRESHOLD) return;
119
+ const retryAfterMs = retryAfterFromError(error);
120
+ const backoff = retryAfterMs ?? jitteredBackoff(this.consecutiveFailures);
121
+ this.circuitOpenUntil = Date.now() + backoff;
122
+ }
58
123
  getBufferSize() {
59
124
  return this.buffer.length;
60
125
  }
126
+ noteDropped(count = 1) {
127
+ this.dropped += Math.max(0, count);
128
+ }
129
+ getStats() {
130
+ return {
131
+ queued: this.buffer.length,
132
+ sent: this.sent,
133
+ failed: this.failed,
134
+ dropped: this.dropped,
135
+ consecutiveFailures: this.consecutiveFailures,
136
+ circuitOpenUntil: this.circuitOpenUntil,
137
+ lastTransportLatencyMs: this.lastTransportLatencyMs,
138
+ lastFlushDurationMs: this.lastFlushDurationMs
139
+ };
140
+ }
61
141
  /**
62
142
  * Wait for the in-flight retry-buffer to drain. Resolves `true` if the
63
143
  * buffer empties within `timeoutMs` (default 2000ms), `false` otherwise.
@@ -74,6 +154,14 @@ var HttpTransport = class {
74
154
  return true;
75
155
  }
76
156
  };
157
+ function jitteredBackoff(failures) {
158
+ const exp = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** Math.min(8, failures - FAILURE_THRESHOLD));
159
+ return Math.floor(exp / 2 + Math.random() * (exp / 2));
160
+ }
161
+ function retryAfterFromError(error) {
162
+ const message = error instanceof Error ? error.message : "";
163
+ return /HTTP\s+(429|503)/.test(message) ? BACKOFF_MAX_MS : null;
164
+ }
77
165
 
78
166
  // src/stack.ts
79
167
  var V8_FRAME_RE = /^\s*at\s+(?:(.+?)\s+\()?((?:.+?):(\d+):(\d+))\)?\s*$/;
@@ -128,6 +216,24 @@ function isInApp(filename) {
128
216
  return true;
129
217
  }
130
218
 
219
+ // src/utils/debug-id.ts
220
+ var REGISTRY_KEY = "_allstakDebugIds";
221
+ var cache = /* @__PURE__ */ new Map();
222
+ function resolveDebugId(filename) {
223
+ if (!filename) return void 0;
224
+ if (cache.has(filename)) return cache.get(filename) ?? void 0;
225
+ const registry = globalThis[REGISTRY_KEY];
226
+ if (registry && typeof registry === "object") {
227
+ const hit = registry[filename];
228
+ if (typeof hit === "string" && hit.length > 0) {
229
+ cache.set(filename, hit);
230
+ return hit;
231
+ }
232
+ }
233
+ cache.set(filename, null);
234
+ return void 0;
235
+ }
236
+
131
237
  // src/scope.ts
132
238
  var Scope = class {
133
239
  constructor() {
@@ -339,6 +445,7 @@ var TracingModule = class {
339
445
  }
340
446
  if (!this.flushTimer) {
341
447
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
448
+ this.flushTimer?.unref?.();
342
449
  }
343
450
  }
344
451
  flush() {
@@ -384,10 +491,11 @@ var ReplaySurrogate = class {
384
491
  if (Math.random() >= this.opts.sampleRate) return false;
385
492
  this.active = true;
386
493
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS2);
494
+ this.flushTimer?.unref?.();
387
495
  return true;
388
496
  }
389
497
  /** Record a screen view. Filters params through the safeParams allow-list. */
390
- recordScreenView(routeName, params) {
498
+ recordScreenView(routeName, params, context) {
391
499
  if (!this.active) return;
392
500
  const safe = {};
393
501
  if (params && this.opts.safeParams.length > 0) {
@@ -395,17 +503,22 @@ var ReplaySurrogate = class {
395
503
  if (key in params) safe[key] = params[key];
396
504
  }
397
505
  }
398
- this.push({ ts: Date.now(), k: "screen", data: { route: routeName, params: safe } });
506
+ this.push({ ts: Date.now(), k: "screen", data: { route: routeName, params: safe, ...compact(context) } });
399
507
  }
400
508
  /** Record an AppState transition (foreground/background/inactive). */
401
- recordAppState(next) {
509
+ recordAppState(next, context) {
402
510
  if (!this.active) return;
403
- this.push({ ts: Date.now(), k: "appstate", data: { state: next } });
511
+ this.push({ ts: Date.now(), k: "appstate", data: { state: next, ...compact(context) } });
404
512
  }
405
513
  /** Record a free-form, customer-validated checkpoint. */
406
- recordManual(label, data) {
514
+ recordManual(label, data, context) {
515
+ if (!this.active) return;
516
+ this.push({ ts: Date.now(), k: "manual", data: { label, ...data ?? {}, ...compact(context) } });
517
+ }
518
+ /** Record a forensic mobile session timeline marker. This is not replay. */
519
+ recordTimelineMarker(kind, label, data, context) {
407
520
  if (!this.active) return;
408
- this.push({ ts: Date.now(), k: "manual", data: { label, ...data ?? {} } });
521
+ this.push({ ts: Date.now(), k: kind, data: { label, ...data ?? {}, ...compact(context) } });
409
522
  }
410
523
  destroy() {
411
524
  this.destroyed = true;
@@ -439,6 +552,14 @@ var ReplaySurrogate = class {
439
552
  });
440
553
  }
441
554
  };
555
+ function compact(context) {
556
+ if (!context) return {};
557
+ const out = {};
558
+ for (const [key, value] of Object.entries(context)) {
559
+ if (value !== void 0 && value !== null && value !== "") out[key] = value;
560
+ }
561
+ return out;
562
+ }
442
563
 
443
564
  // src/http-requests.ts
444
565
  var INGEST_PATH = "/ingest/v1/http-requests";
@@ -450,6 +571,12 @@ function genTraceId() {
450
571
  const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
451
572
  return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
452
573
  }
574
+ function genRequestId() {
575
+ return genTraceId();
576
+ }
577
+ function generateHttpId() {
578
+ return genTraceId();
579
+ }
453
580
  function splitHostPath(url) {
454
581
  try {
455
582
  const u = new URL(url);
@@ -476,6 +603,7 @@ var HttpRequestModule = class {
476
603
  const item = {
477
604
  type: "http_request",
478
605
  traceId: ev.traceId ?? genTraceId(),
606
+ requestId: ev.requestId ?? genRequestId(),
479
607
  direction: "outbound",
480
608
  method: (ev.method || "GET").toUpperCase(),
481
609
  host,
@@ -490,6 +618,16 @@ var HttpRequestModule = class {
490
618
  requestHeaders: ev.requestHeaders,
491
619
  responseHeaders: ev.responseHeaders,
492
620
  error: ev.error,
621
+ spanId: ev.spanId,
622
+ parentSpanId: ev.parentSpanId,
623
+ requestBodyStatus: ev.requestBodyStatus,
624
+ responseBodyStatus: ev.responseBodyStatus,
625
+ requestBodyRedactedFields: ev.requestBodyRedactedFields,
626
+ responseBodyRedactedFields: ev.responseBodyRedactedFields,
627
+ requestBodyTruncated: ev.requestBodyTruncated,
628
+ responseBodyTruncated: ev.responseBodyTruncated,
629
+ capturePolicy: ev.capturePolicy,
630
+ linkConfidence: ev.linkConfidence ?? "exact",
493
631
  environment: this.defaults.environment,
494
632
  release: this.defaults.release,
495
633
  dist: this.defaults.dist,
@@ -508,7 +646,10 @@ var HttpRequestModule = class {
508
646
  this.flush();
509
647
  return;
510
648
  }
511
- if (!this.flushTimer) this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS3);
649
+ if (!this.flushTimer) {
650
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS3);
651
+ this.flushTimer?.unref?.();
652
+ }
512
653
  }
513
654
  /** Snapshot of the last failed requests for error-linking. Newest last. */
514
655
  getRecentFailed() {
@@ -560,6 +701,24 @@ var ALWAYS_REDACT_QUERY = /* @__PURE__ */ new Set([
560
701
  "jwt"
561
702
  ]);
562
703
  var REDACTED = "[REDACTED]";
704
+ var DEFAULT_REDACT_BODY_FIELDS = [
705
+ "password",
706
+ "passcode",
707
+ "otp",
708
+ "token",
709
+ "authorization",
710
+ "cookie",
711
+ "session",
712
+ "refresh_token",
713
+ "access_token",
714
+ "jwt",
715
+ "card",
716
+ "credit_card",
717
+ "iban",
718
+ "national_id",
719
+ "secret",
720
+ "api_key"
721
+ ];
563
722
  function shouldCaptureUrl(url, opts) {
564
723
  if (!url) return false;
565
724
  const lower = url.toLowerCase();
@@ -627,25 +786,125 @@ function sanitizeHeaders(headers, opts) {
627
786
  }
628
787
  return out;
629
788
  }
630
- function captureBody(body, enabled, maxBodyBytes) {
631
- if (!enabled) return void 0;
632
- if (body == null) return void 0;
789
+ function captureBodyResult(body, enabled, maxBodyBytes, opts = {}, contentType) {
790
+ if (!enabled) {
791
+ return {
792
+ status: "disabled",
793
+ redactedFields: [],
794
+ truncated: false,
795
+ capturePolicy: "body_capture_disabled"
796
+ };
797
+ }
798
+ if (body == null) {
799
+ return {
800
+ status: "empty",
801
+ redactedFields: [],
802
+ truncated: false,
803
+ capturePolicy: "empty_body"
804
+ };
805
+ }
806
+ const allowed = opts.allowedContentTypes ?? ["application/json", "text/", "application/problem+json"];
807
+ if (contentType && !allowed.some((needle) => contentType.toLowerCase().includes(needle.toLowerCase()))) {
808
+ return {
809
+ status: "unsupported",
810
+ redactedFields: [],
811
+ truncated: false,
812
+ capturePolicy: `unsupported_content_type:${contentType}`
813
+ };
814
+ }
633
815
  let str;
816
+ let redactedFields = [];
634
817
  if (typeof body === "string") str = body;
635
818
  else if (typeof body === "number" || typeof body === "boolean") str = String(body);
636
819
  else if (typeof body === "object") {
637
820
  const tag = Object.prototype.toString.call(body);
638
- if (tag !== "[object Object]" && tag !== "[object Array]") return "<binary>";
821
+ if (tag !== "[object Object]" && tag !== "[object Array]") {
822
+ return {
823
+ body: "<binary>",
824
+ status: "unsupported",
825
+ redactedFields: [],
826
+ truncated: false,
827
+ capturePolicy: "unsupported_binary_body"
828
+ };
829
+ }
830
+ const redacted = redactJsonValue(body, opts);
831
+ redactedFields = redacted.redactedFields;
639
832
  try {
640
- str = JSON.stringify(body);
833
+ str = JSON.stringify(redacted.value);
641
834
  } catch {
642
- return "<unserializable>";
835
+ return {
836
+ body: "<unserializable>",
837
+ status: "unsupported",
838
+ redactedFields,
839
+ truncated: false,
840
+ capturePolicy: "unserializable_body"
841
+ };
643
842
  }
644
843
  } else {
645
- return "<binary>";
844
+ return {
845
+ body: "<binary>",
846
+ status: "unsupported",
847
+ redactedFields: [],
848
+ truncated: false,
849
+ capturePolicy: "unsupported_body_type"
850
+ };
851
+ }
852
+ if (typeof body === "string" && looksJson(contentType, str)) {
853
+ try {
854
+ const redacted = redactJsonValue(JSON.parse(str), opts);
855
+ redactedFields = redacted.redactedFields;
856
+ str = JSON.stringify(redacted.value);
857
+ } catch {
858
+ str = redactSensitiveText(str, opts);
859
+ }
860
+ }
861
+ let truncated = false;
862
+ if (str.length > maxBodyBytes) {
863
+ str = str.slice(0, maxBodyBytes) + "\u2026[truncated]";
864
+ truncated = true;
646
865
  }
647
- if (str.length > maxBodyBytes) return str.slice(0, maxBodyBytes) + "\u2026[truncated]";
648
- return str;
866
+ return {
867
+ body: str,
868
+ status: truncated ? "truncated" : redactedFields.length > 0 ? "redacted" : "captured",
869
+ redactedFields: Array.from(new Set(redactedFields)).sort(),
870
+ truncated,
871
+ capturePolicy: "opt_in_body_capture"
872
+ };
873
+ }
874
+ function looksJson(contentType, body) {
875
+ if (contentType && contentType.toLowerCase().includes("json")) return true;
876
+ const trimmed = body.trim();
877
+ return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
878
+ }
879
+ function redactJsonValue(value, opts, path = "") {
880
+ const fieldSet = new Set([...DEFAULT_REDACT_BODY_FIELDS, ...opts.redactBodyFields ?? []].map((v) => v.toLowerCase()));
881
+ const redactedFields = [];
882
+ const walk = (input, currentPath) => {
883
+ if (Array.isArray(input)) return input.map((item, index) => walk(item, `${currentPath}[${index}]`));
884
+ if (!input || typeof input !== "object") return input;
885
+ const out = {};
886
+ for (const [key, raw] of Object.entries(input)) {
887
+ const keyLower = key.toLowerCase();
888
+ const nextPath = currentPath ? `${currentPath}.${key}` : key;
889
+ if (fieldSet.has(keyLower) || keyLower.includes("token") || keyLower.includes("password")) {
890
+ out[key] = REDACTED;
891
+ redactedFields.push(nextPath);
892
+ } else {
893
+ out[key] = walk(raw, nextPath);
894
+ }
895
+ }
896
+ return out;
897
+ };
898
+ return { value: walk(value, path), redactedFields };
899
+ }
900
+ function redactSensitiveText(input, opts) {
901
+ const fields = [...DEFAULT_REDACT_BODY_FIELDS, ...opts.redactBodyFields ?? []];
902
+ let out = input;
903
+ for (const key of fields) {
904
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
905
+ out = out.replace(new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`, "gi"), `$1"${REDACTED}"`);
906
+ }
907
+ return out;
649
908
  }
650
909
 
651
910
  // src/http-instrumentation.ts
@@ -655,12 +914,16 @@ var AXIOS_FLAG = /* @__PURE__ */ Symbol.for("allstak.http.axios.instrumented");
655
914
  var DEFAULT_MAX_BODY = 4096;
656
915
  var _currentModule = null;
657
916
  var _currentOpts = null;
917
+ var _currentRuntime = null;
658
918
  function currentModule() {
659
919
  return _currentModule;
660
920
  }
661
921
  function currentOpts() {
662
922
  return _currentOpts;
663
923
  }
924
+ function currentRuntime() {
925
+ return _currentRuntime;
926
+ }
664
927
  function safeCapture(ev) {
665
928
  try {
666
929
  currentModule()?.capture(ev);
@@ -677,6 +940,8 @@ function bind(opts, ownIngestHost) {
677
940
  ignoredUrls: opts.ignoredUrls ?? [],
678
941
  allowedUrls: opts.allowedUrls ?? [],
679
942
  maxBodyBytes: opts.maxBodyBytes ?? DEFAULT_MAX_BODY,
943
+ allowedContentTypes: opts.allowedContentTypes ?? ["application/json", "text/", "application/problem+json"],
944
+ redactBodyFields: opts.redactBodyFields ?? [],
680
945
  ownIngestPrefix: ownIngestHost.replace(/\/$/, "")
681
946
  };
682
947
  }
@@ -723,6 +988,77 @@ function headersToObject(h) {
723
988
  }
724
989
  return {};
725
990
  }
991
+ function normalizeTraceId(traceId) {
992
+ const hex = traceId.replace(/[^a-fA-F0-9]/g, "").toLowerCase();
993
+ return (hex + "00000000000000000000000000000000").slice(0, 32);
994
+ }
995
+ function normalizeSpanId(spanId) {
996
+ const hex = spanId.replace(/[^a-fA-F0-9]/g, "").toLowerCase();
997
+ return (hex + "0000000000000000").slice(0, 16);
998
+ }
999
+ function createRequestContext(method, url) {
1000
+ const runtime = currentRuntime();
1001
+ if (!runtime) return null;
1002
+ const requestId = generateHttpId();
1003
+ const parentSpanId = runtime.tracing.getCurrentSpanId() ?? "";
1004
+ const span = runtime.tracing.startSpan("mobile.http", {
1005
+ description: `${method.toUpperCase()} ${url}`,
1006
+ tags: { requestId, method: method.toUpperCase(), platform: runtime.platform ?? "react-native" }
1007
+ });
1008
+ const traceId = runtime.tracing.getTraceId();
1009
+ const spanId = span.spanId;
1010
+ return {
1011
+ traceId,
1012
+ requestId,
1013
+ spanId,
1014
+ parentSpanId,
1015
+ traceparent: `00-${normalizeTraceId(traceId)}-${normalizeSpanId(spanId)}-01`,
1016
+ span
1017
+ };
1018
+ }
1019
+ function propagationHeaders(ctx) {
1020
+ const headers = {
1021
+ traceparent: ctx.traceparent,
1022
+ "x-allstak-trace-id": ctx.traceId,
1023
+ "x-allstak-request-id": ctx.requestId
1024
+ };
1025
+ if (ctx.parentSpanId) headers["x-allstak-parent-span-id"] = ctx.parentSpanId;
1026
+ return headers;
1027
+ }
1028
+ function mergeHeaders(headers, propagation) {
1029
+ const entries = Object.entries(propagation);
1030
+ if (typeof Headers !== "undefined" && headers instanceof Headers) {
1031
+ const next2 = new Headers(headers);
1032
+ for (const [k, v] of entries) if (!next2.has(k)) next2.set(k, v);
1033
+ return next2;
1034
+ }
1035
+ if (Array.isArray(headers)) {
1036
+ const existing = new Set(headers.map(([k]) => String(k).toLowerCase()));
1037
+ const next2 = [...headers];
1038
+ for (const [k, v] of entries) if (!existing.has(k.toLowerCase())) next2.push([k, v]);
1039
+ return next2;
1040
+ }
1041
+ const next = { ...headers ?? {} };
1042
+ const lower = new Set(Object.keys(next).map((k) => k.toLowerCase()));
1043
+ for (const [k, v] of entries) if (!lower.has(k.toLowerCase())) next[k] = v;
1044
+ return next;
1045
+ }
1046
+ function injectFetchHeaders(input, init, ctx) {
1047
+ const headers = mergeHeaders(init?.headers ?? (input && typeof input === "object" ? input.headers : void 0), propagationHeaders(ctx));
1048
+ return { input, init: { ...init ?? {}, headers } };
1049
+ }
1050
+ function recordTimeline(kind, label, ctx, data) {
1051
+ try {
1052
+ currentRuntime()?.replay?.recordTimelineMarker?.(kind, label, data, {
1053
+ traceId: ctx.traceId,
1054
+ requestId: ctx.requestId,
1055
+ spanId: ctx.spanId,
1056
+ release: currentRuntime()?.release,
1057
+ dist: currentRuntime()?.dist
1058
+ });
1059
+ } catch {
1060
+ }
1061
+ }
726
1062
  function patchFetch() {
727
1063
  const g = globalThis;
728
1064
  if (typeof g.fetch !== "function") return;
@@ -740,23 +1076,37 @@ function patchFetch() {
740
1076
  return original.call(this, input, init);
741
1077
  }
742
1078
  const start = Date.now();
743
- const reqHeaders = sanitizeHeaders(headersToObject(init?.headers ?? (input && input.headers)), opts);
744
- const reqBody = captureBody(init?.body, opts.captureRequestBody, opts.maxBodyBytes);
1079
+ const ctx = createRequestContext(method, sanitizedUrl);
1080
+ const requestInit = ctx ? injectFetchHeaders(input, init, ctx).init : init;
1081
+ if (ctx) recordTimeline("request", "request_started", ctx, { method, url: sanitizedUrl });
1082
+ const reqHeaders = sanitizeHeaders(headersToObject(requestInit?.headers ?? (input && input.headers)), opts);
1083
+ const reqBody = captureBodyResult(requestInit?.body, opts.captureRequestBody, opts.maxBodyBytes, opts, reqHeaders?.["content-type"]);
745
1084
  const reqSize = safeByteLength(typeof init?.body === "string" ? init.body : void 0);
746
1085
  let response;
747
1086
  try {
748
- response = await original.call(this, input, init);
1087
+ response = await original.call(this, input, requestInit);
749
1088
  } catch (err) {
1089
+ ctx?.span.finish("error");
1090
+ if (ctx) recordTimeline("exception", "request_failed", ctx, { method, url: sanitizedUrl, error: String(err?.message ?? err) });
750
1091
  safeCapture({
751
1092
  type: "http_request",
1093
+ traceId: ctx?.traceId ?? generateHttpId(),
1094
+ requestId: ctx?.requestId ?? generateHttpId(),
1095
+ spanId: ctx?.spanId,
1096
+ parentSpanId: ctx?.parentSpanId,
752
1097
  method,
753
1098
  url: sanitizedUrl,
754
1099
  statusCode: 0,
755
1100
  durationMs: Date.now() - start,
756
- requestBody: reqBody,
1101
+ requestBody: reqBody.body,
757
1102
  requestHeaders: reqHeaders,
758
1103
  requestSize: reqSize,
759
- error: String(err?.message ?? err)
1104
+ requestBodyStatus: reqBody.status,
1105
+ requestBodyRedactedFields: reqBody.redactedFields,
1106
+ requestBodyTruncated: reqBody.truncated,
1107
+ capturePolicy: reqBody.capturePolicy,
1108
+ error: String(err?.message ?? err),
1109
+ linkConfidence: "exact"
760
1110
  });
761
1111
  throw err;
762
1112
  }
@@ -772,25 +1122,41 @@ function patchFetch() {
772
1122
  try {
773
1123
  const cloned = response.clone();
774
1124
  const text = await cloned.text();
775
- respBody = captureBody(text, true, opts.maxBodyBytes);
1125
+ respBody = captureBodyResult(text, true, opts.maxBodyBytes, opts, respHeaders?.["content-type"]).body;
776
1126
  if (respSize == null) respSize = safeByteLength(text);
777
1127
  } catch {
778
1128
  }
779
1129
  }
780
1130
  } catch {
781
1131
  }
1132
+ const respBodyResult = captureBodyResult(respBody, opts.captureResponseBody && respBody !== void 0, opts.maxBodyBytes, opts, respHeaders?.["content-type"]);
1133
+ const isError = response.status >= 400;
1134
+ ctx?.span.setTag("requestId", ctx.requestId).setTag("http.status_code", String(response.status)).finish(isError ? "error" : "ok");
1135
+ if (ctx) recordTimeline(isError ? "exception" : "response", isError ? "request_failed" : "response_received", ctx, { statusCode: response.status, durationMs });
782
1136
  safeCapture({
783
1137
  type: "http_request",
1138
+ traceId: ctx?.traceId ?? generateHttpId(),
1139
+ requestId: ctx?.requestId ?? generateHttpId(),
1140
+ spanId: ctx?.spanId,
1141
+ parentSpanId: ctx?.parentSpanId,
784
1142
  method,
785
1143
  url: sanitizedUrl,
786
1144
  statusCode: response.status,
787
1145
  durationMs,
788
- requestBody: reqBody,
1146
+ requestBody: reqBody.body,
789
1147
  requestHeaders: reqHeaders,
790
1148
  requestSize: reqSize,
791
- responseBody: respBody,
1149
+ responseBody: respBodyResult.body,
792
1150
  responseHeaders: respHeaders,
793
- responseSize: respSize
1151
+ responseSize: respSize,
1152
+ requestBodyStatus: reqBody.status,
1153
+ responseBodyStatus: respBodyResult.status,
1154
+ requestBodyRedactedFields: reqBody.redactedFields,
1155
+ responseBodyRedactedFields: respBodyResult.redactedFields,
1156
+ requestBodyTruncated: reqBody.truncated,
1157
+ responseBodyTruncated: respBodyResult.truncated,
1158
+ capturePolicy: `${reqBody.capturePolicy};${respBodyResult.capturePolicy}`,
1159
+ linkConfidence: "exact"
794
1160
  });
795
1161
  return response;
796
1162
  };
@@ -828,7 +1194,17 @@ function patchXhr() {
828
1194
  return origSend.call(this, body);
829
1195
  }
830
1196
  const reqHeaders = sanitizeHeaders(this.__allstak_headers__, opts);
831
- const reqBody = captureBody(body, opts.captureRequestBody, opts.maxBodyBytes);
1197
+ const ctx = createRequestContext(method, sanitizedUrl);
1198
+ if (ctx) {
1199
+ for (const [k, v] of Object.entries(propagationHeaders(ctx))) {
1200
+ try {
1201
+ origSetRequestHeader.call(this, k, v);
1202
+ } catch {
1203
+ }
1204
+ }
1205
+ recordTimeline("request", "request_started", ctx, { method, url: sanitizedUrl });
1206
+ }
1207
+ const reqBody = captureBodyResult(body, opts.captureRequestBody, opts.maxBodyBytes, opts, reqHeaders?.["content-type"]);
832
1208
  const reqSize = safeByteLength(typeof body === "string" ? body : void 0);
833
1209
  const finish = (statusCode, error) => {
834
1210
  const durationMs = Date.now() - start;
@@ -846,28 +1222,43 @@ function patchXhr() {
846
1222
  }
847
1223
  respHeaders = sanitizeHeaders(dict, liveOpts);
848
1224
  }
1225
+ let respBodyResult = captureBodyResult(void 0, false, liveOpts.maxBodyBytes, liveOpts);
849
1226
  if (liveOpts.captureResponseBody) {
850
1227
  const text = this.responseText;
851
1228
  if (typeof text === "string") {
852
- respBody = captureBody(text, true, liveOpts.maxBodyBytes);
1229
+ respBodyResult = captureBodyResult(text, true, liveOpts.maxBodyBytes, liveOpts, respHeaders?.["content-type"]);
1230
+ respBody = respBodyResult.body;
853
1231
  respSize = safeByteLength(text);
854
1232
  }
855
1233
  }
856
1234
  } catch {
857
1235
  }
1236
+ const failed = !!error || statusCode >= 400;
1237
+ ctx?.span.setTag("requestId", ctx.requestId).setTag("http.status_code", String(statusCode)).finish(failed ? "error" : "ok");
1238
+ if (ctx) recordTimeline(failed ? "exception" : "response", failed ? "request_failed" : "response_received", ctx, { statusCode, durationMs, error });
858
1239
  safeCapture({
859
1240
  type: "http_request",
1241
+ traceId: ctx?.traceId ?? generateHttpId(),
1242
+ requestId: ctx?.requestId ?? generateHttpId(),
1243
+ spanId: ctx?.spanId,
1244
+ parentSpanId: ctx?.parentSpanId,
860
1245
  method,
861
1246
  url: sanitizedUrl,
862
1247
  statusCode,
863
1248
  durationMs,
864
- requestBody: reqBody,
1249
+ requestBody: reqBody.body,
865
1250
  requestHeaders: reqHeaders,
866
1251
  requestSize: reqSize,
867
1252
  responseBody: respBody,
868
1253
  responseHeaders: respHeaders,
869
1254
  responseSize: respSize,
870
- error
1255
+ requestBodyStatus: reqBody.status,
1256
+ responseBodyStatus: respBody ? "captured" : opts.captureResponseBody ? "empty" : "disabled",
1257
+ requestBodyRedactedFields: reqBody.redactedFields,
1258
+ requestBodyTruncated: reqBody.truncated,
1259
+ capturePolicy: reqBody.capturePolicy,
1260
+ error,
1261
+ linkConfidence: "exact"
871
1262
  });
872
1263
  };
873
1264
  this.addEventListener?.("load", () => finish(this.status || 0));
@@ -886,10 +1277,17 @@ function instrumentAxiosInstance(axiosInstance, module, opts) {
886
1277
  axiosInstance.interceptors.request.use((config) => {
887
1278
  try {
888
1279
  const rawUrl = (config.baseURL ? config.baseURL.replace(/\/$/, "") : "") + (config.url || "");
1280
+ const method = String(config.method || "GET").toUpperCase();
1281
+ const ctx = createRequestContext(method, rawUrl);
1282
+ if (ctx) {
1283
+ config.headers = mergeHeaders(config.headers, propagationHeaders(ctx));
1284
+ recordTimeline("request", "request_started", ctx, { method, url: rawUrl });
1285
+ }
889
1286
  reqStarts.set(config, {
890
1287
  start: Date.now(),
891
- method: String(config.method || "GET").toUpperCase(),
892
- rawUrl
1288
+ method,
1289
+ rawUrl,
1290
+ ctx
893
1291
  });
894
1292
  } catch {
895
1293
  }
@@ -903,21 +1301,36 @@ function instrumentAxiosInstance(axiosInstance, module, opts) {
903
1301
  if (!shouldCaptureUrl(meta.rawUrl, opts)) return;
904
1302
  const sanitizedUrl = redactUrl(meta.rawUrl, opts);
905
1303
  const reqHeaders = sanitizeHeaders(headersToObject(cfg.headers), opts);
906
- const reqBody = captureBody(cfg.data, opts.captureRequestBody, opts.maxBodyBytes);
1304
+ const reqBody = captureBodyResult(cfg.data, opts.captureRequestBody, opts.maxBodyBytes, opts, reqHeaders?.["content-type"]);
907
1305
  const respHeaders = sanitizeHeaders(headersToObject(response?.headers), opts);
908
- const respBody = captureBody(response?.data, opts.captureResponseBody, opts.maxBodyBytes);
1306
+ const respBody = captureBodyResult(response?.data, opts.captureResponseBody, opts.maxBodyBytes, opts, respHeaders?.["content-type"]);
1307
+ const failed = !!error || statusCode >= 400;
1308
+ meta.ctx?.span.setTag("requestId", meta.ctx.requestId).setTag("http.status_code", String(statusCode)).finish(failed ? "error" : "ok");
1309
+ if (meta.ctx) recordTimeline(failed ? "exception" : "response", failed ? "request_failed" : "response_received", meta.ctx, { statusCode, error });
909
1310
  try {
910
1311
  module.capture({
911
1312
  type: "http_request",
1313
+ traceId: meta.ctx?.traceId ?? generateHttpId(),
1314
+ requestId: meta.ctx?.requestId ?? generateHttpId(),
1315
+ spanId: meta.ctx?.spanId,
1316
+ parentSpanId: meta.ctx?.parentSpanId,
912
1317
  method: meta.method,
913
1318
  url: sanitizedUrl,
914
1319
  statusCode,
915
1320
  durationMs: Date.now() - meta.start,
916
- requestBody: reqBody,
1321
+ requestBody: reqBody.body,
917
1322
  requestHeaders: reqHeaders,
918
- responseBody: respBody,
1323
+ responseBody: respBody.body,
919
1324
  responseHeaders: respHeaders,
920
- error
1325
+ requestBodyStatus: reqBody.status,
1326
+ responseBodyStatus: respBody.status,
1327
+ requestBodyRedactedFields: reqBody.redactedFields,
1328
+ responseBodyRedactedFields: respBody.redactedFields,
1329
+ requestBodyTruncated: reqBody.truncated,
1330
+ responseBodyTruncated: respBody.truncated,
1331
+ capturePolicy: `${reqBody.capturePolicy};${respBody.capturePolicy}`,
1332
+ error,
1333
+ linkConfidence: "exact"
921
1334
  });
922
1335
  } catch {
923
1336
  }
@@ -943,10 +1356,11 @@ function tryAutoInstrumentAxios(module, opts) {
943
1356
  } catch {
944
1357
  }
945
1358
  }
946
- function installHttpInstrumentation(module, options, ownIngestHost) {
1359
+ function installHttpInstrumentation(module, options, ownIngestHost, runtime) {
947
1360
  const bound = bind(options, ownIngestHost);
948
1361
  _currentModule = module;
949
1362
  _currentOpts = bound;
1363
+ _currentRuntime = runtime;
950
1364
  try {
951
1365
  patchFetch();
952
1366
  } catch {
@@ -963,11 +1377,16 @@ function installHttpInstrumentation(module, options, ownIngestHost) {
963
1377
  instrumentAxios: (axios) => instrumentAxiosInstance(axios, module, bound)
964
1378
  };
965
1379
  }
1380
+ function unbindHttpInstrumentation() {
1381
+ _currentModule = null;
1382
+ _currentOpts = null;
1383
+ _currentRuntime = null;
1384
+ }
966
1385
 
967
1386
  // src/client.ts
968
1387
  var INGEST_HOST = "https://api.allstak.sa";
969
1388
  var SDK_NAME = "allstak-react-native";
970
- var SDK_VERSION = "0.3.0";
1389
+ var SDK_VERSION = "0.3.1";
971
1390
  var ERRORS_PATH = "/ingest/v1/errors";
972
1391
  var LOGS_PATH = "/ingest/v1/logs";
973
1392
  var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
@@ -983,6 +1402,17 @@ function generateId() {
983
1402
  const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
984
1403
  return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
985
1404
  }
1405
+ function stringContextValue(context, key) {
1406
+ const value = context[key];
1407
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1408
+ }
1409
+ function firstRecentRequestId(recentFailed) {
1410
+ for (let i = recentFailed.length - 1; i >= 0; i--) {
1411
+ const requestId = recentFailed[i]?.requestId;
1412
+ if (requestId && requestId.trim().length > 0) return requestId;
1413
+ }
1414
+ return void 0;
1415
+ }
986
1416
  var AllStakClient = class {
987
1417
  constructor(config) {
988
1418
  this.breadcrumbs = [];
@@ -990,9 +1420,6 @@ var AllStakClient = class {
990
1420
  this.replay = null;
991
1421
  this.httpRequests = null;
992
1422
  this._instrumentAxios = null;
993
- if (!config.apiKey) {
994
- throw new Error("AllStak: config.apiKey is required");
995
- }
996
1423
  this.config = { ...config };
997
1424
  if (!this.config.environment) this.config.environment = "production";
998
1425
  if (!this.config.sdkName) this.config.sdkName = SDK_NAME;
@@ -1001,7 +1428,7 @@ var AllStakClient = class {
1001
1428
  this.sessionId = generateId();
1002
1429
  this.maxBreadcrumbs = config.maxBreadcrumbs ?? DEFAULT_MAX_BREADCRUMBS;
1003
1430
  const baseUrl = (config.host ?? INGEST_HOST).replace(/\/$/, "");
1004
- this.transport = new HttpTransport(baseUrl, config.apiKey);
1431
+ this.transport = new HttpTransport(baseUrl, config.apiKey ?? "", Boolean(config.apiKey));
1005
1432
  this.tracing = new TracingModule(this.transport, {
1006
1433
  service: config.service ?? config.release ?? "",
1007
1434
  environment: this.config.environment ?? "production",
@@ -1028,7 +1455,15 @@ var AllStakClient = class {
1028
1455
  const { instrumentAxios } = installHttpInstrumentation(
1029
1456
  this.httpRequests,
1030
1457
  config.httpTracking ?? {},
1031
- baseUrl
1458
+ baseUrl,
1459
+ {
1460
+ tracing: this.tracing,
1461
+ replay: this.replay,
1462
+ release: this.config.release,
1463
+ dist: this.config.dist,
1464
+ platform: this.config.platform,
1465
+ environment: this.config.environment
1466
+ }
1032
1467
  );
1033
1468
  this._instrumentAxios = instrumentAxios;
1034
1469
  } catch {
@@ -1052,33 +1487,67 @@ var AllStakClient = class {
1052
1487
  if (!this.passesSampleRate()) return;
1053
1488
  const frames = parseStack(error.stack).map((f) => ({
1054
1489
  ...f,
1055
- platform: this.config.platform
1490
+ platform: this.config.platform,
1491
+ debugId: resolveDebugId(f.filename)
1056
1492
  }));
1493
+ const debugIdSet = /* @__PURE__ */ new Set();
1494
+ for (const f of frames) if (f.debugId) debugIdSet.add(f.debugId);
1495
+ const debugMeta = debugIdSet.size > 0 ? { images: Array.from(debugIdSet).map((id2) => ({ type: "sourcemap", debugId: id2 })) } : void 0;
1057
1496
  const stackTrace = frames.length > 0 ? frames.map(frameToString) : void 0;
1058
1497
  const currentBreadcrumbs = this.breadcrumbs.length > 0 ? [...this.breadcrumbs] : void 0;
1059
1498
  this.breadcrumbs = [];
1060
1499
  const exceptionClass = (error.name && error.name !== "Error" ? error.name : void 0) || error.constructor?.name || "Error";
1061
1500
  const eff = this.effective();
1062
1501
  const traceContext = {};
1063
- const traceId = this.tracing.getTraceId();
1502
+ const recentFailed = this.httpRequests?.getRecentFailed() ?? [];
1503
+ const linkedRequest = recentFailed.length > 0 ? recentFailed[recentFailed.length - 1] : void 0;
1504
+ if (linkedRequest?.traceId) this.tracing.setTraceId(linkedRequest.traceId);
1505
+ const exceptionSpan = linkedRequest ? this.tracing.startSpan("mobile.exception", {
1506
+ description: error.message,
1507
+ tags: {
1508
+ requestId: linkedRequest.requestId,
1509
+ exceptionClass
1510
+ }
1511
+ }) : null;
1512
+ exceptionSpan?.finish("error");
1513
+ const traceId = linkedRequest?.traceId ?? this.tracing.getTraceId();
1064
1514
  if (traceId) traceContext.traceId = traceId;
1065
- const spanId = this.tracing.getCurrentSpanId();
1515
+ const spanId = exceptionSpan?.spanId || this.tracing.getCurrentSpanId();
1066
1516
  if (spanId) traceContext.spanId = spanId;
1067
- const recentFailed = this.httpRequests?.getRecentFailed() ?? [];
1068
1517
  if (recentFailed.length > 0) {
1069
1518
  traceContext["http.recentFailed"] = recentFailed.map((r) => ({
1070
1519
  method: r.method,
1071
1520
  url: r.url,
1072
1521
  statusCode: r.statusCode,
1073
1522
  durationMs: r.durationMs,
1074
- error: r.error
1523
+ error: r.error,
1524
+ requestId: r.requestId,
1525
+ traceId: r.traceId,
1526
+ confidence: r.requestId === linkedRequest?.requestId ? "inferred" : "weak"
1075
1527
  }));
1528
+ traceContext["http.linkConfidence"] = "inferred";
1529
+ }
1530
+ try {
1531
+ if (!linkedRequest) throw new Error("no linked request");
1532
+ this.replay?.recordTimelineMarker?.("exception", "exception_captured", {
1533
+ exceptionClass,
1534
+ message: error.message,
1535
+ requestLinkConfidence: linkedRequest ? "inferred" : "none"
1536
+ }, {
1537
+ traceId,
1538
+ requestId: linkedRequest?.requestId,
1539
+ spanId: spanId ?? void 0,
1540
+ release: this.config.release,
1541
+ dist: this.config.dist
1542
+ });
1543
+ } catch {
1076
1544
  }
1077
1545
  const payload = {
1078
1546
  exceptionClass,
1079
1547
  message: error.message,
1080
1548
  stackTrace,
1081
1549
  frames: frames.length > 0 ? frames : void 0,
1550
+ debugMeta,
1082
1551
  platform: this.config.platform,
1083
1552
  sdkName: this.config.sdkName,
1084
1553
  sdkVersion: this.config.sdkVersion,
@@ -1087,12 +1556,29 @@ var AllStakClient = class {
1087
1556
  environment: this.config.environment,
1088
1557
  release: this.config.release,
1089
1558
  sessionId: this.sessionId,
1559
+ traceId: stringContextValue(traceContext, "traceId"),
1560
+ spanId: stringContextValue(traceContext, "spanId"),
1561
+ requestId: linkedRequest?.requestId ?? firstRecentRequestId(recentFailed),
1562
+ service: this.config.service,
1090
1563
  user: eff.user,
1091
1564
  metadata: { ...this.buildMetadata(context), ...traceContext },
1092
1565
  breadcrumbs: currentBreadcrumbs,
1093
1566
  fingerprint: eff.fingerprint
1094
1567
  };
1095
- this.sendThroughBeforeSend(payload);
1568
+ if (this.shouldCaptureScreenshot()) {
1569
+ void this.withScreenshotMetadata(error, payload).then((enriched) => this.sendThroughBeforeSend(enriched)).catch(() => this.sendThroughBeforeSend({
1570
+ ...payload,
1571
+ metadata: { ...payload.metadata ?? {}, "screenshot.status": "failed" }
1572
+ }));
1573
+ return;
1574
+ }
1575
+ this.sendThroughBeforeSend({
1576
+ ...payload,
1577
+ metadata: {
1578
+ ...payload.metadata ?? {},
1579
+ "screenshot.status": this.config.screenshot?.enabled ? "unsupported" : "disabled"
1580
+ }
1581
+ });
1096
1582
  }
1097
1583
  /** Start a new span. Auto-parented to any currently-active span. */
1098
1584
  startSpan(operation, options) {
@@ -1133,6 +1619,9 @@ var AllStakClient = class {
1133
1619
  environment: this.config.environment,
1134
1620
  release: this.config.release,
1135
1621
  sessionId: this.sessionId,
1622
+ traceId: this.tracing.getTraceId(),
1623
+ spanId: this.tracing.getCurrentSpanId() ?? void 0,
1624
+ service: this.config.service,
1136
1625
  user: eff.user,
1137
1626
  metadata: this.buildMetadata(),
1138
1627
  fingerprint: eff.fingerprint
@@ -1216,6 +1705,9 @@ var AllStakClient = class {
1216
1705
  getConfig() {
1217
1706
  return this.config;
1218
1707
  }
1708
+ getTransportStats() {
1709
+ return this.transport.getStats();
1710
+ }
1219
1711
  destroy() {
1220
1712
  this.tracing.destroy();
1221
1713
  if (this.replay) {
@@ -1226,6 +1718,7 @@ var AllStakClient = class {
1226
1718
  this.httpRequests.destroy();
1227
1719
  this.httpRequests = null;
1228
1720
  }
1721
+ unbindHttpInstrumentation();
1229
1722
  this._instrumentAxios = null;
1230
1723
  this.breadcrumbs = [];
1231
1724
  }
@@ -1244,6 +1737,58 @@ var AllStakClient = class {
1244
1737
  metadata: this.buildMetadata()
1245
1738
  });
1246
1739
  }
1740
+ shouldCaptureScreenshot() {
1741
+ const screenshot = this.config.screenshot;
1742
+ if (!screenshot?.enabled || screenshot.captureOnError === false || !screenshot.provider) return false;
1743
+ const sampleRate = screenshot.sampleRate ?? 1;
1744
+ return !(sampleRate <= 0 || sampleRate < 1 && Math.random() >= sampleRate);
1745
+ }
1746
+ async withScreenshotMetadata(error, payload) {
1747
+ const screenshot = this.config.screenshot;
1748
+ if (!screenshot?.provider) {
1749
+ return { ...payload, metadata: { ...payload.metadata ?? {}, "screenshot.status": "unsupported" } };
1750
+ }
1751
+ const timeoutMs = Math.max(100, Math.min(screenshot.timeoutMs ?? 1500, 5e3));
1752
+ const maxBytes = Math.max(1024, screenshot.maxBytes ?? 2e5);
1753
+ try {
1754
+ const artifact = await Promise.race([
1755
+ Promise.resolve(screenshot.provider({
1756
+ type: "error",
1757
+ error,
1758
+ traceId: payload.traceId,
1759
+ requestId: payload.requestId
1760
+ })),
1761
+ new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs))
1762
+ ]);
1763
+ if (!artifact) {
1764
+ return { ...payload, metadata: { ...payload.metadata ?? {}, "screenshot.status": "timeout_or_empty" } };
1765
+ }
1766
+ const size = artifact.sizeBytes ?? byteSize(artifact.data);
1767
+ if (size > maxBytes) {
1768
+ this.transport.noteDropped();
1769
+ return {
1770
+ ...payload,
1771
+ metadata: { ...payload.metadata ?? {}, "screenshot.status": "dropped_too_large", "screenshot.sizeBytes": size }
1772
+ };
1773
+ }
1774
+ return {
1775
+ ...payload,
1776
+ metadata: {
1777
+ ...payload.metadata ?? {},
1778
+ "screenshot.status": "captured",
1779
+ "screenshot.contentType": artifact.contentType,
1780
+ "screenshot.width": artifact.width,
1781
+ "screenshot.height": artifact.height,
1782
+ "screenshot.sizeBytes": size,
1783
+ "screenshot.redacted": artifact.redacted ?? false,
1784
+ "screenshot.redactionStrategy": artifact.redactionStrategy,
1785
+ ...artifact.data ? { "screenshot.data": artifact.data } : {}
1786
+ }
1787
+ };
1788
+ } catch {
1789
+ return { ...payload, metadata: { ...payload.metadata ?? {}, "screenshot.status": "failed" } };
1790
+ }
1791
+ }
1247
1792
  passesSampleRate() {
1248
1793
  const r = this.config.sampleRate;
1249
1794
  if (typeof r !== "number" || r >= 1) return true;
@@ -1342,10 +1887,22 @@ var AllStakClient = class {
1342
1887
  }
1343
1888
  };
1344
1889
  var instance = null;
1345
- function ensureInit() {
1346
- if (!instance) throw new Error("AllStak.init() must be called before using the SDK");
1890
+ function maybeInit() {
1347
1891
  return instance;
1348
1892
  }
1893
+ function noopSpan(operation = "") {
1894
+ return new Span("", "", "", operation, "", "", "", {}, () => void 0);
1895
+ }
1896
+ function emptyStats() {
1897
+ return {
1898
+ queued: 0,
1899
+ sent: 0,
1900
+ failed: 0,
1901
+ dropped: 0,
1902
+ consecutiveFailures: 0,
1903
+ circuitOpenUntil: 0
1904
+ };
1905
+ }
1349
1906
  function __safeAddBreadcrumbForInstrumentation(type, message, level, data) {
1350
1907
  try {
1351
1908
  instance?.addBreadcrumb(type, message, level, data);
@@ -1354,51 +1911,99 @@ function __safeAddBreadcrumbForInstrumentation(type, message, level, data) {
1354
1911
  }
1355
1912
  var AllStak = {
1356
1913
  init(config) {
1357
- if (instance) instance.destroy();
1358
- instance = new AllStakClient(config);
1359
- return instance;
1914
+ try {
1915
+ if (instance) instance.destroy();
1916
+ instance = new AllStakClient(config);
1917
+ return instance;
1918
+ } catch {
1919
+ instance = new AllStakClient({ ...config, apiKey: "" });
1920
+ return instance;
1921
+ }
1360
1922
  },
1361
1923
  captureException(error, context) {
1362
- ensureInit().captureException(error, context);
1924
+ try {
1925
+ maybeInit()?.captureException(error, context);
1926
+ } catch {
1927
+ }
1363
1928
  },
1364
1929
  captureMessage(message, level = "info", options) {
1365
- ensureInit().captureMessage(message, level, options);
1930
+ try {
1931
+ maybeInit()?.captureMessage(message, level, options);
1932
+ } catch {
1933
+ }
1366
1934
  },
1367
1935
  addBreadcrumb(type, message, level, data) {
1368
- ensureInit().addBreadcrumb(type, message, level, data);
1936
+ try {
1937
+ maybeInit()?.addBreadcrumb(type, message, level, data);
1938
+ } catch {
1939
+ }
1369
1940
  },
1370
1941
  clearBreadcrumbs() {
1371
- ensureInit().clearBreadcrumbs();
1942
+ try {
1943
+ maybeInit()?.clearBreadcrumbs();
1944
+ } catch {
1945
+ }
1372
1946
  },
1373
1947
  setUser(user) {
1374
- ensureInit().setUser(user);
1948
+ try {
1949
+ maybeInit()?.setUser(user);
1950
+ } catch {
1951
+ }
1375
1952
  },
1376
1953
  setTag(key, value) {
1377
- ensureInit().setTag(key, value);
1954
+ try {
1955
+ maybeInit()?.setTag(key, value);
1956
+ } catch {
1957
+ }
1378
1958
  },
1379
1959
  setTags(tags) {
1380
- ensureInit().setTags(tags);
1960
+ try {
1961
+ maybeInit()?.setTags(tags);
1962
+ } catch {
1963
+ }
1381
1964
  },
1382
1965
  setExtra(key, value) {
1383
- ensureInit().setExtra(key, value);
1966
+ try {
1967
+ maybeInit()?.setExtra(key, value);
1968
+ } catch {
1969
+ }
1384
1970
  },
1385
1971
  setExtras(extras) {
1386
- ensureInit().setExtras(extras);
1972
+ try {
1973
+ maybeInit()?.setExtras(extras);
1974
+ } catch {
1975
+ }
1387
1976
  },
1388
1977
  setContext(name, ctx) {
1389
- ensureInit().setContext(name, ctx);
1978
+ try {
1979
+ maybeInit()?.setContext(name, ctx);
1980
+ } catch {
1981
+ }
1390
1982
  },
1391
1983
  setLevel(level) {
1392
- ensureInit().setLevel(level);
1984
+ try {
1985
+ maybeInit()?.setLevel(level);
1986
+ } catch {
1987
+ }
1393
1988
  },
1394
1989
  setFingerprint(fingerprint) {
1395
- ensureInit().setFingerprint(fingerprint);
1990
+ try {
1991
+ maybeInit()?.setFingerprint(fingerprint);
1992
+ } catch {
1993
+ }
1396
1994
  },
1397
1995
  flush(timeoutMs) {
1398
- return ensureInit().flush(timeoutMs);
1996
+ try {
1997
+ return maybeInit()?.flush(timeoutMs) ?? Promise.resolve(true);
1998
+ } catch {
1999
+ return Promise.resolve(false);
2000
+ }
1399
2001
  },
1400
2002
  setIdentity(identity) {
1401
- ensureInit().setIdentity(identity);
2003
+ try {
2004
+ maybeInit()?.setIdentity(identity);
2005
+ } catch {
2006
+ }
1402
2007
  },
1403
2008
  /**
1404
2009
  * Run `callback` with a fresh scoped context. Any user/tag/extra/context/
@@ -1407,37 +2012,79 @@ var AllStak = {
1407
2012
  * for async callbacks and thrown errors.
1408
2013
  */
1409
2014
  withScope(callback) {
1410
- return ensureInit().withScope(callback);
2015
+ try {
2016
+ const client = maybeInit();
2017
+ return client ? client.withScope(callback) : callback(new Scope());
2018
+ } catch {
2019
+ return callback(new Scope());
2020
+ }
1411
2021
  },
1412
2022
  startSpan(operation, options) {
1413
- return ensureInit().startSpan(operation, options);
2023
+ try {
2024
+ return maybeInit()?.startSpan(operation, options) ?? noopSpan(operation);
2025
+ } catch {
2026
+ return noopSpan(operation);
2027
+ }
1414
2028
  },
1415
2029
  getTraceId() {
1416
- return ensureInit().getTraceId();
2030
+ try {
2031
+ return maybeInit()?.getTraceId() ?? "";
2032
+ } catch {
2033
+ return "";
2034
+ }
1417
2035
  },
1418
2036
  setTraceId(traceId) {
1419
- ensureInit().setTraceId(traceId);
2037
+ try {
2038
+ maybeInit()?.setTraceId(traceId);
2039
+ } catch {
2040
+ }
1420
2041
  },
1421
2042
  getCurrentSpanId() {
1422
- return ensureInit().getCurrentSpanId();
2043
+ try {
2044
+ return maybeInit()?.getCurrentSpanId() ?? null;
2045
+ } catch {
2046
+ return null;
2047
+ }
1423
2048
  },
1424
2049
  resetTrace() {
1425
- ensureInit().resetTrace();
2050
+ try {
2051
+ maybeInit()?.resetTrace();
2052
+ } catch {
2053
+ }
1426
2054
  },
1427
2055
  /** Access the privacy-first replay surrogate (or null if disabled / sampled out). */
1428
2056
  getReplay() {
1429
- return ensureInit().getReplay();
2057
+ try {
2058
+ return maybeInit()?.getReplay() ?? null;
2059
+ } catch {
2060
+ return null;
2061
+ }
1430
2062
  },
1431
2063
  /** Manually instrument an axios instance. No-op when HTTP tracking is off. */
1432
2064
  instrumentAxios(axios) {
1433
- return ensureInit().instrumentAxios(axios);
2065
+ try {
2066
+ return maybeInit()?.instrumentAxios(axios) ?? axios;
2067
+ } catch {
2068
+ return axios;
2069
+ }
1434
2070
  },
1435
2071
  getSessionId() {
1436
- return ensureInit().getSessionId();
2072
+ try {
2073
+ return maybeInit()?.getSessionId() ?? "";
2074
+ } catch {
2075
+ return "";
2076
+ }
1437
2077
  },
1438
2078
  getConfig() {
1439
2079
  return instance?.getConfig() ?? null;
1440
2080
  },
2081
+ getTransportStats() {
2082
+ try {
2083
+ return maybeInit()?.getTransportStats() ?? emptyStats();
2084
+ } catch {
2085
+ return emptyStats();
2086
+ }
2087
+ },
1441
2088
  destroy() {
1442
2089
  instance?.destroy();
1443
2090
  instance = null;
@@ -1447,6 +2094,17 @@ var AllStak = {
1447
2094
  return instance;
1448
2095
  }
1449
2096
  };
2097
+ function byteSize(value) {
2098
+ if (!value) return 0;
2099
+ try {
2100
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(value).length;
2101
+ } catch {
2102
+ }
2103
+ return value.length;
2104
+ }
2105
+
2106
+ // src/provider.tsx
2107
+ import * as React from "react";
1450
2108
 
1451
2109
  // src/auto-breadcrumbs.ts
1452
2110
  var FETCH_FLAG2 = "__allstak_fetch_patched__";
@@ -1494,36 +2152,92 @@ function instrumentFetch(addBreadcrumb, ownBaseUrl) {
1494
2152
  wrapped[FETCH_FLAG2] = true;
1495
2153
  g.fetch = wrapped;
1496
2154
  }
1497
- function instrumentConsole(addBreadcrumb) {
2155
+ var CONSOLE_DEFAULTS = {
2156
+ log: false,
2157
+ info: false,
2158
+ warn: true,
2159
+ error: true
2160
+ };
2161
+ var CONSOLE_METHOD_TO_LEVEL = {
2162
+ log: "info",
2163
+ info: "info",
2164
+ warn: "warn",
2165
+ error: "error"
2166
+ };
2167
+ var MAX_ARG_BYTES = 5e3;
2168
+ function instrumentConsole(addBreadcrumb, options = {}) {
1498
2169
  if (typeof console === "undefined") return;
1499
2170
  if (console[CONSOLE_FLAG]) return;
1500
- const origWarn = console.warn;
1501
- const origError = console.error;
1502
- console.warn = function(...args) {
1503
- try {
1504
- addBreadcrumb("log", args.map(safeString).join(" "), "warn");
1505
- } catch {
1506
- }
1507
- return origWarn.apply(console, args);
2171
+ const opts = {
2172
+ log: options.log ?? CONSOLE_DEFAULTS.log,
2173
+ info: options.info ?? CONSOLE_DEFAULTS.info,
2174
+ warn: options.warn ?? CONSOLE_DEFAULTS.warn,
2175
+ error: options.error ?? CONSOLE_DEFAULTS.error
1508
2176
  };
1509
- console.error = function(...args) {
1510
- try {
1511
- addBreadcrumb("log", args.map(safeString).join(" "), "error");
1512
- } catch {
1513
- }
1514
- return origError.apply(console, args);
2177
+ const wrap = (method) => {
2178
+ const orig = console[method];
2179
+ if (typeof orig !== "function") return;
2180
+ const level = CONSOLE_METHOD_TO_LEVEL[method];
2181
+ console[method] = function(...args) {
2182
+ if (opts[method]) {
2183
+ try {
2184
+ const serialized = args.map(safeStringifyArg);
2185
+ const message = truncate(serialized.join(" "));
2186
+ addBreadcrumb("log", message, level, {
2187
+ category: "console",
2188
+ method,
2189
+ args: serialized
2190
+ });
2191
+ } catch {
2192
+ }
2193
+ }
2194
+ return orig.apply(console, args);
2195
+ };
1515
2196
  };
2197
+ if (opts.log) wrap("log");
2198
+ if (opts.info) wrap("info");
2199
+ if (opts.warn) wrap("warn");
2200
+ if (opts.error) wrap("error");
1516
2201
  console[CONSOLE_FLAG] = true;
1517
2202
  }
1518
- function safeString(v) {
1519
- if (v == null) return String(v);
2203
+ function __resetConsoleInstrumentationFlagForTest() {
2204
+ if (typeof console !== "undefined") {
2205
+ delete console[CONSOLE_FLAG];
2206
+ }
2207
+ }
2208
+ function safeStringifyArg(v) {
2209
+ if (v === null || v === void 0) return String(v);
1520
2210
  if (typeof v === "string") return v;
1521
- if (v instanceof Error) return `${v.name}: ${v.message}`;
1522
- try {
1523
- return typeof v === "object" ? JSON.stringify(v) : String(v);
1524
- } catch {
1525
- return Object.prototype.toString.call(v);
2211
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
2212
+ if (typeof v === "symbol") return v.toString();
2213
+ if (typeof v === "function") return `[Function${v.name ? ` ${v.name}` : ""}]`;
2214
+ if (v instanceof Error) {
2215
+ return `${v.name || "Error"}: ${v.message}${v.stack ? `
2216
+ ${v.stack}` : ""}`;
2217
+ }
2218
+ if (typeof v === "object") {
2219
+ try {
2220
+ const seen = /* @__PURE__ */ new WeakSet();
2221
+ const out = JSON.stringify(v, (_key, val) => {
2222
+ if (typeof val === "object" && val !== null) {
2223
+ if (seen.has(val)) return "[Circular]";
2224
+ seen.add(val);
2225
+ }
2226
+ if (typeof val === "bigint") return val.toString();
2227
+ if (typeof val === "function") return `[Function${val.name ? ` ${val.name}` : ""}]`;
2228
+ if (typeof val === "symbol") return val.toString();
2229
+ return val;
2230
+ });
2231
+ return out ?? Object.prototype.toString.call(v);
2232
+ } catch {
2233
+ return Object.prototype.toString.call(v);
2234
+ }
1526
2235
  }
2236
+ return String(v);
2237
+ }
2238
+ function truncate(s) {
2239
+ if (s.length <= MAX_ARG_BYTES) return s;
2240
+ return s.slice(0, MAX_ARG_BYTES) + "\u2026[truncated]";
1527
2241
  }
1528
2242
 
1529
2243
  // src/architecture.ts
@@ -1551,6 +2265,7 @@ function applyArchitectureTags(setTag) {
1551
2265
  // src/navigation.ts
1552
2266
  var NAV_FLAG = /* @__PURE__ */ Symbol.for("allstak.nav.subscribed");
1553
2267
  var LINKING_FLAG = "__allstak_linking_patched__";
2268
+ var NAV_AUTO_PATCH_FLAG = /* @__PURE__ */ Symbol.for("allstak.nav.autoPatched");
1554
2269
  function instrumentReactNavigation(navigationRef, options = {}) {
1555
2270
  if (!navigationRef || typeof navigationRef.addListener !== "function") {
1556
2271
  return () => {
@@ -1602,7 +2317,7 @@ function instrumentReactNavigation(navigationRef, options = {}) {
1602
2317
  }
1603
2318
  function instrumentNavigationFromLinking() {
1604
2319
  try {
1605
- const rn = __require("react-native");
2320
+ const rn = require("react-native");
1606
2321
  const Linking = rn?.Linking;
1607
2322
  if (!Linking || typeof Linking.addEventListener !== "function") return;
1608
2323
  if (Linking[LINKING_FLAG]) return;
@@ -1618,8 +2333,59 @@ function instrumentNavigationFromLinking() {
1618
2333
  } catch {
1619
2334
  }
1620
2335
  }
2336
+ function tryAutoInstrumentNavigation() {
2337
+ const g = globalThis;
2338
+ const isMetro = typeof g.__METRO_GLOBAL_PREFIX__ !== "undefined" || typeof g.__r === "function" || typeof g.HermesInternal !== "undefined";
2339
+ if (isMetro) return false;
2340
+ try {
2341
+ const rnav = require("@react-navigation/native");
2342
+ if (!rnav || !rnav.NavigationContainer) return false;
2343
+ if (rnav[NAV_AUTO_PATCH_FLAG]) return true;
2344
+ const React2 = require("react");
2345
+ if (!React2 || typeof React2.forwardRef !== "function") return false;
2346
+ const OrigContainer = rnav.NavigationContainer;
2347
+ const Wrapped = React2.forwardRef(function AllStakNavigationContainer(props, userRef) {
2348
+ const internalRef = React2.useRef(null);
2349
+ const setRef = React2.useCallback((r) => {
2350
+ internalRef.current = r;
2351
+ if (typeof userRef === "function") userRef(r);
2352
+ else if (userRef) userRef.current = r;
2353
+ }, [userRef]);
2354
+ React2.useEffect(() => {
2355
+ if (internalRef.current) {
2356
+ try {
2357
+ instrumentReactNavigation(internalRef.current);
2358
+ } catch {
2359
+ }
2360
+ }
2361
+ }, []);
2362
+ return React2.createElement(OrigContainer, { ...props, ref: setRef });
2363
+ });
2364
+ Wrapped.displayName = "AllStakNavigationContainer";
2365
+ try {
2366
+ Object.defineProperty(rnav, "NavigationContainer", {
2367
+ value: Wrapped,
2368
+ configurable: true,
2369
+ writable: true
2370
+ });
2371
+ rnav[NAV_AUTO_PATCH_FLAG] = true;
2372
+ return true;
2373
+ } catch {
2374
+ return false;
2375
+ }
2376
+ } catch {
2377
+ return false;
2378
+ }
2379
+ }
2380
+ function __resetAutoNavigationFlagForTest() {
2381
+ try {
2382
+ const rnav = require("@react-navigation/native");
2383
+ if (rnav) delete rnav[NAV_AUTO_PATCH_FLAG];
2384
+ } catch {
2385
+ }
2386
+ }
1621
2387
 
1622
- // src/index.ts
2388
+ // src/install.ts
1623
2389
  function instrumentXmlHttpRequest() {
1624
2390
  const flag = "__allstak_xhr_patched__";
1625
2391
  const X = globalThis.XMLHttpRequest;
@@ -1671,43 +2437,6 @@ function instrumentXmlHttpRequest() {
1671
2437
  };
1672
2438
  X.prototype[flag] = true;
1673
2439
  }
1674
- var __testNativeModule = null;
1675
- function __setNativeModuleForTest(mod) {
1676
- __testNativeModule = mod;
1677
- }
1678
- async function drainPendingNativeCrashes(release) {
1679
- try {
1680
- let native = __testNativeModule;
1681
- if (!native) {
1682
- const rn = __require("react-native");
1683
- native = rn?.NativeModules?.AllStakNative;
1684
- }
1685
- if (!native) return;
1686
- if (typeof native.install === "function") {
1687
- try {
1688
- await native.install(release ?? "");
1689
- } catch {
1690
- }
1691
- }
1692
- if (typeof native.drainPendingCrash === "function") {
1693
- const json = await native.drainPendingCrash();
1694
- if (json && json !== "") {
1695
- try {
1696
- const payload = JSON.parse(json);
1697
- const err = new Error(payload?.message ?? "Native crash");
1698
- err.name = payload?.exceptionClass ?? "NativeCrash";
1699
- err.stack = Array.isArray(payload?.stackTrace) ? payload.stackTrace.join("\n") : String(payload?.stackTrace ?? "");
1700
- AllStak.captureException(err, {
1701
- ...payload?.metadata || {},
1702
- "native.crash": "true"
1703
- });
1704
- } catch {
1705
- }
1706
- }
1707
- }
1708
- } catch {
1709
- }
1710
- }
1711
2440
  function installReactNative(options = {}) {
1712
2441
  const autoError = options.autoErrorHandler !== false;
1713
2442
  const autoPromise = options.autoPromiseRejections !== false;
@@ -1723,7 +2452,7 @@ function installReactNative(options = {}) {
1723
2452
  const hermes = typeof globalThis.HermesInternal !== "undefined";
1724
2453
  let dist;
1725
2454
  try {
1726
- const rn = __require("react-native");
2455
+ const rn = require("react-native");
1727
2456
  const os = rn?.Platform?.OS;
1728
2457
  if (os === "ios" || os === "android") {
1729
2458
  dist = `${os}-${hermes ? "hermes" : "jsc"}`;
@@ -1732,7 +2461,7 @@ function installReactNative(options = {}) {
1732
2461
  }
1733
2462
  AllStak.setIdentity({
1734
2463
  sdkName: "allstak-react-native",
1735
- sdkVersion: "0.3.0",
2464
+ sdkVersion: SDK_VERSION,
1736
2465
  platform: "react-native",
1737
2466
  dist
1738
2467
  });
@@ -1754,13 +2483,25 @@ function installReactNative(options = {}) {
1754
2483
  }
1755
2484
  if (options.autoConsoleBreadcrumbs !== false) {
1756
2485
  try {
1757
- instrumentConsole(__safeAddBreadcrumbForInstrumentation);
2486
+ const cfg = AllStak.getConfig();
2487
+ instrumentConsole(__safeAddBreadcrumbForInstrumentation, cfg?.captureConsole);
1758
2488
  } catch {
1759
2489
  }
1760
2490
  }
2491
+ if (options.autoNavigationBreadcrumbs !== false) {
2492
+ let navResult = false;
2493
+ try {
2494
+ navResult = tryAutoInstrumentNavigation();
2495
+ } catch {
2496
+ }
2497
+ if (options.debugLogs) {
2498
+ if (navResult) console.log("[AllStak] Navigation auto-instrumentation enabled");
2499
+ else console.log("[AllStak] Navigation auto-instrumentation not applied; use instrumentReactNavigation(ref) fallback");
2500
+ }
2501
+ }
1761
2502
  if (autoDevice) {
1762
2503
  try {
1763
- const rn = __require("react-native");
2504
+ const rn = require("react-native");
1764
2505
  const Platform = rn?.Platform;
1765
2506
  if (Platform) {
1766
2507
  AllStak.setTag("device.os", String(Platform.OS ?? ""));
@@ -1774,7 +2515,7 @@ function installReactNative(options = {}) {
1774
2515
  }
1775
2516
  if (autoAppState) {
1776
2517
  try {
1777
- const rn = __require("react-native");
2518
+ const rn = require("react-native");
1778
2519
  const AppState = rn?.AppState;
1779
2520
  if (AppState && typeof AppState.addEventListener === "function") {
1780
2521
  AppState.addEventListener("change", (next) => {
@@ -1807,17 +2548,30 @@ function installReactNative(options = {}) {
1807
2548
  }
1808
2549
  }
1809
2550
  if (autoPromise) {
2551
+ const wrapTrackerReason = (rejection) => rejection instanceof Error ? rejection : new Error(`Unhandled promise rejection: ${String(rejection)}`);
2552
+ const ship = (err) => {
2553
+ try {
2554
+ AllStak.captureException(err, { source: "unhandledRejection" });
2555
+ } catch {
2556
+ }
2557
+ };
2558
+ try {
2559
+ const hermesInternal = globalThis.HermesInternal;
2560
+ if (hermesInternal && typeof hermesInternal.enablePromiseRejectionTracker === "function") {
2561
+ hermesInternal.enablePromiseRejectionTracker({
2562
+ allRejections: true,
2563
+ onUnhandled: (_id, rejection) => ship(wrapTrackerReason(rejection)),
2564
+ onHandled: () => {
2565
+ }
2566
+ });
2567
+ }
2568
+ } catch {
2569
+ }
1810
2570
  try {
1811
- const tracking = __require("promise/setimmediate/rejection-tracking");
2571
+ const tracking = require("promise/setimmediate/rejection-tracking");
1812
2572
  tracking.enable({
1813
2573
  allRejections: true,
1814
- onUnhandled: (_id, rejection) => {
1815
- const err = rejection instanceof Error ? rejection : new Error(`Unhandled promise rejection: ${String(rejection)}`);
1816
- try {
1817
- AllStak.captureException(err, { source: "unhandledRejection" });
1818
- } catch {
1819
- }
1820
- },
2574
+ onUnhandled: (_id, rejection) => ship(wrapTrackerReason(rejection)),
1821
2575
  onHandled: () => {
1822
2576
  }
1823
2577
  });
@@ -1827,30 +2581,237 @@ function installReactNative(options = {}) {
1827
2581
  g.addEventListener("unhandledrejection", (ev) => {
1828
2582
  const reason = ev?.reason;
1829
2583
  const err = reason instanceof Error ? reason : new Error(String(reason));
1830
- try {
1831
- AllStak.captureException(err, { source: "unhandledRejection" });
1832
- } catch {
1833
- }
2584
+ ship(err);
1834
2585
  });
1835
2586
  }
1836
2587
  }
1837
2588
  }
1838
2589
  }
2590
+
2591
+ // src/provider.tsx
2592
+ var AllStakContext = React.createContext(null);
2593
+ var __providerOwnedInstance = null;
2594
+ var AllStakErrorBoundary = class extends React.Component {
2595
+ constructor() {
2596
+ super(...arguments);
2597
+ this.state = { error: null };
2598
+ this.resetError = () => this.setState({ error: null });
2599
+ }
2600
+ static getDerivedStateFromError(error) {
2601
+ return { error };
2602
+ }
2603
+ componentDidCatch(error, info) {
2604
+ try {
2605
+ AllStak.addBreadcrumb("ui", "React error boundary caught error", "error", {
2606
+ componentStack: info.componentStack ?? ""
2607
+ });
2608
+ AllStak.captureException(error, {
2609
+ componentStack: info.componentStack ?? "",
2610
+ source: "AllStakProvider.ErrorBoundary"
2611
+ });
2612
+ if (this.props.debug) {
2613
+ console.log(`[AllStak] Captured render error: ${error.message}`);
2614
+ }
2615
+ } catch {
2616
+ }
2617
+ try {
2618
+ this.props.onError?.(error, info.componentStack ?? void 0);
2619
+ } catch {
2620
+ }
2621
+ }
2622
+ render() {
2623
+ if (this.state.error) {
2624
+ const { fallback } = this.props;
2625
+ if (typeof fallback === "function") {
2626
+ return fallback({ error: this.state.error, resetError: this.resetError });
2627
+ }
2628
+ if (fallback !== void 0) return fallback;
2629
+ return null;
2630
+ }
2631
+ return this.props.children;
2632
+ }
2633
+ };
2634
+ function AllStakProvider({
2635
+ children,
2636
+ apiKey,
2637
+ environment,
2638
+ release,
2639
+ host,
2640
+ user,
2641
+ tags,
2642
+ debug,
2643
+ enableHttpTracking,
2644
+ httpTracking,
2645
+ captureConsole,
2646
+ sampleRate,
2647
+ beforeSend,
2648
+ replay,
2649
+ tracesSampleRate,
2650
+ service,
2651
+ dist,
2652
+ destroyOnUnmount = false,
2653
+ fallback,
2654
+ onError,
2655
+ autoErrorHandler,
2656
+ autoPromiseRejections,
2657
+ autoDeviceTags,
2658
+ autoAppStateBreadcrumbs,
2659
+ autoNetworkCapture,
2660
+ autoFetchBreadcrumbs,
2661
+ autoConsoleBreadcrumbs,
2662
+ autoNavigationBreadcrumbs
2663
+ }) {
2664
+ const clientRef = React.useRef(null);
2665
+ if (!clientRef.current) {
2666
+ const existing = AllStak._getInstance();
2667
+ if (existing && __providerOwnedInstance === existing) {
2668
+ clientRef.current = existing;
2669
+ if (debug) {
2670
+ console.log(`[AllStak] Reusing session ${AllStak.getSessionId()}`);
2671
+ }
2672
+ } else {
2673
+ const config = {
2674
+ apiKey,
2675
+ environment,
2676
+ release,
2677
+ host,
2678
+ user,
2679
+ tags,
2680
+ enableHttpTracking,
2681
+ httpTracking,
2682
+ captureConsole,
2683
+ sampleRate,
2684
+ beforeSend,
2685
+ replay,
2686
+ tracesSampleRate,
2687
+ service,
2688
+ dist
2689
+ };
2690
+ clientRef.current = AllStak.init(config);
2691
+ __providerOwnedInstance = clientRef.current;
2692
+ installReactNative({
2693
+ autoErrorHandler,
2694
+ autoPromiseRejections,
2695
+ autoDeviceTags,
2696
+ autoAppStateBreadcrumbs,
2697
+ autoNetworkCapture,
2698
+ autoFetchBreadcrumbs,
2699
+ autoConsoleBreadcrumbs,
2700
+ autoNavigationBreadcrumbs,
2701
+ debugLogs: debug
2702
+ });
2703
+ if (debug) {
2704
+ console.log(`[AllStak] Initialized \u2014 session ${AllStak.getSessionId()}`);
2705
+ }
2706
+ }
2707
+ }
2708
+ React.useEffect(() => {
2709
+ return () => {
2710
+ if (destroyOnUnmount) {
2711
+ AllStak.destroy();
2712
+ __providerOwnedInstance = null;
2713
+ clientRef.current = null;
2714
+ if (debug) console.log("[AllStak] Destroyed on unmount");
2715
+ }
2716
+ };
2717
+ }, [destroyOnUnmount, debug]);
2718
+ return /* @__PURE__ */ React.createElement(AllStakContext.Provider, { value: clientRef.current }, /* @__PURE__ */ React.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children));
2719
+ }
2720
+ function useAllStak() {
2721
+ return React.useMemo(
2722
+ () => ({
2723
+ captureException: (error, ctx) => AllStak.captureException(error, ctx),
2724
+ captureMessage: (msg, level = "info") => AllStak.captureMessage(msg, level),
2725
+ setUser: (user) => AllStak.setUser(user),
2726
+ setTag: (key, value) => AllStak.setTag(key, value),
2727
+ addBreadcrumb: (type, message, level, data) => AllStak.addBreadcrumb(type, message, level, data)
2728
+ }),
2729
+ []
2730
+ );
2731
+ }
2732
+ function __resetProviderInstanceForTest() {
2733
+ __providerOwnedInstance = null;
2734
+ }
2735
+
2736
+ // src/index.ts
2737
+ var __testNativeModule = null;
2738
+ function __setNativeModuleForTest(mod) {
2739
+ __testNativeModule = mod;
2740
+ }
2741
+ async function __devTriggerNativeCrash() {
2742
+ try {
2743
+ let native = __testNativeModule;
2744
+ if (!native) {
2745
+ const rn = require("react-native");
2746
+ native = rn?.NativeModules?.AllStakNative;
2747
+ }
2748
+ if (!native || typeof native.__devTriggerCrash !== "function") return;
2749
+ await native.__devTriggerCrash();
2750
+ } catch {
2751
+ }
2752
+ }
2753
+ async function drainPendingNativeCrashes(release) {
2754
+ try {
2755
+ let native = __testNativeModule;
2756
+ if (!native) {
2757
+ const rn = require("react-native");
2758
+ native = rn?.NativeModules?.AllStakNative;
2759
+ }
2760
+ if (!native) return;
2761
+ if (typeof native.install === "function") {
2762
+ try {
2763
+ await native.install(release ?? "");
2764
+ } catch {
2765
+ }
2766
+ }
2767
+ if (typeof native.drainPendingCrash === "function") {
2768
+ const json = await native.drainPendingCrash();
2769
+ if (json && json !== "") {
2770
+ try {
2771
+ const payload = JSON.parse(json);
2772
+ const err = new Error(payload?.message ?? "Native crash");
2773
+ err.name = payload?.exceptionClass ?? "NativeCrash";
2774
+ err.stack = Array.isArray(payload?.stackTrace) ? payload.stackTrace.join("\n") : String(payload?.stackTrace ?? "");
2775
+ AllStak.captureException(err, {
2776
+ ...payload?.metadata || {},
2777
+ "native.crash": "true"
2778
+ });
2779
+ } catch {
2780
+ }
2781
+ }
2782
+ }
2783
+ } catch {
2784
+ }
2785
+ }
1839
2786
  export {
2787
+ ALWAYS_REDACT_HEADERS,
2788
+ ALWAYS_REDACT_QUERY,
1840
2789
  AllStak,
1841
2790
  AllStakClient,
2791
+ AllStakProvider,
2792
+ DEFAULT_REDACT_BODY_FIELDS,
1842
2793
  HttpRequestModule,
1843
2794
  INGEST_HOST,
2795
+ REDACTED,
1844
2796
  ReplaySurrogate,
1845
2797
  SDK_NAME,
1846
2798
  SDK_VERSION,
1847
2799
  Scope,
2800
+ __devTriggerNativeCrash,
2801
+ __resetAutoNavigationFlagForTest,
2802
+ __resetConsoleInstrumentationFlagForTest,
2803
+ __resetProviderInstanceForTest,
1848
2804
  __setNativeModuleForTest,
1849
2805
  applyArchitectureTags,
2806
+ captureBodyResult,
1850
2807
  detectArchitecture,
1851
2808
  drainPendingNativeCrashes,
1852
2809
  installReactNative,
1853
2810
  instrumentNavigationFromLinking,
1854
- instrumentReactNavigation
2811
+ instrumentReactNavigation,
2812
+ redactUrl,
2813
+ sanitizeHeaders,
2814
+ tryAutoInstrumentNavigation,
2815
+ useAllStak
1855
2816
  };
1856
2817
  //# sourceMappingURL=index.mjs.map