Compare commits
2 commits
7220ee5667
...
d8583b9ad7
Author | SHA1 | Date | |
---|---|---|---|
d8583b9ad7 | |||
a795e82bbe |
8 changed files with 179 additions and 74 deletions
|
@ -2,6 +2,13 @@
|
|||
|
||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||
|
||||
## [1.11.14](https://github.com/alerte-secours/as-app/compare/v1.11.13...v1.11.14) (2025-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **io:** headless + debug wip ([a795e82](https://github.com/alerte-secours/as-app/commit/a795e82bbe30a425698173156862311d0c964207))
|
||||
|
||||
## [1.11.13](https://github.com/alerte-secours/as-app/compare/v1.11.12...v1.11.13) (2025-07-24)
|
||||
|
||||
|
||||
|
|
|
@ -83,8 +83,8 @@ android {
|
|||
applicationId 'com.alertesecours'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 203
|
||||
versionName "1.11.13"
|
||||
versionCode 204
|
||||
versionName "1.11.14"
|
||||
multiDexEnabled true
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
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,7 +25,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.11.13</string>
|
||||
<string>1.11.14</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
@ -48,7 +48,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>203</string>
|
||||
<string>204</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "alerte-secours",
|
||||
"version": "1.11.13",
|
||||
"version": "1.11.14",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "expo start --dev-client --private-key-path ./keys/private-key.pem",
|
||||
|
@ -50,8 +50,8 @@
|
|||
"screenshot:android": "scripts/screenshot-android.sh"
|
||||
},
|
||||
"customExpoVersioning": {
|
||||
"versionCode": 203,
|
||||
"buildNumber": 203
|
||||
"versionCode": 204,
|
||||
"buildNumber": 204
|
||||
},
|
||||
"commit-and-tag-version": {
|
||||
"scripts": {
|
||||
|
|
|
@ -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