@allstak/react-native 0.3.4 → 0.4.0

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,342 @@ 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
+ var RN = tryRequire("react-native");
1563
+ var state = {
1564
+ isCapturing: false,
1565
+ sensitiveRefs: /* @__PURE__ */ new Set()
1566
+ };
1567
+ var listeners = /* @__PURE__ */ new Set();
1568
+ function __setCapturing(value) {
1569
+ if (state.isCapturing === value) return;
1570
+ state.isCapturing = value;
1571
+ for (const fn of listeners) {
1572
+ try {
1573
+ fn(value);
1574
+ } catch {
1575
+ }
1576
+ }
1577
+ }
1578
+ function __resetPrivacyStateForTest() {
1579
+ state.isCapturing = false;
1580
+ state.sensitiveRefs.clear();
1581
+ listeners.clear();
1582
+ }
1583
+ function isCapturingScreenshot() {
1584
+ return state.isCapturing;
1585
+ }
1586
+ function sensitiveRefCount() {
1587
+ return state.sensitiveRefs.size;
1588
+ }
1589
+ function registerSensitiveRef(ref) {
1590
+ state.sensitiveRefs.add(ref);
1591
+ return () => {
1592
+ state.sensitiveRefs.delete(ref);
1593
+ };
1594
+ }
1595
+ function useIsCapturing() {
1596
+ const [val, setVal] = React.useState(state.isCapturing);
1597
+ React.useEffect(() => {
1598
+ const fn = (v) => setVal(v);
1599
+ listeners.add(fn);
1600
+ return () => {
1601
+ listeners.delete(fn);
1602
+ };
1603
+ }, []);
1604
+ return val;
1605
+ }
1606
+ function useAllStakPrivacy() {
1607
+ const isCapturing = useIsCapturing();
1608
+ return { isCapturing, registerSensitiveRef };
1609
+ }
1610
+ var View = RN?.View ?? ((props) => React.createElement("View", props));
1611
+ var Text = RN?.Text ?? ((props) => React.createElement("Text", props));
1612
+ var TextInput = RN?.TextInput ?? ((props) => React.createElement("TextInput", props));
1613
+ var DEFAULT_MASK_COLOR = "#d8dde7";
1614
+ var DEFAULT_MASK_LABEL = "\u2022\u2022\u2022\u2022\u2022\u2022";
1615
+ function AllStakMaskedView({
1616
+ children,
1617
+ maskLabel = DEFAULT_MASK_LABEL,
1618
+ maskColor = DEFAULT_MASK_COLOR,
1619
+ hideScreenshot = false,
1620
+ privacy = "mask",
1621
+ style,
1622
+ ...rest
1623
+ }) {
1624
+ const isCapturing = useIsCapturing();
1625
+ if (!isCapturing || privacy === "show") {
1626
+ return React.createElement(View, { style, ...rest }, children);
1627
+ }
1628
+ if (privacy === "hide" || hideScreenshot) {
1629
+ return React.createElement(View, { style: [{ backgroundColor: maskColor }, style], ...rest });
1630
+ }
1631
+ return React.createElement(
1632
+ View,
1633
+ { style: [{ backgroundColor: maskColor, alignItems: "center", justifyContent: "center" }, style], ...rest },
1634
+ React.createElement(Text, { style: { color: "#3f4652", fontSize: 12 } }, maskLabel)
1635
+ );
1636
+ }
1637
+ function AllStakPrivacyView(props) {
1638
+ return React.createElement(AllStakMaskedView, { hideScreenshot: true, ...props });
1639
+ }
1640
+ function AllStakTextInput({
1641
+ privacy = "mask",
1642
+ style,
1643
+ maskColor = DEFAULT_MASK_COLOR,
1644
+ ...rest
1645
+ }) {
1646
+ const isCapturing = useIsCapturing();
1647
+ if (isCapturing && privacy !== "show") {
1648
+ return React.createElement(View, {
1649
+ style: [{ minHeight: 40, backgroundColor: maskColor, borderRadius: 4 }, style]
1650
+ });
1651
+ }
1652
+ return React.createElement(TextInput, { style, ...rest });
1653
+ }
1654
+ function AllStakSensitiveText({
1655
+ children,
1656
+ privacy = "mask",
1657
+ style,
1658
+ maskLabel = DEFAULT_MASK_LABEL,
1659
+ ...rest
1660
+ }) {
1661
+ const isCapturing = useIsCapturing();
1662
+ if (isCapturing && privacy !== "show") {
1663
+ return React.createElement(Text, { style, ...rest }, maskLabel);
1664
+ }
1665
+ return React.createElement(Text, { style, ...rest }, children);
1666
+ }
1667
+
1668
+ // src/screenshot.ts
1669
+ var DEFAULT_SCREENSHOT_CONFIG = {
1670
+ captureScreenshotOnError: false,
1671
+ screenshotRedaction: "strict",
1672
+ screenshotMaskStyle: "solid",
1673
+ screenshotMaxBytes: 5e5,
1674
+ screenshotQuality: 0.7,
1675
+ screenshotFormat: "jpg",
1676
+ screenshotSampleRate: 1,
1677
+ screenshotOnUnhandledOnly: true,
1678
+ screenshotUploadTimeoutMs: 8e3,
1679
+ screenshotCaptureTimeoutMs: 2e3,
1680
+ screenshotNativeMode: "auto",
1681
+ screenshotFailPolicy: "send-event-only"
1682
+ };
1683
+ function resolveScreenshotConfig(partial) {
1684
+ const c = { ...DEFAULT_SCREENSHOT_CONFIG, ...partial ?? {} };
1685
+ c.screenshotMaxBytes = clamp(c.screenshotMaxBytes, 1024, 5e6);
1686
+ c.screenshotQuality = clamp(c.screenshotQuality, 0, 1);
1687
+ c.screenshotSampleRate = clamp(c.screenshotSampleRate, 0, 1);
1688
+ c.screenshotUploadTimeoutMs = clamp(c.screenshotUploadTimeoutMs, 500, 6e4);
1689
+ c.screenshotCaptureTimeoutMs = clamp(c.screenshotCaptureTimeoutMs, 100, 3e4);
1690
+ return c;
1691
+ }
1692
+ function clamp(n, lo, hi) {
1693
+ if (typeof n !== "number" || !Number.isFinite(n)) return lo;
1694
+ return Math.max(lo, Math.min(hi, n));
1695
+ }
1696
+ var rootViewRef = null;
1697
+ function __setRootViewRef(ref) {
1698
+ rootViewRef = ref;
1699
+ }
1700
+ var warnedAboutBothApis = false;
1701
+ function warnIfBothApisPresent(callbackPresent, flatPresent) {
1702
+ if (!callbackPresent || !flatPresent || warnedAboutBothApis) return;
1703
+ warnedAboutBothApis = true;
1704
+ console.warn(
1705
+ "[AllStak] Both `screenshot.provider` (deprecated) and the flat `captureScreenshotOnError` API are configured. The flat API takes precedence. Remove `screenshot.provider` to silence this warning."
1706
+ );
1707
+ }
1708
+ function isViewShotAvailable() {
1709
+ return tryRequire("react-native-view-shot") !== null;
1710
+ }
1711
+ async function captureViaViewShot(config) {
1712
+ if (config.screenshotNativeMode === "disabled") return null;
1713
+ const viewShot = tryRequire("react-native-view-shot");
1714
+ if (!viewShot) return null;
1715
+ const captureRef = viewShot.captureRef ?? viewShot.default?.captureRef;
1716
+ if (typeof captureRef !== "function") return null;
1717
+ const refTarget = rootViewRef?.current;
1718
+ if (!refTarget) return null;
1719
+ const format = config.screenshotFormat === "jpg" ? "jpg" : config.screenshotFormat;
1720
+ const dimensions = readDimensions();
1721
+ __setCapturing(true);
1722
+ await new Promise((r) => setTimeout(r, 16));
1723
+ try {
1724
+ const captured = await Promise.race([
1725
+ Promise.resolve(captureRef(refTarget, {
1726
+ format,
1727
+ quality: config.screenshotQuality,
1728
+ result: "base64"
1729
+ })),
1730
+ new Promise((resolve) => setTimeout(() => resolve(null), config.screenshotCaptureTimeoutMs))
1731
+ ]);
1732
+ if (!captured || typeof captured !== "string") return null;
1733
+ const sizeBytes = estimateBase64Size(captured);
1734
+ if (sizeBytes > config.screenshotMaxBytes) {
1735
+ if (__DEV__) {
1736
+ console.warn(`[AllStak] Screenshot ${sizeBytes}B exceeds limit ${config.screenshotMaxBytes}B; dropping.`);
1737
+ }
1738
+ return null;
1739
+ }
1740
+ const contentType = format === "png" ? "image/png" : format === "webp" ? "image/webp" : "image/jpeg";
1741
+ return {
1742
+ dataBase64: captured,
1743
+ contentType,
1744
+ width: dimensions.width,
1745
+ height: dimensions.height,
1746
+ sizeBytes
1747
+ };
1748
+ } catch (err) {
1749
+ if (__DEV__) {
1750
+ console.warn("[AllStak] view-shot capture failed:", err?.message);
1751
+ }
1752
+ return null;
1753
+ } finally {
1754
+ __setCapturing(false);
1755
+ }
1756
+ }
1757
+ function estimateBase64Size(base64) {
1758
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
1759
+ return Math.floor(base64.length * 3 / 4) - padding;
1760
+ }
1761
+ function readDimensions() {
1762
+ try {
1763
+ const RN3 = tryRequire("react-native");
1764
+ const dims = RN3?.Dimensions?.get?.("window");
1765
+ if (dims && typeof dims.width === "number" && typeof dims.height === "number") {
1766
+ return { width: Math.round(dims.width), height: Math.round(dims.height) };
1767
+ }
1768
+ } catch {
1769
+ }
1770
+ return { width: 0, height: 0 };
1771
+ }
1772
+ async function maybeCaptureScreenshot(config, ctx) {
1773
+ try {
1774
+ if (!config.captureScreenshotOnError) return null;
1775
+ if (config.screenshotOnUnhandledOnly && ctx.unhandled === false) return null;
1776
+ if (config.screenshotSampleRate < 1 && Math.random() >= config.screenshotSampleRate) return null;
1777
+ if (!runtimeAllowsScreenshot(ctx.runtimeMode)) return null;
1778
+ if (config.isScreenshotAllowed) {
1779
+ try {
1780
+ const allowed = await config.isScreenshotAllowed(ctx);
1781
+ if (!allowed) return null;
1782
+ } catch {
1783
+ return null;
1784
+ }
1785
+ }
1786
+ if (config.beforeScreenshotCapture) {
1787
+ try {
1788
+ const cont = await config.beforeScreenshotCapture(ctx);
1789
+ if (!cont) return null;
1790
+ } catch {
1791
+ return null;
1792
+ }
1793
+ }
1794
+ const upload = await captureViaViewShot(config);
1795
+ if (!upload) return null;
1796
+ const metadata = {
1797
+ captureMethod: "react-native-view-shot",
1798
+ redactionMode: config.screenshotRedaction,
1799
+ maskStyle: config.screenshotMaskStyle,
1800
+ format: config.screenshotFormat,
1801
+ width: upload.width,
1802
+ height: upload.height,
1803
+ sizeBytes: upload.sizeBytes,
1804
+ privacyComponentsDetected: sensitiveRefCount(),
1805
+ runtimeMode: ctx.runtimeMode
1806
+ };
1807
+ if (config.beforeScreenshotUpload) {
1808
+ try {
1809
+ const filtered = await config.beforeScreenshotUpload(upload, metadata);
1810
+ if (!filtered) return null;
1811
+ return { upload: filtered, metadata };
1812
+ } catch {
1813
+ return null;
1814
+ }
1815
+ }
1816
+ return { upload, metadata };
1817
+ } catch (err) {
1818
+ if (__DEV__) {
1819
+ console.warn("[AllStak] maybeCaptureScreenshot fail-open:", err?.message);
1820
+ }
1821
+ return null;
1822
+ }
1823
+ }
1824
+ function pickScreenshotConfig(source) {
1825
+ const out = {};
1826
+ const pick = (key) => {
1827
+ if (source[key] !== void 0) out[key] = source[key];
1828
+ };
1829
+ pick("captureScreenshotOnError");
1830
+ pick("screenshotRedaction");
1831
+ pick("screenshotMaskStyle");
1832
+ pick("screenshotMaxBytes");
1833
+ pick("screenshotQuality");
1834
+ pick("screenshotFormat");
1835
+ pick("screenshotSampleRate");
1836
+ pick("screenshotOnUnhandledOnly");
1837
+ pick("screenshotUploadTimeoutMs");
1838
+ pick("screenshotCaptureTimeoutMs");
1839
+ pick("screenshotNativeMode");
1840
+ pick("screenshotFailPolicy");
1841
+ pick("beforeScreenshotCapture");
1842
+ pick("beforeScreenshotUpload");
1843
+ pick("isScreenshotAllowed");
1844
+ return out;
1845
+ }
1846
+
1446
1847
  // src/client.ts
