Compare commits

...

2 commits

Author SHA1 Message Date
d8583b9ad7
chore(release): 1.11.14 2025-07-25 10:32:11 +02:00
a795e82bbe
fix(io): headless + debug wip 2025-07-25 10:31:54 +02:00
8 changed files with 179 additions and 74 deletions

View file

@ -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)

View file

@ -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'

View file

@ -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
});
}

View file

@ -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>

View file

@ -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": {

View file

@ -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;

View file

@ -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,
};
}
};

View 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;
}
};