@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/CHANGELOG.md +53 -0
- package/README.md +398 -95
- package/app.plugin.js +1 -3
- package/build-hooks/upload-sourcemaps.js +0 -15
- package/dist/build/sourcemaps.js +2 -18
- package/dist/build/sourcemaps.js.map +1 -1
- package/dist/build/sourcemaps.mjs +4 -20
- package/dist/build/sourcemaps.mjs.map +1 -1
- package/dist/expo-plugin.js +5 -1
- package/dist/expo-plugin.js.map +1 -1
- package/dist/expo-plugin.mjs +5 -1
- package/dist/expo-plugin.mjs.map +1 -1
- package/dist/index.d.mts +237 -3
- package/dist/index.d.ts +237 -3
- package/dist/index.js +578 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +578 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
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.
|
|
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
|
|
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
|
|
2405
|
-
if (!
|
|
2913
|
+
const React3 = require("react");
|
|
2914
|
+
if (!React3 || typeof React3.forwardRef !== "function") return false;
|
|
2406
2915
|
const OrigContainer = rnav.NavigationContainer;
|
|
2407
|
-
const Wrapped =
|
|
2408
|
-
const internalRef =
|
|
2409
|
-
const setRef =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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),
|