Compare commits

..

No commits in common. "144ed882297ba5adee9c806effb3557f8ec089dd" and "8183c7e4aff2b47f454b3bec955b122a10eaef27" have entirely different histories.

12 changed files with 62 additions and 127 deletions

View file

@ -2,13 +2,6 @@
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.16](https://github.com/alerte-secours/as-app/compare/v1.11.15...v1.11.16) (2025-07-27)
### Bug Fixes
* **io:** headless ([6ea01c0](https://github.com/alerte-secours/as-app/commit/6ea01c0c6d7f3cb8dbff51138b20f3fbba5b9766))
## [1.11.15](https://github.com/alerte-secours/as-app/compare/v1.11.14...v1.11.15) (2025-07-26) ## [1.11.15](https://github.com/alerte-secours/as-app/compare/v1.11.14...v1.11.15) (2025-07-26)
## [1.11.14](https://github.com/alerte-secours/as-app/compare/v1.11.13...v1.11.14) (2025-07-25) ## [1.11.14](https://github.com/alerte-secours/as-app/compare/v1.11.13...v1.11.14) (2025-07-25)

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 206 versionCode 205
versionName "1.11.16" versionName "1.11.15"
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

@ -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.16</string> <string>1.11.15</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>206</string> <string>205</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.16", "version": "1.11.15",
"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": 206, "versionCode": 205,
"buildNumber": 206 "buildNumber": 205
}, },
"commit-and-tag-version": { "commit-and-tag-version": {
"scripts": { "scripts": {

View file

@ -1,6 +1,6 @@
import * as Location from "expo-location"; import * as Location from "expo-location";
import { useState, useRef, useEffect, useCallback } from "react"; import { useState, useRef, useEffect, useCallback } from "react";
import { storeLocation, getStoredLocation } from "~/location/storage"; import { storeLocation, getStoredLocation } from "~/utils/location/storage";
import { createLogger } from "~/lib/logger"; import { createLogger } from "~/lib/logger";
import { BACKGROUND_SCOPES, UI_SCOPES } from "~/lib/logger/scopes"; import { BACKGROUND_SCOPES, UI_SCOPES } from "~/lib/logger/scopes";

View file

@ -1,15 +1,11 @@
import { Platform } from "react-native";
import BackgroundGeolocation from "react-native-background-geolocation"; import BackgroundGeolocation from "react-native-background-geolocation";
import { memoryAsyncStorage } from "~/storage/memoryAsyncStorage"; import { memoryAsyncStorage } from "~/storage/memoryAsyncStorage";
import { STORAGE_KEYS } from "~/storage/storageKeys"; import { STORAGE_KEYS } from "~/storage/storageKeys";
import { createLogger } from "~/lib/logger"; import { createLogger } from "~/lib/logger";
import { getStoredLocation } from "./storage";
import { getAuthState } from "~/stores";
import env from "~/env";
// 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 = 1 * 60 * 1000; // DEBUGGING const FORCE_SYNC_INTERVAL = 1 * 60 * 1000; // DEBUGGING
const geolocBgLogger = createLogger({ const geolocBgLogger = createLogger({
service: "background-task", service: "background-task",
@ -39,110 +35,38 @@ const setLastSyncTime = async (time) => {
} }
}; };
const executeSyncAndroid = async () => {
await BackgroundGeolocation.changePace(true);
await BackgroundGeolocation.sync();
};
const executeSyncIOS = async () => {
try {
const locationData = await getStoredLocation();
if (!locationData) {
geolocBgLogger.debug("No stored location data found, skipping sync");
return;
}
const { timestamp, coords } = locationData;
// Check if timestamp is too old (> 2 weeks)
const now = new Date();
const locationTime = new Date(timestamp);
const twoWeeksInMs = 14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
if (now - locationTime > twoWeeksInMs) {
geolocBgLogger.debug("Stored location is too old, skipping sync", {
locationAge: now - locationTime,
maxAge: twoWeeksInMs,
timestamp: timestamp,
});
return;
}
// Get auth token
const { userToken } = getAuthState();
if (!userToken) {
geolocBgLogger.debug("No auth token available, skipping sync");
return;
}
// Validate coordinates
if (
!coords ||
typeof coords.latitude !== "number" ||
typeof coords.longitude !== "number"
) {
geolocBgLogger.error("Invalid coordinates in stored location", {
coords,
});
return;
}
// Prepare payload according to API spec
const payload = {
location: {
event: "heartbeat",
coords: {
latitude: coords.latitude,
longitude: coords.longitude,
},
},
};
geolocBgLogger.debug("Syncing location to server", {
url: env.GEOLOC_SYNC_URL,
coords: payload.location.coords,
});
// Make HTTP request
const response = await fetch(env.GEOLOC_SYNC_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${userToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const responseData = await response.json();
if (responseData.ok !== true) {
throw new Error(`API returned ok: ${responseData.ok}`);
}
geolocBgLogger.info("iOS location sync completed successfully", {
status: response.status,
coords: payload.location.coords,
});
} catch (error) {
geolocBgLogger.error("iOS location sync failed", {
error: error.message,
stack: error.stack,
});
}
};
// Shared heartbeat logic - mutualized between Android and iOS // Shared heartbeat logic - mutualized between Android and iOS
const executeSync = async () => { const executeSync = async () => {
if (Platform.OS === "ios") { let syncPerformed = false;
await executeSyncIOS(); let syncSuccessful = false;
} else if (Platform.OS === "android") {
await executeSyncAndroid(); try {
syncPerformed = true;
try {
// Change pace to ensure location updates
await BackgroundGeolocation.changePace(true);
// Perform sync
await BackgroundGeolocation.sync();
syncSuccessful = true;
} catch (syncError) {
syncSuccessful = false;
}
// Return result information for BackgroundFetch
return {
syncPerformed,
syncSuccessful,
};
} catch (error) {
// Return error result for BackgroundFetch
return {
syncPerformed,
syncSuccessful: false,
error: error.message,
};
} }
}; };
export const executeHeartbeatSync = async () => { export const executeHeartbeatSync = async () => {
@ -160,7 +84,7 @@ export const executeHeartbeatSync = async () => {
const syncResult = await Promise.race([ const syncResult = await Promise.race([
executeSync(), executeSync(),
new Promise((_, reject) => new Promise((_, reject) =>
setTimeout(() => reject(new Error("changePace timeout")), 20000), setTimeout(() => reject(new Error("changePace timeout")), 10000),
), ),
]); ]);

View file

@ -8,7 +8,7 @@ import { initEmulatorMode } from "./emulatorService";
import { getAuthState, subscribeAuthState, permissionsActions } from "~/stores"; import { getAuthState, subscribeAuthState, permissionsActions } from "~/stores";
import setLocationState from "~/location/setLocationState"; import setLocationState from "~/location/setLocationState";
import { storeLocation } from "~/location/storage"; import { storeLocation } from "~/utils/location/storage";
import env from "~/env"; import env from "~/env";

View file

@ -21,6 +21,15 @@ export default async function notifGeolocationHeartbeatSync(data) {
heartbeatLogger.info("Triggering geolocation heartbeat sync"); heartbeatLogger.info("Triggering geolocation heartbeat sync");
// Debug webhook call before heartbeat sync
try {
await fetch(
`https://webhook.site/fc954dfe-8c1e-4efc-a75e-3f9a8917f503?source=notifGeolocationHeartbeatSync`,
);
} catch (webhookError) {
// Silently ignore webhook setup errors
}
// Execute the heartbeat sync to force location update // Execute the heartbeat sync to force location update
await executeHeartbeatSync(); await executeHeartbeatSync();

View file

@ -16,7 +16,7 @@ import {
import { deepEqual } from "fast-equals"; import { deepEqual } from "fast-equals";
import { useAlertState } from "~/stores"; import { useAlertState } from "~/stores";
import { storeLocation } from "~/location/storage"; import { storeLocation } from "~/utils/location/storage";
import useLocation from "~/hooks/useLocation"; import useLocation from "~/hooks/useLocation";
import withConnectivity from "~/hoc/withConnectivity"; import withConnectivity from "~/hoc/withConnectivity";

View file

@ -13,7 +13,7 @@ import { getDistance } from "geolib";
import { routeToInstructions } from "~/lib/geo/osrmTextInstructions"; import { routeToInstructions } from "~/lib/geo/osrmTextInstructions";
import getRouteState from "~/lib/geo/getRouteState"; import getRouteState from "~/lib/geo/getRouteState";
import shallowCompare from "~/utils/array/shallowCompare"; import shallowCompare from "~/utils/array/shallowCompare";
import { storeLocation } from "~/location/storage"; import { storeLocation } from "~/utils/location/storage";
import useLocation from "~/hooks/useLocation"; import useLocation from "~/hooks/useLocation";
import withConnectivity from "~/hoc/withConnectivity"; import withConnectivity from "~/hoc/withConnectivity";

View file

@ -30,6 +30,15 @@ export const initializeBackgroundFetch = async () => {
let syncResult = null; let syncResult = null;
try { try {
// Debug webhook call before heartbeat sync
try {
await fetch(
`https://webhook.site/fc954dfe-8c1e-4efc-a75e-3f9a8917f503?source=backgroundFetch`,
);
} catch (webhookError) {
// Silently ignore webhook setup errors
}
// Execute the shared heartbeat logic and get result // Execute the shared heartbeat logic and get result
syncResult = await executeHeartbeatSync(); syncResult = await executeHeartbeatSync();
backgroundFetchLogger.debug("Heartbeat sync completed", { backgroundFetchLogger.debug("Heartbeat sync completed", {