@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/CHANGELOG.md +38 -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 +556 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +556 -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,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.
|
|
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
|
|
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
|
|
2405
|
-
if (!
|
|
2897
|
+
const React3 = require("react");
|
|
2898
|
+
if (!React3 || typeof React3.forwardRef !== "function") return false;
|
|
2406
2899
|
const OrigContainer = rnav.NavigationContainer;
|
|
2407
|
-
const Wrapped =
|
|
2408
|
-
const internalRef =
|
|
2409
|
-
const setRef =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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),
|