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 BackgroundGeolocation from "react-native-background-geolocation";
|
||||||
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import BackgroundFetch from "react-native-background-fetch";
|
|
||||||
|
|
||||||
import notifee from "@notifee/react-native";
|
import notifee from "@notifee/react-native";
|
||||||
import messaging from "@react-native-firebase/messaging";
|
import messaging from "@react-native-firebase/messaging";
|
||||||
|
@ -56,62 +55,4 @@ const HeadlessTask = async (event) => {
|
||||||
|
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
BackgroundGeolocation.registerHeadlessTask(HeadlessTask);
|
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 Error from "~/components/Error";
|
||||||
|
|
||||||
import useTrackLocation from "~/hooks/useTrackLocation";
|
import useTrackLocation from "~/hooks/useTrackLocation";
|
||||||
|
import { initializeBackgroundFetch } from "~/services/backgroundFetch";
|
||||||
|
import useMount from "~/hooks/useMount";
|
||||||
|
|
||||||
const appLogger = createLogger({
|
const appLogger = createLogger({
|
||||||
module: SYSTEM_SCOPES.APP,
|
module: SYSTEM_SCOPES.APP,
|
||||||
|
@ -219,6 +221,23 @@ function AppContent() {
|
||||||
useNetworkListener();
|
useNetworkListener();
|
||||||
useTrackLocation();
|
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
|
// Handle deep links after app is initialized with error handling
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let subscription;
|
let subscription;
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
|
|
||||||
// Constants for persistence
|
// Constants for persistence
|
||||||
const FORCE_SYNC_INTERVAL = 12 * 60 * 60 * 1000;
|
// const FORCE_SYNC_INTERVAL = 12 * 60 * 60 * 1000;
|
||||||
// const FORCE_SYNC_INTERVAL = 5 * 60 * 1000; // DEBUGGING
|
const FORCE_SYNC_INTERVAL = 1 * 60 * 1000; // DEBUGGING
|
||||||
|
|
||||||
const geolocBgLogger = createLogger({
|
const geolocBgLogger = createLogger({
|
||||||
service: "background-task",
|
service: "background-task",
|
||||||
|
@ -73,21 +73,50 @@ export const executeHeartbeatSync = async () => {
|
||||||
const lastSyncTime = await getLastSyncTime();
|
const lastSyncTime = await getLastSyncTime();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const timeSinceLastSync = now - lastSyncTime;
|
const timeSinceLastSync = now - lastSyncTime;
|
||||||
|
|
||||||
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
||||||
geolocBgLogger.info("Forcing location sync");
|
geolocBgLogger.info("Forcing location sync", {
|
||||||
|
timeSinceLastSync,
|
||||||
|
forceInterval: FORCE_SYNC_INTERVAL,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.race([
|
const syncResult = await Promise.race([
|
||||||
async () => {
|
executeSync(),
|
||||||
await executeSync();
|
|
||||||
},
|
|
||||||
new Promise((_, reject) =>
|
new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error("changePace timeout")), 10000),
|
setTimeout(() => reject(new Error("changePace timeout")), 10000),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await setLastSyncTime(now);
|
await setLastSyncTime(now);
|
||||||
|
|
||||||
|
geolocBgLogger.info("Force sync completed successfully", {
|
||||||
|
syncResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
return syncResult;
|
||||||
} catch (syncError) {
|
} 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