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.
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
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,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>
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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