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. 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) ## [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' applicationId 'com.alertesecours'
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 203 versionCode 204
versionName "1.11.13" versionName "1.11.14"
multiDexEnabled true multiDexEnabled true
testBuildType System.getProperty('testBuildType', 'debug') testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View file

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

View file

@ -25,7 +25,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.11.13</string> <string>1.11.14</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@ -48,7 +48,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>203</string> <string>204</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSApplicationQueriesSchemes</key> <key>LSApplicationQueriesSchemes</key>

View file

@ -1,6 +1,6 @@
{ {
"name": "alerte-secours", "name": "alerte-secours",
"version": "1.11.13", "version": "1.11.14",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "expo start --dev-client --private-key-path ./keys/private-key.pem", "start": "expo start --dev-client --private-key-path ./keys/private-key.pem",
@ -50,8 +50,8 @@
"screenshot:android": "scripts/screenshot-android.sh" "screenshot:android": "scripts/screenshot-android.sh"
}, },
"customExpoVersioning": { "customExpoVersioning": {
"versionCode": 203, "versionCode": 204,
"buildNumber": 203 "buildNumber": 204
}, },
"commit-and-tag-version": { "commit-and-tag-version": {
"scripts": { "scripts": {

View file

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

View file

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

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