Compare commits

..

No commits in common. "4a0f3ab7effd79f75efd976f5fd9e1d1531e5b19" and "d780fb4190acc24ceb9811682f8684ffb232a716" have entirely different histories.

5 changed files with 208 additions and 259 deletions

View file

@ -93,7 +93,6 @@
"@react-navigation/native": "^6.0.8", "@react-navigation/native": "^6.0.8",
"@react-navigation/stack": "^6.3.21", "@react-navigation/stack": "^6.3.21",
"@sentry/react-native": "~6.10.0", "@sentry/react-native": "~6.10.0",
"@sentry/tracing": "^7.120.3",
"@turf/along": "^7.1.0", "@turf/along": "^7.1.0",
"@turf/boolean-equal": "^7.1.0", "@turf/boolean-equal": "^7.1.0",
"@turf/distance": "^7.1.0", "@turf/distance": "^7.1.0",

View file

@ -346,67 +346,65 @@ export default async function trackLocation() {
if (count > 0) { if (count > 0) {
locationLogger.info(`Found ${count} pending records, forcing sync`); locationLogger.info(`Found ${count} pending records, forcing sync`);
await Sentry.startSpan( const transaction = Sentry.startTransaction({
{ name: "force-sync-pending-records",
name: "force-sync-pending-records", op: "geolocation-sync",
op: "geolocation-sync", });
},
async (span) => {
try {
const { userToken } = getAuthState();
const state = await BackgroundGeolocation.getState();
if (userToken && state.enabled) {
const records = await BackgroundGeolocation.sync();
locationLogger.debug("Forced sync result", {
recordsCount: records?.length || 0,
});
Sentry.addBreadcrumb({ try {
message: "Forced sync completed", const { userToken } = getAuthState();
category: "geolocation", const state = await BackgroundGeolocation.getState();
level: "info", if (userToken && state.enabled) {
data: { const records = await BackgroundGeolocation.sync();
recordsCount: records?.length || 0, locationLogger.debug("Forced sync result", {
hadToken: true, recordsCount: records?.length || 0,
wasEnabled: true, });
},
});
span.setStatus("ok"); Sentry.addBreadcrumb({
} else { message: "Forced sync completed",
Sentry.addBreadcrumb({ category: "geolocation",
message: "Forced sync skipped", level: "info",
category: "geolocation", data: {
level: "warning", recordsCount: records?.length || 0,
data: { hadToken: true,
hasToken: !!userToken, wasEnabled: true,
isEnabled: state.enabled, },
}, });
});
span.setStatus("cancelled"); transaction.setStatus("ok");
} } else {
} catch (error) { Sentry.addBreadcrumb({
locationLogger.error("Forced sync failed", { message: "Forced sync skipped",
error: error, category: "geolocation",
stack: error.stack, level: "warning",
}); data: {
hasToken: !!userToken,
isEnabled: state.enabled,
},
});
Sentry.captureException(error, { transaction.setStatus("cancelled");
tags: { }
module: "track-location", } catch (error) {
operation: "force-sync-pending", locationLogger.error("Forced sync failed", {
}, error: error,
contexts: { stack: error.stack,
pendingRecords: { count }, });
},
});
span.setStatus("internal_error"); Sentry.captureException(error, {
throw error; // Re-throw to ensure span captures the error tags: {
} module: "track-location",
}, operation: "force-sync-pending",
); },
contexts: {
pendingRecords: { count },
},
});
transaction.setStatus("internal_error");
} finally {
transaction.finish();
}
} }
} catch (error) { } catch (error) {
locationLogger.error("Failed to get pending records count", { locationLogger.error("Failed to get pending records count", {

View file

@ -12,176 +12,176 @@ const logger = createLogger({
// Background task to cancel expired notifications // Background task to cancel expired notifications
const backgroundTask = async () => { const backgroundTask = async () => {
await Sentry.startSpan( const transaction = Sentry.startTransaction({
{ name: "auto-cancel-expired-notifications",
name: "auto-cancel-expired-notifications", op: "background-task",
op: "background-task", });
},
async (span) => { Sentry.getCurrentScope().setSpan(transaction);
try {
logger.info("Starting auto-cancel expired notifications task");
Sentry.addBreadcrumb({
message: "Auto-cancel task started",
category: "notifications",
level: "info",
});
// Get displayed notifications with timeout protection
const getNotificationsSpan = transaction.startChild({
op: "get-displayed-notifications",
description: "Getting displayed notifications",
});
let notifications;
try {
// Add timeout protection for the API call
notifications = await Promise.race([
notifee.getDisplayedNotifications(),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error("Timeout getting notifications")),
10000,
),
),
]);
getNotificationsSpan.setStatus("ok");
} catch (error) {
getNotificationsSpan.setStatus("internal_error");
throw error;
} finally {
getNotificationsSpan.finish();
}
if (!Array.isArray(notifications)) {
logger.warn("No notifications array received", { notifications });
Sentry.addBreadcrumb({
message: "No notifications array received",
category: "notifications",
level: "warning",
});
return;
}
const currentTime = Math.round(new Date() / 1000);
let cancelledCount = 0;
let errorCount = 0;
logger.info("Processing notifications", {
totalNotifications: notifications.length,
currentTime,
});
Sentry.addBreadcrumb({
message: "Processing notifications",
category: "notifications",
level: "info",
data: {
totalNotifications: notifications.length,
currentTime,
},
});
// Process notifications with individual error handling
for (const notification of notifications) {
try { try {
logger.info("Starting auto-cancel expired notifications task"); if (!notification || !notification.id) {
logger.warn("Invalid notification object", { notification });
Sentry.addBreadcrumb({ continue;
message: "Auto-cancel task started",
category: "notifications",
level: "info",
});
// Get displayed notifications with timeout protection
let notifications;
await Sentry.startSpan(
{
op: "get-displayed-notifications",
description: "Getting displayed notifications",
},
async (getNotificationsSpan) => {
try {
// Add timeout protection for the API call
notifications = await Promise.race([
notifee.getDisplayedNotifications(),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error("Timeout getting notifications")),
10000,
),
),
]);
getNotificationsSpan.setStatus("ok");
} catch (error) {
getNotificationsSpan.setStatus("internal_error");
throw error;
}
},
);
if (!Array.isArray(notifications)) {
logger.warn("No notifications array received", { notifications });
Sentry.addBreadcrumb({
message: "No notifications array received",
category: "notifications",
level: "warning",
});
return;
} }
const currentTime = Math.round(new Date() / 1000); const expires = notification.data?.expires;
let cancelledCount = 0; if (!expires) {
let errorCount = 0; continue; // Skip notifications without expiry
}
logger.info("Processing notifications", { if (typeof expires !== "number" || expires < currentTime) {
totalNotifications: notifications.length, logger.debug("Cancelling expired notification", {
currentTime, notificationId: notification.id,
}); expires,
Sentry.addBreadcrumb({
message: "Processing notifications",
category: "notifications",
level: "info",
data: {
totalNotifications: notifications.length,
currentTime, currentTime,
}, expired: expires < currentTime,
}); });
// Process notifications with individual error handling // Cancel notification with timeout protection
for (const notification of notifications) { await Promise.race([
try { notifee.cancelNotification(notification.id),
if (!notification || !notification.id) { new Promise((_, reject) =>
logger.warn("Invalid notification object", { notification }); setTimeout(
continue; () => reject(new Error("Timeout cancelling notification")),
} 5000,
),
),
]);
const expires = notification.data?.expires; cancelledCount++;
if (!expires) {
continue; // Skip notifications without expiry
}
if (typeof expires !== "number" || expires < currentTime) { Sentry.addBreadcrumb({
logger.debug("Cancelling expired notification", { message: "Notification cancelled",
notificationId: notification.id, category: "notifications",
expires, level: "info",
currentTime, data: {
expired: expires < currentTime, notificationId: notification.id,
}); expires,
},
// Cancel notification with timeout protection });
await Promise.race([
notifee.cancelNotification(notification.id),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error("Timeout cancelling notification")),
5000,
),
),
]);
cancelledCount++;
Sentry.addBreadcrumb({
message: "Notification cancelled",
category: "notifications",
level: "info",
data: {
notificationId: notification.id,
expires,
},
});
}
} catch (notificationError) {
errorCount++;
logger.error("Failed to process notification", {
error: notificationError,
notificationId: notification?.id,
});
Sentry.captureException(notificationError, {
tags: {
module: "auto-cancel-expired",
operation: "cancel-notification",
},
contexts: {
notification: {
id: notification?.id,
expires: notification?.data?.expires,
},
},
});
}
} }
} catch (notificationError) {
logger.info("Auto-cancel task completed", { errorCount++;
totalNotifications: notifications.length, logger.error("Failed to process notification", {
cancelledCount, error: notificationError,
errorCount, notificationId: notification?.id,
}); });
Sentry.addBreadcrumb({ Sentry.captureException(notificationError, {
message: "Auto-cancel task completed",
category: "notifications",
level: "info",
data: {
totalNotifications: notifications.length,
cancelledCount,
errorCount,
},
});
span.setStatus("ok");
} catch (error) {
logger.error("Auto-cancel task failed", { error });
Sentry.captureException(error, {
tags: { tags: {
module: "auto-cancel-expired", module: "auto-cancel-expired",
operation: "background-task", operation: "cancel-notification",
},
contexts: {
notification: {
id: notification?.id,
expires: notification?.data?.expires,
},
}, },
}); });
span.setStatus("internal_error");
throw error; // Re-throw to be handled by caller
} }
}, }
);
logger.info("Auto-cancel task completed", {
totalNotifications: notifications.length,
cancelledCount,
errorCount,
});
Sentry.addBreadcrumb({
message: "Auto-cancel task completed",
category: "notifications",
level: "info",
data: {
totalNotifications: notifications.length,
cancelledCount,
errorCount,
},
});
transaction.setStatus("ok");
} catch (error) {
logger.error("Auto-cancel task failed", { error });
Sentry.captureException(error, {
tags: {
module: "auto-cancel-expired",
operation: "background-task",
},
});
transaction.setStatus("internal_error");
throw error; // Re-throw to be handled by caller
} finally {
transaction.finish();
}
}; };
export const useAutoCancelExpired = () => { export const useAutoCancelExpired = () => {

View file

@ -1,5 +1,4 @@
import * as Sentry from "@sentry/react-native"; import * as Sentry from "@sentry/react-native";
import "@sentry/tracing";
import { Platform } from "react-native"; import { Platform } from "react-native";
import env from "~/env"; import env from "~/env";

View file

@ -5735,17 +5735,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sentry-internal/tracing@npm:7.120.3":
version: 7.120.3
resolution: "@sentry-internal/tracing@npm:7.120.3"
dependencies:
"@sentry/core": "npm:7.120.3"
"@sentry/types": "npm:7.120.3"
"@sentry/utils": "npm:7.120.3"
checksum: 10/bd6adcced941c651596de9b2c8a35f1492c5557bda36c3283b0ef0386e72586481d4288704d0cc71eb78fd2675715488ebc4239e001a571abc44dd3363022401
languageName: node
linkType: hard
"@sentry/babel-plugin-component-annotate@npm:3.2.2": "@sentry/babel-plugin-component-annotate@npm:3.2.2":
version: 3.2.2 version: 3.2.2
resolution: "@sentry/babel-plugin-component-annotate@npm:3.2.2" resolution: "@sentry/babel-plugin-component-annotate@npm:3.2.2"
@ -5852,16 +5841,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sentry/core@npm:7.120.3":
version: 7.120.3
resolution: "@sentry/core@npm:7.120.3"
dependencies:
"@sentry/types": "npm:7.120.3"
"@sentry/utils": "npm:7.120.3"
checksum: 10/fee971b8e0bbb5b499cd1161e18c6495f9d5472c286f5de5f84dc183dcfa739d31b7b57379f1fa01eb02b67f55c8ec008c1bcdb4f8da75144efd700592099602
languageName: node
linkType: hard
"@sentry/core@npm:8.54.0": "@sentry/core@npm:8.54.0":
version: 8.54.0 version: 8.54.0
resolution: "@sentry/core@npm:8.54.0" resolution: "@sentry/core@npm:8.54.0"
@ -5906,22 +5885,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sentry/tracing@npm:^7.120.3":
version: 7.120.3
resolution: "@sentry/tracing@npm:7.120.3"
dependencies:
"@sentry-internal/tracing": "npm:7.120.3"
checksum: 10/6d5e673a5cd4276bd717392d5da92c9977058a2b7a6d732718b16f088a335b8c4ab8a29662781cb658010bdcac4191950cc87edf4e3fd805b48626ed2afb8994
languageName: node
linkType: hard
"@sentry/types@npm:7.120.3":
version: 7.120.3
resolution: "@sentry/types@npm:7.120.3"
checksum: 10/56b9f32393b506e5e7250713fd764d755decae827ee545399dc66653eff2ddeb2f03a9c98ba5a0a846546dc37ab3af8d3535cf57ed01d9a7d00cd9dc72a55a36
languageName: node
linkType: hard
"@sentry/types@npm:8.54.0": "@sentry/types@npm:8.54.0":
version: 8.54.0 version: 8.54.0
resolution: "@sentry/types@npm:8.54.0" resolution: "@sentry/types@npm:8.54.0"
@ -5931,15 +5894,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sentry/utils@npm:7.120.3":
version: 7.120.3
resolution: "@sentry/utils@npm:7.120.3"
dependencies:
"@sentry/types": "npm:7.120.3"
checksum: 10/c50fa4b7334898c0db7840899b2fd1da1bc47a097ecbc433bc835b6e90d3e76b1761ef926cd5e9f0c15e9b00c1f091dd763862f4c98468f9628214be83fe5426
languageName: node
linkType: hard
"@sentry/utils@npm:8.54.0": "@sentry/utils@npm:8.54.0":
version: 8.54.0 version: 8.54.0
resolution: "@sentry/utils@npm:8.54.0" resolution: "@sentry/utils@npm:8.54.0"
@ -6929,7 +6883,6 @@ __metadata:
"@react-navigation/native": "npm:^6.0.8" "@react-navigation/native": "npm:^6.0.8"
"@react-navigation/stack": "npm:^6.3.21" "@react-navigation/stack": "npm:^6.3.21"
"@sentry/react-native": "npm:~6.10.0" "@sentry/react-native": "npm:~6.10.0"
"@sentry/tracing": "npm:^7.120.3"
"@turf/along": "npm:^7.1.0" "@turf/along": "npm:^7.1.0"
"@turf/boolean-equal": "npm:^7.1.0" "@turf/boolean-equal": "npm:^7.1.0"
"@turf/distance": "npm:^7.1.0" "@turf/distance": "npm:^7.1.0"