@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.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,52 +17,103 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
33
+ ALWAYS_REDACT_HEADERS: () => ALWAYS_REDACT_HEADERS,
34
+ ALWAYS_REDACT_QUERY: () => ALWAYS_REDACT_QUERY,
23
35
  AllStak: () => AllStak,
24
36
  AllStakClient: () => AllStakClient,
37
+ AllStakProvider: () => AllStakProvider,
38
+ DEFAULT_REDACT_BODY_FIELDS: () => DEFAULT_REDACT_BODY_FIELDS,
25
39
  HttpRequestModule: () => HttpRequestModule,
26
40
  INGEST_HOST: () => INGEST_HOST,
41
+ REDACTED: () => REDACTED,
27
42
  ReplaySurrogate: () => ReplaySurrogate,
28
43
  SDK_NAME: () => SDK_NAME,
29
44
  SDK_VERSION: () => SDK_VERSION,
30
45
  Scope: () => Scope,
46
+ __devTriggerNativeCrash: () => __devTriggerNativeCrash,
47
+ __resetAutoNavigationFlagForTest: () => __resetAutoNavigationFlagForTest,
48
+ __resetConsoleInstrumentationFlagForTest: () => __resetConsoleInstrumentationFlagForTest,
49
+ __resetProviderInstanceForTest: () => __resetProviderInstanceForTest,
31
50
  __setNativeModuleForTest: () => __setNativeModuleForTest,
32
51
  applyArchitectureTags: () => applyArchitectureTags,
52
+ captureBodyResult: () => captureBodyResult,
33
53
  detectArchitecture: () => detectArchitecture,
34
54
  drainPendingNativeCrashes: () => drainPendingNativeCrashes,
35
55
  installReactNative: () => installReactNative,
36
56
  instrumentNavigationFromLinking: () => instrumentNavigationFromLinking,
37
- instrumentReactNavigation: () => instrumentReactNavigation
57
+ instrumentReactNavigation: () => instrumentReactNavigation,
58
+ redactUrl: () => redactUrl,
59
+ sanitizeHeaders: () => sanitizeHeaders,
60
+ tryAutoInstrumentNavigation: () => tryAutoInstrumentNavigation,
61
+ useAllStak: () => useAllStak
38
62
  });
39
63
  module.exports = __toCommonJS(index_exports);
40
64
 
41
65
  // src/transport.ts
42
- var REQUEST_TIMEOUT = 3e3;
66
+ var REQUEST_TIMEOUT = 2e3;
43
67
  var MAX_BUFFER = 100;
