@sentry/react-native 8.2.0 → 8.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.
Files changed (122) hide show
  1. package/README.md +0 -1
  2. package/RNSentry.podspec +1 -1
  3. package/android/AGENTS.md +51 -0
  4. package/android/libs/replay-stubs.jar +0 -0
  5. package/android/src/main/java/io/sentry/react/RNSentryLogger.java +96 -0
  6. package/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +13 -3
  7. package/android/src/main/java/io/sentry/react/RNSentryStart.java +21 -9
  8. package/android/src/main/java/io/sentry/react/RNSentryVersion.java +1 -1
  9. package/dist/js/NativeLogListener.d.ts +16 -0
  10. package/dist/js/NativeLogListener.d.ts.map +1 -0
  11. package/dist/js/NativeLogListener.js +72 -0
  12. package/dist/js/NativeLogListener.js.map +1 -0
  13. package/dist/js/client.d.ts +1 -0
  14. package/dist/js/client.d.ts.map +1 -1
  15. package/dist/js/client.js +14 -3
  16. package/dist/js/client.js.map +1 -1
  17. package/dist/js/index.d.ts +4 -4
  18. package/dist/js/index.d.ts.map +1 -1
  19. package/dist/js/index.js +2 -2
  20. package/dist/js/index.js.map +1 -1
  21. package/dist/js/integrations/default.d.ts.map +1 -1
  22. package/dist/js/integrations/default.js +2 -1
  23. package/dist/js/integrations/default.js.map +1 -1
  24. package/dist/js/integrations/expoconstants.d.ts +24 -0
  25. package/dist/js/integrations/expoconstants.d.ts.map +1 -0
  26. package/dist/js/integrations/expoconstants.js +77 -0
  27. package/dist/js/integrations/expoconstants.js.map +1 -0
  28. package/dist/js/integrations/expocontext.d.ts.map +1 -1
  29. package/dist/js/integrations/expocontext.js +35 -1
  30. package/dist/js/integrations/expocontext.js.map +1 -1
  31. package/dist/js/integrations/exports.d.ts +1 -0
  32. package/dist/js/integrations/exports.d.ts.map +1 -1
  33. package/dist/js/integrations/exports.js +1 -0
  34. package/dist/js/integrations/exports.js.map +1 -1
  35. package/dist/js/options.d.ts +26 -0
  36. package/dist/js/options.d.ts.map +1 -1
  37. package/dist/js/options.js.map +1 -1
  38. package/dist/js/sdk.d.ts.map +1 -1
  39. package/dist/js/sdk.js +3 -2
  40. package/dist/js/sdk.js.map +1 -1
  41. package/dist/js/tools/easBuildHooks.d.ts +48 -0
  42. package/dist/js/tools/easBuildHooks.d.ts.map +1 -0
  43. package/dist/js/tools/easBuildHooks.js +228 -0
  44. package/dist/js/tools/easBuildHooks.js.map +1 -0
  45. package/dist/js/tools/metroconfig.d.ts +4 -0
  46. package/dist/js/tools/metroconfig.d.ts.map +1 -1
  47. package/dist/js/tools/metroconfig.js +46 -1
  48. package/dist/js/tools/metroconfig.js.map +1 -1
  49. package/dist/js/tracing/expoAsset.d.ts +42 -0
  50. package/dist/js/tracing/expoAsset.d.ts.map +1 -0
  51. package/dist/js/tracing/expoAsset.js +60 -0
  52. package/dist/js/tracing/expoAsset.js.map +1 -0
  53. package/dist/js/tracing/expoImage.d.ts +61 -0
  54. package/dist/js/tracing/expoImage.d.ts.map +1 -0
  55. package/dist/js/tracing/expoImage.js +101 -0
  56. package/dist/js/tracing/expoImage.js.map +1 -0
  57. package/dist/js/tracing/index.d.ts +4 -0
  58. package/dist/js/tracing/index.d.ts.map +1 -1
  59. package/dist/js/tracing/index.js +2 -0
  60. package/dist/js/tracing/index.js.map +1 -1
  61. package/dist/js/tracing/integrations/appStart.d.ts.map +1 -1
  62. package/dist/js/tracing/integrations/appStart.js +4 -1
  63. package/dist/js/tracing/integrations/appStart.js.map +1 -1
  64. package/dist/js/tracing/onSpanEndUtils.d.ts +7 -0
  65. package/dist/js/tracing/onSpanEndUtils.d.ts.map +1 -1
  66. package/dist/js/tracing/onSpanEndUtils.js +45 -6
  67. package/dist/js/tracing/onSpanEndUtils.js.map +1 -1
  68. package/dist/js/tracing/origin.d.ts +2 -0
  69. package/dist/js/tracing/origin.d.ts.map +1 -1
  70. package/dist/js/tracing/origin.js +2 -0
  71. package/dist/js/tracing/origin.js.map +1 -1
  72. package/dist/js/tracing/reactnavigation.d.ts +15 -0
  73. package/dist/js/tracing/reactnavigation.d.ts.map +1 -1
  74. package/dist/js/tracing/reactnavigation.js +50 -17
  75. package/dist/js/tracing/reactnavigation.js.map +1 -1
  76. package/dist/js/tracing/span.d.ts +6 -6
  77. package/dist/js/tracing/span.d.ts.map +1 -1
  78. package/dist/js/tracing/span.js +3 -3
  79. package/dist/js/tracing/span.js.map +1 -1
  80. package/dist/js/tracing/utils.d.ts +27 -1
  81. package/dist/js/tracing/utils.d.ts.map +1 -1
  82. package/dist/js/tracing/utils.js +66 -1
  83. package/dist/js/tracing/utils.js.map +1 -1
  84. package/dist/js/utils/expoglobalobject.d.ts +47 -7
  85. package/dist/js/utils/expoglobalobject.d.ts.map +1 -1
  86. package/dist/js/utils/expoglobalobject.js.map +1 -1
  87. package/dist/js/version.d.ts +1 -1
  88. package/dist/js/version.js +6 -3
  89. package/dist/js/version.js.map +1 -1
  90. package/dist/js/wrapper.d.ts.map +1 -1
  91. package/dist/js/wrapper.js +1 -1
  92. package/dist/js/wrapper.js.map +1 -1
  93. package/ios/AGENTS.md +60 -0
  94. package/ios/RNSentry.mm +4 -1
  95. package/ios/RNSentryEvents.h +1 -0
  96. package/ios/RNSentryEvents.m +1 -0
  97. package/ios/RNSentryNativeLogsForwarder.h +20 -0
  98. package/ios/RNSentryNativeLogsForwarder.m +145 -0
  99. package/ios/RNSentryVersion.m +1 -1
  100. package/package.json +14 -11
  101. package/plugin/build/utils.d.ts +1 -0
  102. package/plugin/build/utils.js +19 -1
  103. package/plugin/build/withSentry.d.ts +1 -0
  104. package/plugin/build/withSentry.js +28 -0
  105. package/scripts/eas-build-hook.js +234 -0
  106. package/scripts/sentry-xcode.sh +7 -0
  107. package/ts3.8/dist/js/NativeLogListener.d.ts +16 -0
  108. package/ts3.8/dist/js/client.d.ts +1 -0
  109. package/ts3.8/dist/js/index.d.ts +4 -4
  110. package/ts3.8/dist/js/integrations/expoconstants.d.ts +24 -0
  111. package/ts3.8/dist/js/integrations/exports.d.ts +1 -0
  112. package/ts3.8/dist/js/options.d.ts +26 -0
  113. package/ts3.8/dist/js/tracing/expoAsset.d.ts +42 -0
  114. package/ts3.8/dist/js/tracing/expoImage.d.ts +61 -0
  115. package/ts3.8/dist/js/tracing/index.d.ts +4 -0
  116. package/ts3.8/dist/js/tracing/onSpanEndUtils.d.ts +7 -0
  117. package/ts3.8/dist/js/tracing/origin.d.ts +2 -0
  118. package/ts3.8/dist/js/tracing/reactnavigation.d.ts +15 -0
  119. package/ts3.8/dist/js/tracing/span.d.ts +6 -6
  120. package/ts3.8/dist/js/tracing/utils.d.ts +27 -1
  121. package/ts3.8/dist/js/utils/expoglobalobject.d.ts +47 -7
  122. package/ts3.8/dist/js/version.d.ts +1 -1
