@allstak/react-native 0.4.0 → 0.5.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
@@ -56,23 +56,30 @@ __export(index_exports, {
56
56
  __resetRuntimeModeForTest: () => __resetRuntimeModeForTest,
57
57
  __setNativeModuleForTest: () => __setNativeModuleForTest,
58
58
  applyArchitectureTags: () => applyArchitectureTags,
59
+ buildExceptionChain: () => buildExceptionChain,
60
+ buildUserContext: () => buildUserContext,
59
61
  captureBodyResult: () => captureBodyResult,
60
62
  captureViaViewShot: () => captureViaViewShot,
63
+ classifyHttpError: () => classifyHttpError,
64
+ collectAutoContexts: () => collectAutoContexts,
61
65
  detectArchitecture: () => detectArchitecture,
62
66
  detectRuntimeMode: () => detectRuntimeMode,
63
67
  drainPendingNativeCrashes: () => drainPendingNativeCrashes,
68
+ extractAxiosRequest: () => extractAxiosRequest,
64
69
  installReactNative: () => installReactNative,
65
70
  instrumentNavigationFromLinking: () => instrumentNavigationFromLinking,
66
71
  instrumentReactNavigation: () => instrumentReactNavigation,
67
72
  isCapturingScreenshot: () => isCapturingScreenshot,
68
73
  isViewShotAvailable: () => isViewShotAvailable,
69
74
  maybeCaptureScreenshot: () => maybeCaptureScreenshot,
75
+ maybeExtractHttpRequest: () => maybeExtractHttpRequest,
70
76
  pickScreenshotConfig: () => pickScreenshotConfig,
71
77
  redactUrl: () => redactUrl,
72
78
  registerSensitiveRef: () => registerSensitiveRef,
73
79
  resolveScreenshotConfig: () => resolveScreenshotConfig,
74
80
  runtimeAllowsScreenshot: () => runtimeAllowsScreenshot,
75
81
  sanitizeHeaders: () => sanitizeHeaders,
82
+ sanitizeUrl: () => sanitizeUrl,
76
83
  tryAutoInstrumentNavigation: () => tryAutoInstrumentNavigation,
77
84
  useAllStak: () => useAllStak,
78
85
  useAllStakPrivacy: () => useAllStakPrivacy
@@ -1559,7 +1566,9 @@ function tryRequire(id2) {
1559
1566
 
1560
1567
  // src/privacy.tsx
1561
1568
  var React = __toESM(require("react"));
1562
- var RN = tryRequire("react-native");
1569
+ function getRN() {
1570
+ return tryRequire("react-native");
1571
+ }
1563
1572
  var state = {
1564
1573
  isCapturing: false,
1565
1574
  sensitiveRefs: /* @__PURE__ */ new Set()
@@ -1607,9 +1616,18 @@ function useAllStakPrivacy() {
1607
1616
  const isCapturing = useIsCapturing();
1608
1617
  return { isCapturing, registerSensitiveRef };
1609
1618
  }
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));
1619
+ function getView() {
1620
+ const RN = getRN();
1621
+ return RN?.View ?? ((props) => React.createElement(React.Fragment, null, props.children));
1622
+ }
1623
+ function getText() {
1624
+ const RN = getRN();
1625
+ return RN?.Text ?? ((props) => React.createElement(React.Fragment, null, props.children));
1626
+ }
1627
+ function getTextInput() {
1628
+ const RN = getRN();
1629
+ return RN?.TextInput ?? ((props) => React.createElement(React.Fragment, null, null));
1630
+ }
1613
1631
  var DEFAULT_MASK_COLOR = "#d8dde7";
1614
1632
  var DEFAULT_MASK_LABEL = "\u2022\u2022\u2022\u2022\u2022\u2022";
1615
1633
  function AllStakMaskedView({
@@ -1622,6 +1640,8 @@ function AllStakMaskedView({
1622
1640
  ...rest
1623
1641
  }) {
1624
1642
  const isCapturing = useIsCapturing();
1643
+ const View = getView();
1644
+ const Text = getText();
1625
1645
  if (!isCapturing || privacy === "show") {
1626
1646
  return React.createElement(View, { style, ...rest }, children);
1627
1647
  }
@@ -1644,6 +1664,8 @@ function AllStakTextInput({
1644
1664
  ...rest
1645
1665
  }) {
1646
1666
  const isCapturing = useIsCapturing();
1667
+ const View = getView();
1668
+ const TextInput = getTextInput();
1647
1669
  if (isCapturing && privacy !== "show") {
1648
1670
  return React.createElement(View, {
1649
1671
  style: [{ minHeight: 40, backgroundColor: maskColor, borderRadius: 4 }, style]
@@ -1659,6 +1681,7 @@ function AllStakSensitiveText({
1659
1681
  ...rest
1660
1682
  }) {
1661
1683
  const isCapturing = useIsCapturing();
1684
+ const Text = getText();
1662
1685
  if (isCapturing && privacy !== "show") {
1663
1686
  return React.createElement(Text, { style, ...rest }, maskLabel);
1664
1687
  }
@@ -1760,8 +1783,8 @@ function estimateBase64Size(base64) {
1760
1783
  }
1761
1784
  function readDimensions() {
1762
1785
  try {
1763
- const RN3 = tryRequire("react-native");
1764
- const dims = RN3?.Dimensions?.get?.("window");
1786
+ const RN = tryRequire("react-native");
1787
+ const dims = RN?.Dimensions?.get?.("window");
1765
1788
  if (dims && typeof dims.width === "number" && typeof dims.height === "number") {
1766
1789
  return { width: Math.round(dims.width), height: Math.round(dims.height) };
1767
1790
  }
@@ -1844,15 +1867,325 @@ function pickScreenshotConfig(source) {
1844
1867
  return out;
1845
1868
  }
1846
1869
 
1870
+ // src/contexts.ts
1871
+ function tryReq(id2) {
1872
+ try {
1873
+ return require(id2);
1874
+ } catch {
1875
+ return null;
1876
+ }
1877
+ }
1878
+ function defaultExport(mod) {
1879
+ if (!mod) return null;
1880
+ return mod.default ?? mod;
1881
+ }
1882
+ function strOrUndef(v) {
1883
+ if (v == null) return void 0;
1884
+ const s = String(v);
1885
+ return s.length > 0 ? s : void 0;
1886
+ }
1887
+ function collectAutoContexts(opts = {}) {
1888
+ const captureDev = opts.captureDeviceContext !== false;
1889
+ const captureScreen = opts.captureScreenContext !== false;
1890
+ const contexts = {};
1891
+ const tags = {};
1892
+ const RN = tryReq("react-native");
1893
+ const Platform = RN?.Platform;
1894
+ const Dimensions = RN?.Dimensions;
1895
+ const NativeModules = RN?.NativeModules;
1896
+ const osName = strOrUndef(Platform?.OS);
1897
+ const osVersion = strOrUndef(Platform?.Version);
1898
+ const osConstants = Platform?.constants ?? {};
1899
+ if (osName) {
1900
+ contexts.os = {
1901
+ name: osName === "ios" ? "iOS" : osName === "android" ? "Android" : osName,
1902
+ version: osVersion,
1903
+ build: strOrUndef(osConstants?.osBuildId ?? osConstants?.Release)
1904
+ };
1905
+ tags["os.name"] = osName;
1906
+ if (osVersion) tags["os.version"] = osVersion;
1907
+ }
1908
+ const g = globalThis;
1909
+ const hermes = typeof g.HermesInternal !== "undefined";
1910
+ const fabric = typeof g.__turboModuleProxy !== "undefined";
1911
+ const turboModules = fabric;
1912
+ const bridgeless = typeof g.RN$Bridgeless !== "undefined" && !!g.RN$Bridgeless;
1913
+ const jsEngine = hermes ? "hermes" : "jsc";
1914
+ tags["js_engine"] = jsEngine;
1915
+ tags["fabric"] = String(fabric);
1916
+ tags["turbo_modules"] = String(turboModules);
1917
+ const hermesVersion = (() => {
1918
+ try {
1919
+ return g.HermesInternal?.getRuntimeProperties?.()?.["OSS Release Version"];
1920
+ } catch {
1921
+ return void 0;
1922
+ }
1923
+ })();
1924
+ contexts.runtime = {
1925
+ name: jsEngine,
1926
+ version: strOrUndef(hermesVersion) ?? "unknown",
1927
+ bridgeless
1928
+ };
1929
+ if (captureDev) {
1930
+ const device = {};
1931
+ if (osConstants?.Model) device.model = String(osConstants.Model);
1932
+ if (osConstants?.Brand) device.brand = String(osConstants.Brand);
1933
+ if (osConstants?.Manufacturer) device.manufacturer = String(osConstants.Manufacturer);
1934
+ if (osConstants?.systemName) device.systemName = String(osConstants.systemName);
1935
+ if (osConstants?.interfaceIdiom) device.family = String(osConstants.interfaceIdiom);
1936
+ const isSim = Boolean(osConstants?.isTesting) || typeof osConstants?.reactNativeVersion === "object" && String(osConstants?.systemName ?? "").toLowerCase().includes("simulator");
1937
+ if (isSim) {
1938
+ device.simulator = true;
1939
+ tags["simulator"] = "true";
1940
+ }
1941
+ const expoDevice = tryReq("expo-device");
1942
+ if (expoDevice) {
1943
+ const ed = defaultExport(expoDevice) ?? expoDevice;
1944
+ if (ed.modelName) device.model = String(ed.modelName);
1945
+ if (ed.brand) device.brand = String(ed.brand);
1946
+ if (ed.manufacturer) device.manufacturer = String(ed.manufacturer);
1947
+ if (ed.deviceYearClass != null) device.yearClass = ed.deviceYearClass;
1948
+ if (ed.totalMemory != null) device.memory_size = ed.totalMemory;
1949
+ if (typeof ed.supportedCpuArchitectures !== "undefined") {
1950
+ const arr = ed.supportedCpuArchitectures;
1951
+ if (Array.isArray(arr) && arr.length > 0) device.arch = String(arr[0]);
1952
+ }
1953
+ if (typeof ed.isDevice === "boolean" && !ed.isDevice) {
1954
+ device.simulator = true;
1955
+ tags["simulator"] = "true";
1956
+ }
1957
+ } else {
1958
+ const dinfo = tryReq("react-native-device-info");
1959
+ if (dinfo) {
1960
+ const d = defaultExport(dinfo) ?? dinfo;
1961
+ try {
1962
+ device.model = device.model ?? d.getModel?.();
1963
+ } catch {
1964
+ }
1965
+ try {
1966
+ device.manufacturer = device.manufacturer ?? d.getManufacturerSync?.();
1967
+ } catch {
1968
+ }
1969
+ try {
1970
+ device.memory_size = device.memory_size ?? d.getTotalMemorySync?.();
1971
+ } catch {
1972
+ }
1973
+ try {
1974
+ const isEm = d.isEmulatorSync?.();
1975
+ if (isEm) {
1976
+ device.simulator = true;
1977
+ tags["simulator"] = "true";
1978
+ }
1979
+ } catch {
1980
+ }
1981
+ }
1982
+ }
1983
+ if (captureScreen && Dimensions && typeof Dimensions.get === "function") {
1984
+ try {
1985
+ const screen = Dimensions.get("screen");
1986
+ if (screen?.width && screen?.height) {
1987
+ device.screen_width_pixels = Math.round(screen.width * (screen.scale ?? 1));
1988
+ device.screen_height_pixels = Math.round(screen.height * (screen.scale ?? 1));
1989
+ device.screen_density = screen.scale;
1990
+ device.orientation = screen.width >= screen.height ? "landscape" : "portrait";
1991
+ tags["app_orientation"] = device.orientation;
1992
+ }
1993
+ } catch {
1994
+ }
1995
+ }
1996
+ if (opts.captureBattery) {
1997
+ const expoBattery = tryReq("expo-battery");
1998
+ if (expoBattery) {
1999
+ try {
2000
+ const eb = defaultExport(expoBattery) ?? expoBattery;
2001
+ device.battery_available = true;
2002
+ if (typeof eb.getBatteryLevelAsync === "function") {
2003
+ }
2004
+ } catch {
2005
+ }
2006
+ }
2007
+ }
2008
+ if (Object.keys(device).length > 0) {
2009
+ contexts.device = device;
2010
+ if (device.model) tags["device.model"] = String(device.model);
2011
+ }
2012
+ }
2013
+ const app = {};
2014
+ app.app_start_time = (/* @__PURE__ */ new Date()).toISOString();
2015
+ const expoApp = tryReq("expo-application");
2016
+ if (expoApp) {
2017
+ const ea = defaultExport(expoApp) ?? expoApp;
2018
+ if (ea.applicationName) app.app_name = String(ea.applicationName);
2019
+ if (ea.applicationId) app.app_identifier = String(ea.applicationId);
2020
+ if (ea.nativeBuildVersion) app.app_build = String(ea.nativeBuildVersion);
2021
+ if (ea.nativeApplicationVersion) app.app_version = String(ea.nativeApplicationVersion);
2022
+ } else {
2023
+ const dinfo = tryReq("react-native-device-info");
2024
+ if (dinfo) {
2025
+ const d = defaultExport(dinfo) ?? dinfo;
2026
+ try {
2027
+ app.app_name = d.getApplicationName?.();
2028
+ } catch {
2029
+ }
2030
+ try {
2031
+ app.app_identifier = d.getBundleId?.();
2032
+ } catch {
2033
+ }
2034
+ try {
2035
+ app.app_build = d.getBuildNumber?.();
2036
+ } catch {
2037
+ }
2038
+ try {
2039
+ app.app_version = d.getVersion?.();
2040
+ } catch {
2041
+ }
2042
+ }
2043
+ }
2044
+ if (Object.keys(app).length > 0) {
2045
+ contexts.app = app;
2046
+ if (app.app_version) tags["app.version"] = String(app.app_version);
2047
+ if (app.app_build) tags["app.build"] = String(app.app_build);
2048
+ }
2049
+ const rn = {
2050
+ hermes,
2051
+ fabric,
2052
+ turbo_modules: turboModules,
2053
+ bridgeless,
2054
+ js_engine: jsEngine
2055
+ };
2056
+ if (osConstants?.reactNativeVersion) {
2057
+ const v = osConstants.reactNativeVersion;
2058
+ if (typeof v === "object" && v.major != null) {
2059
+ rn.react_native_version = `${v.major}.${v.minor ?? 0}.${v.patch ?? 0}`;
2060
+ } else if (typeof v === "string") {
2061
+ rn.react_native_version = v;
2062
+ }
2063
+ }
2064
+ const expoConstants = tryReq("expo-constants");
2065
+ if (expoConstants) {
2066
+ const ec = defaultExport(expoConstants) ?? expoConstants;
2067
+ if (ec.expoVersion) rn.expo = ec.expoVersion;
2068
+ else if (ec.expoConfig?.sdkVersion) rn.expo = ec.expoConfig.sdkVersion;
2069
+ if (ec.appOwnership) rn.expo_application_ownership = String(ec.appOwnership);
2070
+ if (ec.executionEnvironment) rn.expo_execution_environment = String(ec.executionEnvironment);
2071
+ } else {
2072
+ rn.expo = false;
2073
+ }
2074
+ contexts.react_native = rn;
2075
+ if (rn.expo && rn.expo !== false) tags["expo"] = "true";
2076
+ if (rn.react_native_version) tags["rn.version"] = String(rn.react_native_version);
2077
+ return { contexts, tags };
2078
+ }
2079
+ function buildUserContext(user, opts = {}) {
2080
+ if (!user) return void 0;
2081
+ const out = {};
2082
+ if (user.id) out.id = user.id;
2083
+ if (user.username) out.username = user.username;
2084
+ if (opts.sendDefaultPii) {
2085
+ if (user.email) out.email = user.email;
2086
+ if (user.ip_address) out.ip_address = user.ip_address;
2087
+ }
2088
+ return Object.keys(out).length > 0 ? out : void 0;
2089
+ }
2090
+
2091
+ // src/mechanism.ts
2092
+ var URL_QUERY_RE = /\?.*$/;
2093
+ function sanitizeUrl(url) {
2094
+ if (!url) return "";
2095
+ const s = String(url).replace(URL_QUERY_RE, "");
2096
+ return s;
2097
+ }
2098
+ function exceptionClassName(err) {
2099
+ if (err && typeof err === "object") {
2100
+ const e = err;
2101
+ const fromName = e.name && e.name !== "Error" ? e.name : void 0;
2102
+ return fromName ?? e.constructor?.name ?? "Error";
2103
+ }
2104
+ return "Error";
2105
+ }
2106
+ function exceptionValue(err) {
2107
+ if (err == null) return "";
2108
+ if (err instanceof Error) return err.message ?? "";
2109
+ if (typeof err === "string") return err;
2110
+ try {
2111
+ return JSON.stringify(err);
2112
+ } catch {
2113
+ return String(err);
2114
+ }
2115
+ }
2116
+ function buildExceptionChain(err, mechanism, handled) {
2117
+ const values = [];
2118
+ const seen = /* @__PURE__ */ new Set();
2119
+ let cursor = err;
2120
+ let depth = 0;
2121
+ while (cursor && depth < 5 && !seen.has(cursor)) {
2122
+ seen.add(cursor);
2123
+ const e = cursor;
2124
+ const frames = parseStack(e?.stack);
2125
+ values.push({
2126
+ type: exceptionClassName(e),
2127
+ value: exceptionValue(e),
2128
+ stacktrace: frames.length > 0 ? { frames } : void 0,
2129
+ // Only the outermost exception carries the mechanism, like Sentry.
2130
+ ...depth === 0 ? { mechanism: { type: mechanism, handled } } : {}
2131
+ });
2132
+ cursor = e?.cause;
2133
+ depth += 1;
2134
+ }
2135
+ return values.reverse();
2136
+ }
2137
+ function extractAxiosRequest(err) {
2138
+ if (!err || typeof err !== "object") return null;
2139
+ const e = err;
2140
+ if (e.isAxiosError !== true) return null;
2141
+ const cfg = e.config ?? {};
2142
+ const resp = e.response ?? null;
2143
+ const method = strUpper(cfg.method);
2144
+ const rawUrl = composeAxiosUrl(cfg);
2145
+ const url_sanitized = sanitizeUrl(rawUrl);
2146
+ const status_code = typeof resp?.status === "number" ? resp.status : void 0;
2147
+ const duration_ms = typeof e.duration === "number" ? e.duration : void 0;
2148
+ const category = classifyHttpError(e, status_code);
2149
+ return {
2150
+ method,
2151
+ url_sanitized,
2152
+ status_code,
2153
+ duration_ms,
2154
+ category
2155
+ };
2156
+ }
2157
+ function composeAxiosUrl(cfg) {
2158
+ const base = typeof cfg?.baseURL === "string" ? cfg.baseURL.replace(/\/$/, "") : "";
2159
+ const path = typeof cfg?.url === "string" ? cfg.url : "";
2160
+ if (!base && !path) return "";
2161
+ if (path.startsWith("http://") || path.startsWith("https://")) return path;
2162
+ return base + path;
2163
+ }
2164
+ function strUpper(v) {
2165
+ if (typeof v !== "string" || v.length === 0) return void 0;
2166
+ return v.toUpperCase();
2167
+ }
2168
+ function classifyHttpError(err, status) {
2169
+ const code = typeof err?.code === "string" ? err.code.toUpperCase() : "";
2170
+ if (code === "ECONNABORTED" || code === "ETIMEDOUT") return "timeout";
2171
+ if (code === "ERR_CANCELED" || code === "CANCELED") return "cancel";
2172
+ if (typeof status === "number" && status >= 500) return "http_server_error";
2173
+ if (typeof status === "number" && status >= 400) return "http_client_error";
2174
+ return "network";
2175
+ }
2176
+ function maybeExtractHttpRequest(err) {
2177
+ return extractAxiosRequest(err);
2178
+ }
2179
+
1847
2180
  // src/client.ts
1848
2181
  var INGEST_HOST = "https://api.allstak.sa";
1849
2182
  var SDK_NAME = "allstak-react-native";
1850
- var SDK_VERSION = "0.4.0";
2183
+ var SDK_VERSION = "0.5.0";
1851
2184
  var ERRORS_PATH = "/ingest/v1/errors";
1852
2185
  var LOGS_PATH = "/ingest/v1/logs";
1853
2186
  var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);
1854
2187
  var VALID_BREADCRUMB_LEVELS = /* @__PURE__ */ new Set(["info", "warn", "error", "debug"]);
1855
- var DEFAULT_MAX_BREADCRUMBS = 50;
2188
+ var DEFAULT_MAX_BREADCRUMBS = 100;
1856
2189
  function frameToString(f) {
1857
2190
  const fn = f.function && f.function.length > 0 ? f.function : "<anonymous>";
1858
2191
  const file = f.filename || f.absPath || "<anonymous>";
@@ -1881,6 +2214,12 @@ var AllStakClient = class {
1881
2214
  this.replay = null;
1882
2215
  this.httpRequests = null;
1883
2216
  this._instrumentAxios = null;
2217
+ /** Auto-collected Sentry-shape contexts (device/os/app/react_native/runtime). */
2218
+ this.autoContexts = {};
2219
+ /** Auto-collected tags (device.model, os.name, js_engine, …). */
2220
+ this.autoTags = {};
2221
+ /** Current screen / transaction name — set via setCurrentScreen() or nav auto-instrument. */
2222
+ this.currentTransaction = null;
1884
2223
  this.config = { ...config };
1885
2224
  if (!this.config.environment) this.config.environment = "production";
1886
2225
  if (!this.config.sdkName) this.config.sdkName = SDK_NAME;
@@ -1902,6 +2241,18 @@ var AllStakClient = class {
1902
2241
  } catch {
1903
2242
  }
1904
2243
  }
2244
+ try {
2245
+ const opts = {
2246
+ captureDeviceContext: config.captureDeviceContext !== false,
2247
+ captureBattery: config.captureBattery === true,
2248
+ captureScreenContext: config.captureScreenContext !== false,
2249
+ sendDefaultPii: config.sendDefaultPii === true
2250
+ };
2251
+ const { contexts, tags } = collectAutoContexts(opts);
2252
+ this.autoContexts = contexts;
2253
+ this.autoTags = tags;
2254
+ } catch {
2255
+ }
1905
2256
  if (config.enableHttpTracking) {
1906
2257
  try {
1907
2258
  this.httpRequests = new HttpRequestModule(this.transport);
@@ -1944,7 +2295,7 @@ var AllStakClient = class {
1944
2295
  return this.httpRequests?.getRecentFailed() ?? [];
1945
2296
  }
1946
2297
  // ── Public API ────────────────────────────────────────────────────
1947
- captureException(error, context) {
2298
+ captureException(error, context, opts) {
1948
2299
  if (!this.passesSampleRate()) return;
1949
2300
  const frames = parseStack(error.stack).map((f) => ({
1950
2301
  ...f,
@@ -2026,6 +2377,35 @@ var AllStakClient = class {
2026
2377
  breadcrumbs: currentBreadcrumbs,
2027
2378
  fingerprint: eff.fingerprint
2028
2379
  };
2380
+ const mechanism = opts?.mechanism ?? "captureException";
2381
+ const handled = opts?.handled ?? (mechanism === "captureException" || mechanism === "errorboundary");
2382
+ payload.eventId = generateEventId();
2383
+ payload.timestamp = (/* @__PURE__ */ new Date()).toISOString();
2384
+ payload.handled = handled;
2385
+ payload.mechanism = mechanism;
2386
+ if (this.currentTransaction) payload.transaction = this.currentTransaction;
2387
+ payload.exception = { values: buildExceptionChain(error, mechanism, handled) };
2388
+ const req = maybeExtractHttpRequest(error);
2389
+ if (req) payload.request = req;
2390
+ const userCtx = buildUserContext(eff.user, { sendDefaultPii: this.config.sendDefaultPii });
2391
+ const traceCtx = {};
2392
+ if (payload.traceId) traceCtx.trace_id = payload.traceId;
2393
+ if (payload.spanId) traceCtx.span_id = payload.spanId;
2394
+ if (payload.parentSpanId) traceCtx.parent_span_id = payload.parentSpanId;
2395
+ payload.contexts = {
2396
+ ...this.autoContexts,
2397
+ ...eff.contexts ?? {},
2398
+ ...userCtx ? { user: userCtx } : {},
2399
+ ...Object.keys(traceCtx).length > 0 ? { trace: traceCtx } : {}
2400
+ };
2401
+ payload.tags = {
2402
+ ...this.autoTags,
2403
+ ...this.config.tags ?? {},
2404
+ ...eff.tags ?? {},
2405
+ environment: this.config.environment ?? "production",
2406
+ ...this.config.release ? { release: this.config.release } : {},
2407
+ ...this.config.dist ? { dist: this.config.dist } : {}
2408
+ };
2029
2409
  const flatPresent = this.config.captureScreenshotOnError === true;
2030
2410
  const callbackPresent = Boolean(this.config.screenshot?.provider);
2031
2411
  warnIfBothApisPresent(callbackPresent, flatPresent);
@@ -2183,16 +2563,59 @@ var AllStakClient = class {
2183
2563
  }
2184
2564
  }
2185
2565
  addBreadcrumb(type, message, level, data) {
2186
- const crumb = {
2566
+ let crumb = {
2187
2567
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2188
2568
  type: VALID_BREADCRUMB_TYPES.has(type) ? type : "default",
2189
2569
  message,
2190
2570
  level: level && VALID_BREADCRUMB_LEVELS.has(level) ? level : "info",
2191
2571
  ...data ? { data } : {}
2192
2572
  };
2573
+ if (crumb.type === "http" && (this.config.denyUrls || this.config.allowUrls)) {
2574
+ const url = typeof crumb.data?.url === "string" ? crumb.data.url : "";
2575
+ if (url) {
2576
+ if (this.config.denyUrls && this.config.denyUrls.some((p) => matchUrlPattern(url, p))) return;
2577
+ if (this.config.allowUrls && this.config.allowUrls.length > 0 && !this.config.allowUrls.some((p) => matchUrlPattern(url, p))) return;
2578
+ }
2579
+ }
2580
+ if (crumb.data && this.config.scrubKeys && this.config.scrubKeys.length > 0) {
2581
+ crumb = { ...crumb, data: scrubObject(crumb.data, this.config.scrubKeys) };
2582
+ }
2583
+ if (this.config.beforeBreadcrumb) {
2584
+ try {
2585
+ const out = this.config.beforeBreadcrumb(crumb);
2586
+ if (out === null) return;
2587
+ if (out) crumb = out;
2588
+ } catch {
2589
+ }
2590
+ }
2193
2591
  if (this.breadcrumbs.length >= this.maxBreadcrumbs) this.breadcrumbs.shift();
2194
2592
  this.breadcrumbs.push(crumb);
2195
2593
  }
2594
+ /**
2595
+ * Set the current screen / route name. Stamps `transaction` on every
2596
+ * subsequent event and emits a `navigation` breadcrumb. Use this when
2597
+ * not on `@react-navigation/native` (the nav auto-instrument calls
2598
+ * this for you).
2599
+ */
2600
+ setCurrentScreen(name) {
2601
+ if (!name) return;
2602
+ const prev = this.currentTransaction;
2603
+ this.currentTransaction = name;
2604
+ if (prev !== name) {
2605
+ this.addBreadcrumb("navigation", `${prev ?? "<start>"} -> ${name}`, "info", {
2606
+ from: prev,
2607
+ to: name
2608
+ });
2609
+ }
2610
+ }
2611
+ /** @internal — current transaction (or undefined). */
2612
+ getCurrentTransaction() {
2613
+ return this.currentTransaction ?? void 0;
2614
+ }
2615
+ /** @internal — set transaction without emitting a breadcrumb. */
2616
+ __setTransactionSilent(name) {
2617
+ this.currentTransaction = name && name.length > 0 ? name : null;
2618
+ }
2196
2619
  clearBreadcrumbs() {
2197
2620
  this.breadcrumbs = [];
2198
2621
  }
@@ -2473,9 +2896,9 @@ var AllStak = {
2473
2896
  return instance;
2474
2897
  }
2475
2898
  },
2476
- captureException(error, context) {
2899
+ captureException(error, context, opts) {
2477
2900
  try {
2478
- maybeInit()?.captureException(error, context);
2901
+ maybeInit()?.captureException(error, context, opts);
2479
2902
  } catch {
2480
2903
  }
2481
2904
  },
@@ -2558,6 +2981,27 @@ var AllStak = {
2558
2981
  } catch {
2559
2982
  }
2560
2983
  },
2984
+ /** Set the current screen / transaction name. Stamps event.transaction + emits nav breadcrumb. */
2985
+ setCurrentScreen(name) {
2986
+ try {
2987
+ maybeInit()?.setCurrentScreen(name);
2988
+ } catch {
2989
+ }
2990
+ },
2991
+ getCurrentTransaction() {
2992
+ try {
2993
+ return maybeInit()?.getCurrentTransaction();
2994
+ } catch {
2995
+ return void 0;
2996
+ }
2997
+ },
2998
+ /** @internal — set transaction without emitting a breadcrumb (nav auto-instrument uses this). */
2999
+ __setTransactionSilent(name) {
3000
+ try {
3001
+ maybeInit()?.__setTransactionSilent(name);
3002
+ } catch {
3003
+ }
3004
+ },
2561
3005
  /**
2562
3006
  * Run `callback` with a fresh scoped context. Any user/tag/extra/context/
2563
3007
  * fingerprint/level set on the passed `Scope` is visible only inside the
@@ -2647,6 +3091,29 @@ var AllStak = {
2647
3091
  return instance;
2648
3092
  }
2649
3093
  };
3094
+ function matchUrlPattern(url, p) {
3095
+ if (!url || !p) return false;
3096
+ if (typeof p === "string") return url.includes(p);
3097
+ try {
3098
+ return p.test(url);
3099
+ } catch {
3100
+ return false;
3101
+ }
3102
+ }
3103
+ function scrubObject(obj, keys) {
3104
+ if (!obj || keys.length === 0) return obj;
3105
+ const out = {};
3106
+ const lower = new Set(keys.map((k) => k.toLowerCase()));
3107
+ for (const [k, v] of Object.entries(obj)) {
3108
+ out[k] = lower.has(k.toLowerCase()) ? "[Filtered]" : v;
3109
+ }
3110
+ return out;
3111
+ }
3112
+ function generateEventId() {
3113
+ const hex = (n) => Math.floor(Math.random() * n).toString(16).padStart(1, "0");
3114
+ const seg = (len) => Array.from({ length: len }, () => hex(16)).join("");
3115
+ return `${seg(8)}-${seg(4)}-4${seg(3)}-${(8 + Math.floor(Math.random() * 4)).toString(16)}${seg(3)}-${seg(12)}`;
3116
+ }
2650
3117
  function byteSize(value) {
2651
3118
  if (!value) return 0;
2652
3119
  try {
@@ -2851,6 +3318,10 @@ function instrumentReactNavigation(navigationRef, options = {}) {
2851
3318
  );
2852
3319
  } catch {
2853
3320
  }
3321
+ try {
3322
+ AllStak.__setTransactionSilent?.(next);
3323
+ } catch {
3324
+ }
2854
3325
  if (forwardToReplay) {
2855
3326
  try {
2856
3327
  const replay = AllStak.getReplay?.();
@@ -3090,7 +3561,7 @@ function installReactNative(options = {}) {
3090
3561
  AllStak.captureException(error, {
3091
3562
  source: "react-native-ErrorUtils",
3092
3563
  fatal: String(Boolean(isFatal))
3093
- });
3564
+ }, { mechanism: "onerror", handled: false });
3094
3565
  } catch {
3095
3566
  }
3096
3567
  try {
@@ -3104,7 +3575,11 @@ function installReactNative(options = {}) {
3104
3575
  const wrapTrackerReason = (rejection) => rejection instanceof Error ? rejection : new Error(`Unhandled promise rejection: ${String(rejection)}`);
3105
3576
  const ship = (err) => {
3106
3577
  try {
3107
- AllStak.captureException(err, { source: "unhandledRejection" });
3578
+ AllStak.captureException(
3579
+ err,
3580
+ { source: "unhandledRejection" },
3581
+ { mechanism: "onunhandledrejection", handled: false }
3582
+ );
3108
3583
  } catch {
3109
3584
  }
3110
3585
  };
@@ -3142,8 +3617,13 @@ function installReactNative(options = {}) {
3142
3617
  }
3143
3618
 
3144
3619
  // src/provider.tsx
3145
- var RN2 = tryRequire("react-native");
3146
- var RootView = RN2?.View ?? ((props) => React2.createElement("View", props));
3620
+ function getRN2() {
3621
+ return tryRequire("react-native");
3622
+ }
3623
+ function getRootView() {
3624
+ const RN = getRN2();
3625
+ return RN?.View;
3626
+ }
3147
3627
  var AllStakContext = React2.createContext(null);
3148
3628
  var __providerOwnedInstance = null;
3149
3629
  var AllStakErrorBoundary = class extends React2.Component {
@@ -3163,7 +3643,7 @@ var AllStakErrorBoundary = class extends React2.Component {
3163
3643
  AllStak.captureException(error, {
3164
3644
  componentStack: info.componentStack ?? "",
3165
3645
  source: "AllStakProvider.ErrorBoundary"
3166
- });
3646
+ }, { mechanism: "errorboundary", handled: true });
3167
3647
  if (this.props.debug) {
3168
3648
  console.log(`[AllStak] Captured render error: ${error.message}`);
3169
3649
  }
@@ -3281,6 +3761,10 @@ function AllStakProvider({
3281
3761
  };
3282
3762
  clientRef.current = AllStak.init(config);
3283
3763
  __providerOwnedInstance = clientRef.current;
3764
+ try {
3765
+ AllStak.addBreadcrumb("default", "app.start", "info", { source: "AllStakProvider" });
3766
+ } catch {
3767
+ }
3284
3768
  installReactNative({
3285
3769
  autoErrorHandler,
3286
3770
  autoPromiseRejections,
@@ -3308,7 +3792,8 @@ function AllStakProvider({
3308
3792
  };
3309
3793
  }, [destroyOnUnmount, debug]);
3310
3794
  const boundary = /* @__PURE__ */ React2.createElement(AllStakErrorBoundary, { fallback, onError, debug }, children);
3311
- const wrapped = RN2 ? React2.createElement(
3795
+ const RootView = getRootView();
3796
+ const wrapped = RootView ? React2.createElement(
3312
3797
  RootView,
3313
3798
  { ref: rootRef, style: { flex: 1 }, collapsable: false },
3314
3799
  boundary
@@ -3373,7 +3858,7 @@ async function drainPendingNativeCrashes(release) {
3373
3858
  AllStak.captureException(err, {
3374
3859
  ...payload?.metadata || {},
3375
3860
  "native.crash": "true"
3376
- });
3861
+ }, { mechanism: "native_crash", handled: false });
3377
3862
  } catch {
3378
3863
  }
3379
3864
  }