68
+ var FAILURE_THRESHOLD = 3;
69
+ var BACKOFF_BASE_MS = 500;
70
+ var BACKOFF_MAX_MS = 3e4;
44
71
  var HttpTransport = class {
45
- constructor(baseUrl, apiKey) {
72
+ constructor(baseUrl, apiKey, enabled = true) {
46
73
  this.baseUrl = baseUrl;
47
74
  this.apiKey = apiKey;
75
+ this.enabled = enabled;
48
76
  this.buffer = [];
49
77
  this.flushing = false;
78
+ this.consecutiveFailures = 0;
79
+ this.circuitOpenUntil = 0;
80
+ this.sent = 0;
81
+ this.failed = 0;
82
+ this.dropped = 0;
83
+ }
84
+ send(path, payload) {
85
+ if (!this.enabled) {
86
+ this.noteDropped();
87
+ return Promise.resolve();
88
+ }
89
+ this.enqueueOrDispatch({ path, payload });
90
+ return Promise.resolve();
91
+ }
92
+ enqueueOrDispatch(item) {
93
+ if (Date.now() < this.circuitOpenUntil) {
94
+ this.push(item);
95
+ return;
96
+ }
97
+ void this.dispatch(item).catch(() => void 0);
50
98
  }
51
- async send(path, payload) {
99
+ async dispatch(item) {
52
100
  try {
53
- await this.doFetch(path, payload);
54
- await this.flushBuffer();
55
- } catch {
56
- if (this.buffer.length >= MAX_BUFFER) this.buffer.shift();
57
- this.buffer.push({ path, payload });
101
+ await this.doFetch(item.path, item.payload);
102
+ this.sent++;
103
+ this.consecutiveFailures = 0;
104
+ this.circuitOpenUntil = 0;
105
+ this.scheduleFlush();
106
+ } catch (err) {
107
+ this.failed++;
108
+ this.recordFailure(err);
109
+ this.push(item);
58
110
  }
59
111
  }
60
112
  async doFetch(path, payload) {
61
113
  const url = `${this.baseUrl}${path}`;
62
114
  const controller = new AbortController();
63
115
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
116
+ const started = Date.now();
64
117
  try {
65
118
  const res = await fetch(url, {
66
119
  method: "POST",
@@ -74,26 +127,77 @@ var HttpTransport = class {
74
127
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
75
128
  } finally {
76
129
  clearTimeout(timeoutId);
130
+ this.lastTransportLatencyMs = Date.now() - started;
77
131
  }
78
132
  }
133
+ push(item) {
134
+ if (this.buffer.length >= MAX_BUFFER) {
135
+ this.buffer.shift();
136
+ this.dropped++;
137
+ }
138
+ this.buffer.push(item);
139
+ }
140
+ scheduleFlush() {
141
+ if (this.flushing || this.buffer.length === 0) return;
142
+ const delay = Math.max(0, this.circuitOpenUntil - Date.now());
143
+ const timer = setTimeout(() => {
144
+ void this.flushBuffer().catch(() => void 0);
145
+ }, delay);
146
+ if (typeof timer === "object" && typeof timer.unref === "function") timer.unref();
147
+ }
79
148
  async flushBuffer() {
80
149
  if (this.flushing || this.buffer.length === 0) return;
81
150
  this.flushing = true;
151
+ const started = Date.now();
82
152
  try {
83
153
  const items = this.buffer.splice(0, this.buffer.length);
84
154
  for (const item of items) {
155
+ if (Date.now() < this.circuitOpenUntil) {
156
+ this.push(item);
157
+ continue;
158
+ }
85
159
  try {
86
160
  await this.doFetch(item.path, item.payload);
87
- } catch {
161
+ this.sent++;
162
+ this.consecutiveFailures = 0;
163
+ this.circuitOpenUntil = 0;
164
+ } catch (err) {
165
+ this.failed++;
166
+ this.recordFailure(err);
167
+ this.push(item);
88
168
  }
89
169
  }
90
170
  } finally {
171
+ this.lastFlushDurationMs = Date.now() - started;
91
172
  this.flushing = false;
173
+ if (this.buffer.length > 0) this.scheduleFlush();
92
174
  }
93
175
  }
176
+ recordFailure(error) {
177
+ this.consecutiveFailures++;
178
+ if (this.consecutiveFailures < FAILURE_THRESHOLD) return;
179
+ const retryAfterMs = retryAfterFromError(error);
180
+ const backoff = retryAfterMs ?? jitteredBackoff(this.consecutiveFailures);
181
+ this.circuitOpenUntil = Date.now() + backoff;
182
+ }
94
183
  getBufferSize() {
95
184
  return this.buffer.length;
96
185
  }
186
+ noteDropped(count = 1) {
187
+ this.dropped += Math.max(0, count);
188
+ }
189
+ getStats() {
190
+ return {
191
+ queued: this.buffer.length,
192
+ sent: this.sent,
193
+ failed: this.failed,
194
+ dropped: this.dropped,
195
+ consecutiveFailures: this.consecutiveFailures,
196
+ circuitOpenUntil: this.circuitOpenUntil,
197
+ lastTransportLatencyMs: this.lastTransportLatencyMs,
198
+ lastFlushDurationMs: this.lastFlushDurationMs
199
+ };
200
+ }
97
201
  /**
98
202
  * Wait for the in-flight retry-buffer to drain. Resolves `true` if the
99
203
  * buffer empties within `timeoutMs` (default 2000ms), `false` otherwise.
@@ -110,6 +214,14 @@ var HttpTransport = class {
110
214
  return true;
111
215
  }
112
216
  };
217
+ function jitteredBackoff(failures) {
218
+ const exp = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** Math.min(8, failures - FAILURE_THRESHOLD));
219
+ return Math.floor(exp / 2 + Math.random() * (exp / 2));
220
+ }
221
+ function retryAfterFromError(error) {
222
+ const message = error instanceof Error ? error.message : "";
223
+ return /HTTP\s+(429|503)/.test(message) ? BACKOFF_MAX_MS : null;
224
+ }
113
225
 
114
226
  // src/stack.ts
115
227
  var V8_FRAME_RE = /^\s*at\s+(?:(.+?)\s+\()?((?:.+?):(\d+):(\d+))\)?\s*$/;
@@ -164,6 +276,24 @@ function isInApp(filename) {
164
276
  return true;
165
277
  }
166
278
 
279
+ // src/utils/debug-id.ts
280
+ var REGISTRY_KEY = "_allstakDebugIds";
281
+ var cache = /* @__PURE__ */ new Map();
282
+ function resolveDebugId(filename) {
283
+ if (!filename) return void 0;
284
+ if (cache.has(filename)) return cache.get(filename) ?? void 0;
285
+ const registry = globalThis[REGISTRY_KEY];
286
+ if (registry && typeof registry === "object") {
287
+ const hit = registry[filename];
288
+ if (typeof hit === "string" && hit.length > 0) {
289
+ cache.set(filename, hit);
290
+ return hit;
291
+ }
292
+ }
293
+ cache.set(filename, null);
294
+ return void 0;
295
+ }
296
+
167
297
  // src/scope.ts
168
298
  var Scope = class {
169
299
  constructor() {
@@ -375,6 +505,7 @@ var TracingModule = class {
375
505
  }
376
506
  if (!this.flushTimer) {
377
507
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
508
+ this.flushTimer?.unref?.();
378
509
  }
379
510
  }
380
511
  flush() {
@@ -420,10 +551,11 @@ var ReplaySurrogate = class {
420
551
  if (Math.random() >= this.opts.sampleRate) return false;
421
552
  this.active = true;
422
553
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS2);
554
+ this.flushTimer?.unref?.();
423
555
  return true;
424
556
  }
425
557
  /** Record a screen view. Filters params through the safeParams allow-list. */
426
- recordScreenView(routeName, params) {
558
+ recordScreenView(routeName, params, context) {
427
559
  if (!this.active) return;
428
560
  const safe = {};
429
561
  if (params && this.opts.safeParams.length > 0) {
@@ -431,17 +563,22 @@ var ReplaySurrogate = class {
431
563
  if (key in params) safe[key] = params[key];
432
564
  }
433
565
  }
434
- this.push({ ts: Date.now(), k: "screen", data: { route: routeName, params: safe } });
566
+ this.push({ ts: Date.now(), k: "screen", data: { route: routeName, params: safe, ...compact(context) } });
435
567
  }
436
568
  /** Record an AppState transition (foreground/background/inactive). */
437
- recordAppState(next) {
569
+ recordAppState(next, context) {
438
570
  if (!this.active) return;
439
- this.push({ ts: Date.now(), k: "appstate", data: { state: next } });
571
+ this.push({ ts: Date.now(), k: "appstate", data: { state: next, ...compact(context) } });
440
572
  }
441
573
  /** Record a free-form, customer-validated checkpoint. */
442
- recordManual(label, data) {
574
+ recordManual(label, data, context) {
575
+ if (!this.active) return;
576
+ this.push({ ts: Date.now(), k: "manual", data: { label, ...data ?? {}, ...compact(context) } });
577
+ }
578
+ /** Record a forensic mobile session timeline marker. This is not replay. */
579
+ recordTimelineMarker(kind, label, data, context) {
443
580
  if (!this.active) return;
444
- this.push({ ts: Date.now(), k: "manual", data: { label, ...data ?? {} } });
581
+ this.push({ ts: Date.now(), k: kind, data: { label, ...data ?? {}, ...compact(context) } });
445
582
  }
446
583
  destroy() {
447
584
  this.destroyed = true;
@@ -475,6 +612,14 @@ var ReplaySurrogate = class {
475
612
  });
476
613
  }
477
614
  };
615
+ function compact(context) {
616
+ if (!context) return {};
617
+ const out = {};
618
+ for (const [key, value] of Object.entries(context)) {
619
+ if (value !== void 0 && value !== null && value !== "") out[key] = value;
620
+ }
621
+ return out;
622
+ }
478
623
 
479
624
  // src/http-requests.ts
480
625
  var INGEST_PATH = "/ingest/v1/http-requests";
@@ -486,6 +631,12 @@ function genTraceId() {
486
631
  const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
487
632
  return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
488
633
  }
634
+ function genRequestId() {
635
+ return genTraceId();
636
+ }
637
+ function generateHttpId() {
638
+ return genTraceId();
639
+ }
489
640
  function splitHostPath(url) {
490
641
  try {
491
642
  const u = new URL(url);
@@ -512,6 +663,7 @@ var HttpRequestModule = class {
512
663
  const item = {
513
664
  type: "http_request",
514
665
  traceId: ev.traceId ?? genTraceId(),
666
+ requestId: ev.requestId ?? genRequestId(),
515
667
  direction: "outbound",
516
668
  method: (ev.method || "GET").toUpperCase(),
517
669
  host,
@@ -526,6 +678,16 @@ var HttpRequestModule = class {
526
678
  requestHeaders: ev.requestHeaders,
527
679
  responseHeaders: ev.responseHeaders,
528
680
  error: ev.error,
681
+ spanId: ev.spanId,
682
+ parentSpanId: ev.parentSpanId,
683
+ requestBodyStatus: ev.requestBodyStatus,
684
+ responseBodyStatus: ev.responseBodyStatus,
685
+ requestBodyRedactedFields: ev.requestBodyRedactedFields,
686
+ responseBodyRedactedFields: ev.responseBodyRedactedFields,
687
+ requestBodyTruncated: ev.requestBodyTruncated,
688
+ responseBodyTruncated: ev.responseBodyTruncated,
689
+ capturePolicy: ev.capturePolicy,
690
+ linkConfidence: ev.linkConfidence ?? "exact",
529
691
  environment: this.defaults.environment,
530
692
  release: this.defaults.release,
531
693
  dist: this.defaults.dist,
@@ -544,7 +706,10 @@ var HttpRequestModule = class {
544
706
  this.flush();
545
707
  return;
546
708
  }
547
- if (!this.flushTimer) this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS3);
709
+ if (!this.flushTimer) {
710
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS3);
711
+ this.flushTimer?.unref?.();
712
+ }
548
713
  }
549
714
  /** Snapshot of the last failed requests for error-linking. Newest last. */
550
715
  getRecentFailed() {
@@ -596,6 +761,24 @@ var ALWAYS_REDACT_QUERY = /* @__PURE__ */ new Set([
596
761
  "jwt"
597
762
  ]);
598
763
  var REDACTED = "[REDACTED]";
764
+ var DEFAULT_REDACT_BODY_FIELDS = [
765
+ "password",
766
+ "passcode",
767
+ "otp",
768
+ "token",
769
+ "authorization",
770
+ "cookie",
771
+ "session",
772
+ "refresh_token",
773
+ "access_token",
774
+ "jwt",
775
+ "card",
776
+ "credit_card",
777
+ "iban",
778
+ "national_id",
779
+ "secret",
780
+ "api_key"
781
+ ];
599
782
  function shouldCaptureUrl(url, opts) {
600
783
  if (!url) return false;
601
784
  const lower = url.toLowerCase();
@@ -663,25 +846,125 @@ function sanitizeHeaders(headers, opts) {
663
846
  }
664
847
  return out;
665
848
  }
666
- function captureBody(body, enabled, maxBodyBytes) {
667
- if (!enabled) return void 0;
668
- if (body == null) return void 0;
849
+ function captureBodyResult(body, enabled, maxBodyBytes, opts = {}, contentType) {
850
+ if (!enabled) {
851
+ return {
852
+ status: "disabled",
853
+ redactedFields: [],
854
+ truncated: false,
855
+ capturePolicy: "body_capture_disabled"
856
+ };
857
+ }
858
+ if (body == null) {
859
+ return {
860
+ status: "empty",
861
+ redactedFields: [],
862
+ truncated: false,
863
+ capturePolicy: "empty_body"
864
+ };
865
+ }
866
+ const allowed = opts.allowedContentTypes ?? ["application/json", "text/", "application/problem+json"];
867
+ if (contentType && !allowed.some((needle) => contentType.toLowerCase().includes(needle.toLowerCase()))) {
868
+ return {
869
+ status: "unsupported",
870
+ redactedFields: [],
871
+ truncated: false,
872
+ capturePolicy: `unsupported_content_type:${contentType}`
873
+ };
874
+ }
669
875
  let str;
876
+ let redactedFields = [];
670
877
  if (typeof body === "string") str = body;
671
878
  else if (typeof body === "number" || typeof body === "boolean") str = String(body);
672
879
  else if (typeof body === "object") {
673
880
  const tag = Object.prototype.toString.call(body);
674
- if (tag !== "[object Object]" && tag !== "[object Array]") return "<binary>";
881
+ if (tag !== "[object Object]" && tag !== "[object Array]") {
882
+ return {
883
+ body: "<binary>",
884
+ status: "unsupported",
885
+ redactedFields: [],
886
+ truncated: false,
887
+ capturePolicy: "unsupported_binary_body"
888
+ };
889
+ }
890
+ const redacted = redactJsonValue(body, opts);
891
+ redactedFields = redacted.redactedFields;
675
892
  try {
676
- str = JSON.stringify(body);
893
+ str = JSON.stringify(redacted.value);
677
894
  } catch {
678
- return "<unserializable>";
895
+ return {
896
+ body: "<unserializable>",
897
+ status: "unsupported",
898
+ redactedFields,
899
+ truncated: false,
900
+ capturePolicy: "unserializable_body"
901
+ };
679
902
  }
680
903
  } else {
681
- return "<binary>";
904
+ return {
905
+ body: "<binary>",
906
+ status: "unsupported",
907
+ redactedFields: [],
908
+ truncated: false,
909
+ capturePolicy: "unsupported_body_type"
910
+ };
682
911
  }
683
- if (str.length > maxBodyBytes) return str.slice(0, maxBodyBytes) + "\u2026[truncated]";
684
- return str;
912
+ if (typeof body === "string" && looksJson(contentType, str)) {
913
+ try {
914
+ const redacted = redactJsonValue(JSON.parse(str), opts);
915
+ redactedFields = redacted.redactedFields;
916
+ str = JSON.stringify(redacted.value);
917
+ } catch {
918
+ str = redactSensitiveText(str, opts);
919
+ }
920
+ }
921
+ let truncated = false;
922
+ if (str.length > maxBodyBytes) {
923
+ str = str.slice(0, maxBodyBytes) + "\u2026[truncated]";
924
+ truncated = true;
925
+ }
926
+ return {
927
+ body: str,
928
+ status: truncated ? "truncated" : redactedFields.length > 0 ? "redacted" : "captured",
929
+ redactedFields: Array.from(new Set(redactedFields)).sort(),
930
+ truncated,
931
+ capturePolicy: "opt_in_body_capture"
932
+ };
933
+ }
934
+ function looksJson(contentType, body) {
935
+ if (contentType && contentType.toLowerCase().includes("json")) return true;
936
+ const trimmed = body.trim();
937
+ return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
938
+ }
939
+ function redactJsonValue(value, opts, path = "") {
940
+ const fieldSet = new Set([...DEFAULT_REDACT_BODY_FIELDS, ...opts.redactBodyFields ?? []].map((v) => v.toLowerCase()));
941
+ const redactedFields = [];
942
+ const walk = (input, currentPath) => {
943
+ if (Array.isArray(input)) return input.map((item, index) => walk(item, `${currentPath}[${index}]`));
944
+ if (!input || typeof input !== "object") return input;
945
+ const out = {};
946
+ for (const [key, raw] of Object.entries(input)) {
947
+ const keyLower = key.toLowerCase();
948
+ const nextPath = currentPath ? `${currentPath}.${key}` : key;
949
+ if (fieldSet.has(keyLower) || keyLower.includes("token") || keyLower.includes("password")) {
950
+ out[key] = REDACTED;
951
+ redactedFields.push(nextPath);
952
+ } else {
953
+ out[key] = walk(raw, nextPath);
954
+ }
955
+ }
956
+ return out;
957
+ };
958
+ return { value: walk(value, path), redactedFields };
959
+ }
960
+ function redactSensitiveText(input, opts) {
961
+ const fields = [...DEFAULT_REDACT_BODY_FIELDS, ...opts.redactBodyFields ?? []];
962
+ let out = input;
963
+ for (const key of fields) {
964
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
965
+ out = out.replace(new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`, "gi"), `$1"${REDACTED}"`);
966
+ }
967
+ return out;
685
968
  }
686
969
 
687
970
  // src/http-instrumentation.ts
@@ -691,12 +974,16 @@ var AXIOS_FLAG = /* @__PURE__ */ Symbol.for("allstak.http.axios.instrumented");
691
974
  var DEFAULT_MAX_BODY = 4096;
692
975
  var _currentModule = null;
693
976
  var _currentOpts = null;
977
+ var _currentRuntime = null;
694
978
  function currentModule() {
695
979
  return _currentModule;
696
980
  }
697
981
  function currentOpts() {
698
982
  return _currentOpts;
699
983
  }
984
+ function currentRuntime() {
985
+ return _currentRuntime;
986
+ }
700
987
  function safeCapture(ev) {
701
988
  try {
702
989
  currentModule()?.capture(ev);
@@ -713,6 +1000,8 @@ function bind(opts, ownIngestHost) {
713
1000
  ignoredUrls: opts.ignoredUrls ?? [],
714
1001
  allowedUrls: opts.allowedUrls ?? [],
715
1002
  maxBodyBytes: opts.maxBodyBytes ?? DEFAULT_MAX_BODY,
1003
+ allowedContentTypes: opts.allowedContentTypes ?? ["application/json", "text/", "application/problem+json"],
1004
+ redactBodyFields: opts.redactBodyFields ?? [],
716
1005
  ownIngestPrefix: ownIngestHost.replace(/\/$/, "")
717
1006
  };
718
1007
  }
@@ -759,6 +1048,77 @@ function headersToObject(h) {
759
1048
  }
760
1049
  return {};
761
1050
  }
1051
+ function normalizeTraceId(traceId) {
1052
+ const hex = traceId.replace(/[^a-fA-F0-9]/g, "").toLowerCase();
1053
+ return (hex + "00000000000000000000000000000000").slice(0, 32);
1054
+ }
1055
+ function normalizeSpanId(spanId) {
1056
+ const hex = spanId.replace(/[^a-fA-F0-9]/g, "").toLowerCase();
1057
+ return (hex + "0000000000000000").slice(0, 16);
1058
+ }
1059
+ function createRequestContext(method, url) {
1060
+ const runtime = currentRuntime();
1061
+ if (!runtime) return null;
1062
+ const requestId = generateHttpId();
1063
+ const parentSpanId = runtime.tracing.getCurrentSpanId() ?? "";
1064
+ const span = runtime.tracing.startSpan("mobile.http", {
1065
+ description: `${method.toUpperCase()} ${url}`,
1066
+ tags: { requestId, method: method.toUpperCase(), platform: runtime.platform ?? "react-native" }
1067
+ });
1068
+ const traceId = runtime.tracing.getTraceId();
1069
+ const spanId = span.spanId;
1070
+ return {
1071
+ traceId,
1072
+ requestId,
1073
+ spanId,
1074
+ parentSpanId,
1075
+ traceparent: `00-${normalizeTraceId(traceId)}-${normalizeSpanId(spanId)}-01`,
1076
+ span
1077
+ };
1078
+ }
1079
+ function propagationHeaders(ctx) {
1080
+ const headers = {
1081
+ traceparent: ctx.traceparent,
1082
+ "x-allstak-trace-id": ctx.traceId,
1083
+ "x-allstak-request-id": ctx.requestId
1084
+ };
1085
+ if (ctx.parentSpanId) headers["x-allstak-parent-span-id"] = ctx.parentSpanId;
1086
+ return headers;
1087
+ }
1088
+ function mergeHeaders(headers, propagation) {
1089
+ const entries = Object.entries(propagation);
1090
+ if (typeof Headers !== "undefined" && headers instanceof Headers) {
1091
+ const next2 = new Headers(headers);
1092
+ for (const [k, v] of entries) if (!next2.has(k)) next2.set(k, v);
1093
+ return next2;
1094
+ }
1095
+ if (Array.isArray(headers)) {
1096
+ const existing = new Set(headers.map(([k]) => String(k).toLowerCase()));
1097
+ const next2 = [...headers];
1098
+ for (const [k, v] of entries) if (!existing.has(k.toLowerCase())) next2.push([k, v]);
1099
+ return next2;
1100
+ }
1101
+ const next = { ...headers ?? {} };
1102
+ const lower = new Set(Object.keys(next).map((k) => k.toLowerCase()));
1103
+ for (const [k, v] of entries) if (!lower.has(k.toLowerCase())) next[k] = v;
1104
+ return next;
1105
+ }
1106
+ function injectFetchHeaders(input, init, ctx) {
1107
+ const headers = mergeHeaders(init?.headers ?? (input && typeof input === "object" ? input.headers : void 0), propagationHeaders(ctx));
1108
+ return { input, init: { ...init ?? {}, headers } };
1109
+ }
1110
+ function recordTimeline(kind, label, ctx, data) {
1111
+ try {
1112
+ currentRuntime()?.replay?.recordTimelineMarker?.(kind, label, data, {
1113
+ traceId: ctx.traceId,
1114
+ requestId: ctx.requestId,
1115
+ spanId: ctx.spanId,
1116
+ release: currentRuntime()?.release,
1117
+ dist: currentRuntime()?.dist
1118
+ });
1119
+ } catch {
1120
+ }
1121
+ }
762
1122
  function patchFetch() {
763
1123
  const g = globalThis;
764
1124
  if (typeof g.fetch !== "function") return;
@@ -776,23 +1136,37 @@ function patchFetch() {
776
1136
  return original.call(this, input, init);
777
1137
  }
778
1138
  const start = Date.now();
779
- const reqHeaders = sanitizeHeaders(headersToObject(init?.headers ?? (input && input.headers)), opts);
780
- const reqBody = captureBody(init?.body, opts.captureRequestBody, opts.maxBodyBytes);
1139
+ const ctx = createRequestContext(method, sanitizedUrl);
1140
+ const requestInit = ctx ? injectFetchHeaders(input, init, ctx).init : init;
1141
+ if (ctx) recordTimeline("request", "request_started", ctx, { method, url: sanitizedUrl });
1142
+ const reqHeaders = sanitizeHeaders(headersToObject(requestInit?.headers ?? (input && input.headers)), opts);
1143
+ const reqBody = captureBodyResult(requestInit?.body, opts.captureRequestBody, opts.maxBodyBytes, opts, reqHeaders?.["content-type"]);
781
1144
  const reqSize = safeByteLength(typeof init?.body === "string" ? init.body : void 0);
782
1145
  let response;
783
1146
  try {
784
- response = await original.call(this, input, init);
1147
+ response = await original.call(this, input, requestInit);
785
1148
  } catch (err) {
1149
+ ctx?.span.finish("error");
1150
+ if (ctx) recordTimeline("exception", "request_failed", ctx, { method, url: sanitizedUrl, error: String(err?.message ?? err) });
786
1151
  safeCapture({
787
1152
  type: "http_request",
1153
+ traceId: ctx?.traceId ?? generateHttpId(),
1154
+ requestId: ctx?.requestId ?? generateHttpId(),
1155
+ spanId: ctx?.spanId,
1156
+ parentSpanId: ctx?.parentSpanId,
788
1157
  method,
789
1158
  url: sanitizedUrl,
790
1159
  statusCode: 0,
791
1160
  durationMs: Date.now() - start,
792
- requestBody: reqBody,
1161
+ requestBody: reqBody.body,
793
1162
  requestHeaders: reqHeaders,
794
1163
  requestSize: reqSize,
795
- error: String(err?.message ?? err)
1164
+ requestBodyStatus: reqBody.status,
1165
+ requestBodyRedactedFields: reqBody.redactedFields,
1166
+ requestBodyTruncated: reqBody.truncated,
1167
+ capturePolicy: reqBody.capturePolicy,
1168
+ error: String(err?.message ?? err),
1169
+ linkConfidence: "exact"
796
1170
  });
797
1171
  throw err;
798
1172
  }
@@ -808,25 +1182,41 @@ function patchFetch() {
808
1182
  try {
809
1183
  const cloned = response.clone();
810
1184
  const text = await cloned.text();
811
- respBody = captureBody(text, true, opts.maxBodyBytes);
1185
+ respBody = captureBodyResult(text, true, opts.maxBodyBytes, opts, respHeaders?.["content-type"]).body;
812
1186
  if (respSize == null) respSize = safeByteLength(text);
813
1187
  } catch {
814
1188
  }
815
1189
  }
816
1190
  } catch {
817
1191
  }
1192
+ const respBodyResult = captureBodyResult(respBody, opts.captureResponseBody && respBody !== void 0, opts.maxBodyBytes, opts, respHeaders?.["content-type"]);
1193
+ const isError = response.status >= 400;
1194
+ ctx?.span.setTag("requestId", ctx.requestId).setTag("http.status_code", String(response.status)).finish(isError ? "error" : "ok");
1195
+ if (ctx) recordTimeline(isError ? "exception" : "response", isError ? "request_failed" : "response_received", ctx, { statusCode: response.status, durationMs });
818
1196
  safeCapture({
819
1197
  type: "http_request",
1198
+ traceId: ctx?.traceId ?? generateHttpId(),
1199
+ requestId: ctx?.requestId ?? generateHttpId(),
1200
+ spanId: ctx?.spanId,
1201
+ parentSpanId: ctx?.parentSpanId,
820
1202
  method,
821
1203
  url: sanitizedUrl,
822
1204
  statusCode: response.status,
823
1205
  durationMs,
824
- requestBody: reqBody,
1206
+ requestBody: reqBody.body,
825
1207
  requestHeaders: reqHeaders,
826
1208
  requestSize: reqSize,
827
- responseBody: respBody,
1209
+ responseBody: respBodyResult.body,
828
1210
  responseHeaders: respHeaders,
829
- responseSize: respSize
1211
+ responseSize: respSize,
1212
+ requestBodyStatus: reqBody.status,
1213
+ responseBodyStatus: respBodyResult.status,
1214
+ requestBodyRedactedFields: reqBody.redactedFields,
1215
+ responseBodyRedactedFields: respBodyResult.redactedFields,
1216
+ requestBodyTruncated: reqBody.truncated,
1217
+ responseBodyTruncated: respBodyResult.truncated,
1218
+ capturePolicy: `${reqBody.capturePolicy};${respBodyResult.capturePolicy}`,
1219
+ linkConfidence: "exact"
830
1220
  });
831
1221
  return response;
832
1222
  };
@@ -864,7 +1254,17 @@ function patchXhr() {
864
1254
  return origSend.call(this, body);
865
1255
  }
866
1256
  const reqHeaders = sanitizeHeaders(this.__allstak_headers__, opts);
867
- const reqBody = captureBody(body, opts.captureRequestBody, opts.maxBodyBytes);
1257
+ const ctx = createRequestContext(method, sanitizedUrl);
1258
+ if (ctx) {
1259
+ for (const [k, v] of Object.entries(propagationHeaders(ctx))) {
1260
+ try {
1261
+ origSetRequestHeader.call(this, k, v);
1262
+ } catch {
1263
+ }
1264
+ }
1265
+ recordTimeline("request", "request_started", ctx, { method, url: sanitizedUrl });
1266
+ }
1267
+ const reqBody = captureBodyResult(body, opts.captureRequestBody, opts.maxBodyBytes, opts, reqHeaders?.["content-type"]);
868
1268
  const reqSize = safeByteLength(typeof body === "string" ? body : void 0);
869
1269
  const finish = (statusCode, error) => {
870
1270
  const durationMs = Date.now() - start;
@@ -882,28 +1282,43 @@ function patchXhr() {
882
1282
  }
883
1283
  respHeaders = sanitizeHeaders(dict, liveOpts);
884
1284
  }
1285
+ let respBodyResult = captureBodyResult(void 0, false, liveOpts.maxBodyBytes, liveOpts);
885
1286
  if (liveOpts.captureResponseBody) {
886
1287
  const text = this.responseText;
887
1288
  if (typeof text === "string") {
888
- respBody = captureBody(text, true, liveOpts.maxBodyBytes);
1289
+ respBodyResult = captureBodyResult(text, true, liveOpts.maxBodyBytes, liveOpts, respHeaders?.["content-type"]);
1290
+ respBody = respBodyResult.body;
889
1291
  respSize = safeByteLength(text);
890
1292
  }
891
1293
  }
892
1294
  } catch {
893
1295
  }
1296
+ const failed = !!error || statusCode >= 400;
1297
+ ctx?.span.setTag("requestId", ctx.requestId).setTag("http.status_code", String(statusCode)).finish(failed ? "error" : "ok");
1298
+ if (ctx) recordTimeline(failed ? "exception" : "response", failed ? "request_failed" : "response_received", ctx, { statusCode, durationMs, error });
894
1299
  safeCapture({
895
1300
  type: "http_request",
1301
+ traceId: ctx?.traceId ?? generateHttpId(),
1302
+ requestId: ctx?.requestId ?? generateHttpId(),
1303
+ spanId: ctx?.spanId,
1304
+ parentSpanId: ctx?.parentSpanId,
896
1305
  method,
897
1306
  url: sanitizedUrl,
898
1307
  statusCode,
899
1308
  durationMs,
900
- requestBody: reqBody,
1309
+ requestBody: reqBody.body,
901
1310
  requestHeaders: reqHeaders,
902
1311
  requestSize: reqSize,
903
1312
  responseBody: respBody,
904
1313
  responseHeaders: respHeaders,
905
1314
  responseSize: respSize,
906
- error
1315
+ requestBodyStatus: reqBody.status,
1316
+ responseBodyStatus: respBody ? "captured" : opts.captureResponseBody ? "empty" : "disabled",
1317
+ requestBodyRedactedFields: reqBody.redactedFields,
1318
+ requestBodyTruncated: reqBody.truncated,
1319
+ capturePolicy: reqBody.capturePolicy,
1320
+ error,
1321
+ linkConfidence: "exact"
907
1322
  });
908
1323
  };
909
1324
  this.addEventListener?.("load", () => finish(this.status || 0));
@@ -922,10 +1337,17 @@ function instrumentAxiosInstance(axiosInstance, module2, opts) {
922
1337
  axiosInstance.interceptors.request.use((config) => {
923
1338
  try {
924
1339
  const rawUrl = (config.baseURL ? config.baseURL.replace(/\/$/, "") : "") + (config.url || "");
1340
+ const method = String(config.method || "GET").toUpperCase();
1341
+ const ctx = createRequestContext(method, rawUrl);
1342
+ if (ctx) {
1343
+ config.headers = mergeHeaders(config.headers, propagationHeaders(ctx));
1344
+ recordTimeline("request", "request_started", ctx, { method, url: rawUrl });
1345
+ }
925
1346
  reqStarts.set(config, {
926
1347
  start: Date.now(),
927
- method: String(config.method || "GET").toUpperCase(),
928
- rawUrl
1348
+ method,
1349
+ rawUrl,
1350
+ ctx
929
1351
  });
930
1352
  } catch {
931
1353
  }
@@ -939,21 +1361,36 @@ function instrumentAxiosInstance(axiosInstance, module2, opts) {
939
1361
  if (!shouldCaptureUrl(meta.rawUrl, opts)) return;
940
1362
  const sanitizedUrl = redactUrl(meta.rawUrl, opts);
941
1363
  const reqHeaders = sanitizeHeaders(headersToObject(cfg.headers), opts);
942
- const reqBody = captureBody(cfg.data, opts.captureRequestBody, opts.maxBodyBytes);
1364
+ const reqBody = captureBodyResult(cfg.data, opts.captureRequestBody, opts.maxBodyBytes, opts, reqHeaders?.["content-type"]);
943
1365
  const respHeaders = sanitizeHeaders(headersToObject(response?.headers), opts);
944
- const respBody = captureBody(response?.data, opts.captureResponseBody, opts.maxBodyBytes);
1366
+ const respBody = captureBodyResult(response?.data, opts.captureResponseBody, opts.maxBodyBytes, opts, respHeaders?.["content-type"]);
1367
+ const failed = !!error || statusCode >= 400;
1368
+ meta.ctx?.span.setTag("requestId", meta.ctx.requestId).setTag("http.status_code", String(statusCode)).finish(failed ? "error" : "ok");
1369
+ if (meta.ctx) recordTimeline(failed ? "exception" : "response", failed ? "request_failed" : "response_received", meta.ctx, { statusCode, error });
945
1370
  try {
946
1371
  module2.capture({
947
1372
  type: "http_request",
1373
+ traceId: meta.ctx?.traceId ?? generateHttpId(),
1374
+ requestId: meta.ctx?.requestId ?? generateHttpId(),
1375
+ spanId: meta.ctx?.spanId,
1376
+ parentSpanId: meta.ctx?.parentSpanId,
948
1377
  method: meta.method,
949
1378
  url: sanitizedUrl,
950
1379
  statusCode,
951
1380
  durationMs: Date.now() - meta.start,
952
- requestBody: reqBody,
1381
+ requestBody: reqBody.body,
953
1382
  requestHeaders: reqHeaders,
954
- responseBody: respBody,
1383
+ responseBody: respBody.body,
955
1384
  responseHeaders: respHeaders,
956
- error
1385
+ requestBodyStatus: reqBody.status,
1386
+ responseBodyStatus: respBody.status,
1387
+ requestBodyRedactedFields: reqBody.redactedFields,
1388
+ responseBodyRedactedFields: respBody.redactedFields,
1389
+ requestBodyTruncated: reqBody.truncated,
1390
+ responseBodyTruncated: respBody.truncated,
1391
+ capturePolicy: `${reqBody.capturePolicy};${respBody.capturePolicy}`,
1392
+ error,
1393
+ linkConfidence: "exact"
957
1394
  });
958
1395
  } catch {
959
1396
  }
@@ -979,10 +1416,11 @@ function tryAutoInstrumentAxios(module2, opts) {
979
1416
  } catch {
980
1417
  }
981
1418
  }
982
- function installHttpInstrumentation(module2, options, ownIngestHost) {
1419
+ function installHttpInstrumentation(module2, options, ownIngestHost, runtime) {
983
1420
  const bound = bind(options, ownIngestHost);
984
1421
  _currentModule = module2;
985
1422
  _currentOpts = bound;
1423
+ _currentRuntime = runtime;
986
1424
  try {
987
1425
  patchFetch();
988
1426
  } catch {
@@ -999,11 +1437,16 @@ function installHttpInstrumentation(module2, options, ownIngestHost) {
999
1437
  instrumentAxios: (axios) => instrumentAxiosInstance(axios, module2, bound)
1000
1438
  };
1001
1439
  }
1440
+ function unbindHttpInstrumentation() {
1441
+ _currentModule = null;
1442
+ _currentOpts = null;
1443
+ _currentRuntime = null;
1444
+ }
1002
1445
 
1003
1446
  // src/client.ts
1004
1447
  var INGEST_HOST = "https://api.allstak.sa";
1005
1448
  var SDK_NAME = "allstak-react-native";
1006
- var SDK_VERSION = "0.3.0";
1449
+ var SDK_VERSION = "0.3.1";
1007
1450
  var ERRORS_PATH = "/ingest/v1/errors";
1008
1451
  var LOGS_PATH = "/ingest/v1/logs";
1009
1452
  var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
@@ -1019,6 +1462,17 @@ function generateId() {
1019
1462
  const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
1020
1463
  return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
1021
1464
  }
1465
+ function stringContextValue(context, key) {
1466
+ const value = context[key];
1467
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1468
+ }
1469
+ function firstRecentRequestId(recentFailed) {
1470
+ for (let i = recentFailed.length - 1; i >= 0; i--) {
1471
+ const requestId = recentFailed[i]?.requestId;
1472
+ if (requestId && requestId.trim().length > 0) return requestId;
1473
+ }
1474
+ return void 0;
1475
+ }
1022
1476
  var AllStakClient = class {
1023
1477
  constructor(config) {
1024
1478
  this.breadcrumbs = [];
@@ -1026,9 +1480,6 @@ var AllStakClient = class {
1026
1480
  this.replay = null;
1027
1481
  this.httpRequests = null;
1028
1482
  this._instrumentAxios = null;
1029
- if (!config.apiKey) {
1030
- throw new Error("AllStak: config.apiKey is required");
1031
- }
1032
1483
  this.config = { ...config };
1033
1484
  if (!this.config.environment) this.config.environment = "production";
1034
1485
  if (!this.config.sdkName) this.config.sdkName = SDK_NAME;
@@ -1037,7 +1488,7 @@ var AllStakClient = class {
1037
1488
  this.sessionId = generateId();
1038
1489
  this.maxBreadcrumbs = config.maxBreadcrumbs ?? DEFAULT_MAX_BREADCRUMBS;
1039
1490
  const baseUrl = (config.host ?? INGEST_HOST).replace(/\/$/, "");
1040
- this.transport = new HttpTransport(baseUrl, config.apiKey);
1491
+ this.transport = new HttpTransport(baseUrl, config.apiKey ?? "", Boolean(config.apiKey));
1041
1492
  this.tracing = new TracingModule(this.transport, {
1042
1493
  service: config.service ?? config.release ?? "",
1043
1494
  environment: this.config.environment ?? "production",
@@ -1064,7 +1515,15 @@ var AllStakClient = class {
1064
1515
  const { instrumentAxios } = installHttpInstrumentation(
1065
1516
  this.httpRequests,
1066
1517
  config.httpTracking ?? {},
1067
- baseUrl
1518
+ baseUrl,
1519
+ {
1520
+ tracing: this.tracing,
1521
+ replay: this.replay,
1522
+ release: this.config.release,
1523
+ dist: this.config.dist,
1524
+ platform: this.config.platform,
1525
+ environment: this.config.environment
1526
+ }
1068
1527
  );
1069
1528
  this._instrumentAxios = instrumentAxios;
1070
1529
  } catch {
@@ -1088,33 +1547,67 @@ var AllStakClient = class {
1088
1547
  if (!this.passesSampleRate()) return;
1089
1548
  const frames = parseStack(error.stack).map((f) => ({
1090
1549
  ...f,
1091
- platform: this.config.platform
1550
+ platform: this.config.platform,
1551
+ debugId: resolveDebugId(f.filename)
1092
1552
  }));
1553
+ const debugIdSet = /* @__PURE__ */ new Set();
1554
+ for (const f of frames) if (f.debugId) debugIdSet.add(f.debugId);
1555
+ const debugMeta = debugIdSet.size > 0 ? { images: Array.from(debugIdSet).map((id2) => ({ type: "sourcemap", debugId: id2 })) } : void 0;
1093
1556
  const stackTrace = frames.length > 0 ? frames.map(frameToString) : void 0;
1094
1557
  const currentBreadcrumbs = this.breadcrumbs.length > 0 ? [...this.breadcrumbs] : void 0;
1095
1558
  this.breadcrumbs = [];
1096
1559
  const exceptionClass = (error.name && error.name !== "Error" ? error.name : void 0) || error.constructor?.name || "Error";
1097
1560
  const eff = this.effective();
1098
1561
  const traceContext = {};
1099
- const traceId = this.tracing.getTraceId();
1562
+ const recentFailed = this.httpRequests?.getRecentFailed() ?? [];
1563
+ const linkedRequest = recentFailed.length > 0 ? recentFailed[recentFailed.length - 1] : void 0;
1564
+ if (linkedRequest?.traceId) this.tracing.setTraceId(linkedRequest.traceId);
1565
+ const exceptionSpan = linkedRequest ? this.tracing.startSpan("mobile.exception", {
1566
+ description: error.message,
1567
+ tags: {
1568
+ requestId: linkedRequest.requestId,
1569
+ exceptionClass
1570
+ }
1571
+ }) : null;
1572
+ exceptionSpan?.finish("error");
1573
+ const traceId = linkedRequest?.traceId ?? this.tracing.getTraceId();
1100
1574
  if (traceId) traceContext.traceId = traceId;
1101
- const spanId = this.tracing.getCurrentSpanId();
1575
+ const spanId = exceptionSpan?.spanId || this.tracing.getCurrentSpanId();
1102
1576
  if (spanId) traceContext.spanId = spanId;
1103
- const recentFailed = this.httpRequests?.getRecentFailed() ?? [];
1104
1577
  if (recentFailed.length > 0) {
1105
1578
  traceContext["http.recentFailed"] = recentFailed.map((r) => ({
1106
1579
  method: r.method,
1107
1580
  url: r.url,
1108
1581
  statusCode: r.statusCode,
1109
1582
  durationMs: r.durationMs,
1110
- error: r.error
1583
+ error: r.error,
1584
+ requestId: r.requestId,
1585
+ traceId: r.traceId,
1586
+ confidence: r.requestId === linkedRequest?.requestId ? "inferred" : "weak"
1111
1587
  }));
1588
+ traceContext["http.linkConfidence"] = "inferred";
1589
+ }
1590
+ try {
1591
+ if (!linkedRequest) throw new Error("no linked request");
1592
+ this.replay?.recordTimelineMarker?.("exception", "exception_captured", {
1593
+ exceptionClass,
1594
+ message: error.message,
1595
+ requestLinkConfidence: linkedRequest ? "inferred" : "none"
1596
+ }, {
1597
+ traceId,
1598
+ requestId: linkedRequest?.requestId,
1599
+ spanId: spanId ?? void 0,
1600
+ release: this.config.release,
1601
+ dist: this.config.dist
1602
+ });
1603
+ } catch {
1112
1604
  }
1113
1605
  const payload = {
1114
1606
  exceptionClass,
1115
1607
  message: error.message,
1116
1608
  stackTrace,
1117
1609
  frames: frames.length > 0 ? frames : void 0,
1610
+ debugMeta,
1118
1611
  platform: this.config.platform,
1119
1612
  sdkName: this.config.sdkName,
1120
1613
  sdkVersion: this.config.sdkVersion,
@@ -1123,12 +1616,29 @@ var AllStakClient = class {
1123
1616
  environment: this.config.environment,
1124
1617
  release: this.config.release,
1125
1618
  sessionId: this.sessionId,
1619
+ traceId: stringContextValue(traceContext, "traceId"),
1620
+ spanId: stringContextValue(traceContext, "spanId"),
1621
+ requestId: linkedRequest?.requestId ?? firstRecentRequestId(recentFailed),
1622
+ service: this.config.service,
1126
1623
  user: eff.user,
1127
1624
  metadata: { ...this.buildMetadata(context), ...traceContext },
1128
1625
  breadcrumbs: currentBreadcrumbs,
1129
1626
  fingerprint: eff.fingerprint
1130
1627
  };
1131
- this.sendThroughBeforeSend(payload);
1628
+ if (this.shouldCaptureScreenshot()) {
1629
+ void this.withScreenshotMetadata(error, payload).then((enriched) => this.sendThroughBeforeSend(enriched)).catch(() => this.sendThroughBeforeSend({
1630
+ ...payload,
1631
+ metadata: { ...payload.metadata ?? {}, "screenshot.status": "failed" }
1632
+ }));
1633
+ return;
1634
+ }
1635
+ this.sendThroughBeforeSend({
1636
+ ...payload,
1637
+ metadata: {
1638
+ ...payload.metadata ?? {},
1639
+ "screenshot.status": this.config.screenshot?.enabled ? "unsupported" : "disabled"
1640
+ }
1641
+ });
1132
1642
  }
1133
1643
  /** Start a new span. Auto-parented to any currently-active span. */
1134
1644
  startSpan(operation, options) {
@@ -1169,6 +1679,9 @@ var AllStakClient = class {
1169
1679
  environment: this.config.environment,
1170
1680
  release: this.config.release,
1171
1681
  sessionId: this.sessionId,
1682
+ traceId: this.tracing.getTraceId(),
1683
+ spanId: this.tracing.getCurrentSpanId() ?? void 0,
1684
+ service: this.config.service,
1172
1685
  user: eff.user,
1173
1686
  metadata: this.buildMetadata(),
1174
1687
  fingerprint: eff.fingerprint
@@ -1252,6 +1765,9 @@ var AllStakClient = class {
1252
1765
  getConfig() {
1253
1766
  return this.config;
1254
1767
  }
1768
+ getTransportStats() {
1769
+ return this.transport.getStats();
1770
+ }
1255
1771
  destroy() {
1256
1772
  this.tracing.destroy();
1257
1773
  if (this.replay) {
@@ -1262,6 +1778,7 @@ var AllStakClient = class {
1262
1778
  this.httpRequests.destroy();
1263
1779
  this.httpRequests = null;
1264
1780
  }
1781
+ unbindHttpInstrumentation();
1265
1782
  this._instrumentAxios = null;
1266
1783
  this.breadcrumbs = [];
1267
1784
  }
@@ -1280,6 +1797,58 @@ var AllStakClient = class {
1280
1797
  metadata: this.buildMetadata()
1281
1798
  });
1282
1799
  }
1800
+ shouldCaptureScreenshot() {
1801
+ const screenshot = this.config.screenshot;
1802
+ if (!screenshot?.enabled || screenshot.captureOnError === false || !screenshot.provider) return false;
1803
+ const sampleRate = screenshot.sampleRate ?? 1;
1804
+ return !(sampleRate <= 0 || sampleRate < 1 && Math.random() >= sampleRate);
1805
+ }
1806
+ async withScreenshotMetadata(error, payload) {
1807
+ const screenshot = this.config.screenshot;
1808
+ if (!screenshot?.provider) {
1809
+ return { ...payload, metadata: { ...payload.metadata ?? {}, "screenshot.status": "unsupported" } };
1810
+ }
1811
+ const timeoutMs = Math.max(100, Math.min(screenshot.timeoutMs ?? 1500, 5e3));
1812
+ const maxBytes = Math.max(1024, screenshot.maxBytes ?? 2e5);
1813
+ try {
1814
+ const artifact = await Promise.race([
1815
+ Promise.resolve(screenshot.provider({
1816
+ type: "error",
1817
+ error,
1818
+ traceId: payload.traceId,
1819
+ requestId: payload.requestId
1820
+ })),
1821
+ new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs))
1822
+ ]);
1823
+ if (!artifact) {
1824
+ return { ...payload, metadata: { ...payload.metadata ?? {}, "screenshot.status": "timeout_or_empty" } };
1825
+ }
1826
+ const size = artifact.sizeBytes ?? byteSize(artifact.data);
1827
+ if (size > maxBytes) {
1828
+ this.transport.noteDropped();
1829
+ return {
1830
+ ...payload,
1831
+ metadata: { ...payload.metadata ?? {}, "screenshot.status": "dropped_too_large", "screenshot.sizeBytes": size }
1832
+ };
1833
+ }
1834
+ return {
1835
+ ...payload,
1836
+ metadata: {
1837
+ ...payload.metadata ?? {},
1838
+ "screenshot.status": "captured",
1839
+ "screenshot.contentType": artifact.contentType,
1840
+ "screenshot.width": artifact.width,
1841
+ "screenshot.height": artifact.height,
1842
+ "screenshot.sizeBytes": size,
1843
+ "screenshot.redacted": artifact.redacted ?? false,
1844
+ "screenshot.redactionStrategy": artifact.redactionStrategy,
1845
+ ...artifact.data ? { "screenshot.data": artifact.data } : {}
1846
+ }
1847
+ };
1848
+ } catch {
1849
+ return { ...payload, metadata: { ...payload.metadata ?? {}, "screenshot.status": "failed" } };
1850
+ }
1851
+ }
1283
1852
  passesSampleRate() {
1284
1853
  const r = this.config.sampleRate;
1285
1854
  if (typeof r !== "number" || r >= 1) return true;
@@ -1378,10 +1947,22 @@ var AllStakClient = class {
1378
1947
  }
1379
1948
  };
1380
1949
  var instance = null;
1381
- function ensureInit() {
1382
- if (!instance) throw new Error("AllStak.init() must be called before using the SDK");
1950
+ function maybeInit() {
1383
1951
  return instance;
1384
1952
  }
1953
+ function noopSpan(operation = "") {
1954
+ return new Span("", "", "", operation, "", "", "", {}, () => void 0);
1955
+ }
1956
+ function emptyStats() {
1957
+ return {
1958
+ queued: 0,
1959
+ sent: 0,
1960
+ failed: 0,
1961
+ dropped: 0,
1962
+ consecutiveFailures: 0,
1963
+ circuitOpenUntil: 0
1964
+ };
1965
+ }
1385
1966
  function __safeAddBreadcrumbForInstrumentation(type, message, level, data) {
1386
1967
  try {
1387
1968
  instance?.addBreadcrumb(type, message, level, data);
@@ -1390,51 +1971,99 @@ function __safeAddBreadcrumbForInstrumentation(type, message, level, data) {
1390
1971
  }
1391
1972
  var AllStak = {
1392
1973
  init(config) {
1393
- if (instance) instance.destroy();
1394
- instance = new AllStakClient(config);
1395
- return instance;
1974
+ try {
1975
+ if (instance) instance.destroy();
1976
+ instance = new AllStakClient(config);
1977
+ return instance;
1978
+ } catch {
1979
+ instance = new AllStakClient({ ...config, apiKey: "" });
1980
+ return instance;
1981
+ }
1396
1982
  },
1397
1983
  captureException(error, context) {
1398
- ensureInit().captureException(error, context);
1984
+ try {
1985
+ maybeInit()?.captureException(error, context);
1986
+ } catch {
1987
+ }
1399
1988
  },
1400
1989
  captureMessage(message, level = "info", options) {
1401
- ensureInit().captureMessage(message, level, options);
1990
+ try {
1991
+ maybeInit()?.captureMessage(message, level, options);
1992
+ } catch {
1993
+ }
1402
1994
  },
1403
1995
  addBreadcrumb(type, message, level, data) {
1404
- ensureInit().addBreadcrumb(type, message, level, data);
1996
+ try {
1997
+ maybeInit()?.addBreadcrumb(type, message, level, data);
1998
+ } catch {
1999
+ }
1405
2000
  },
1406
2001
  clearBreadcrumbs() {
1407
- ensureInit().clearBreadcrumbs();
2002
+ try {
2003
+ maybeInit()?.clearBreadcrumbs();
2004
+ } catch {
2005
+ }
1408
2006
  },
1409
2007
  setUser(user) {
1410
- ensureInit().setUser(user);
2008
+ try {
2009
+ maybeInit()?.setUser(user);
2010
+ } catch {
2011
+ }
1411
2012
  },
1412
2013
  setTag(key, value) {
1413
- ensureInit().setTag(key, value);
2014
+ try {
2015
+ maybeInit()?.setTag(key, value);
2016
+ } catch {
2017
+ }
1414
2018
  },
1415
2019
  setTags(tags) {
1416
- ensureInit().setTags(tags);
2020
+ try {
2021
+ maybeInit()?.setTags(tags);
2022
+ } catch {
2023
+ }
1417
2024
  },
1418
2025
  setExtra(key, value) {
1419
- ensureInit().setExtra(key, value);
2026
+ try {
2027
+ maybeInit()?.setExtra(key, value);
2028
+ } catch {
2029
+ }
1420
2030
  },
1421
2031
  setExtras(extras) {
1422
- ensureInit().setExtras(extras);
2032
+ try {
2033
+ maybeInit()?.setExtras(extras);
2034
+ } catch {
2035
+ }
1423
2036
  },
1424
2037
  setContext(name, ctx) {
1425
- ensureInit().setContext(name, ctx);
2038
+ try {
2039
+ maybeInit()?.setContext(name, ctx);
2040
+ } catch {
2041
+ }
1426
2042
  },
1427
2043
  setLevel(level) {
1428
- ensureInit().setLevel(level);
2044
+ try {
2045
+ maybeInit()?.setLevel(level);
2046
+ } catch {
2047
+ }
1429
2048
  },
1430
2049
  setFingerprint(fingerprint) {
1431
- ensureInit().setFingerprint(fingerprint);
2050
+ try {
2051
+ maybeInit()?.setFingerprint(fingerprint);
2052
+ } catch {
2053
+ }
1432
2054
  },
1433
2055
  flush(timeoutMs) {
1434
- return ensureInit().flush(timeoutMs);
2056
+ try {
2057
+ return maybeInit()?.flush(timeoutMs) ?? Promise.resolve(true);
2058
+ } catch {
2059
+ return Promise.resolve(false);
2060
+ }
1435
2061
  },
1436
2062
  setIdentity(identity) {
1437
- ensureInit().setIdentity(identity);
2063
+ try {
2064
+ maybeInit()?.setIdentity(identity);
2065
+ } catch {
2066
+ }
1438
2067
  },
1439
2068
  /**
1440
2069
  * Run `callback` with a fresh scoped context. Any user/tag/extra/context/
@@ -1443,37 +2072,79 @@ var AllStak = {
1443
2072
  * for async callbacks and thrown errors.
1444
2073
  */
1445
2074
  withScope(callback) {
1446
- return ensureInit().withScope(callback);
2075
+ try {
2076
+ const client = maybeInit();
2077
+ return client ? client.withScope(callback) : callback(new Scope());
2078
+ } catch {
2079
+ return callback(new Scope());
2080
+ }
1447
2081
  },
1448
2082
  startSpan(operation, options) {
1449
- return ensureInit().startSpan(operation, options);
2083
+ try {
2084
+ return maybeInit()?.startSpan(operation, options) ?? noopSpan(operation);
2085
+ } catch {
2086
+ return noopSpan(operation);
2087
+ }
1450
2088
  },
1451
2089
  getTraceId() {
1452
- return ensureInit().getTraceId();
2090
+ try {
2091
+ return maybeInit()?.getTraceId() ?? "";
2092
+ } catch {
2093
+ return "";
2094
+ }
1453
2095
  },
1454
2096
  setTraceId(traceId) {
1455
- ensureInit().setTraceId(traceId);
2097
+ try {
2098
+ maybeInit()?.setTraceId(traceId);
2099
+ } catch {
2100
+ }
1456
2101
  },
1457
2102
  getCurrentSpanId() {
1458
- return ensureInit().getCurrentSpanId();
2103
+ try {
2104
+ return maybeInit()?.getCurrentSpanId() ?? null;
2105
+ } catch {
2106
+ return null;
2107
+ }
1459
2108
  },
1460
2109
  resetTrace() {
1461
- ensureInit().resetTrace();
2110
+ try {
2111
+ maybeInit()?.resetTrace();
2112
+ } catch {
2113
+ }
1462
2114
  },
1463
2115
  /** Access the privacy-first replay surrogate (or null if disabled / sampled out). */
1464
2116
  getReplay() {
1465
- return ensureInit().getReplay();
2117
+ try {
2118
+ return maybeInit()?.getReplay() ?? null;
2119
+ } catch {
2120
+ return null;
2121
+ }
1466
2122
  },
1467
2123
  /** Manually instrument an axios instance. No-op when HTTP tracking is off. */
1468
2124
  instrumentAxios(axios) {
1469
- return ensureInit().instrumentAxios(axios);
2125
+ try {
2126
+ return maybeInit()?.instrumentAxios(axios) ?? axios;
2127
+ } catch {
2128
+ return axios;
2129
+ }
1470
2130
  },
1471
2131
  getSessionId() {
1472
- return ensureInit().getSessionId();
2132
+ try {
2133
+ return maybeInit()?.getSessionId() ?? "";
2134
+ } catch {
2135
+ return "";
2136
+ }
1473
2137
  },
1474
2138
  getConfig() {
1475
2139
  return instance?.getConfig() ?? null;
1476
2140
  },
2141
+ getTransportStats() {
2142
+ try {
2143
+ return maybeInit()?.getTransportStats() ?? emptyStats();
2144
+ } catch {
2145
+ return emptyStats();
2146
+ }
2147
+ },
1477
2148
  destroy() {
1478
2149
  instance?.destroy();
1479
2150
  instance = null;
@@ -1483,6 +2154,17 @@ var AllStak = {
1483
2154
  return instance;
1484
2155
  }
1485
2156
  };
2157
+ function byteSize(value) {
2158
+ if (!value) return 0;
2159
+ try {
2160
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(value).length;
2161
+ } catch {
2162
+ }
2163
+ return value.length;
2164
+ }
2165
+
2166
+ // src/provider.tsx
2167
+ var React = __toESM(require("react"));
1486
2168
 
1487
2169
  // src/auto-breadcrumbs.ts
1488
2170
  var FETCH_FLAG2 = "__allstak_fetch_patched__";
@@ -1530,36 +2212,92 @@ function instrumentFetch(addBreadcrumb, ownBaseUrl) {
1530
2212
  wrapped[FETCH_FLAG2] = true;
1531
2213
  g.fetch = wrapped;
1532
2214
  }
1533
- function instrumentConsole(addBreadcrumb) {
2215
+ var CONSOLE_DEFAULTS = {
2216
+ log: false,
2217
+ info: false,
2218
+ warn: true,
2219
+ error: true
2220
+ };
2221
+ var CONSOLE_METHOD_TO_LEVEL = {
2222
+ log: "info",
2223
+ info: "info",
2224
+ warn: "warn",
2225
+ error: "error"
2226
+ };
2227
+ var MAX_ARG_BYTES = 5e3;
2228
+ function instrumentConsole(addBreadcrumb, options = {}) {
1534
2229
  if (typeof console === "undefined") return;
1535
2230
  if (console[CONSOLE_FLAG]) return;
1536
- const origWarn = console.warn;
1537
- const origError = console.error;
1538
- console.warn = function(...args) {
1539
- try {
1540
- addBreadcrumb("log", args.map(safeString).join(" "), "warn");
1541
- } catch {
1542
- }
1543
- return origWarn.apply(console, args);
2231
+ const opts = {
2232
+ log: options.log ?? CONSOLE_DEFAULTS.log,
2233
+ info: options.info ?? CONSOLE_DEFAULTS.info,
2234
+ warn: options.warn ?? CONSOLE_DEFAULTS.warn,
2235
+ error: options.error ?? CONSOLE_DEFAULTS.error
1544
2236
  };
1545
- console.error = function(...args) {
1546
- try {
1547
- addBreadcrumb("log", args.map(safeString).join(" "), "error");
1548
- } catch {
1549
- }
1550
- return origError.apply(console, args);
2237
+ const wrap = (method) => {
2238
+ const orig = console[method];
2239
+ if (typeof orig !== "function") return;
2240
+ const level = CONSOLE_METHOD_TO_LEVEL[method];
2241
+ console[method] = function(...args) {
2242
+ if (opts[method]) {
2243
+ try {
2244
+ const serialized = args.map(safeStringifyArg);
2245
+ const message = truncate(serialized.join(" "));
2246
+ addBreadcrumb("log", message, level, {
2247
+ category: "console",
2248
+ method,
2249
+ args: serialized
2250
+ });
2251
+ } catch {
2252
+ }
2253
+ }
2254
+ return orig.apply(console, args);
2255
+ };
1551
2256
  };
2257
+ if (opts.log) wrap("log");
2258
+ if (opts.info) wrap("info");
2259
+ if (opts.warn) wrap("warn");
2260
+ if (opts.error) wrap("error");
1552
2261
  console[CONSOLE_FLAG] = true;
1553
2262
  }
1554
- function safeString(v) {
1555
- if (v == null) return String(v);
2263
+ function __resetConsoleInstrumentationFlagForTest() {
2264
+ if (typeof console !== "undefined") {
2265
+ delete console[CONSOLE_FLAG];
2266
+ }
2267
+ }
2268
+ function safeStringifyArg(v) {
2269
+ if (v === null || v === void 0) return String(v);
1556
2270
  if (typeof v === "string") return v;
1557
- if (v instanceof Error) return `${v.name}: ${v.message}`;
1558
- try {
1559
- return typeof v === "object" ? JSON.stringify(v) : String(v);
1560
- } catch {
1561
- return Object.prototype.toString.call(v);
2271
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
2272
+ if (typeof v === "symbol") return v.toString();
2273
+ if (typeof v === "function") return `[Function${v.name ? ` ${v.name}` : ""}]`;
2274
+ if (v instanceof Error) {
2275
+ return `${v.name || "Error"}: ${v.message}${v.stack ? `
2276
+ ${v.stack}` : ""}`;
2277
+ }
2278
+ if (typeof v === "object") {
2279
+ try {
2280
+ const seen = /* @__PURE__ */ new WeakSet();
2281
+ const out = JSON.stringify(v, (_key, val) => {
2282
+ if (typeof val === "object" && val !== null) {
2283
+ if (seen.has(val)) return "[Circular]";
2284
+ seen.add(val);
2285
+ }
2286
+ if (typeof val === "bigint") return val.toString();
2287
+ if (typeof val === "function") return `[Function${val.name ? ` ${val.name}` : ""}]`;
2288
+ if (typeof val === "symbol") return val.toString();
2289
+ return val;
2290
+ });
2291
+ return out ?? Object.prototype.toString.call(v);
2292
+ } catch {
2293
+ return Object.prototype.toString.call(v);
2294
+ }
1562
2295
  }
2296
+ return String(v);
2297
+ }
2298
+ function truncate(s) {
2299
+ if (s.length <= MAX_ARG_BYTES) return s;
2300
+ return s.slice(0, MAX_ARG_BYTES) + "\u2026[truncated]";
1563
2301
  }
1564
2302
 
1565
2303
  // src/architecture.ts
@@ -1587,6 +2325,7 @@ function applyArchitectureTags(setTag) {
1587
2325
  // src/navigation.ts
1588
2326
  var NAV_FLAG = /* @__PURE__ */ Symbol.for("allstak.nav.subscribed");
1589
2327
  var LINKING_FLAG = "__allstak_linking_patched__";
2328
+ var NAV_AUTO_PATCH_FLAG = /* @__PURE__ */ Symbol.for("allstak.nav.autoPatched");
1590
2329
  function instrumentReactNavigation(navigationRef, options = {}) {
1591
2330
  if (!navigationRef || typeof navigationRef.addListener !== "function") {
1592
2331
  return () => {
@@ -1654,8 +2393,59 @@ function instrumentNavigationFromLinking() {
1654
2393
  } catch {
1655
2394
  }
1656
2395
  }
2396
+ function tryAutoInstrumentNavigation() {
2397
+ const g = globalThis;
2398
+ const isMetro = typeof g.__METRO_GLOBAL_PREFIX__ !== "undefined" || typeof g.__r === "function" || typeof g.HermesInternal !== "undefined";
2399
+ if (isMetro) return false;
2400
+ try {
2401
+ const rnav = require("@react-navigation/native");
2402
+ if (!rnav || !rnav.NavigationContainer) return false;
2403
+ if (rnav[NAV_AUTO_PATCH_FLAG]) return true;
2404
+ const React2 = require("react");
2405
+ if (!React2 || typeof React2.forwardRef !== "function") return false;
2406
+ const OrigContainer = rnav.NavigationContainer;
2407
+ const Wrapped = React2.forwardRef(function AllStakNavigationContainer(props, userRef) {
2408
+ const internalRef = React2.useRef(null);
2409
+ const setRef = React2.useCallback((r) => {
2410
+ internalRef.current = r;
2411
+ if (typeof userRef === "function") userRef(r);
2412
+ else if (userRef) userRef.current = r;
2413
+ }, [userRef]);
2414
+ React2.useEffect(() => {
2415
+ if (internalRef.current) {
2416
+ try {
2417
+ instrumentReactNavigation(internalRef.current);
2418
+ } catch {
2419
+ }
2420
+ }
2421
+ }, []);
2422
+ return React2.createElement(OrigContainer, { ...props, ref: setRef });
2423
+ });
2424
+ Wrapped.displayName = "AllStakNavigationContainer";
2425
+ try {
2426
+ Object.defineProperty(rnav, "NavigationContainer", {
2427
+ value: Wrapped,
2428
+ configurable: true,
2429
+ writable: true
2430
+ });
2431
+ rnav[NAV_AUTO_PATCH_FLAG] = true;
2432
+ return true;
2433
+ } catch {
2434
+ return false;
2435
+ }
2436
+ } catch {
2437
+ return false;
2438
+ }
2439
+ }
2440
+ function __resetAutoNavigationFlagForTest() {
2441
+ try {
2442
+ const rnav = require("@react-navigation/native");
2443
+ if (rnav) delete rnav[NAV_AUTO_PATCH_FLAG];
2444
+ } catch {
2445
+ }
2446
+ }
1657
2447
 
1658
- // src/index.ts
2448
+ // src/install.ts
1659
2449
  function instrumentXmlHttpRequest() {
1660
2450
  const flag = "__allstak_xhr_patched__";
1661
2451
  const X = globalThis.XMLHttpRequest;
@@ -1707,43 +2497,6 @@ function instrumentXmlHttpRequest() {
1707
2497
  };
1708
2498
  X.prototype[flag] = true;
1709
2499
  }
1710
- var __testNativeModule = null;
1711
- function __setNativeModuleForTest(mod) {
1712
- __testNativeModule = mod;
1713
- }
1714
- async function drainPendingNativeCrashes(release) {
1715
- try {
1716
- let native = __testNativeModule;
1717
- if (!native) {
1718
- const rn = require("react-native");
1719
- native = rn?.NativeModules?.AllStakNative;
1720
- }
1721
- if (!native) return;
1722
- if (typeof native.install === "function") {
1723
- try {
1724
- await native.install(release ?? "");
1725
- } catch {
1726
- }
1727
- }
1728
- if (typeof native.drainPendingCrash === "function") {
1729
- const json = await native.drainPendingCrash();
1730
- if (json && json !== "") {
1731
- try {
1732
- const payload = JSON.parse(json);
1733
- const err = new Error(payload?.message ?? "Native crash");
1734
- err.name = payload?.exceptionClass ?? "NativeCrash";
1735
- err.stack = Array.isArray(payload?.stackTrace) ? payload.stackTrace.join("\n") : String(payload?.stackTrace ?? "");
1736
- AllStak.captureException(err, {
1737
- ...payload?.metadata || {},
1738
- "native.crash": "true"
1739
- });
1740
- } catch {
1741
- }
1742
- }
1743
- }
1744
- } catch {
1745
- }
1746
- }
1747
2500
  function installReactNative(options = {}) {
1748
2501
  const autoError = options.autoErrorHandler !== false;
1749
2502
  const autoPromise = options.autoPromiseRejections !== false;
@@ -1768,7 +2521,7 @@ function installReactNative(options = {}) {
1768
2521
  }
1769
2522
  AllStak.setIdentity({
1770
2523
  sdkName: "allstak-react-native",
1771
- sdkVersion: "0.3.0",
2524
+ sdkVersion: SDK_VERSION,
1772
2525
  platform: "react-native",
1773
2526
  dist
1774
2527
  });
@@ -1790,10 +2543,22 @@ function installReactNative(options = {}) {
1790
2543
  }
1791
2544
  if (options.autoConsoleBreadcrumbs !== false) {
1792
2545
  try {
1793
- instrumentConsole(__safeAddBreadcrumbForInstrumentation);
2546
+ const cfg = AllStak.getConfig();
2547
+ instrumentConsole(__safeAddBreadcrumbForInstrumentation, cfg?.captureConsole);
1794
2548
  } catch {
1795
2549
  }
1796
2550
  }
2551
+ if (options.autoNavigationBreadcrumbs !== false) {
2552
+ let navResult = false;
2553
+ try {
2554
+ navResult = tryAutoInstrumentNavigation();
2555
+ } catch {
2556
+ }
2557
+ if (options.debugLogs) {
2558
+ if (navResult) console.log("[AllStak] Navigation auto-instrumentation enabled");
2559
+ else console.log("[AllStak] Navigation auto-instrumentation not applied; use instrumentReactNavigation(ref) fallback");
2560
+ }
2561
+ }
1797
2562
  if (autoDevice) {
1798
2563
  try {
1799
2564
  const rn = require("react-native");
@@ -1843,17 +2608,30 @@ function installReactNative(options = {}) {
1843
2608
  }
1844
2609
  }
1845
2610
  if (autoPromise) {
2611
+ const wrapTrackerReason = (rejection) => rejection instanceof Error ? rejection : new Error(`Unhandled promise rejection: ${String(rejection)}`);
2612
+ const ship = (err) => {
2613
+ try {
2614
+ AllStak.captureException(err, { source: "unhandledRejection" });
2615
+ } catch {
2616
+ }
2617
+ };
2618
+ try {
2619
+ const hermesInternal = globalThis.HermesInternal;
2620
+ if (hermesInternal && typeof hermesInternal.enablePromiseRejectionTracker === "function") {
2621
+ hermesInternal.enablePromiseRejectionTracker({
2622
+ allRejections: true,
2623
+ onUnhandled: (_id, rejection) => ship(wrapTrackerReason(rejection)),
2624
+ onHandled: () => {
2625
+ }
2626
+ });
2627
+ }
2628
+ } catch {
2629
+ }
1846
2630
  try {
1847
2631
  const tracking = require("promise/setimmediate/rejection-tracking");
1848
2632
  tracking.enable({
1849
2633
  allRejections: true,
1850
- onUnhandled: (_id, rejection) => {
1851
- const err = rejection instanceof Error ? rejection : new Error(`Unhandled promise rejection: ${String(rejection)}`);
1852
- try {
1853
- AllStak.captureException(err, { source: "unhandledRejection" });
1854
- } catch {
1855
- }
1856
- },
2634
+ onUnhandled: (_id, rejection) => ship(wrapTrackerReason(rejection)),
1857
2635
  onHandled: () => {
1858
2636
  }
1859
2637
  });
@@ -1863,13 +2641,206 @@ function installReactNative(options = {}) {
1863
2641
  g.addEventListener("unhandledrejection", (ev) => {
1864
2642
  const reason = ev?.reason;
1865
2643
  const err = reason instanceof Error ? reason : new Error(String(reason));
1866
- try {
1867
- AllStak.captureException(err, { source: "unhandledRejection" });
1868
- } catch {
1869
- }
2644
+ ship(err);
1870
2645
  });
1871
2646
  }
1872
2647
  }
1873
2648
  }
1874
2649
  }
2650
+
2651
+ // src/provider.tsx
2652
+ var AllStakContext = React.createContext(null);
2653
+ var __providerOwnedInstance = null;
2654
+ var AllStakErrorBoundary = class extends React.Component {
2655
+ constructor() {
2656
+ super(...arguments);
2657
+ this.state = { error: null };
2658
+ this.resetError = () => this.setState({ error: null });
2659
+ }
2660
+ static getDerivedStateFromError(error) {
2661
+ return { error };
2662
+ }
2663
+ componentDidCatch(error, info) {
2664
+ try {
2665
+ AllStak.addBreadcrumb("ui", "React error boundary caught error", "error", {
2666
+ componentStack: info.componentStack ?? ""
2667
+ });
2668
+ AllStak.captureException(error, {
2669
+ componentStack: info.componentStack ?? "",
2670
+ source: "AllStakProvider.ErrorBoundary"
2671
+ });
2672
+ if (this.props.debug) {
2673
+ console.log(`[AllStak] Captured render error: ${error.message}`);
2674
+ }
2675
+ } catch {
2676
+ }
2677
+ try {
2678
+ this.props.onError?.(error, info.componentStack ?? void 0);
2679
+ } catch {
2680
+ }
2681
+ }
2682
+ render() {
2683
+ if (this.state.error) {
2684
+ const { fallback } = this.props;
2685
+ if (typeof fallback === "function") {
2686
+ return fallback({ error: this.state.error, resetError: this.resetError });
2687
+ }
2688
+ if (fallback !== void 0) return fallback;
2689
+ return null;
2690
+ }
2691
+ return this.props.children;
2692
+ }
2693
+ };
2694
+ function AllStakProvider({
2695
+ children,
2696
+ apiKey,
2697
+ environment,
2698
+ release,
2699
+ host,
2700
+ user,
2701
+ tags,
2702
+ debug,
2703
+ enableHttpTracking,
2704
+ httpTracking,
2705
+ captureConsole,
2706
+ sampleRate,
2707
+ beforeSend,
2708
+ replay,
2709
+ tracesSampleRate,
2710
+ service,
2711
+ dist,
2712
+ destroyOnUnmount = false,
2713
+ fallback,
2714
+ onError,
2715
+ autoErrorHandler,
2716
+ autoPromiseRejections,
2717
+ autoDeviceTags,
2718
+ autoAppStateBreadcrumbs,
2719
+ autoNetworkCapture,
2720
+ autoFetchBreadcrumbs,
2721
+ autoConsoleBreadcrumbs,
2722
+ autoNavigationBreadcrumbs
2723
+ }) {
2724
+ const clientRef = React.useRef(null);
2725
+ if (!clientRef.current) {
2726
+ const existing = AllStak._getInstance();
2727
+ if (existing && __providerOwnedInstance === existing) {
2728
+ clientRef.current = existing;
2729
+ if (debug) {
2730
+ console.log(`[AllStak] Reusing session ${AllStak.getSessionId()}`);
2731
+ }
2732
+ } else {
2733
+ const config = {
2734
+ apiKey,
2735
+ environment,
2736
+ release,
2737
+ host,
2738
+ user,
2739
+ tags,
2740
+ enableHttpTracking,
2741
+ httpTracking,
2742
+ captureConsole,
2743
+ sampleRate,
2744
+ beforeSend,
2745
+ replay,
2746
+ tracesSampleRate,
2747
+ service,
2748
+ dist
2749
+ };
2750
+ clientRef.current = AllStak.init(config);
2751
+ __providerOwnedInstance = clientRef.current;
2752
+ installReactNative({
2753
+ autoErrorHandler,
2754
+ autoPromiseRejections,
2755
+ autoDeviceTags,
2756
+ autoAppStateBreadcrumbs,
2757
+ autoNetworkCapture,
2758
+ autoFetchBreadcrumbs,
2759
+ autoConsoleBreadcrumbs,
2760
+ autoNavigationBreadcrumbs,
2761
+ debugLogs: debug
2762
+ });
2763
+ if (debug) {
2764
+ console.log(`[AllStak] Initialized \u2014 session ${AllStak.getSessionId()}`);
2765
+ }
2766
+ }
2767
+ }
2768
+ React.useEffect(() => {
2769
+ return () => {
2770
+ if (destroyOnUnmount) {
2771
+ AllStak.destroy();
2772
+ __providerOwnedInstance = null;
2773
+ clientRef.current = null;
2774
+ if (debug) console.log("[AllStak] Destroyed on unmount");
2775
+ }
2776
+ };
2777
+ }, [destroyOnUnmount, debug]);
2778
+ return /* @__PURE__ */ React.createElement(AllStakContext.Provider, { value: clientRef.current }, /* @__PURE__ */ React.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children));
2779
+ }
2780
+ function useAllStak() {
2781
+ return React.useMemo(
2782
+ () => ({
2783
+ captureException: (error, ctx) => AllStak.captureException(error, ctx),
2784
+ captureMessage: (msg, level = "info") => AllStak.captureMessage(msg, level),
2785
+ setUser: (user) => AllStak.setUser(user),
2786
+ setTag: (key, value) => AllStak.setTag(key, value),
2787
+ addBreadcrumb: (type, message, level, data) => AllStak.addBreadcrumb(type, message, level, data)
2788
+ }),
2789
+ []
2790
+ );
2791
+ }
2792
+ function __resetProviderInstanceForTest() {
2793
+ __providerOwnedInstance = null;
2794
+ }
2795
+
2796
+ // src/index.ts
2797
+ var __testNativeModule = null;
2798
+ function __setNativeModuleForTest(mod) {
2799
+ __testNativeModule = mod;
2800
+ }
2801
+ async function __devTriggerNativeCrash() {
2802
+ try {
2803
+ let native = __testNativeModule;
2804
+ if (!native) {
2805
+ const rn = require("react-native");
2806
+ native = rn?.NativeModules?.AllStakNative;
2807
+ }
2808
+ if (!native || typeof native.__devTriggerCrash !== "function") return;
2809
+ await native.__devTriggerCrash();
2810
+ } catch {
2811
+ }
2812
+ }
2813
+ async function drainPendingNativeCrashes(release) {
2814
+ try {
2815
+ let native = __testNativeModule;
2816
+ if (!native) {
2817
+ const rn = require("react-native");
2818
+ native = rn?.NativeModules?.AllStakNative;
2819
+ }
2820
+ if (!native) return;
2821
+ if (typeof native.install === "function") {
2822
+ try {
2823
+ await native.install(release ?? "");
2824
+ } catch {
2825
+ }
2826
+ }
2827
+ if (typeof native.drainPendingCrash === "function") {
2828
+ const json = await native.drainPendingCrash();
2829
+ if (json && json !== "") {
2830
+ try {
2831
+ const payload = JSON.parse(json);
2832
+ const err = new Error(payload?.message ?? "Native crash");
2833
+ err.name = payload?.exceptionClass ?? "NativeCrash";
2834
+ err.stack = Array.isArray(payload?.stackTrace) ? payload.stackTrace.join("\n") : String(payload?.stackTrace ?? "");
2835
+ AllStak.captureException(err, {
2836
+ ...payload?.metadata || {},
2837
+ "native.crash": "true"
2838
+ });
2839
+ } catch {
2840
+ }
2841
+ }
2842
+ }
2843
+ } catch {
2844
+ }
2845
+ }
1875
2846
  //# sourceMappingURL=index.js.map