@@ -0,0 +1,145 @@
1
+ #import "RNSentryNativeLogsForwarder.h"
2
+ #import "RNSentryEvents.h"
3
+
4
+ @import Sentry;
5
+
6
+ @interface RNSentryNativeLogsForwarder ()
7
+
8
+ @property (nonatomic, weak) RCTEventEmitter *eventEmitter;
9
+
10
+ @end
11
+
12
+ @implementation RNSentryNativeLogsForwarder
13
+
14
+ + (instancetype)shared
15
+ {
16
+ static RNSentryNativeLogsForwarder *instance = nil;
17
+ static dispatch_once_t onceToken;
18
+ dispatch_once(&onceToken, ^{ instance = [[RNSentryNativeLogsForwarder alloc] init]; });
19
+ return instance;
20
+ }
21
+
22
+ - (void)configureWithEventEmitter:(RCTEventEmitter *)emitter
23
+ {
24
+ self.eventEmitter = emitter;
25
+
26
+ __weak RNSentryNativeLogsForwarder *weakSelf = self;
27
+
28
+ // Set up the Sentry SDK log output to forward logs to JS
29
+ [SentrySDKLog setOutput:^(NSString *_Nonnull message) {
30
+ // Always print to console (default behavior)
31
+ NSLog(@"%@", message);
32
+
33
+ // Forward to JS if we have an emitter
34
+ RNSentryNativeLogsForwarder *strongSelf = weakSelf;
35
+ if (strongSelf) {
36
+ [strongSelf forwardLogMessage:message];
37
+ }
38
+ }];
39
+
40
+ // Send a log to notify user the forwarding works
41
+ [self forwardLogMessage:
42
+ @"[Sentry] [info] [0] [RNSentryNativeLogsForwarder] Native log forwarding "
43
+ @"configured successfully"];
44
+ }
45
+
46
+ - (void)stopForwarding
47
+ {
48
+ self.eventEmitter = nil;
49
+
50
+ // TODO: Ideally we should save the previous output block in configureWithEventEmitter:
51
+ // and restore it here instead of hardcoding NSLog.
52
+ // Reset to default print behavior
53
+ [SentrySDKLog setOutput:^(NSString *_Nonnull message) { NSLog(@"%@", message); }];
54
+ }
55
+
56
+ - (void)forwardLogMessage:(NSString *)message
57
+ {
58
+ RCTEventEmitter *emitter = self.eventEmitter;
59
+ if (emitter == nil) {
60
+ return;
61
+ }
62
+
63
+ // Only forward messages that look like Sentry SDK logs
64
+ if (![message hasPrefix:@"[Sentry]"]) {
65
+ return;
66
+ }
67
+
68
+ // Parse the log message to extract level and component
69
+ // Format: "[Sentry] [level] [timestamp] [Component:line] message"
70
+ // or: "[Sentry] [level] [timestamp] message"
71
+ NSString *level = [self extractLevelFromMessage:message];
72
+ NSString *component = [self extractComponentFromMessage:message];
73
+ NSString *cleanMessage = [self extractCleanMessageFromMessage:message];
74
+
75
+ NSDictionary *body = @{
76
+ @"level" : level,
77
+ @"component" : component,
78
+ @"message" : cleanMessage,
79
+ };
80
+
81
+ // Dispatch async to avoid blocking the calling thread and potential deadlocks
82
+ dispatch_async(dispatch_get_main_queue(), ^{
83
+ RCTEventEmitter *currentEmitter = self.eventEmitter;
84
+ if (currentEmitter != nil) {
85
+ [currentEmitter sendEventWithName:RNSentryNativeLogEvent body:body];
86
+ }
87
+ });
88
+ }
89
+
90
+ - (NSString *)extractLevelFromMessage:(NSString *)message
91
+ {
92
+ // Look for patterns like [debug], [info], [warning], [error], [fatal]
93
+ NSRegularExpression *regex =
94
+ [NSRegularExpression regularExpressionWithPattern:@"\\[(debug|info|warning|error|fatal)\\]"
95
+ options:NSRegularExpressionCaseInsensitive
96
+ error:nil];
97
+
98
+ NSTextCheckingResult *match = [regex firstMatchInString:message
99
+ options:0
100
+ range:NSMakeRange(0, message.length)];
101
+
102
+ if (match && match.numberOfRanges > 1) {
103
+ return [[message substringWithRange:[match rangeAtIndex:1]] lowercaseString];
104
+ }
105
+
106
+ return @"info";
107
+ }
108
+
109
+ - (NSString *)extractComponentFromMessage:(NSString *)message
110
+ {
111
+ // Look for pattern like [ComponentName:123]
112
+ NSRegularExpression *regex =
113
+ [NSRegularExpression regularExpressionWithPattern:@"\\[([A-Za-z]+):\\d+\\]"
114
+ options:0
115
+ error:nil];
116
+
117
+ NSTextCheckingResult *match = [regex firstMatchInString:message
118
+ options:0
119
+ range:NSMakeRange(0, message.length)];
120
+
121
+ if (match && match.numberOfRanges > 1) {
122
+ return [message substringWithRange:[match rangeAtIndex:1]];
123
+ }
124
+
125
+ return @"Sentry";
126
+ }
127
+
128
+ - (NSString *)extractCleanMessageFromMessage:(NSString *)message
129
+ {
130
+ // Remove the prefix parts: [Sentry] [level] [timestamp] [Component:line]
131
+ // and return just the actual message content
132
+ NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
133
+ @"^\\[Sentry\\]\\s*\\[[^\\]]+\\]\\s*\\[[^\\]]+\\]\\s*(?:\\[[^\\]]+\\]\\s*)?"
134
+ options:0
135
+ error:nil];
136
+
137
+ NSString *cleanMessage = [regex stringByReplacingMatchesInString:message
138
+ options:0
139
+ range:NSMakeRange(0, message.length)
140
+ withTemplate:@""];
141
+
142
+ return [cleanMessage stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
143
+ }
144
+
145
+ @end
@@ -3,4 +3,4 @@
3
3
  NSString *const NATIVE_SDK_NAME = @"sentry.cocoa.react-native";
