Compare commits

..

2 commits

Author SHA1 Message Date
144ed88229
chore(release): 1.11.16 2025-07-27 23:15:34 +02:00
6ea01c0c6d
fix(io): headless 2025-07-27 23:15:28 +02:00
12 changed files with 121 additions and 56 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.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 205 versionCode 206
versionName "1.11.15" versionName "1.11.16"
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.15</string> <string>1.11.16</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>205</string> <string>206</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.15", "version": "1.11.16",
"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": 205, "versionCode": 206,
"buildNumber": 205 "buildNumber": 206
}, },
"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 "~/utils/location/storage"; import { storeLocation, getStoredLocation } from "~/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,11 +1,15 @@
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",
@ -35,38 +39,110 @@ const setLastSyncTime = async (time) => {
} }
}; };
// Shared heartbeat logic - mutualized between Android and iOS const executeSyncAndroid = async () => {
const executeSync = async () => { await BackgroundGeolocation.changePace(true);
let syncPerformed = false; await BackgroundGeolocation.sync();
let syncSuccessful = false; };
const executeSyncIOS = async () => {
try { try {
syncPerformed = true; const locationData = await getStoredLocation();
try { if (!locationData) {
// Change pace to ensure location updates geolocBgLogger.debug("No stored location data found, skipping sync");
await BackgroundGeolocation.changePace(true); return;
// Perform sync
await BackgroundGeolocation.sync();
syncSuccessful = true;
} catch (syncError) {
syncSuccessful = false;
} }
// Return result information for BackgroundFetch const { timestamp, coords } = locationData;
return {
syncPerformed, // Check if timestamp is too old (> 2 weeks)
syncSuccessful, 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) { } catch (error) {
// Return error result for BackgroundFetch geolocBgLogger.error("iOS location sync failed", {
return {
syncPerformed,
syncSuccessful: false,
error: error.message, error: error.message,
}; stack: error.stack,
});
}
};
// Shared heartbeat logic - mutualized between Android and iOS
const executeSync = async () => {
if (Platform.OS === "ios") {
await executeSyncIOS();
} else if (Platform.OS === "android") {
await executeSyncAndroid();
} }
}; };
export const executeHeartbeatSync = async () => { export const executeHeartbeatSync = async () => {
@ -84,7 +160,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")), 10000), setTimeout(() => reject(new Error("changePace timeout")), 20000),
), ),
]); ]);

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 "~/utils/location/storage"; import { storeLocation } from "~/location/storage";
import env from "~/env"; import env from "~/env";

View file

@ -21,15 +21,6 @@ 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 "~/utils/location/storage"; import { storeLocation } from "~/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 "~/utils/location/storage"; import { storeLocation } from "~/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,15 +30,6 @@ 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", {