1447
1848
  var INGEST_HOST = "https://api.allstak.sa";
1448
1849
  var SDK_NAME = "allstak-react-native";
1449
- var SDK_VERSION = "0.3.4";
1850
+ var SDK_VERSION = "0.4.0";
1450
1851
  var ERRORS_PATH = "/ingest/v1/errors";
1451
1852
  var LOGS_PATH = "/ingest/v1/logs";
1452
1853
  var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
@@ -1625,6 +2026,18 @@ var AllStakClient = class {
1625
2026
  breadcrumbs: currentBreadcrumbs,
1626
2027
  fingerprint: eff.fingerprint
1627
2028
  };
2029
+ const flatPresent = this.config.captureScreenshotOnError === true;
2030
+ const callbackPresent = Boolean(this.config.screenshot?.provider);
2031
+ warnIfBothApisPresent(callbackPresent, flatPresent);
2032
+ if (flatPresent) {
2033
+ void this.runFlatScreenshotPipeline(error, payload).catch(() => {
2034
+ void this.sendThroughBeforeSend({
2035
+ ...payload,
2036
+ metadata: { ...payload.metadata ?? {}, "screenshot.status": "failed" }
2037
+ });
2038
+ });
2039
+ return;
2040
+ }
1628
2041
  if (this.shouldCaptureScreenshot()) {
1629
2042
  void this.withScreenshotMetadata(error, payload).then((enriched) => this.sendThroughBeforeSend(enriched)).catch(() => this.sendThroughBeforeSend({
1630
2043
  ...payload,
@@ -1640,6 +2053,86 @@ var AllStakClient = class {
1640
2053
  }
1641
2054
  });
1642
2055
  }
2056
+ /**
2057
+ * Flat screenshot API path. Capture first (so masking primitives can
2058
+ * swap), send the event with a `screenshot.status` metadata tag, read
2059
+ * back the server-assigned errorId, then upload the attachment.
2060
+ *
2061
+ * Fail-open at every step.
2062
+ */
2063
+ async runFlatScreenshotPipeline(error, payload) {
2064
+ const sc = resolveScreenshotConfig(pickScreenshotConfig(this.config));
2065
+ const runtimeMode = detectRuntimeMode();
2066
+ const ctx = { error, unhandled: true, runtimeMode };
2067
+ let captured = null;
2068
+ try {
2069
+ captured = await maybeCaptureScreenshot(sc, ctx);
2070
+ } catch {
2071
+ captured = null;
2072
+ }
2073
+ const status = !sc.captureScreenshotOnError ? "disabled" : captured ? "captured" : runtimeMode === "expo-go" ? "unsupported_runtime" : "unavailable";
2074
+ const eventPayload = {
2075
+ ...payload,
2076
+ metadata: {
2077
+ ...payload.metadata ?? {},
2078
+ "screenshot.status": status,
2079
+ "screenshot.runtimeMode": runtimeMode,
2080
+ ...captured ? {
2081
+ "screenshot.contentType": captured.upload.contentType,
2082
+ "screenshot.width": captured.upload.width,
2083
+ "screenshot.height": captured.upload.height,
2084
+ "screenshot.sizeBytes": captured.upload.sizeBytes,
2085
+ "screenshot.redactionMode": captured.metadata.redactionMode,
2086
+ "screenshot.maskStyle": captured.metadata.maskStyle,
2087
+ "screenshot.captureMethod": captured.metadata.captureMethod
2088
+ } : {}
2089
+ }
2090
+ };
2091
+ let finalPayload = eventPayload;
2092
+ if (this.config.beforeSend) {
2093
+ try {
2094
+ finalPayload = await this.config.beforeSend(eventPayload);
2095
+ } catch {
2096
+ finalPayload = eventPayload;
2097
+ }
2098
+ }
2099
+ if (!finalPayload) return;
2100
+ let eventId = null;
2101
+ try {
2102
+ const resp = await this.transport.sendAndRead(
2103
+ ERRORS_PATH,
2104
+ finalPayload,
2105
+ { timeoutMs: 5e3, retries: 1 }
2106
+ );
2107
+ eventId = resp?.data?.id ?? resp?.id ?? null;
2108
+ } catch {
2109
+ }
2110
+ if (!captured || !eventId) return;
2111
+ try {
2112
+ await this.transport.sendAndRead(
2113
+ `/ingest/v1/errors/${encodeURIComponent(eventId)}/attachments`,
2114
+ {
2115
+ kind: "screenshot",
2116
+ contentType: captured.upload.contentType,
2117
+ dataBase64: captured.upload.dataBase64,
2118
+ width: captured.upload.width,
2119
+ height: captured.upload.height,
2120
+ redactionMode: captured.metadata.redactionMode,
2121
+ captureMethod: captured.metadata.captureMethod,
2122
+ sizeBytes: captured.upload.sizeBytes,
2123
+ metadata: {
2124
+ maskStyle: captured.metadata.maskStyle,
2125
+ format: captured.metadata.format,
2126
+ runtimeMode: captured.metadata.runtimeMode,
2127
+ privacyComponentsDetected: captured.metadata.privacyComponentsDetected ?? 0,
2128
+ sdkVersion: SDK_VERSION
2129
+ }
2130
+ },
2131
+ { timeoutMs: sc.screenshotUploadTimeoutMs, retries: 2 }
2132
+ );
2133
+ } catch {
2134
+ }
2135
+ }
1643
2136
  /** Start a new span. Auto-parented to any currently-active span. */
1644
2137
  startSpan(operation, options) {
1645
2138
  return this.tracing.startSpan(operation, options);
@@ -2164,7 +2657,7 @@ function byteSize(value) {
2164
2657
  }
2165
2658
 
2166
2659
  // src/provider.tsx
2167
- var React = __toESM(require("react"));
2660
+ var React2 = __toESM(require("react"));
2168
2661
 
2169
2662
  // src/auto-breadcrumbs.ts
2170
2663
  var FETCH_FLAG2 = "__allstak_fetch_patched__";
@@ -2401,17 +2894,17 @@ function tryAutoInstrumentNavigation() {
2401
2894
  const rnav = require("@react-navigation/native");
2402
2895
  if (!rnav || !rnav.NavigationContainer) return false;
2403
2896
  if (rnav[NAV_AUTO_PATCH_FLAG]) return true;
2404
- const React2 = require("react");
2405
- if (!React2 || typeof React2.forwardRef !== "function") return false;
2897
+ const React3 = require("react");
2898
+ if (!React3 || typeof React3.forwardRef !== "function") return false;
2406
2899
  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) => {
2900
+ const Wrapped = React3.forwardRef(function AllStakNavigationContainer(props, userRef) {
2901
+ const internalRef = React3.useRef(null);
2902
+ const setRef = React3.useCallback((r) => {
2410
2903
  internalRef.current = r;
2411
2904
  if (typeof userRef === "function") userRef(r);
2412
2905
  else if (userRef) userRef.current = r;
2413
2906
  }, [userRef]);
2414
- React2.useEffect(() => {
2907
+ React3.useEffect(() => {
2415
2908
  if (internalRef.current) {
2416
2909
  try {
2417
2910
  instrumentReactNavigation(internalRef.current);
@@ -2419,7 +2912,7 @@ function tryAutoInstrumentNavigation() {
2419
2912
  }
2420
2913
  }
2421
2914
  }, []);
2422
- return React2.createElement(OrigContainer, { ...props, ref: setRef });
2915
+ return React3.createElement(OrigContainer, { ...props, ref: setRef });
2423
2916
  });
2424
2917
  Wrapped.displayName = "AllStakNavigationContainer";
2425
2918
  try {
@@ -2649,9 +3142,11 @@ function installReactNative(options = {}) {
2649
3142
  }
2650
3143
 
2651
3144
  // src/provider.tsx
2652
- var AllStakContext = React.createContext(null);
3145
+ var RN2 = tryRequire("react-native");
3146
+ var RootView = RN2?.View ?? ((props) => React2.createElement("View", props));
3147
+ var AllStakContext = React2.createContext(null);
2653
3148
  var __providerOwnedInstance = null;
2654
- var AllStakErrorBoundary = class extends React.Component {
3149
+ var AllStakErrorBoundary = class extends React2.Component {
2655
3150
  constructor() {
2656
3151
  super(...arguments);
2657
3152
  this.state = { error: null };
@@ -2719,9 +3214,31 @@ function AllStakProvider({
2719
3214
  autoNetworkCapture,
2720
3215
  autoFetchBreadcrumbs,
2721
3216
  autoConsoleBreadcrumbs,
2722
- autoNavigationBreadcrumbs
3217
+ autoNavigationBreadcrumbs,
3218
+ captureScreenshotOnError,
3219
+ screenshotRedaction,
3220
+ screenshotMaskStyle,
3221
+ screenshotMaxBytes,
3222
+ screenshotQuality,
3223
+ screenshotFormat,
3224
+ screenshotSampleRate,
3225
+ screenshotOnUnhandledOnly,
3226
+ screenshotUploadTimeoutMs,
3227
+ screenshotCaptureTimeoutMs,
3228
+ screenshotNativeMode,
3229
+ screenshotFailPolicy,
3230
+ beforeScreenshotCapture,
3231
+ beforeScreenshotUpload,
3232
+ isScreenshotAllowed
2723
3233
  }) {
2724
- const clientRef = React.useRef(null);
3234
+ const clientRef = React2.useRef(null);
3235
+ const rootRef = React2.useRef(null);
3236
+ React2.useEffect(() => {
3237
+ __setRootViewRef(rootRef);
3238
+ return () => {
3239
+ __setRootViewRef(null);
3240
+ };
3241
+ }, []);
2725
3242
  if (!clientRef.current) {
2726
3243
  const existing = AllStak._getInstance();
2727
3244
  if (existing && __providerOwnedInstance === existing) {
@@ -2745,7 +3262,22 @@ function AllStakProvider({
2745
3262
  replay,
2746
3263
  tracesSampleRate,
2747
3264
  service,
2748
- dist
3265
+ dist,
3266
+ captureScreenshotOnError,
3267
+ screenshotRedaction,
3268
+ screenshotMaskStyle,
3269
+ screenshotMaxBytes,
3270
+ screenshotQuality,
3271
+ screenshotFormat,
3272
+ screenshotSampleRate,
3273
+ screenshotOnUnhandledOnly,
3274
+ screenshotUploadTimeoutMs,
3275
+ screenshotCaptureTimeoutMs,
3276
+ screenshotNativeMode,
3277
+ screenshotFailPolicy,
3278
+ beforeScreenshotCapture,
3279
+ beforeScreenshotUpload,
3280
+ isScreenshotAllowed
2749
3281
  };
2750
3282
  clientRef.current = AllStak.init(config);
2751
3283
  __providerOwnedInstance = clientRef.current;
@@ -2765,7 +3297,7 @@ function AllStakProvider({
2765
3297
  }
2766
3298
  }
2767
3299
  }
2768
- React.useEffect(() => {
3300
+ React2.useEffect(() => {
2769
3301
  return () => {
2770
3302
  if (destroyOnUnmount) {
2771
3303
  AllStak.destroy();
@@ -2775,10 +3307,16 @@ function AllStakProvider({
2775
3307
  }
2776
3308
  };
2777
3309
  }, [destroyOnUnmount, debug]);
2778
- return /* @__PURE__ */ React.createElement(AllStakContext.Provider, { value: clientRef.current }, /* @__PURE__ */ React.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children));
3310
+ const boundary = /* @__PURE__ */ React2.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children);
3311
+ const wrapped = RN2 ? React2.createElement(
3312
+ RootView,
3313
+ { ref: rootRef, style: { flex: 1 }, collapsable: false },
3314
+ boundary
3315
+ ) : boundary;
3316
+ return /* @__PURE__ */ React2.createElement(AllStakContext.Provider, { value: clientRef.current }, wrapped);
2779
3317
  }
2780
3318
  function useAllStak() {
2781
- return React.useMemo(
3319
+ return React2.useMemo(
2782
3320
  () => ({
2783
3321
  captureException: (error, ctx) => AllStak.captureException(error, ctx),
2784
3322
  captureMessage: (msg, level = "info") => AllStak.captureMessage(msg, level),