4
4
  NSString *const REACT_NATIVE_SDK_NAME = @"sentry.javascript.react-native";
5
5
  NSString *const REACT_NATIVE_SDK_PACKAGE_NAME = @"npm:@sentry/react-native";
6
- NSString *const REACT_NATIVE_SDK_PACKAGE_VERSION = @"8.2.0";
6
+ NSString *const REACT_NATIVE_SDK_PACKAGE_VERSION = @"8.4.0";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@sentry/react-native",
3
3
  "homepage": "https://github.com/getsentry/sentry-react-native",
4
4
  "repository": "https://github.com/getsentry/sentry-react-native",
5
- "version": "8.2.0",
5
+ "version": "8.4.0",
6
6
  "description": "Official Sentry SDK for react-native",
7
7
  "typings": "dist/js/index.d.ts",
8
8
  "types": "dist/js/index.d.ts",
@@ -45,6 +45,9 @@
45
45
  "lint:prettier": "prettier --config ../../.prettierrc.json --ignore-path ../../.prettierignore --check \"{src,test,scripts,plugin/src}/**/**.ts\""
46
46
  },
47
47
  "bin": {
48
+ "sentry-eas-build-on-complete": "scripts/eas-build-hook.js",
49
+ "sentry-eas-build-on-error": "scripts/eas-build-hook.js",
50
+ "sentry-eas-build-on-success": "scripts/eas-build-hook.js",
48
51
  "sentry-expo-upload-sourcemaps": "scripts/expo-upload-sourcemaps.js"
49
52
  },
