fix(io): headless + debug wip
This commit is contained in:
parent
7220ee5667
commit
a795e82bbe
4 changed files with 165 additions and 67 deletions
59
index.js
59
index.js
|
@ -6,7 +6,6 @@ import "expo-splash-screen";
|
|||
import BackgroundGeolocation from "react-native-background-geolocation";
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import BackgroundFetch from "react-native-background-fetch";
|
||||
|
||||
import notifee from "@notifee/react-native";
|
||||
import messaging from "@react-native-firebase/messaging";
|
||||
|
@ -56,62 +55,4 @@ const HeadlessTask = async (event) => {
|
|||
|
||||
if (Platform.OS === "android") {
|
||||
BackgroundGeolocation.registerHeadlessTask(HeadlessTask);
|
||||
} else if (Platform.OS === "ios") {
|
||||
BackgroundGeolocation.onLocation(async () => {
|
||||
await executeHeartbeatSync();
|
||||
});
|
||||
|
||||
BackgroundGeolocation.onMotionChange(async () => {
|
||||
await executeHeartbeatSync();
|
||||
});
|
||||
|
||||
// Configure BackgroundFetch for iOS (iOS-specific configuration)
|
||||
BackgroundFetch.configure(
|
||||
{
|
||||
minimumFetchInterval: 15, // Only valid option for iOS - gives best chance of execution
|
||||
},
|
||||
// Event callback
|
||||
async (taskId) => {
|
||||
let syncResult = null;
|
||||
|
||||
try {
|
||||
// Execute the shared heartbeat logic and get result
|
||||
syncResult = await executeHeartbeatSync();
|
||||
} catch (error) {
|
||||
// silent error
|
||||
} finally {
|
||||
// CRITICAL: Always call finish with appropriate result
|
||||
try {
|
||||
if (taskId) {
|
||||
let fetchResult;
|
||||
|
||||
if (syncResult?.error || !syncResult?.syncSuccessful) {
|
||||
// Task failed
|
||||
fetchResult = BackgroundFetch.FETCH_RESULT_FAILED;
|
||||
} else if (
|
||||
syncResult?.syncPerformed &&
|
||||
syncResult?.syncSuccessful
|
||||
) {
|
||||
// Force sync was performed successfully - new data
|
||||
fetchResult = BackgroundFetch.FETCH_RESULT_NEW_DATA;
|
||||
} else {
|
||||
// No sync was needed - no new data
|
||||
fetchResult = BackgroundFetch.FETCH_RESULT_NO_DATA;
|
||||
}
|
||||
|
||||
BackgroundFetch.finish(taskId, fetchResult);
|
||||
}
|
||||
} catch (finishError) {
|
||||
// silent error
|
||||
}
|
||||
}
|
||||
},
|
||||
// Timeout callback (REQUIRED by BackgroundFetch API)
|
||||
async (taskId) => {
|
||||
// CRITICAL: Must call finish on timeout with FAILED result
|
||||
BackgroundFetch.finish(taskId, BackgroundFetch.FETCH_RESULT_FAILED);
|
||||
},
|
||||
).catch(() => {
|
||||
// silent error
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import { useUpdates } from "~/updates";
|
|||
import Error from "~/components/Error";
|
||||
|
||||
import useTrackLocation from "~/hooks/useTrackLocation";
|
||||
import { initializeBackgroundFetch } from "~/services/backgroundFetch";
|
||||
import useMount from "~/hooks/useMount";
|
||||
|
||||
const appLogger = createLogger({
|
||||
module: SYSTEM_SCOPES.APP,
|
||||
|
@ -219,6 +221,23 @@ function AppContent() {
|
|||
useNetworkListener();
|
||||
useTrackLocation();
|
||||
|
||||
useMount(() => {
|
||||
const setupBackgroundFetch = async () => {
|
||||
try {
|
||||
appLogger.info("Setting up BackgroundFetch");
|
||||
await initializeBackgroundFetch();
|
||||
appLogger.debug("BackgroundFetch setup completed");
|
||||
} catch (error) {
|
||||
lifecycleLogger.error("BackgroundFetch setup failed", {
|
||||
error: error?.message,
|
||||
});
|
||||
errorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
setupBackgroundFetch();
|
||||
});
|
||||
|
||||
// Handle deep links after app is initialized with error handling
|
||||
useEffect(() => {
|
||||
let subscription;
|
||||
|
|
|
@ -4,8 +4,8 @@ import { STORAGE_KEYS } from "~/storage/storageKeys";
|
|||
import { createLogger } from "~/lib/logger";
|
||||
|
||||
// Constants for persistence
|
||||
const FORCE_SYNC_INTERVAL = 12 * 60 * 60 * 1000;
|
||||
// const FORCE_SYNC_INTERVAL = 5 * 60 * 1000; // DEBUGGING
|
||||
// const FORCE_SYNC_INTERVAL = 12 * 60 * 60 * 1000;
|
||||
const FORCE_SYNC_INTERVAL = 1 * 60 * 1000; // DEBUGGING
|
||||
|
||||
const geolocBgLogger = createLogger({
|
||||
service: "background-task",
|
||||
|
@ -73,21 +73,50 @@ export const executeHeartbeatSync = async () => {
|
|||
const lastSyncTime = await getLastSyncTime();
|
||||
const now = Date.now();
|
||||
const timeSinceLastSync = now - lastSyncTime;
|
||||
|
||||
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
||||
geolocBgLogger.info("Forcing location sync");
|
||||
geolocBgLogger.info("Forcing location sync", {
|
||||
timeSinceLastSync,
|
||||
forceInterval: FORCE_SYNC_INTERVAL,
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
async () => {
|
||||
await executeSync();
|
||||
},
|
||||
const syncResult = await Promise.race([
|
||||
executeSync(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("changePace timeout")), 10000),
|
||||
),
|
||||
]);
|
||||
|
||||
await setLastSyncTime(now);
|
||||
|
||||
geolocBgLogger.info("Force sync completed successfully", {
|
||||
syncResult,
|
||||
});
|
||||
|
||||
return syncResult;
|
||||
} catch (syncError) {
|
||||
geolocBgLogger.error("Force sync failed", { error: syncError });
|
||||
geolocBgLogger.error("Force sync failed", {
|
||||
error: syncError.message,
|
||||
timeSinceLastSync,
|
||||
});
|
||||
|
||||
return {
|
||||
syncPerformed: true,
|
||||
syncSuccessful: false,
|
||||
error: syncError.message,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
geolocBgLogger.debug("Sync not needed yet", {
|
||||
timeSinceLastSync,
|
||||
forceInterval: FORCE_SYNC_INTERVAL,
|
||||
timeUntilNextSync: FORCE_SYNC_INTERVAL - timeSinceLastSync,
|
||||
});
|
||||
|
||||
return {
|
||||
syncPerformed: false,
|
||||
syncSuccessful: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
109
src/services/backgroundFetch.js
Normal file
109
src/services/backgroundFetch.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { Platform } from "react-native";
|
||||
import BackgroundFetch from "react-native-background-fetch";
|
||||
import { createLogger } from "~/lib/logger";
|
||||
import { executeHeartbeatSync } from "~/location/backgroundTask";
|
||||
|
||||
const backgroundFetchLogger = createLogger({
|
||||
service: "background-fetch",
|
||||
task: "service",
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize BackgroundFetch according to the documentation best practices.
|
||||
* This should be called once when the root component mounts.
|
||||
*/
|
||||
export const initializeBackgroundFetch = async () => {
|
||||
try {
|
||||
backgroundFetchLogger.info("Initializing BackgroundFetch service");
|
||||
|
||||
// Configure BackgroundFetch for both platforms
|
||||
const status = await BackgroundFetch.configure(
|
||||
{
|
||||
minimumFetchInterval: 15, // Only valid option - gives best chance of execution
|
||||
},
|
||||
// Event callback - handles both default fetch events and custom scheduled tasks
|
||||
async (taskId) => {
|
||||
backgroundFetchLogger.info("BackgroundFetch event received", {
|
||||
taskId,
|
||||
});
|
||||
|
||||
let syncResult = null;
|
||||
|
||||
try {
|
||||
// Execute the shared heartbeat logic and get result
|
||||
syncResult = await executeHeartbeatSync();
|
||||
backgroundFetchLogger.debug("Heartbeat sync completed", {
|
||||
syncResult,
|
||||
});
|
||||
} catch (error) {
|
||||
backgroundFetchLogger.error("Heartbeat sync failed", {
|
||||
error: error.message,
|
||||
taskId,
|
||||
});
|
||||
} finally {
|
||||
// CRITICAL: Always call finish with appropriate result
|
||||
try {
|
||||
if (taskId) {
|
||||
let fetchResult;
|
||||
|
||||
if (syncResult?.error || !syncResult?.syncSuccessful) {
|
||||
// Task failed
|
||||
fetchResult = BackgroundFetch.FETCH_RESULT_FAILED;
|
||||
} else if (
|
||||
syncResult?.syncPerformed &&
|
||||
syncResult?.syncSuccessful
|
||||
) {
|
||||
// Force sync was performed successfully - new data
|
||||
fetchResult = BackgroundFetch.FETCH_RESULT_NEW_DATA;
|
||||
} else {
|
||||
// No sync was needed - no new data
|
||||
fetchResult = BackgroundFetch.FETCH_RESULT_NO_DATA;
|
||||
}
|
||||
|
||||
BackgroundFetch.finish(taskId, fetchResult);
|
||||
backgroundFetchLogger.debug("BackgroundFetch task finished", {
|
||||
taskId,
|
||||
fetchResult,
|
||||
});
|
||||
}
|
||||
} catch (finishError) {
|
||||
backgroundFetchLogger.error(
|
||||
"Failed to finish BackgroundFetch task",
|
||||
{
|
||||
error: finishError.message,
|
||||
taskId,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Timeout callback (REQUIRED by BackgroundFetch API)
|
||||
async (taskId) => {
|
||||
backgroundFetchLogger.warn("BackgroundFetch task timeout", { taskId });
|
||||
// CRITICAL: Must call finish on timeout with FAILED result
|
||||
try {
|
||||
BackgroundFetch.finish(taskId, BackgroundFetch.FETCH_RESULT_FAILED);
|
||||
} catch (error) {
|
||||
backgroundFetchLogger.error("Failed to finish timed out task", {
|
||||
error: error.message,
|
||||
taskId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
backgroundFetchLogger.info("BackgroundFetch configured successfully", {
|
||||
status,
|
||||
platform: Platform.OS,
|
||||
});
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
backgroundFetchLogger.error("Failed to initialize BackgroundFetch", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
platform: Platform.OS,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
Loading…
Add table
Reference in a new issue