@allstak/react-native 0.3.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,8 +34,13 @@ __export(index_exports, {
34
34
  ALWAYS_REDACT_QUERY: () => ALWAYS_REDACT_QUERY,
35
35
  AllStak: () => AllStak,
36
36
  AllStakClient: () => AllStakClient,
37
+ AllStakMaskedView: () => AllStakMaskedView,
38
+ AllStakPrivacyView: () => AllStakPrivacyView,
37
39
  AllStakProvider: () => AllStakProvider,
40
+ AllStakSensitiveText: () => AllStakSensitiveText,
41
+ AllStakTextInput: () => AllStakTextInput,
38
42
  DEFAULT_REDACT_BODY_FIELDS: () => DEFAULT_REDACT_BODY_FIELDS,
43
+ DEFAULT_SCREENSHOT_CONFIG: () => DEFAULT_SCREENSHOT_CONFIG,
39
44
  HttpRequestModule: () => HttpRequestModule,
40
45
  INGEST_HOST: () => INGEST_HOST,
41
46
  REDACTED: () => REDACTED,
@@ -46,19 +51,31 @@ __export(index_exports, {
46
51
  __devTriggerNativeCrash: () => __devTriggerNativeCrash,
47
52
  __resetAutoNavigationFlagForTest: () => __resetAutoNavigationFlagForTest,
48
53
  __resetConsoleInstrumentationFlagForTest: () => __resetConsoleInstrumentationFlagForTest,
54
+ __resetPrivacyStateForTest: () => __resetPrivacyStateForTest,
49
55
  __resetProviderInstanceForTest: () => __resetProviderInstanceForTest,
56
+ __resetRuntimeModeForTest: () => __resetRuntimeModeForTest,
50
57
  __setNativeModuleForTest: () => __setNativeModuleForTest,
51
58
  applyArchitectureTags: () => applyArchitectureTags,
52
59
  captureBodyResult: () => captureBodyResult,
60
+ captureViaViewShot: () => captureViaViewShot,
53
61
  detectArchitecture: () => detectArchitecture,
62
+ detectRuntimeMode: () => detectRuntimeMode,
54
63
  drainPendingNativeCrashes: () => drainPendingNativeCrashes,
55
64
  installReactNative: () => installReactNative,
56
65
  instrumentNavigationFromLinking: () => instrumentNavigationFromLinking,
57
66
  instrumentReactNavigation: () => instrumentReactNavigation,
67
+ isCapturingScreenshot: () => isCapturingScreenshot,
68
+ isViewShotAvailable: () => isViewShotAvailable,
69
+ maybeCaptureScreenshot: () => maybeCaptureScreenshot,
70
+ pickScreenshotConfig: () => pickScreenshotConfig,
58
71
  redactUrl: () => redactUrl,
72
+ registerSensitiveRef: () => registerSensitiveRef,
73
+ resolveScreenshotConfig: () => resolveScreenshotConfig,
74
+ runtimeAllowsScreenshot: () => runtimeAllowsScreenshot,
59
75
  sanitizeHeaders: () => sanitizeHeaders,
60
76
  tryAutoInstrumentNavigation: () => tryAutoInstrumentNavigation,
61
- useAllStak: () => useAllStak
77
+ useAllStak: () => useAllStak,
78
+ useAllStakPrivacy: () => useAllStakPrivacy
62
79
  });
63
80
  module.exports = __toCommonJS(index_exports);
64
81
 
@@ -89,6 +106,58 @@ var HttpTransport = class {
89
106
  this.enqueueOrDispatch({ path, payload });
90
107
  return Promise.resolve();
91
108
  }
109
+ /**
110
+ * One-shot POST that resolves with the parsed JSON response body. Used
111
+ * by `captureException` to retrieve the server-assigned event id so
112
+ * follow-up attachment uploads can be linked.
113
+ *
114
+ * Fail-open: returns `null` on any error (network, non-2xx, parse).
115
+ * Respects {@link timeoutMs} via `AbortController`. Bounded retries.
116
+ */
117
+ async sendAndRead(path, payload, options = {}) {
118
+ if (!this.enabled) return null;
119
+ const timeoutMs = options.timeoutMs ?? 5e3;
120
+ const retries = Math.max(0, options.retries ?? 1);
121
+ let attempt = 0;
122
+ let lastError = null;
123
+ while (attempt <= retries) {
124
+ try {
125
+ const url = `${this.baseUrl}${path}`;
126
+ const controller = new AbortController();
127
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
128
+ try {
129
+ const res = await fetch(url, {
130
+ method: "POST",
131
+ headers: {
132
+ "Content-Type": "application/json",
133
+ "X-AllStak-Key": this.apiKey
134
+ },
135
+ body: JSON.stringify(payload),
136
+ signal: controller.signal
137
+ });
138
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
139
+ const text = await res.text();
140
+ if (!text) return null;
141
+ try {
142
+ return JSON.parse(text);
143
+ } catch {
144
+ return null;
145
+ }
146
+ } finally {
147
+ clearTimeout(timeoutId);
148
+ }
149
+ } catch (err) {
150
+ lastError = err;
151
+ attempt += 1;
152
+ if (attempt > retries) break;
153
+ await new Promise((r) => setTimeout(r, 200 * attempt));
154
+ }
155
+ }
156
+ if (lastError && typeof __DEV__ !== "undefined" && __DEV__) {
157
+ console.warn("[AllStak] sendAndRead failed:", lastError?.message);
158
+ }
159
+ return null;
160
+ }
92
161
  enqueueOrDispatch(item) {
93
162
  if (Date.now() < this.circuitOpenUntil) {
94
163
  this.push(item);
@@ -1443,10 +1512,358 @@ function unbindHttpInstrumentation() {
1443
1512
  _currentRuntime = null;
1444
1513
  }
1445
1514
 
1515
+ // src/runtime.ts
1516
+ var cached = null;
1517
+ function detectRuntimeMode() {
1518
+ if (cached) return cached;
1519
+ cached = computeRuntimeMode();
1520
+ return cached;
1521
+ }
1522
+ function __resetRuntimeModeForTest() {
1523
+ cached = null;
1524
+ }
1525
+ function computeRuntimeMode() {
1526
+ try {
1527
+ const Constants = require("expo-constants");
1528
+ const appOwnership = Constants?.default?.appOwnership ?? Constants?.appOwnership;
1529
+ if (appOwnership === "expo") return "expo-go";
1530
+ if (appOwnership === "standalone" || appOwnership === "guest") return "expo-dev-client";
1531
+ const exec = Constants?.default?.executionEnvironment ?? Constants?.executionEnvironment;
1532
+ if (exec === "storeClient") return "expo-go";
1533
+ if (exec === "standalone" || exec === "bare") return "expo-dev-client";
1534
+ } catch {
1535
+ }
1536
+ try {
1537
+ require("expo");
1538
+ return "expo-dev-client";
1539
+ } catch {
1540
+ }
1541
+ try {
1542
+ require("react-native");
1543
+ return "rn-cli";
1544
+ } catch {
1545
+ }
1546
+ return "unknown";
1547
+ }
1548
+ function runtimeAllowsScreenshot(mode = detectRuntimeMode()) {
1549
+ if (mode === "expo-go") return false;
1550
+ return true;
1551
+ }
1552
+ function tryRequire(id2) {
1553
+ try {
1554
+ return require(id2);
1555
+ } catch {
1556
+ return null;
1557
+ }
1558
+ }
1559
+
1560
+ // src/privacy.tsx
1561
+ var React = __toESM(require("react"));
1562
+ function getRN() {
1563
+ return tryRequire("react-native");
1564
+ }
1565
+ var state = {
1566
+ isCapturing: false,
1567
+ sensitiveRefs: /* @__PURE__ */ new Set()
1568
+ };
1569
+ var listeners = /* @__PURE__ */ new Set();
1570
+ function __setCapturing(value) {
1571
+ if (state.isCapturing === value) return;
1572
+ state.isCapturing = value;
1573
+ for (const fn of listeners) {
1574
+ try {
1575
+ fn(value);
1576
+ } catch {
1577
+ }
1578
+ }
1579
+ }
1580
+ function __resetPrivacyStateForTest() {
1581
+ state.isCapturing = false;
1582
+ state.sensitiveRefs.clear();
1583
+ listeners.clear();
1584
+ }
1585
+ function isCapturingScreenshot() {
1586
+ return state.isCapturing;
1587
+ }
1588
+ function sensitiveRefCount() {
1589
+ return state.sensitiveRefs.size;
1590
+ }
1591
+ function registerSensitiveRef(ref) {
1592
+ state.sensitiveRefs.add(ref);
1593
+ return () => {
1594
+ state.sensitiveRefs.delete(ref);
1595
+ };
1596
+ }
1597
+ function useIsCapturing() {
1598
+ const [val, setVal] = React.useState(state.isCapturing);
1599
+ React.useEffect(() => {
1600
+ const fn = (v) => setVal(v);
1601
+ listeners.add(fn);
1602
+ return () => {
1603
+ listeners.delete(fn);
1604
+ };
1605
+ }, []);
1606
+ return val;
1607
+ }
1608
+ function useAllStakPrivacy() {
1609
+ const isCapturing = useIsCapturing();
1610
+ return { isCapturing, registerSensitiveRef };
1611
+ }
1612
+ function getView() {
1613
+ const RN = getRN();
1614
+ return RN?.View ?? ((props) => React.createElement(React.Fragment, null, props.children));
1615
+ }
1616
+ function getText() {
1617
+ const RN = getRN();
1618
+ return RN?.Text ?? ((props) => React.createElement(React.Fragment, null, props.children));
1619
+ }
1620
+ function getTextInput() {
1621
+ const RN = getRN();
1622
+ return RN?.TextInput ?? ((props) => React.createElement(React.Fragment, null, null));
1623
+ }
1624
+ var DEFAULT_MASK_COLOR = "#d8dde7";
1625
+ var DEFAULT_MASK_LABEL = "\u2022\u2022\u2022\u2022\u2022\u2022";
1626
+ function AllStakMaskedView({
1627
+ children,
1628
+ maskLabel = DEFAULT_MASK_LABEL,
1629
+ maskColor = DEFAULT_MASK_COLOR,
1630
+ hideScreenshot = false,
1631
+ privacy = "mask",
1632
+ style,
1633
+ ...rest
1634
+ }) {
1635
+ const isCapturing = useIsCapturing();
1636
+ const View = getView();
1637
+ const Text = getText();
1638
+ if (!isCapturing || privacy === "show") {
1639
+ return React.createElement(View, { style, ...rest }, children);
1640
+ }
1641
+ if (privacy === "hide" || hideScreenshot) {
1642
+ return React.createElement(View, { style: [{ backgroundColor: maskColor }, style], ...rest });
1643
+ }
1644
+ return React.createElement(
1645
+ View,
1646
+ { style: [{ backgroundColor: maskColor, alignItems: "center", justifyContent: "center" }, style], ...rest },
1647
+ React.createElement(Text, { style: { color: "#3f4652", fontSize: 12 } }, maskLabel)
1648
+ );
1649
+ }
1650
+ function AllStakPrivacyView(props) {
1651
+ return React.createElement(AllStakMaskedView, { hideScreenshot: true, ...props });
1652
+ }
1653
+ function AllStakTextInput({
1654
+ privacy = "mask",
1655
+ style,
1656
+ maskColor = DEFAULT_MASK_COLOR,
1657
+ ...rest
1658
+ }) {
1659
+ const isCapturing = useIsCapturing();
1660
+ const View = getView();
1661
+ const TextInput = getTextInput();
1662
+ if (isCapturing && privacy !== "show") {
1663
+ return React.createElement(View, {
1664
+ style: [{ minHeight: 40, backgroundColor: maskColor, borderRadius: 4 }, style]
1665
+ });
1666
+ }
1667
+ return React.createElement(TextInput, { style, ...rest });
1668
+ }
1669
+ function AllStakSensitiveText({
1670
+ children,
1671
+ privacy = "mask",
1672
+ style,
1673
+ maskLabel = DEFAULT_MASK_LABEL,
1674
+ ...rest
1675
+ }) {
1676
+ const isCapturing = useIsCapturing();
1677
+ const Text = getText();
1678
+ if (isCapturing && privacy !== "show") {
1679
+ return React.createElement(Text, { style, ...rest }, maskLabel);
1680
+ }
1681
+ return React.createElement(Text, { style, ...rest }, children);
1682
+ }
1683
+
1684
+ // src/screenshot.ts
1685
+ var DEFAULT_SCREENSHOT_CONFIG = {
1686
+ captureScreenshotOnError: false,
1687
+ screenshotRedaction: "strict",
1688
+ screenshotMaskStyle: "solid",
1689
+ screenshotMaxBytes: 5e5,
1690
+ screenshotQuality: 0.7,
1691
+ screenshotFormat: "jpg",
1692
+ screenshotSampleRate: 1,
1693
+ screenshotOnUnhandledOnly: true,
1694
+ screenshotUploadTimeoutMs: 8e3,
1695
+ screenshotCaptureTimeoutMs: 2e3,
1696
+ screenshotNativeMode: "auto",
1697
+ screenshotFailPolicy: "send-event-only"
1698
+ };
1699
+ function resolveScreenshotConfig(partial) {
1700
+ const c = { ...DEFAULT_SCREENSHOT_CONFIG, ...partial ?? {} };
1701
+ c.screenshotMaxBytes = clamp(c.screenshotMaxBytes, 1024, 5e6);
1702
+ c.screenshotQuality = clamp(c.screenshotQuality, 0, 1);
1703
+ c.screenshotSampleRate = clamp(c.screenshotSampleRate, 0, 1);
1704
+ c.screenshotUploadTimeoutMs = clamp(c.screenshotUploadTimeoutMs, 500, 6e4);
1705
+ c.screenshotCaptureTimeoutMs = clamp(c.screenshotCaptureTimeoutMs, 100, 3e4);
1706
+ return c;
1707
+ }
1708
+ function clamp(n, lo, hi) {
1709
+ if (typeof n !== "number" || !Number.isFinite(n)) return lo;
1710
+ return Math.max(lo, Math.min(hi, n));
1711
+ }
1712
+ var rootViewRef = null;
1713
+ function __setRootViewRef(ref) {
1714
+ rootViewRef = ref;
1715
+ }
1716
+ var warnedAboutBothApis = false;
1717
+ function warnIfBothApisPresent(callbackPresent, flatPresent) {
1718
+ if (!callbackPresent || !flatPresent || warnedAboutBothApis) return;
1719
+ warnedAboutBothApis = true;
1720
+ console.warn(
1721
+ "[AllStak] Both `screenshot.provider` (deprecated) and the flat `captureScreenshotOnError` API are configured. The flat API takes precedence. Remove `screenshot.provider` to silence this warning."
1722
+ );
1723
+ }
1724
+ function isViewShotAvailable() {
1725
+ return tryRequire("react-native-view-shot") !== null;
1726
+ }
1727
+ async function captureViaViewShot(config) {
1728
+ if (config.screenshotNativeMode === "disabled") return null;
1729
+ const viewShot = tryRequire("react-native-view-shot");
1730
+ if (!viewShot) return null;
1731
+ const captureRef = viewShot.captureRef ?? viewShot.default?.captureRef;
1732
+ if (typeof captureRef !== "function") return null;
1733
+ const refTarget = rootViewRef?.current;
1734
+ if (!refTarget) return null;
1735
+ const format = config.screenshotFormat === "jpg" ? "jpg" : config.screenshotFormat;
1736
+ const dimensions = readDimensions();
1737
+ __setCapturing(true);
1738
+ await new Promise((r) => setTimeout(r, 16));
1739
+ try {
1740
+ const captured = await Promise.race([
1741
+ Promise.resolve(captureRef(refTarget, {
1742
+ format,
1743
+ quality: config.screenshotQuality,
1744
+ result: "base64"
1745
+ })),
1746
+ new Promise((resolve) => setTimeout(() => resolve(null), config.screenshotCaptureTimeoutMs))
1747
+ ]);
1748
+ if (!captured || typeof captured !== "string") return null;
1749
+ const sizeBytes = estimateBase64Size(captured);
1750
+ if (sizeBytes > config.screenshotMaxBytes) {
1751
+ if (__DEV__) {
1752
+ console.warn(`[AllStak] Screenshot ${sizeBytes}B exceeds limit ${config.screenshotMaxBytes}B; dropping.`);
1753
+ }
1754
+ return null;
1755
+ }
1756
+ const contentType = format === "png" ? "image/png" : format === "webp" ? "image/webp" : "image/jpeg";
1757
+ return {
1758
+ dataBase64: captured,
1759
+ contentType,
1760
+ width: dimensions.width,
1761
+ height: dimensions.height,
1762
+ sizeBytes
1763
+ };
1764
+ } catch (err) {
1765
+ if (__DEV__) {
1766
+ console.warn("[AllStak] view-shot capture failed:", err?.message);
1767
+ }
1768
+ return null;
1769
+ } finally {
1770
+ __setCapturing(false);
1771
+ }
1772
+ }
1773
+ function estimateBase64Size(base64) {
1774
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
1775
+ return Math.floor(base64.length * 3 / 4) - padding;
1776
+ }
1777
+ function readDimensions() {
1778
+ try {
1779
+ const RN = tryRequire("react-native");
1780
+ const dims = RN?.Dimensions?.get?.("window");
1781
+ if (dims && typeof dims.width === "number" && typeof dims.height === "number") {
1782
+ return { width: Math.round(dims.width), height: Math.round(dims.height) };
1783
+ }
1784
+ } catch {
1785
+ }
1786
+ return { width: 0, height: 0 };
1787
+ }
1788
+ async function maybeCaptureScreenshot(config, ctx) {
1789
+ try {
1790
+ if (!config.captureScreenshotOnError) return null;
1791
+ if (config.screenshotOnUnhandledOnly && ctx.unhandled === false) return null;
1792
+ if (config.screenshotSampleRate < 1 && Math.random() >= config.screenshotSampleRate) return null;
1793
+ if (!runtimeAllowsScreenshot(ctx.runtimeMode)) return null;
1794
+ if (config.isScreenshotAllowed) {
1795
+ try {
1796
+ const allowed = await config.isScreenshotAllowed(ctx);
1797
+ if (!allowed) return null;
1798
+ } catch {
1799
+ return null;
1800
+ }
1801
+ }
1802
+ if (config.beforeScreenshotCapture) {
1803
+ try {
1804
+ const cont = await config.beforeScreenshotCapture(ctx);
1805
+ if (!cont) return null;
1806
+ } catch {
1807
+ return null;
1808
+ }
1809
+ }
1810
+ const upload = await captureViaViewShot(config);
1811
+ if (!upload) return null;
1812
+ const metadata = {
1813
+ captureMethod: "react-native-view-shot",
1814
+ redactionMode: config.screenshotRedaction,
1815
+ maskStyle: config.screenshotMaskStyle,
1816
+ format: config.screenshotFormat,
1817
+ width: upload.width,
1818
+ height: upload.height,
1819
+ sizeBytes: upload.sizeBytes,
1820
+ privacyComponentsDetected: sensitiveRefCount(),
1821
+ runtimeMode: ctx.runtimeMode
1822
+ };
1823
+ if (config.beforeScreenshotUpload) {
1824
+ try {
1825
+ const filtered = await config.beforeScreenshotUpload(upload, metadata);
1826
+ if (!filtered) return null;
1827
+ return { upload: filtered, metadata };
1828
+ } catch {
1829
+ return null;
1830
+ }
1831
+ }
1832
+ return { upload, metadata };
1833
+ } catch (err) {
1834
+ if (__DEV__) {
1835
+ console.warn("[AllStak] maybeCaptureScreenshot fail-open:", err?.message);
1836
+ }
1837
+ return null;
1838
+ }
1839
+ }
1840
+ function pickScreenshotConfig(source) {
1841
+ const out = {};
1842
+ const pick = (key) => {
1843
+ if (source[key] !== void 0) out[key] = source[key];
1844
+ };
1845
+ pick("captureScreenshotOnError");
1846
+ pick("screenshotRedaction");
1847
+ pick("screenshotMaskStyle");
1848
+ pick("screenshotMaxBytes");
1849
+ pick("screenshotQuality");
1850
+ pick("screenshotFormat");
1851
+ pick("screenshotSampleRate");
1852
+ pick("screenshotOnUnhandledOnly");
1853
+ pick("screenshotUploadTimeoutMs");
1854
+ pick("screenshotCaptureTimeoutMs");
1855
+ pick("screenshotNativeMode");
1856
+ pick("screenshotFailPolicy");
1857
+ pick("beforeScreenshotCapture");
1858
+ pick("beforeScreenshotUpload");
1859
+ pick("isScreenshotAllowed");
1860
+ return out;
1861
+ }
1862
+
1446
1863
  // src/client.ts
1447
1864
  var INGEST_HOST = "https://api.allstak.sa";
1448
1865
  var SDK_NAME = "allstak-react-native";
1449
- var SDK_VERSION = "0.3.4";
1866
+ var SDK_VERSION = "0.4.1";
1450
1867
  var ERRORS_PATH = "/ingest/v1/errors";
1451
1868
  var LOGS_PATH = "/ingest/v1/logs";
1452
1869
  var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
@@ -1625,6 +2042,18 @@ var AllStakClient = class {
1625
2042
  breadcrumbs: currentBreadcrumbs,
1626
2043
  fingerprint: eff.fingerprint
1627
2044
  };
2045
+ const flatPresent = this.config.captureScreenshotOnError === true;
2046
+ const callbackPresent = Boolean(this.config.screenshot?.provider);
2047
+ warnIfBothApisPresent(callbackPresent, flatPresent);
2048
+ if (flatPresent) {
2049
+ void this.runFlatScreenshotPipeline(error, payload).catch(() => {
2050
+ void this.sendThroughBeforeSend({
2051
+ ...payload,
2052
+ metadata: { ...payload.metadata ?? {}, "screenshot.status": "failed" }
2053
+ });
2054
+ });
2055
+ return;
2056
+ }
1628
2057
  if (this.shouldCaptureScreenshot()) {
1629
2058
  void this.withScreenshotMetadata(error, payload).then((enriched) => this.sendThroughBeforeSend(enriched)).catch(() => this.sendThroughBeforeSend({
1630
2059
  ...payload,
@@ -1640,6 +2069,86 @@ var AllStakClient = class {
1640
2069
  }
1641
2070
  });
1642
2071
  }
2072
+ /**
2073
+ * Flat screenshot API path. Capture first (so masking primitives can
2074
+ * swap), send the event with a `screenshot.status` metadata tag, read
2075
+ * back the server-assigned errorId, then upload the attachment.
2076
+ *
2077
+ * Fail-open at every step.
2078
+ */
2079
+ async runFlatScreenshotPipeline(error, payload) {
2080
+ const sc = resolveScreenshotConfig(pickScreenshotConfig(this.config));
2081
+ const runtimeMode = detectRuntimeMode();
2082
+ const ctx = { error, unhandled: true, runtimeMode };
2083
+ let captured = null;
2084
+ try {
2085
+ captured = await maybeCaptureScreenshot(sc, ctx);
2086
+ } catch {
2087
+ captured = null;
2088
+ }
2089
+ const status = !sc.captureScreenshotOnError ? "disabled" : captured ? "captured" : runtimeMode === "expo-go" ? "unsupported_runtime" : "unavailable";
2090
+ const eventPayload = {
2091
+ ...payload,
2092
+ metadata: {
2093
+ ...payload.metadata ?? {},
2094
+ "screenshot.status": status,
2095
+ "screenshot.runtimeMode": runtimeMode,
2096
+ ...captured ? {
2097
+ "screenshot.contentType": captured.upload.contentType,
2098
+ "screenshot.width": captured.upload.width,
2099
+ "screenshot.height": captured.upload.height,
2100
+ "screenshot.sizeBytes": captured.upload.sizeBytes,
2101
+ "screenshot.redactionMode": captured.metadata.redactionMode,
2102
+ "screenshot.maskStyle": captured.metadata.maskStyle,
2103
+ "screenshot.captureMethod": captured.metadata.captureMethod
2104
+ } : {}
2105
+ }
2106
+ };
2107
+ let finalPayload = eventPayload;
2108
+ if (this.config.beforeSend) {
2109
+ try {
2110
+ finalPayload = await this.config.beforeSend(eventPayload);
2111
+ } catch {
2112
+ finalPayload = eventPayload;
2113
+ }
2114
+ }
2115
+ if (!finalPayload) return;
2116
+ let eventId = null;
2117
+ try {
2118
+ const resp = await this.transport.sendAndRead(
2119
+ ERRORS_PATH,
2120
+ finalPayload,
2121
+ { timeoutMs: 5e3, retries: 1 }
2122
+ );
2123
+ eventId = resp?.data?.id ?? resp?.id ?? null;
2124
+ } catch {
2125
+ }
2126
+ if (!captured || !eventId) return;
2127
+ try {
2128
+ await this.transport.sendAndRead(
2129
+ `/ingest/v1/errors/${encodeURIComponent(eventId)}/attachments`,
2130
+ {
2131
+ kind: "screenshot",
2132
+ contentType: captured.upload.contentType,
2133
+ dataBase64: captured.upload.dataBase64,
2134
+ width: captured.upload.width,
2135
+ height: captured.upload.height,
2136
+ redactionMode: captured.metadata.redactionMode,
2137
+ captureMethod: captured.metadata.captureMethod,
2138
+ sizeBytes: captured.upload.sizeBytes,
2139
+ metadata: {
2140
+ maskStyle: captured.metadata.maskStyle,
2141
+ format: captured.metadata.format,
2142
+ runtimeMode: captured.metadata.runtimeMode,
2143
+ privacyComponentsDetected: captured.metadata.privacyComponentsDetected ?? 0,
2144
+ sdkVersion: SDK_VERSION
2145
+ }
2146
+ },
2147
+ { timeoutMs: sc.screenshotUploadTimeoutMs, retries: 2 }
2148
+ );
2149
+ } catch {
2150
+ }
2151
+ }
1643
2152
  /** Start a new span. Auto-parented to any currently-active span. */
1644
2153
  startSpan(operation, options) {
1645
2154
  return this.tracing.startSpan(operation, options);
@@ -2164,7 +2673,7 @@ function byteSize(value) {
2164
2673
  }
2165
2674
 
2166
2675
  // src/provider.tsx
2167
- var React = __toESM(require("react"));
2676
+ var React2 = __toESM(require("react"));
2168
2677
 
2169
2678
  // src/auto-breadcrumbs.ts
2170
2679
  var FETCH_FLAG2 = "__allstak_fetch_patched__";
@@ -2401,17 +2910,17 @@ function tryAutoInstrumentNavigation() {
2401
2910
  const rnav = require("@react-navigation/native");
2402
2911
  if (!rnav || !rnav.NavigationContainer) return false;
2403
2912
  if (rnav[NAV_AUTO_PATCH_FLAG]) return true;
2404
- const React2 = require("react");
2405
- if (!React2 || typeof React2.forwardRef !== "function") return false;
2913
+ const React3 = require("react");
2914
+ if (!React3 || typeof React3.forwardRef !== "function") return false;
2406
2915
  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) => {
2916
+ const Wrapped = React3.forwardRef(function AllStakNavigationContainer(props, userRef) {
2917
+ const internalRef = React3.useRef(null);
2918
+ const setRef = React3.useCallback((r) => {
2410
2919
  internalRef.current = r;
2411
2920
  if (typeof userRef === "function") userRef(r);
2412
2921
  else if (userRef) userRef.current = r;
2413
2922
  }, [userRef]);
2414
- React2.useEffect(() => {
2923
+ React3.useEffect(() => {
2415
2924
  if (internalRef.current) {
2416
2925
  try {
2417
2926
  instrumentReactNavigation(internalRef.current);
@@ -2419,7 +2928,7 @@ function tryAutoInstrumentNavigation() {
2419
2928
  }
2420
2929
  }
2421
2930
  }, []);
2422
- return React2.createElement(OrigContainer, { ...props, ref: setRef });
2931
+ return React3.createElement(OrigContainer, { ...props, ref: setRef });
2423
2932
  });
2424
2933
  Wrapped.displayName = "AllStakNavigationContainer";
2425
2934
  try {
@@ -2649,9 +3158,16 @@ function installReactNative(options = {}) {
2649
3158
  }
2650
3159
 
2651
3160
  // src/provider.tsx
2652
- var AllStakContext = React.createContext(null);
3161
+ function getRN2() {
3162
+ return tryRequire("react-native");
3163
+ }
3164
+ function getRootView() {
3165
+ const RN = getRN2();
3166
+ return RN?.View;
3167
+ }
3168
+ var AllStakContext = React2.createContext(null);
2653
3169
  var __providerOwnedInstance = null;
2654
- var AllStakErrorBoundary = class extends React.Component {
3170
+ var AllStakErrorBoundary = class extends React2.Component {
2655
3171
  constructor() {
2656
3172
  super(...arguments);
2657
3173
  this.state = { error: null };
@@ -2719,9 +3235,31 @@ function AllStakProvider({
2719
3235
  autoNetworkCapture,
2720
3236
  autoFetchBreadcrumbs,
2721
3237
  autoConsoleBreadcrumbs,
2722
- autoNavigationBreadcrumbs
3238
+ autoNavigationBreadcrumbs,
3239
+ captureScreenshotOnError,
3240
+ screenshotRedaction,
3241
+ screenshotMaskStyle,
3242
+ screenshotMaxBytes,
3243
+ screenshotQuality,
3244
+ screenshotFormat,
3245
+ screenshotSampleRate,
3246
+ screenshotOnUnhandledOnly,
3247
+ screenshotUploadTimeoutMs,
3248
+ screenshotCaptureTimeoutMs,
3249
+ screenshotNativeMode,
3250
+ screenshotFailPolicy,
3251
+ beforeScreenshotCapture,
3252
+ beforeScreenshotUpload,
3253
+ isScreenshotAllowed
2723
3254
  }) {
2724
- const clientRef = React.useRef(null);
3255
+ const clientRef = React2.useRef(null);
3256
+ const rootRef = React2.useRef(null);
3257
+ React2.useEffect(() => {
3258
+ __setRootViewRef(rootRef);
3259
+ return () => {
3260
+ __setRootViewRef(null);
3261
+ };
3262
+ }, []);
2725
3263
  if (!clientRef.current) {
2726
3264
  const existing = AllStak._getInstance();
2727
3265
  if (existing && __providerOwnedInstance === existing) {
@@ -2745,7 +3283,22 @@ function AllStakProvider({
2745
3283
  replay,
2746
3284
  tracesSampleRate,
2747
3285
  service,
2748
- dist
3286
+ dist,
3287
+ captureScreenshotOnError,
3288
+ screenshotRedaction,
3289
+ screenshotMaskStyle,
3290
+ screenshotMaxBytes,
3291
+ screenshotQuality,
3292
+ screenshotFormat,
3293
+ screenshotSampleRate,
3294
+ screenshotOnUnhandledOnly,
3295
+ screenshotUploadTimeoutMs,
3296
+ screenshotCaptureTimeoutMs,
3297
+ screenshotNativeMode,
3298
+ screenshotFailPolicy,
3299
+ beforeScreenshotCapture,
3300
+ beforeScreenshotUpload,
3301
+ isScreenshotAllowed
2749
3302
  };
2750
3303
  clientRef.current = AllStak.init(config);
2751
3304
  __providerOwnedInstance = clientRef.current;
@@ -2765,7 +3318,7 @@ function AllStakProvider({
2765
3318
  }
2766
3319
  }
2767
3320
  }
2768
- React.useEffect(() => {
3321
+ React2.useEffect(() => {
2769
3322
  return () => {
2770
3323
  if (destroyOnUnmount) {
2771
3324
  AllStak.destroy();
@@ -2775,10 +3328,17 @@ function AllStakProvider({
2775
3328
  }
2776
3329
  };
2777
3330
  }, [destroyOnUnmount, debug]);
2778
- return /* @__PURE__ */ React.createElement(AllStakContext.Provider, { value: clientRef.current }, /* @__PURE__ */ React.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children));
3331
+ const boundary = /* @__PURE__ */ React2.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children);
3332
+ const RootView = getRootView();
3333
+ const wrapped = RootView ? React2.createElement(
3334
+ RootView,
3335
+ { ref: rootRef, style: { flex: 1 }, collapsable: false },
3336
+ boundary
3337
+ ) : boundary;
3338
+ return /* @__PURE__ */ React2.createElement(AllStakContext.Provider, { value: clientRef.current }, wrapped);
2779
3339
  }
2780
3340
  function useAllStak() {
2781
- return React.useMemo(
3341
+ return React2.useMemo(
2782
3342
  () => ({
2783
3343
  captureException: (error, ctx) => AllStak.captureException(error, ctx),
2784
3344
  captureMessage: (msg, level = "info") => AllStak.captureMessage(msg, level),