50
53
  "keywords": [
@@ -68,22 +71,22 @@
68
71
  "react-native": ">=0.65.0"
69
72
  },
70
73
  "dependencies": {
71
- "@sentry/babel-plugin-component-annotate": "4.9.1",
72
- "@sentry/browser": "10.39.0",
73
- "@sentry/cli": "3.2.2",
74
- "@sentry/core": "10.39.0",
75
- "@sentry/react": "10.39.0",
76
- "@sentry/types": "10.39.0"
74
+ "@sentry/babel-plugin-component-annotate": "5.1.1",
75
+ "@sentry/browser": "10.43.0",
76
+ "@sentry/cli": "3.3.3",
77
+ "@sentry/core": "10.43.0",
78
+ "@sentry/react": "10.43.0",
79
+ "@sentry/types": "10.43.0"
77
80
  },
78
81
  "devDependencies": {
79
82
  "@babel/core": "^7.26.7",
80
83
  "@expo/metro-config": "~0.20.0",
81
84
  "@mswjs/interceptors": "^0.25.15",
82
85
  "@react-native/babel-preset": "0.80.0",
83
- "@sentry-internal/eslint-config-sdk": "10.39.0",
84
- "@sentry-internal/eslint-plugin-sdk": "10.39.0",
85
- "@sentry-internal/typescript": "10.39.0",
86
- "@sentry/wizard": "6.11.0",
86
+ "@sentry-internal/eslint-config-sdk": "10.43.0",
87
+ "@sentry-internal/eslint-plugin-sdk": "10.43.0",
88
+ "@sentry-internal/typescript": "10.43.0",
89
+ "@sentry/wizard": "6.12.0",
87
90
  "@testing-library/react-native": "^13.2.2",
88
91
  "@types/jest": "^29.5.13",
89
92
  "@types/node": "^20.9.3",
@@ -1 +1,2 @@
1
1
  export declare function writeSentryPropertiesTo(filepath: string, sentryProperties: string): void;
2
+ export declare function writeSentryOptions(projectRoot: string, pluginOptions: Record<string, unknown>): void;
@@ -23,9 +23,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.writeSentryPropertiesTo = void 0;
26
+ exports.writeSentryOptions = exports.writeSentryPropertiesTo = void 0;
27
27
  const fs = __importStar(require("fs"));
28
28
  const path = __importStar(require("path"));
29
+ const logger_1 = require("./logger");
29
30
  function writeSentryPropertiesTo(filepath, sentryProperties) {
30
31
  if (!fs.existsSync(filepath)) {
31
32
  throw new Error(`Directory '${filepath}' does not exist.`);
@@ -33,3 +34,20 @@ function writeSentryPropertiesTo(filepath, sentryProperties) {
33
34
  fs.writeFileSync(path.resolve(filepath, 'sentry.properties'), sentryProperties);
34
35
  }
35
36
  exports.writeSentryPropertiesTo = writeSentryPropertiesTo;
37
+ const SENTRY_OPTIONS_FILE_NAME = 'sentry.options.json';
38
+ function writeSentryOptions(projectRoot, pluginOptions) {
39
+ const optionsFilePath = path.resolve(projectRoot, SENTRY_OPTIONS_FILE_NAME);
40
+ let existingOptions = {};
41
+ if (fs.existsSync(optionsFilePath)) {
42
+ try {
43
+ existingOptions = JSON.parse(fs.readFileSync(optionsFilePath, 'utf8'));
44
+ }
45
+ catch (e) {
46
+ (0, logger_1.warnOnce)(`Failed to parse ${SENTRY_OPTIONS_FILE_NAME}: ${e}. These options will not be set.`);
47
+ return;
48
+ }
49
+ }
50
+ const mergedOptions = { ...existingOptions, ...pluginOptions };
51
+ fs.writeFileSync(optionsFilePath, `${JSON.stringify(mergedOptions, null, 2)}\n`);
52
+ }
53
+ exports.writeSentryOptions = writeSentryOptions;
@@ -6,6 +6,7 @@ interface PluginProps {
6
6
  authToken?: string;
7
7
  url?: string;
8
8
  useNativeInit?: boolean;
9
+ options?: Record<string, unknown>;
9
10
  experimental_android?: SentryAndroidGradlePluginOptions;
10
11
  }
11
12
  export declare function getSentryProperties(props: PluginProps | void): string | null;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withSentry = exports.getSentryProperties = void 0;
4
4
  const config_plugins_1 = require("expo/config-plugins");
5
5
  const logger_1 = require("./logger");
6
+ const utils_1 = require("./utils");
6
7
  const version_1 = require("./version");
7
8
  const withSentryAndroid_1 = require("./withSentryAndroid");
8
9
  const withSentryAndroidGradlePlugin_1 = require("./withSentryAndroidGradlePlugin");
@@ -14,6 +15,14 @@ const withSentryPlugin = (config, props) => {
14
15
  delete props.authToken;
15
16
  }
16
17
  let cfg = config;
18
+ const pluginOptions = props?.options ? { ...props.options } : {};
19
+ const environment = process.env.SENTRY_ENVIRONMENT;
20
+ if (environment) {
21
+ pluginOptions.environment = environment;
22
+ }
23
+ if (Object.keys(pluginOptions).length > 0) {
24
+ cfg = withSentryOptionsFile(cfg, pluginOptions);
25
+ }
17
26
  if (sentryProperties !== null) {
18
27
  try {
19
28
  cfg = (0, withSentryAndroid_1.withSentryAndroid)(cfg, { sentryProperties, useNativeInit: props?.useNativeInit });
@@ -61,6 +70,25 @@ ${project ? `defaults.project=${project}` : missingProjectMessage}
61
70
  ${authToken ? `${existingAuthTokenMessage}\nauth.token=${authToken}` : missingAuthTokenMessage}`;
62
71
  }
63
72
  exports.getSentryProperties = getSentryProperties;
73
+ function withSentryOptionsFile(config, pluginOptions) {
74
+ // withDangerousMod requires a platform key, but sentry.options.json is at the project root.
75
+ // We apply to both platforms so it works with `expo prebuild --platform ios` or `--platform android`.
76
+ let cfg = (0, config_plugins_1.withDangerousMod)(config, [
77
+ 'android',
78
+ mod => {
79
+ (0, utils_1.writeSentryOptions)(mod.modRequest.projectRoot, pluginOptions);
80
+ return mod;
81
+ },
82
+ ]);
83
+ cfg = (0, config_plugins_1.withDangerousMod)(cfg, [
84
+ 'ios',
85
+ mod => {
86
+ (0, utils_1.writeSentryOptions)(mod.modRequest.projectRoot, pluginOptions);
87
+ return mod;
88
+ },
89
+ ]);
90
+ return cfg;
91
+ }
64
92
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
65
93
  const withSentry = (0, config_plugins_1.createRunOncePlugin)(withSentryPlugin, version_1.PLUGIN_NAME, version_1.PLUGIN_VERSION);
66
94
  exports.withSentry = withSentry;
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * EAS Build Hook
4
+ *
5
+ * Unified entry point for all EAS build hooks (on-complete, on-error, on-success).
6
+ * The hook name is determined from the bin command name in process.argv[1]
7
+ * (e.g. sentry-eas-build-on-error → on-error) or can be passed as a CLI argument.
8
+ *
9
+ * Required environment variables:
10
+ * - SENTRY_DSN: Your Sentry DSN
11
+ *
12
+ * Optional environment variables:
13
+ * - SENTRY_EAS_BUILD_CAPTURE_SUCCESS: Set to 'true' to also capture successful builds
14
+ * - SENTRY_EAS_BUILD_TAGS: JSON string of additional tags
15
+ * - SENTRY_EAS_BUILD_ERROR_MESSAGE: Custom error message for failed builds
16
+ * - SENTRY_EAS_BUILD_SUCCESS_MESSAGE: Custom success message for successful builds
17
+ *
18
+ * @see https://docs.expo.dev/build-reference/npm-hooks/
19
+ * @see https://docs.sentry.io/platforms/react-native/
20
+ */
21
+
22
+ /* eslint-disable no-console */
23
+
24
+ const path = require('path');
25
+ const fs = require('fs');
26
+
27
+ // ─── Environment loading ─────────────────────────────────────────────────────
28
+
29
+ /**
30
+ * Merges parsed env vars into process.env without overwriting existing values.
31
+ * This preserves EAS secrets and other pre-set environment variables.
32
+ * @param {object} parsed - Parsed environment variables from dotenv
33
+ */
34
+ function mergeEnvWithoutOverwrite(parsed) {
35
+ for (const key of Object.keys(parsed)) {
36
+ if (process.env[key] === undefined) {
37
+ process.env[key] = parsed[key];
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Loads environment variables from various sources:
44
+ * - @expo/env (if available)
45
+ * - .env file (via dotenv, if available)
46
+ * - .env.sentry-build-plugin file
47
+ *
48
+ * NOTE: Existing environment variables (like EAS secrets) are NOT overwritten.
49
+ */
50
+ function loadEnv() {
51
+ // Try @expo/env first
52
+ try {
53
+ require('@expo/env').load('.');
54
+ } catch (_e) {
55
+ // Fallback to dotenv if available
56
+ try {
57
+ const dotenvPath = path.join(process.cwd(), '.env');
58
+ if (fs.existsSync(dotenvPath)) {
59
+ const dotenvFile = fs.readFileSync(dotenvPath, 'utf-8');
60
+ const dotenv = require('dotenv');
61
+ mergeEnvWithoutOverwrite(dotenv.parse(dotenvFile));
62
+ }
63
+ } catch (_e2) {
64
+ // No dotenv available, continue with existing env vars
65
+ }
66
+ }
67
+
68
+ // Also load .env.sentry-build-plugin if it exists
69
+ try {
70
+ const sentryEnvPath = path.join(process.cwd(), '.env.sentry-build-plugin');
71
+ if (fs.existsSync(sentryEnvPath)) {
72
+ const dotenvFile = fs.readFileSync(sentryEnvPath, 'utf-8');
73
+ const dotenv = require('dotenv');
74
+ mergeEnvWithoutOverwrite(dotenv.parse(dotenvFile));
75
+ }
76
+ } catch (_e) {
77
+ // Continue without .env.sentry-build-plugin
78
+ }
79
+ }
80
+
81
+ // ─── Hooks module & options ──────────────────────────────────────────────────
82
+
83
+ /**
84
+ * Loads the EAS build hooks module from the compiled output.
85
+ * @returns {object} The hooks module exports
86
+ * @throws {Error} If the module cannot be loaded
87
+ */
88
+ function loadHooksModule() {
89
+ try {
90
+ return require('../dist/js/tools/easBuildHooks.js');
91
+ } catch (_e) {
92
+ console.error('[Sentry] Could not load EAS build hooks module. Make sure @sentry/react-native is properly installed.');
93
+ process.exit(1);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Parses common options from environment variables.
99
+ * @returns {object} Parsed options object
100
+ */
101
+ function parseBaseOptions() {
102
+ const options = {
103
+ dsn: process.env.SENTRY_DSN,
104
+ };
105
+
106
+ // Parse additional tags if provided
107
+ if (process.env.SENTRY_EAS_BUILD_TAGS) {
108
+ try {
109
+ const parsed = JSON.parse(process.env.SENTRY_EAS_BUILD_TAGS);
110
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
111
+ options.tags = parsed;
112
+ } else {
113
+ console.warn('[Sentry] SENTRY_EAS_BUILD_TAGS must be a JSON object (e.g., {"key":"value"}). Ignoring.');
114
+ }
115
+ } catch (_e) {
116
+ console.warn('[Sentry] Could not parse SENTRY_EAS_BUILD_TAGS as JSON. Ignoring.');
117
+ }
118
+ }
119
+
120
+ return options;
121
+ }
122
+
123
+ // ─── Hook configuration & execution ─────────────────────────────────────────
124
+
125
+ /**
126
+ * Hook configuration keyed by hook name.
127
+ *
128
+ * Each entry defines which extra env vars to read and which hooks module
129
+ * method to call.
130
+ */
131
+ const HOOK_CONFIGS = {
132
+ 'on-complete': {
133
+ envKeys: {
134
+ errorMessage: 'SENTRY_EAS_BUILD_ERROR_MESSAGE',
135
+ successMessage: 'SENTRY_EAS_BUILD_SUCCESS_MESSAGE',
136
+ captureSuccessfulBuilds: 'SENTRY_EAS_BUILD_CAPTURE_SUCCESS',
137
+ },
138
+ method: 'captureEASBuildComplete',
139
+ },
140
+ 'on-error': {
141
+ envKeys: {
142
+ errorMessage: 'SENTRY_EAS_BUILD_ERROR_MESSAGE',
143
+ },
144
+ method: 'captureEASBuildError',
145
+ },
146
+ 'on-success': {
147
+ envKeys: {
148
+ successMessage: 'SENTRY_EAS_BUILD_SUCCESS_MESSAGE',
149
+ captureSuccessfulBuilds: 'SENTRY_EAS_BUILD_CAPTURE_SUCCESS',
150
+ },
151
+ // When a user explicitly configures the on-success hook, they intend to
152
+ // capture successful builds, so default captureSuccessfulBuilds to true.
153
+ defaults: { captureSuccessfulBuilds: true },
154
+ method: 'captureEASBuildSuccess',
155
+ },
156
+ };
157
+
158
+ /**
159
+ * Runs an EAS build hook by name.
160
+ *
161
+ * Loads the environment, resolves hook-specific options from env vars,
162
+ * and calls the corresponding hooks module method.
163
+ *
164
+ * @param {'on-complete' | 'on-error' | 'on-success'} hookName
165
+ */
166
+ async function runEASBuildHook(hookName) {
167
+ const config = HOOK_CONFIGS[hookName];
168
+ if (!config) {
169
+ throw new Error(`Unknown EAS build hook: ${hookName}`);
170
+ }
171
+
172
+ loadEnv();
173
+
174
+ const hooks = loadHooksModule();
175
+ const options = {
176
+ ...parseBaseOptions(),
177
+ ...config.defaults,
178
+ };
179
+
180
+ for (const [optionKey, envKey] of Object.entries(config.envKeys)) {
181
+ if (optionKey === 'captureSuccessfulBuilds') {
182
+ // Only override the default when the env var is explicitly set
183
+ if (process.env[envKey] !== undefined) {
184
+ options[optionKey] = process.env[envKey] === 'true';
185
+ }
186
+ } else if (process.env[envKey] !== undefined) {
187
+ options[optionKey] = process.env[envKey];
188
+ }
189
+ }
190
+
191
+ try {
192
+ await hooks[config.method](options);
193
+ console.log(`[Sentry] EAS build ${hookName} hook completed.`);
194
+ } catch (error) {
195
+ console.error(`[Sentry] Error in eas-build-${hookName} hook:`, error);
196
+ // Don't fail the build hook itself
197
+ }
198
+ }
199
+
200
+ // ─── Hook name resolution & entry point ─────────────────────────────────────
201
+
202
+ const HOOK_NAME_RE = /(?:sentry-eas-build-|build-)(on-(?:complete|error|success))/;
203
+
204
+ /**
205
+ * Resolves which hook to run.
206
+ *
207
+ * 1. Explicit CLI argument: `node build-hook.js on-error`
208
+ * 2. Derived from the script path in process.argv[1]
209
+ */
210
+ function resolveHookName() {
211
+ const arg = process.argv[2];
212
+ if (arg && /^on-(complete|error|success)$/.test(arg)) {
213
+ return arg;
214
+ }
215
+
216
+ const caller = path.basename(process.argv[1] || '', '.js');
217
+ const match = caller.match(HOOK_NAME_RE);
218
+ if (match) {
219
+ return match[1];
220
+ }
221
+
222
+ console.error(
223
+ '[Sentry] Could not determine EAS build hook name. ' +
224
+ 'Pass one of: on-complete, on-error, on-success',
225
+ );
226
+ process.exit(1);
227
+ }
228
+
229
+ const hookName = resolveHookName();
230
+
231
+ runEASBuildHook(hookName).catch(error => {
232
+ console.error(`[Sentry] Unexpected error in eas-build-${hookName} hook:`, error);
233
+ process.exit(1);
234
+ });
@@ -15,6 +15,13 @@ RN_PROJECT_ROOT="${PROJECT_DIR}/.."
15
15
  [ -z "$SENTRY_PROPERTIES" ] && export SENTRY_PROPERTIES=sentry.properties
16
16
  [ -z "$SENTRY_DOTENV_PATH" ] && [ -f "$RN_PROJECT_ROOT/.env.sentry-build-plugin" ] && export SENTRY_DOTENV_PATH="$RN_PROJECT_ROOT/.env.sentry-build-plugin"
17
17
  [ -z "$SOURCEMAP_FILE" ] && export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"
18
+ # Resolve relative SOURCEMAP_FILE to absolute. The script runs from `ios/` (Xcode's PWD),
19
+ # but users typically specify paths relative to the project root. Without this, sentry-cli
20
+ # would resolve relative paths against `ios/` and fail to find the file.
21
+ # See: https://github.com/getsentry/sentry-react-native/issues/3889
22
+ if [[ "$SOURCEMAP_FILE" != /* ]]; then
23
+ export SOURCEMAP_FILE="$(cd "$RN_PROJECT_ROOT" && pwd)/${SOURCEMAP_FILE#./}"
24
+ fi
18
25
 
19
26
  if [ -z "$SENTRY_CLI_EXECUTABLE" ]; then
20
27
  # Try standard resolution safely
@@ -0,0 +1,16 @@
1
+ import type { NativeLogEntry } from './options';
2
+ /**
3
+ * Sets up the native log listener that forwards logs from the native SDK to JS.
4
+ * This only works when `debug: true` is set in Sentry options.
5
+ *
6
+ * @param callback - The callback to invoke when a native log is received.
7
+ * @returns A function to remove the listener, or undefined if setup failed.
8
+ */
9
+ export declare function setupNativeLogListener(callback: (log: NativeLogEntry) => void): (() => void) | undefined;
10
+ /**
11
+ * Default handler for native logs that uses Sentry's debug logger.
12
+ * This avoids interference with captureConsoleIntegration which would
13
+ * otherwise capture these logs as breadcrumbs or events.
14
+ */
15
+ export declare function defaultNativeLogHandler(log: NativeLogEntry): void;
16
+ //# sourceMappingURL=NativeLogListener.d.ts.map
@@ -10,6 +10,7 @@ import type { ReactNativeClientOptions } from './options';
10
10
  export declare class ReactNativeClient extends Client<ReactNativeClientOptions> {
11
11
  private _outcomesBuffer;
12
12
  private _logFlushIdleTimeout;
13
+ private _removeNativeLogListener;
13
14
  /**
14
15
  * Creates a new React Native SDK instance.
15
16
  * @param options Configuration options for this SDK.
@@ -1,16 +1,16 @@
1
1
  export type { Breadcrumb, SdkInfo, Event, Exception, SendFeedbackParams, SeverityLevel, Span, StackFrame, Stacktrace, Thread, User, UserFeedback, ErrorEvent, TransactionEvent, Metric, } from '@sentry/core';
2
- export { addBreadcrumb, addIntegration, captureException, captureEvent, captureFeedback, captureMessage, Scope, setContext, setExtra, setExtras, setTag, setTags, setUser, startInactiveSpan, startSpan, startSpanManual, getActiveSpan, getRootSpan, withActiveSpan, suppressTracing, spanToJSON, spanIsSampled, setMeasurement, getCurrentScope, getGlobalScope, getIsolationScope, getClient, setCurrentClient, addEventProcessor, lastEventId, } from '@sentry/core';
2
+ export { addBreadcrumb, addIntegration, captureException, captureEvent, captureFeedback, captureMessage, Scope, setContext, setExtra, setExtras, setTag, setTags, setUser, startInactiveSpan, startSpan, startSpanManual, getActiveSpan, getRootSpan, withActiveSpan, suppressTracing, spanToJSON, spanIsSampled, setMeasurement, getCurrentScope, getGlobalScope, getIsolationScope, getClient, setCurrentClient, addEventProcessor, lastEventId, consoleSandbox, } from '@sentry/core';
3
3
  export { ErrorBoundary, withErrorBoundary, createReduxEnhancer, Profiler, useProfiler, withProfiler, } from '@sentry/react';
4
4
  export type { FeatureFlagsIntegration } from '@sentry/browser';
5
5
  export { logger, consoleLoggingIntegration, featureFlagsIntegration, metrics } from '@sentry/browser';
6
6
  export * from './integrations/exports';
7
7
  export { SDK_NAME, SDK_VERSION } from './version';
8
- export type { ReactNativeOptions } from './options';
8
+ export type { ReactNativeOptions, NativeLogEntry } from './options';
9
9
  export { ReactNativeClient } from './client';
10
10
  export { init, wrap, nativeCrash, flush, close, withScope, crashedLastRun } from './sdk';
11
11
  export { TouchEventBoundary, withTouchEventBoundary } from './touchevents';
12
- export { reactNativeTracingIntegration, getCurrentReactNativeTracingIntegration, getReactNativeTracingIntegration, reactNavigationIntegration, reactNativeNavigationIntegration, sentryTraceGesture, TimeToInitialDisplay, TimeToFullDisplay, startTimeToInitialDisplaySpan, startTimeToFullDisplaySpan, startIdleNavigationSpan, startIdleSpan, getDefaultIdleNavigationSpanOptions, createTimeToFullDisplay, createTimeToInitialDisplay, wrapExpoRouter, } from './tracing';
13
- export type { TimeToDisplayProps, ExpoRouter } from './tracing';
12
+ export { reactNativeTracingIntegration, getCurrentReactNativeTracingIntegration, getReactNativeTracingIntegration, reactNavigationIntegration, reactNativeNavigationIntegration, sentryTraceGesture, TimeToInitialDisplay, TimeToFullDisplay, startTimeToInitialDisplaySpan, startTimeToFullDisplaySpan, startIdleNavigationSpan, startIdleSpan, getDefaultIdleNavigationSpanOptions, createTimeToFullDisplay, createTimeToInitialDisplay, wrapExpoRouter, wrapExpoImage, wrapExpoAsset, } from './tracing';
13
+ export type { TimeToDisplayProps, ExpoRouter, ExpoImage, ExpoAsset } from './tracing';
14
14
  export { Mask, Unmask } from './replay/CustomMask';
15
15
  export { FeedbackButton } from './feedback/FeedbackButton';
16
16
  export { FeedbackWidget } from './feedback/FeedbackWidget';
@@ -0,0 +1,24 @@
1
+ import type { Integration } from '@sentry/core';
2
+ export declare const EXPO_CONSTANTS_CONTEXT_KEY = "expo_constants";
3
+ /** Load Expo Constants as event context. */
4
+ export declare const expoConstantsIntegration: () => Integration;
5
+ /**
6
+ * @internal Exposed for testing purposes
7
+ */
8
+ export declare function getExpoConstantsContext(): ExpoConstantsContext;
9
+ type ExpoConstantsContext = Partial<{
10
+ execution_environment: string;
11
+ app_ownership: string;
12
+ debug_mode: boolean;
13
+ expo_version: string;
14
+ expo_runtime_version: string;
15
+ session_id: string;
16
+ status_bar_height: number;
17
+ app_name: string;
18
+ app_slug: string;
19
+ app_version: string;
20
+ expo_sdk_version?: string;
21
+ eas_project_id: string;
22
+ }>;
23
+ export {};
24
+ //# sourceMappingURL=expoconstants.d.ts.map
@@ -11,6 +11,7 @@ export { hermesProfilingIntegration } from '../profiling/integration';
11
11
  export { screenshotIntegration } from './screenshot';
12
12
  export { viewHierarchyIntegration } from './viewhierarchy';
13
13
  export { expoContextIntegration } from './expocontext';
14
+ export { expoConstantsIntegration } from './expoconstants';
14
15
  export { spotlightIntegration } from './spotlight';
15
16
  export { mobileReplayIntegration } from '../replay/mobilereplay';
16
17
  export { feedbackIntegration } from '../feedback/integration';
@@ -313,6 +313,32 @@ export interface BaseReactNativeOptions {
313
313
  * @default 'all'
314
314
  */
315
315
  logsOrigin?: 'all' | 'js' | 'native';
316
+ /**
317
+ * A callback that is invoked when the native SDK emits a log message.
318
+ * This is useful for surfacing native SDK logs (e.g., transport errors like HTTP 413)
319
+ * in the JavaScript console.
320
+ *
321
+ * Only works when `debug: true` is set.
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * Sentry.init({
326
+ * debug: true,
327
+ * onNativeLog: ({ level, component, message }) => {
328
+ * console.log(`[Sentry Native] [${level}] [${component}] ${message}`);
329
+ * },
330
+ * });
331
+ * ```
332
+ */
333
+ onNativeLog?: (log: NativeLogEntry) => void;
334
+ }
335
+ /**
336
+ * Represents a log entry from the native SDK.
337
+ */
338
+ export interface NativeLogEntry {
339
+ level: string;
340
+ component: string;
341
+ message: string;
316
342
  }
317
343
  export type SentryReplayQuality = 'low' | 'medium' | 'high';
318
344
  /**