diff --git a/src/app/index.js b/src/app/index.js
index 8b2f401..a54a5c8 100644
--- a/src/app/index.js
+++ b/src/app/index.js
@@ -6,7 +6,7 @@ import { ErrorUtils } from "react-native";
import { createLogger } from "~/lib/logger";
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
-import { authActions, permissionWizardActions } from "~/stores";
+import { authActions, permissionWizardActions, paramsActions } from "~/stores";
import { secureStore } from "~/storage/memorySecureStore";
import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
@@ -62,6 +62,7 @@ const initializeStores = () => {
// Then initialize other stores sequentially
initializeStore("authActions", authActions.init);
initializeStore("permissionWizard", permissionWizardActions.init);
+ initializeStore("paramsActions", paramsActions.init);
initializeStore("storeSubscriptions", storeSubscriptions.init);
appLogger.info("Core initialization complete");
diff --git a/src/scenes/Params/SentryOptOut.js b/src/scenes/Params/SentryOptOut.js
new file mode 100644
index 0000000..b15bb6e
--- /dev/null
+++ b/src/scenes/Params/SentryOptOut.js
@@ -0,0 +1,78 @@
+import React, { useCallback } from "react";
+import { View } from "react-native";
+import { Title, Switch } from "react-native-paper";
+import { createStyles } from "~/theme";
+import { useParamsState, paramsActions } from "~/stores";
+import Text from "~/components/Text";
+import { setSentryEnabled } from "~/sentry";
+
+function SentryOptOut() {
+ const styles = useStyles();
+ const { sentryEnabled } = useParamsState(["sentryEnabled"]);
+
+ const handleToggle = useCallback(async () => {
+ const newValue = !sentryEnabled;
+ await paramsActions.setSentryEnabled(newValue);
+
+ // Dynamically enable/disable Sentry
+ setSentryEnabled(newValue);
+ }, [sentryEnabled]);
+
+ return (
+
+ Rapport d'erreurs
+
+
+ Envoyer les rapports d'erreurs
+
+
+
+ Les rapports d'erreurs nous aident à améliorer l'application en nous
+ permettant de mieux identifier et corriger les problèmes techniques.
+
+
+
+ );
+}
+
+const useStyles = createStyles(({ theme: { colors } }) => ({
+ container: {
+ width: "100%",
+ alignItems: "center",
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: "bold",
+ marginVertical: 15,
+ },
+ content: {
+ width: "100%",
+ },
+ switchContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-between",
+ marginBottom: 5,
+ paddingVertical: 5,
+ },
+ label: {
+ fontSize: 16,
+ flex: 1,
+ marginRight: 15,
+ },
+ switch: {
+ flexShrink: 0,
+ },
+ description: {
+ fontSize: 14,
+ color: colors.onSurfaceVariant,
+ textAlign: "left",
+ lineHeight: 20,
+ },
+}));
+
+export default SentryOptOut;
diff --git a/src/scenes/Params/View.js b/src/scenes/Params/View.js
index 4b0c0a9..3c89035 100644
--- a/src/scenes/Params/View.js
+++ b/src/scenes/Params/View.js
@@ -6,6 +6,7 @@ import ParamsRadius from "./Radius";
import ParamsEmergencyCall from "./EmergencyCall";
import ThemeSwitcher from "./ThemeSwitcher";
import Permissions from "./Permissions";
+import SentryOptOut from "./SentryOptOut";
export default function ParamsView({ data }) {
const styles = useStyles();
@@ -25,6 +26,9 @@ export default function ParamsView({ data }) {
+
+
+
diff --git a/src/sentry/index.js b/src/sentry/index.js
index 67e1e79..284155e 100644
--- a/src/sentry/index.js
+++ b/src/sentry/index.js
@@ -3,6 +3,15 @@ import { Platform } from "react-native";
import env from "~/env";
import packageJson from "../../package.json";
+import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
+import { STORAGE_KEYS } from "~/storage/storageKeys";
+import { createLogger } from "~/lib/logger";
+import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
+
+const sentryLogger = createLogger({
+ module: SYSTEM_SCOPES.APP,
+ feature: "sentry",
+});
// Get the build number from native code
const getBuildNumber = () => {
@@ -23,60 +32,139 @@ const getReleaseVersion = () => {
return `com.alertesecours@${version}+${buildNumber}`;
};
-Sentry.init({
- dsn: env.SENTRY_DSN,
- tracesSampleRate: 0.1,
- debug: __DEV__,
- // Configure release to match ios-archive.sh format
- release: getReleaseVersion(),
- // Use BUILD_TIME from env to match the value used in sourcemap upload
- dist: env.BUILD_TIME,
- enableNative: true,
- attachStacktrace: true,
- environment: __DEV__ ? "development" : "production",
- normalizeDepth: 10,
- maxBreadcrumbs: 100,
- // Enable debug ID tracking
- _experiments: {
- debugIds: true,
- },
- beforeSend(event) {
- event.extra = {
- ...event.extra,
- jsEngine: global.HermesInternal ? "hermes" : "jsc",
- hermesEnabled: !!global.HermesInternal,
- version: packageJson.version,
- buildNumber: getBuildNumber(),
- buildTime: env.BUILD_TIME,
- };
+// Check if Sentry is enabled by user preference
+const checkSentryEnabled = async () => {
+ try {
+ // Wait for memory storage to be initialized
+ let retries = 0;
+ const maxRetries = 10;
- if (event.exception) {
- event.exception.values = event.exception.values?.map((value) => ({
- ...value,
- mechanism: {
- ...value.mechanism,
- handled: true,
- synthetic: false,
- type: "hermes",
- },
- }));
+ while (retries < maxRetries) {
+ try {
+ const stored = await memoryAsyncStorage.getItem(
+ STORAGE_KEYS.SENTRY_ENABLED,
+ );
+ if (stored !== null) {
+ return JSON.parse(stored);
+ }
+ break; // Storage is ready, no preference stored
+ } catch (error) {
+ if (
+ error.message?.includes("not initialized") &&
+ retries < maxRetries - 1
+ ) {
+ // Wait a bit and retry if storage not initialized
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ retries++;
+ continue;
+ }
+ sentryLogger.warn("Failed to check Sentry preference", {
+ error: error.message,
+ });
+ break;
+ }
}
+ } catch (error) {
+ sentryLogger.warn("Failed to check Sentry preference", {
+ error: error.message,
+ });
+ }
+ // Default to enabled if no preference stored or error occurred
+ return true;
+};
- return event;
- },
- beforeBreadcrumb(breadcrumb) {
- if (breadcrumb.category === "console") {
+// Initialize Sentry with user preference check
+const initializeSentry = async () => {
+ const isEnabled = await checkSentryEnabled();
+
+ Sentry.init({
+ dsn: env.SENTRY_DSN,
+ enabled: isEnabled,
+ tracesSampleRate: 0.1,
+ debug: __DEV__,
+ // Configure release to match ios-archive.sh format
+ release: getReleaseVersion(),
+ // Use BUILD_TIME from env to match the value used in sourcemap upload
+ dist: env.BUILD_TIME,
+ enableNative: true,
+ attachStacktrace: true,
+ environment: __DEV__ ? "development" : "production",
+ normalizeDepth: 10,
+ maxBreadcrumbs: 100,
+ // Enable debug ID tracking
+ _experiments: {
+ debugIds: true,
+ },
+ beforeSend(event) {
+ event.extra = {
+ ...event.extra,
+ jsEngine: global.HermesInternal ? "hermes" : "jsc",
+ hermesEnabled: !!global.HermesInternal,
+ version: packageJson.version,
+ buildNumber: getBuildNumber(),
+ buildTime: env.BUILD_TIME,
+ };
+
+ if (event.exception) {
+ event.exception.values = event.exception.values?.map((value) => ({
+ ...value,
+ mechanism: {
+ ...value.mechanism,
+ handled: true,
+ synthetic: false,
+ type: "hermes",
+ },
+ }));
+ }
+
+ return event;
+ },
+ beforeBreadcrumb(breadcrumb) {
+ if (breadcrumb.category === "console") {
+ return breadcrumb;
+ }
return breadcrumb;
- }
- return breadcrumb;
- },
- replaysSessionSampleRate: 0.1,
- replaysOnErrorSampleRate: 1.0,
- integrations: [
- // Sentry.mobileReplayIntegration({
- // maskAllText: false,
- // maskAllImages: false,
- // maskAllVectors: false,
- // }),
- ],
+ },
+ replaysSessionSampleRate: 0.1,
+ replaysOnErrorSampleRate: 1.0,
+ integrations: [
+ // Sentry.mobileReplayIntegration({
+ // maskAllText: false,
+ // maskAllImages: false,
+ // maskAllVectors: false,
+ // }),
+ ],
+ });
+};
+
+// Initialize Sentry asynchronously
+initializeSentry().catch((error) => {
+ sentryLogger.warn("Failed to initialize Sentry", {
+ error: error.message,
+ });
});
+
+// Export function to dynamically control Sentry
+export const setSentryEnabled = (enabled) => {
+ try {
+ // Use the newer Sentry API
+ const client = Sentry.getClient();
+ if (client) {
+ const options = client.getOptions();
+ options.enabled = enabled;
+ if (!enabled) {
+ // Clear any pending events when disabling
+ Sentry.withScope((scope) => {
+ scope.clear();
+ });
+ }
+ sentryLogger.info("Sentry state toggled", { enabled });
+ } else {
+ sentryLogger.warn("Sentry client not available for toggling");
+ }
+ } catch (error) {
+ sentryLogger.warn("Failed to toggle Sentry state", {
+ error: error.message,
+ });
+ }
+};
diff --git a/src/storage/storageKeys.js b/src/storage/storageKeys.js
index f616faa..1256575 100644
--- a/src/storage/storageKeys.js
+++ b/src/storage/storageKeys.js
@@ -80,4 +80,5 @@ export const STORAGE_KEYS = {
LAST_KNOWN_LOCATION: registerAsyncStorageKey("@last_known_location"),
EULA_ACCEPTED_SIMPLE: registerAsyncStorageKey("eula_accepted"),
EMULATOR_MODE_ENABLED: registerAsyncStorageKey("emulator_mode_enabled"),
+ SENTRY_ENABLED: registerAsyncStorageKey("@sentry_enabled"),
};
diff --git a/src/stores/params.js b/src/stores/params.js
index eedb4b2..25e61b5 100644
--- a/src/stores/params.js
+++ b/src/stores/params.js
@@ -1,4 +1,13 @@
import { createAtom } from "~/lib/atomic-zustand";
+import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
+import { STORAGE_KEYS } from "~/storage/storageKeys";
+import { createLogger } from "~/lib/logger";
+import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
+
+const paramsLogger = createLogger({
+ module: SYSTEM_SCOPES.APP,
+ feature: "params",
+});
export default createAtom(({ merge, reset }) => {
const setDevModeEnabled = (b) => {
@@ -37,6 +46,45 @@ export default createAtom(({ merge, reset }) => {
});
};
+ const setSentryEnabled = async (sentryEnabled) => {
+ merge({
+ sentryEnabled,
+ });
+
+ // Persist to storage
+ try {
+ await memoryAsyncStorage.setItem(
+ STORAGE_KEYS.SENTRY_ENABLED,
+ JSON.stringify(sentryEnabled),
+ );
+ } catch (error) {
+ paramsLogger.warn("Failed to persist Sentry preference", {
+ error: error.message,
+ });
+ }
+ };
+
+ const initSentryEnabled = async () => {
+ try {
+ const stored = await memoryAsyncStorage.getItem(
+ STORAGE_KEYS.SENTRY_ENABLED,
+ );
+ if (stored !== null) {
+ const sentryEnabled = JSON.parse(stored);
+ merge({ sentryEnabled });
+ return sentryEnabled;
+ }
+ } catch (error) {
+ paramsLogger.warn("Failed to load Sentry preference", {
+ error: error.message,
+ });
+ }
+ };
+
+ const init = async () => {
+ await initSentryEnabled();
+ };
+
return {
default: {
// devModeEnabled: false,
@@ -46,6 +94,7 @@ export default createAtom(({ merge, reset }) => {
mapColorScheme: "auto",
hasRegisteredRelatives: null,
alertListSortBy: "location",
+ sentryEnabled: true,
},
actions: {
reset,
@@ -55,6 +104,8 @@ export default createAtom(({ merge, reset }) => {
setMapColorScheme,
setHasRegisteredRelatives,
setAlertListSortBy,
+ setSentryEnabled,
+ init,
},
};
});