Compare commits
No commits in common. "main" and "v1.11.0" have entirely different histories.
18 changed files with 616 additions and 636 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -2,33 +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.11](https://github.com/alerte-secours/as-app/compare/v1.11.10...v1.11.11) (2025-07-23)
|
|
||||||
|
|
||||||
## [1.11.10](https://github.com/alerte-secours/as-app/compare/v1.11.9...v1.11.10) (2025-07-22)
|
|
||||||
|
|
||||||
## [1.11.9](https://github.com/alerte-secours/as-app/compare/v1.11.8...v1.11.9) (2025-07-22)
|
|
||||||
|
|
||||||
## [1.11.8](https://github.com/alerte-secours/as-app/compare/v1.11.7...v1.11.8) (2025-07-22)
|
|
||||||
|
|
||||||
## [1.11.7](https://github.com/alerte-secours/as-app/compare/v1.11.6...v1.11.7) (2025-07-22)
|
|
||||||
|
|
||||||
## [1.11.6](https://github.com/alerte-secours/as-app/compare/v1.11.5...v1.11.6) (2025-07-21)
|
|
||||||
|
|
||||||
## [1.11.5](https://github.com/alerte-secours/as-app/compare/v1.11.4...v1.11.5) (2025-07-21)
|
|
||||||
|
|
||||||
## [1.11.4](https://github.com/alerte-secours/as-app/compare/v1.11.3...v1.11.4) (2025-07-20)
|
|
||||||
|
|
||||||
## [1.11.3](https://github.com/alerte-secours/as-app/compare/v1.11.2...v1.11.3) (2025-07-20)
|
|
||||||
|
|
||||||
## [1.11.2](https://github.com/alerte-secours/as-app/compare/v1.11.1...v1.11.2) (2025-07-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **ios:** BGTaskSchedulerPermittedIdentifiers + prebuild wip ([e6f23b8](https://github.com/alerte-secours/as-app/commit/e6f23b83be7f30adc6385dc8f3e074521c7caf22))
|
|
||||||
|
|
||||||
## [1.11.1](https://github.com/alerte-secours/as-app/compare/v1.11.0...v1.11.1) (2025-07-18)
|
|
||||||
|
|
||||||
## [1.11.0](https://github.com/alerte-secours/as-app/compare/v1.10.9...v1.11.0) (2025-07-12)
|
## [1.11.0](https://github.com/alerte-secours/as-app/compare/v1.10.9...v1.11.0) (2025-07-12)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 201
|
versionCode 190
|
||||||
versionName "1.11.11"
|
versionName "1.11.0"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
testBuildType System.getProperty('testBuildType', 'debug')
|
testBuildType System.getProperty('testBuildType', 'debug')
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||||
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
|
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
|
||||||
<string name="expo_runtime_version">1.0.0</string>
|
<string name="expo_runtime_version">1.0.0</string>
|
||||||
|
|
||||||
|
<!-- Override permission message with French text -->
|
||||||
<string name="message_permission_required">Alerte Secours nécessite la localisation en arrière-plan pour les alertes de proximité.</string>
|
<string name="message_permission_required">Alerte Secours nécessite la localisation en arrière-plan pour les alertes de proximité.</string>
|
||||||
<string name="title_permission_required">Autorisation requise</string>
|
<string name="title_permission_required">Autorisation requise</string>
|
||||||
</resources>
|
</resources>
|
|
@ -131,13 +131,12 @@ let config = {
|
||||||
"tel",
|
"tel",
|
||||||
"telprompt",
|
"telprompt",
|
||||||
],
|
],
|
||||||
BGTaskSchedulerPermittedIdentifiers: [
|
|
||||||
"com.transistorsoft",
|
|
||||||
"com.transistorsoft.fetch",
|
|
||||||
"com.transistorsoft.customtask",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
UIBackgroundModes: ["location", "fetch", "processing"],
|
UIBackgroundModes: ["location", "fetch", "processing"],
|
||||||
|
BGTaskSchedulerPermittedIdentifiers: [
|
||||||
|
"com.transistorsoft.fetch",
|
||||||
|
"com.transistorsoft.customtask",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
|
|
282
index.js
282
index.js
|
@ -5,9 +5,6 @@ import "./warnFilter";
|
||||||
import "expo-splash-screen";
|
import "expo-splash-screen";
|
||||||
import BackgroundGeolocation from "react-native-background-geolocation";
|
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 notifee from "@notifee/react-native";
|
||||||
import messaging from "@react-native-firebase/messaging";
|
import messaging from "@react-native-firebase/messaging";
|
||||||
|
|
||||||
|
@ -21,7 +18,9 @@ import { onBackgroundEvent as notificationBackgroundEvent } from "~/notification
|
||||||
import onMessageReceived from "~/notifications/onMessageReceived";
|
import onMessageReceived from "~/notifications/onMessageReceived";
|
||||||
|
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { executeHeartbeatSync } from "~/location/backgroundTask";
|
import * as Sentry from "@sentry/react-native";
|
||||||
|
import { memoryAsyncStorage } from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
|
|
||||||
// setup notification, this have to stay in index.js
|
// setup notification, this have to stay in index.js
|
||||||
notifee.onBackgroundEvent(notificationBackgroundEvent);
|
notifee.onBackgroundEvent(notificationBackgroundEvent);
|
||||||
|
@ -32,82 +31,239 @@ messaging().setBackgroundMessageHandler(onMessageReceived);
|
||||||
// the environment is set up appropriately
|
// the environment is set up appropriately
|
||||||
registerRootComponent(App);
|
registerRootComponent(App);
|
||||||
|
|
||||||
|
// Constants for persistence
|
||||||
|
const FORCE_SYNC_INTERVAL = 12 * 60 * 60 * 1000;
|
||||||
|
// const FORCE_SYNC_INTERVAL = 5 * 60 * 1000; // DEBUGGING
|
||||||
|
|
||||||
|
// Helper functions for persisting sync time
|
||||||
|
const getLastSyncTime = async () => {
|
||||||
|
try {
|
||||||
|
const value = await memoryAsyncStorage.getItem(
|
||||||
|
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
|
||||||
|
);
|
||||||
|
return value ? parseInt(value, 10) : Date.now();
|
||||||
|
} catch (error) {
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: { module: "headless-task", operation: "get-last-sync-time" },
|
||||||
|
});
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLastSyncTime = async (time) => {
|
||||||
|
try {
|
||||||
|
await memoryAsyncStorage.setItem(
|
||||||
|
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
|
||||||
|
time.toString(),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: { module: "headless-task", operation: "set-last-sync-time" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// this have to stay in index.js, see also https://github.com/transistorsoft/react-native-background-geolocation/wiki/Android-Headless-Mode
|
||||||
|
const getCurrentPosition = () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Add timeout protection
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
resolve({ code: -1, message: "getCurrentPosition timeout" });
|
||||||
|
}, 15000); // 15 second timeout
|
||||||
|
|
||||||
|
BackgroundGeolocation.getCurrentPosition(
|
||||||
|
{
|
||||||
|
samples: 1,
|
||||||
|
persist: true,
|
||||||
|
extras: { background: true },
|
||||||
|
timeout: 10, // 10 second timeout in the plugin itself
|
||||||
|
},
|
||||||
|
(location) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(location);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const geolocBgLogger = createLogger({
|
const geolocBgLogger = createLogger({
|
||||||
service: "background-geolocation",
|
service: "background-geolocation",
|
||||||
task: "headless",
|
task: "headless",
|
||||||
});
|
});
|
||||||
|
|
||||||
const HeadlessTask = async (event) => {
|
const HeadlessTask = async (event) => {
|
||||||
|
// Add timeout protection for the entire headless task
|
||||||
|
const taskTimeout = setTimeout(() => {
|
||||||
|
geolocBgLogger.error("HeadlessTask timeout", { event });
|
||||||
|
|
||||||
|
Sentry.captureException(new Error("HeadlessTask timeout"), {
|
||||||
|
tags: {
|
||||||
|
module: "background-geolocation",
|
||||||
|
operation: "headless-task-timeout",
|
||||||
|
eventName: event?.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 60000); // 60 second timeout
|
||||||
|
|
||||||
|
// Simple performance tracking without deprecated APIs
|
||||||
|
const taskStartTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (event?.name) {
|
// Validate event structure
|
||||||
|
if (!event || typeof event !== "object") {
|
||||||
|
throw new Error("Invalid event object received");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, params } = event;
|
||||||
|
|
||||||
|
if (!name || typeof name !== "string") {
|
||||||
|
throw new Error("Invalid event name received");
|
||||||
|
}
|
||||||
|
|
||||||
|
geolocBgLogger.info("HeadlessTask event received", { name, params });
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
case "heartbeat":
|
case "heartbeat":
|
||||||
await executeHeartbeatSync();
|
// Get persisted last sync time
|
||||||
|
const lastSyncTime = await getLastSyncTime();
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSinceLastSync = now - lastSyncTime;
|
||||||
|
|
||||||
|
// Get current position with performance tracking
|
||||||
|
const location = await getCurrentPosition();
|
||||||
|
|
||||||
|
geolocBgLogger.debug("getCurrentPosition result", { location });
|
||||||
|
|
||||||
|
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
||||||
|
geolocBgLogger.info("Forcing location sync");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Change pace to ensure location updates with timeout
|
||||||
|
await Promise.race([
|
||||||
|
BackgroundGeolocation.changePace(true),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error("changePace timeout")),
|
||||||
|
10000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Perform sync with timeout
|
||||||
|
const syncResult = await Promise.race([
|
||||||
|
BackgroundGeolocation.sync(),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error("sync timeout")), 20000),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update last sync time after successful sync
|
||||||
|
await setLastSyncTime(now);
|
||||||
|
} catch (syncError) {
|
||||||
|
Sentry.captureException(syncError, {
|
||||||
|
tags: {
|
||||||
|
module: "headless-task",
|
||||||
|
operation: "force-sync",
|
||||||
|
eventName: name,
|
||||||
|
},
|
||||||
|
contexts: {
|
||||||
|
syncAttempt: {
|
||||||
|
timeSinceLastSync: timeSinceLastSync,
|
||||||
|
lastSyncTime: new Date(lastSyncTime).toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
geolocBgLogger.error("Force sync failed", { error: syncError });
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "location":
|
||||||
|
// Validate location parameters
|
||||||
|
if (!params || typeof params !== "object") {
|
||||||
|
geolocBgLogger.warn("Invalid location params", { params });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
geolocBgLogger.debug("Location update received", {
|
||||||
|
location: params.location,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "http":
|
||||||
|
// Validate HTTP parameters
|
||||||
|
if (!params || typeof params !== "object" || !params.response) {
|
||||||
|
geolocBgLogger.warn("Invalid HTTP params", { params });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpStatus = params.response?.status;
|
||||||
|
const isHttpSuccess = httpStatus === 200;
|
||||||
|
|
||||||
|
geolocBgLogger.debug("HTTP response received", {
|
||||||
|
response: params.response,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update last sync time on successful HTTP response
|
||||||
|
if (isHttpSuccess) {
|
||||||
|
try {
|
||||||
|
const now = Date.now();
|
||||||
|
await setLastSyncTime(now);
|
||||||
|
} catch (syncTimeError) {
|
||||||
|
geolocBgLogger.error("Failed to update sync time", {
|
||||||
|
error: syncTimeError,
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.captureException(syncTimeError, {
|
||||||
|
tags: {
|
||||||
|
module: "headless-task",
|
||||||
|
operation: "update-sync-time-http",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Task completed successfully
|
||||||
|
const taskDuration = Date.now() - taskStartTime;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const taskDuration = Date.now() - taskStartTime;
|
||||||
|
|
||||||
|
// Capture any unexpected errors
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: {
|
||||||
|
module: "headless-task",
|
||||||
|
eventName: event?.name || "unknown",
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
duration: taskDuration,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
geolocBgLogger.error("HeadlessTask error", {
|
geolocBgLogger.error("HeadlessTask error", {
|
||||||
error,
|
error,
|
||||||
event,
|
event,
|
||||||
|
duration: taskDuration,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// Clear the timeout
|
||||||
|
clearTimeout(taskTimeout);
|
||||||
|
|
||||||
|
const finalDuration = Date.now() - taskStartTime;
|
||||||
|
geolocBgLogger.debug("HeadlessTask completed", {
|
||||||
|
event: event?.name,
|
||||||
|
duration: finalDuration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Platform.OS === "android") {
|
BackgroundGeolocation.registerHeadlessTask(HeadlessTask);
|
||||||
BackgroundGeolocation.registerHeadlessTask(HeadlessTask);
|
|
||||||
} else if (Platform.OS === "ios") {
|
|
||||||
BackgroundGeolocation.onLocation(async (_location) => {
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
249
install-ios.sh
249
install-ios.sh
|
@ -1,249 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Function to print colored output
|
|
||||||
print_error() {
|
|
||||||
echo -e "${RED}Error: $1${NC}" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
print_success() {
|
|
||||||
echo -e "${GREEN}$1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_warning() {
|
|
||||||
echo -e "${YELLOW}Warning: $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_info() {
|
|
||||||
echo -e "${BLUE}$1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_plain() {
|
|
||||||
echo -e "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if Xcode command line tools are available
|
|
||||||
if ! command -v xcrun &> /dev/null; then
|
|
||||||
print_error "Xcode command line tools are not installed."
|
|
||||||
print_info "Please install them by running: xcode-select --install"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Define IPA path
|
|
||||||
IPA_PATH="ios/build/AlerteSecours.ipa"
|
|
||||||
|
|
||||||
# Check if IPA file exists
|
|
||||||
if [ ! -f "$IPA_PATH" ]; then
|
|
||||||
print_error "IPA file not found at: $IPA_PATH"
|
|
||||||
print_info "Please build the iOS bundle first by running:"
|
|
||||||
print_info " yarn bundle:ios"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "Found IPA file: $IPA_PATH"
|
|
||||||
|
|
||||||
# Function to get connected physical iOS devices
|
|
||||||
get_physical_devices() {
|
|
||||||
xcrun devicectl list devices 2>/dev/null | awk 'NR>2 && $4=="available" {print $3}' || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to get available simulators (booted ones)
|
|
||||||
get_booted_simulators() {
|
|
||||||
xcrun simctl list devices | grep -E "\(Booted\)" | sed -E 's/.*\(([A-F0-9-]{36})\) \(Booted\)/\1/' || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to get simulator name by UDID
|
|
||||||
get_simulator_name() {
|
|
||||||
local udid="$1"
|
|
||||||
xcrun simctl list devices | grep "$udid" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | xargs
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to get device name by UDID (for physical devices)
|
|
||||||
get_device_name() {
|
|
||||||
local udid="$1"
|
|
||||||
xcrun devicectl list devices 2>/dev/null | awk -v target_udid="$udid" 'NR>2 && $3==target_udid {print $1}' || echo "Unknown Device"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate physical device UDID
|
|
||||||
validate_physical_device() {
|
|
||||||
local device_id="$1"
|
|
||||||
local devices=$(get_physical_devices)
|
|
||||||
|
|
||||||
if [ -z "$devices" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$devices" | grep -q "^$device_id$"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to validate simulator UDID
|
|
||||||
validate_simulator() {
|
|
||||||
local simulator_id="$1"
|
|
||||||
local simulators=$(get_booted_simulators)
|
|
||||||
|
|
||||||
if [ -z "$simulators" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$simulators" | grep -q "^$simulator_id$"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to install on physical device
|
|
||||||
install_on_device() {
|
|
||||||
local device_id="$1"
|
|
||||||
local device_name=$(get_device_name "$device_id")
|
|
||||||
|
|
||||||
print_info "Installing on physical device: $device_name ($device_id)"
|
|
||||||
|
|
||||||
if xcrun devicectl device install app --device "$device_id" "$IPA_PATH"; then
|
|
||||||
print_success "Installation completed successfully on $device_name!"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
print_error "Installation failed on $device_name"
|
|
||||||
print_info "Common solutions:"
|
|
||||||
print_info " 1. Make sure the device is unlocked and trusted"
|
|
||||||
print_info " 2. Check that the provisioning profile matches the device"
|
|
||||||
print_info " 3. Verify the device has enough storage space"
|
|
||||||
print_info " 4. Try disconnecting and reconnecting the device"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to install on simulator
|
|
||||||
install_on_simulator() {
|
|
||||||
local simulator_id="$1"
|
|
||||||
local simulator_name=$(get_simulator_name "$simulator_id")
|
|
||||||
|
|
||||||
print_info "Installing on simulator: $simulator_name ($simulator_id)"
|
|
||||||
|
|
||||||
if xcrun simctl install "$simulator_id" "$IPA_PATH"; then
|
|
||||||
print_success "Installation completed successfully on $simulator_name!"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
print_error "Installation failed on $simulator_name"
|
|
||||||
print_info "Make sure the simulator is booted and try again"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main installation logic
|
|
||||||
if [ -n "$IOS_DEVICE" ]; then
|
|
||||||
# Specific physical device requested
|
|
||||||
print_info "Using specified physical device: $IOS_DEVICE"
|
|
||||||
|
|
||||||
if ! validate_physical_device "$IOS_DEVICE"; then
|
|
||||||
print_error "Physical device $IOS_DEVICE is not connected or not found."
|
|
||||||
print_info "Connected physical devices:"
|
|
||||||
physical_devices=$(get_physical_devices)
|
|
||||||
if [ -n "$physical_devices" ]; then
|
|
||||||
echo "$physical_devices" | while read -r device; do
|
|
||||||
device_name=$(get_device_name "$device")
|
|
||||||
print_info " - $device ($device_name)"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
print_info " No physical devices found"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_on_device "$IOS_DEVICE"
|
|
||||||
|
|
||||||
elif [ -n "$IOS_SIMULATOR" ]; then
|
|
||||||
# Specific simulator requested
|
|
||||||
print_info "Using specified simulator: $IOS_SIMULATOR"
|
|
||||||
|
|
||||||
if ! validate_simulator "$IOS_SIMULATOR"; then
|
|
||||||
print_error "Simulator $IOS_SIMULATOR is not booted or not found."
|
|
||||||
print_info "Booted simulators:"
|
|
||||||
booted_simulators=$(get_booted_simulators)
|
|
||||||
if [ -n "$booted_simulators" ]; then
|
|
||||||
echo "$booted_simulators" | while read -r sim; do
|
|
||||||
sim_name=$(get_simulator_name "$sim")
|
|
||||||
print_info " - $sim ($sim_name)"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
print_info " No booted simulators found"
|
|
||||||
print_info " Start a simulator from Xcode or run: xcrun simctl boot <simulator-udid>"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_on_simulator "$IOS_SIMULATOR"
|
|
||||||
|
|
||||||
else
|
|
||||||
# Auto-detect: prefer physical devices, fallback to simulators
|
|
||||||
print_info "Auto-detecting iOS targets..."
|
|
||||||
|
|
||||||
# Try physical devices first
|
|
||||||
physical_devices=$(get_physical_devices)
|
|
||||||
if [ -n "$physical_devices" ]; then
|
|
||||||
target_device=$(echo "$physical_devices" | head -n 1)
|
|
||||||
device_count=$(echo "$physical_devices" | wc -l | tr -d ' ')
|
|
||||||
|
|
||||||
if [ "$device_count" -gt 1 ]; then
|
|
||||||
print_warning "Multiple physical devices found. Using first device: $target_device"
|
|
||||||
print_info "Available physical devices:"
|
|
||||||
echo "$physical_devices" | while read -r device; do
|
|
||||||
device_name=$(get_device_name "$device")
|
|
||||||
if [ "$device" = "$target_device" ]; then
|
|
||||||
print_info " - $device ($device_name) [selected]"
|
|
||||||
else
|
|
||||||
print_info " - $device ($device_name)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_info "To use a specific device, run: IOS_DEVICE=<device-udid> yarn install:ios"
|
|
||||||
else
|
|
||||||
device_name=$(get_device_name "$target_device")
|
|
||||||
print_info "Using physical device: $device_name ($target_device)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_on_device "$target_device"
|
|
||||||
else
|
|
||||||
# No physical devices, try simulators
|
|
||||||
print_info "No physical devices found. Looking for booted simulators..."
|
|
||||||
|
|
||||||
booted_simulators=$(get_booted_simulators)
|
|
||||||
if [ -n "$booted_simulators" ]; then
|
|
||||||
target_simulator=$(echo "$booted_simulators" | head -n 1)
|
|
||||||
simulator_count=$(echo "$booted_simulators" | wc -l | tr -d ' ')
|
|
||||||
|
|
||||||
if [ "$simulator_count" -gt 1 ]; then
|
|
||||||
print_warning "Multiple booted simulators found. Using first simulator: $target_simulator"
|
|
||||||
print_info "Available booted simulators:"
|
|
||||||
echo "$booted_simulators" | while read -r sim; do
|
|
||||||
sim_name=$(get_simulator_name "$sim")
|
|
||||||
if [ "$sim" = "$target_simulator" ]; then
|
|
||||||
print_info " - $sim ($sim_name) [selected]"
|
|
||||||
else
|
|
||||||
print_info " - $sim ($sim_name)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_info "To use a specific simulator, run: IOS_SIMULATOR=<simulator-udid> yarn install:ios"
|
|
||||||
else
|
|
||||||
simulator_name=$(get_simulator_name "$target_simulator")
|
|
||||||
print_info "Using simulator: $simulator_name ($target_simulator)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_on_simulator "$target_simulator"
|
|
||||||
else
|
|
||||||
print_error "No iOS devices or booted simulators found."
|
|
||||||
print_info "Please either:"
|
|
||||||
print_info " 1. Connect and trust an iOS device, or"
|
|
||||||
print_info " 2. Boot a simulator from Xcode"
|
|
||||||
print_info ""
|
|
||||||
print_info "Usage examples:"
|
|
||||||
print_info " Auto-detect: yarn install:ios"
|
|
||||||
print_info " Specific device: IOS_DEVICE=<device-udid> yarn install:ios"
|
|
||||||
print_info " Specific simulator: IOS_SIMULATOR=<simulator-udid> yarn install:ios"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
|
@ -158,12 +158,9 @@
|
||||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||||
6C173A6450034E8CAB58FB0C /* Fix Xcode 15 Bug */,
|
6C173A6450034E8CAB58FB0C /* Fix Xcode 15 Bug */,
|
||||||
CBB0E03A64A84F6FB794EDB2 /* Upload Debug Symbols to Sentry */,
|
CBB0E03A64A84F6FB794EDB2 /* Upload Debug Symbols to Sentry */,
|
||||||
|
6EA5AE3725914306AC3A5BE5 /* Remove signature files (Xcode workaround) */,
|
||||||
8A20F54D80BCC2E27CF783AE /* [CP] Embed Pods Frameworks */,
|
8A20F54D80BCC2E27CF783AE /* [CP] Embed Pods Frameworks */,
|
||||||
36EBB336DD5343908AA35FFC /* [CP-User] [RNFB] Core Configuration */,
|
36EBB336DD5343908AA35FFC /* [CP-User] [RNFB] Core Configuration */,
|
||||||
8EC12A68941D40E98E0D60BE /* Fix Xcode 15 Bug */,
|
|
||||||
49AEAB1D332B45ED9A37B009 /* Fix Xcode 15 Bug */,
|
|
||||||
D75A41050AB3445786799848 /* Fix Xcode 15 Bug */,
|
|
||||||
ABC6C5A0D48A4B7980D60E1B /* Remove signature files (Xcode workaround) */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -476,108 +473,6 @@
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
|
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
|
||||||
};
|
};
|
||||||
8EC12A68941D40E98E0D60BE /* Fix Xcode 15 Bug */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
name = "Fix Xcode 15 Bug";
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "if [ \"$XCODE_VERSION_MAJOR\" = \"1500\" ]; then
|
|
||||||
echo \"Remove signature files (Xcode 15 workaround)\"
|
|
||||||
find \"$BUILD_DIR/${CONFIGURATION}-iphoneos\" -name \"*.signature\" -type f | xargs -r rm
|
|
||||||
fi";
|
|
||||||
};
|
|
||||||
700E335CAFA54AABB46BAB62 /* Remove signature files (Xcode workaround) */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
name = "Remove signature files (Xcode workaround)";
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "
|
|
||||||
echo \"Remove signature files (Xcode workaround)\";
|
|
||||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
|
||||||
";
|
|
||||||
};
|
|
||||||
49AEAB1D332B45ED9A37B009 /* Fix Xcode 15 Bug */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
name = "Fix Xcode 15 Bug";
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "if [ \"$XCODE_VERSION_MAJOR\" = \"1500\" ]; then
|
|
||||||
echo \"Remove signature files (Xcode 15 workaround)\"
|
|
||||||
find \"$BUILD_DIR/${CONFIGURATION}-iphoneos\" -name \"*.signature\" -type f | xargs -r rm
|
|
||||||
fi";
|
|
||||||
};
|
|
||||||
E611D841DFAE4F71B8077AD3 /* Remove signature files (Xcode workaround) */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
name = "Remove signature files (Xcode workaround)";
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "
|
|
||||||
echo \"Remove signature files (Xcode workaround)\";
|
|
||||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
|
||||||
";
|
|
||||||
};
|
|
||||||
D75A41050AB3445786799848 /* Fix Xcode 15 Bug */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
name = "Fix Xcode 15 Bug";
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "if [ \"$XCODE_VERSION_MAJOR\" = \"1500\" ]; then
|
|
||||||
echo \"Remove signature files (Xcode 15 workaround)\"
|
|
||||||
find \"$BUILD_DIR/${CONFIGURATION}-iphoneos\" -name \"*.signature\" -type f | xargs -r rm
|
|
||||||
fi";
|
|
||||||
};
|
|
||||||
ABC6C5A0D48A4B7980D60E1B /* Remove signature files (Xcode workaround) */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
name = "Remove signature files (Xcode workaround)";
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "
|
|
||||||
echo \"Remove signature files (Xcode workaround)\";
|
|
||||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
|
||||||
";
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
@ -621,7 +516,7 @@ fi";
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.alertesecours.alertesecours;
|
PRODUCT_BUNDLE_IDENTIFIER = com.alertesecours.alertesecours;
|
||||||
PRODUCT_NAME = "AlerteSecours";
|
PRODUCT_NAME = AlerteSecours;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "AlerteSecours/AlerteSecours-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "AlerteSecours/AlerteSecours-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -640,7 +535,7 @@ fi";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 2PZ49Y23LX;
|
DEVELOPMENT_TEAM = 2PZ49Y23LX;
|
||||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64";
|
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||||
INFOPLIST_FILE = AlerteSecours/Info.plist;
|
INFOPLIST_FILE = AlerteSecours/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
@ -652,7 +547,7 @@ fi";
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.alertesecours.alertesecours;
|
PRODUCT_BUNDLE_IDENTIFIER = com.alertesecours.alertesecours;
|
||||||
PRODUCT_NAME = "AlerteSecours";
|
PRODUCT_NAME = AlerteSecours;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "AlerteSecours/AlerteSecours-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "AlerteSecours/AlerteSecours-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "1"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 71 KiB |
|
@ -2,12 +2,6 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
||||||
<array>
|
|
||||||
<string>com.transistorsoft</string>
|
|
||||||
<string>com.transistorsoft.fetch</string>
|
|
||||||
<string>com.transistorsoft.customtask</string>
|
|
||||||
</array>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
@ -25,7 +19,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.11</string>
|
<string>1.11.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
@ -48,7 +42,7 @@
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>201</string>
|
<string>190</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
@ -95,14 +89,23 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>alertesecours.fr</key>
|
<key>alertesecours.fr</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSIncludesSubdomains</key>
|
||||||
|
<true/>
|
||||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||||
|
<false/>
|
||||||
<key>NSExceptionMinimumTLSVersion</key>
|
<key>NSExceptionMinimumTLSVersion</key>
|
||||||
<string>TLSv1.0</string>
|
<string>TLSv1.0</string>
|
||||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
</dict>
|
||||||
<false/>
|
<key>sentry.io</key>
|
||||||
|
<dict>
|
||||||
<key>NSIncludesSubdomains</key>
|
<key>NSIncludesSubdomains</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSExceptionMinimumTLSVersion</key>
|
||||||
|
<string>TLSv1.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>localhost</key>
|
<key>localhost</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -111,15 +114,6 @@
|
||||||
<key>NSIncludesSubdomains</key>
|
<key>NSIncludesSubdomains</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>sentry.io</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionMinimumTLSVersion</key>
|
|
||||||
<string>TLSv1.0</string>
|
|
||||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSIncludesSubdomains</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
|
@ -151,7 +145,6 @@
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
<string>location</string>
|
<string>location</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
<string>processing</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>SplashScreen</string>
|
<string>SplashScreen</string>
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// RNBackgroundGeolocation+AppDelegate.m
|
|
||||||
// RNBackgroundGeolocationSample
|
|
||||||
//
|
|
||||||
// Created by Christopher Scott on 2016-08-01.
|
|
||||||
// Copyright © 2016 Facebook. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import <TSBackgroundFetch/TSBackgroundFetch.h>
|
|
||||||
|
|
||||||
@implementation AppDelegate(AppDelegate)
|
|
||||||
|
|
||||||
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
|
|
||||||
{
|
|
||||||
NSLog(@"RNBackgroundFetch AppDelegate received fetch event");
|
|
||||||
TSBackgroundFetch *fetchManager = [TSBackgroundFetch sharedInstance];
|
|
||||||
[fetchManager performFetchWithCompletionHandler:completionHandler applicationState:application.applicationState];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "alerte-secours",
|
"name": "alerte-secours",
|
||||||
"version": "1.11.11",
|
"version": "1.11.0",
|
||||||
"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",
|
||||||
|
@ -37,7 +37,6 @@
|
||||||
"e2e:test": "detox test --configuration android.emu.debug",
|
"e2e:test": "detox test --configuration android.emu.debug",
|
||||||
"e2e:run": "yarn start & yarn e2e:build && yarn e2e:deploy && yarn e2e:test",
|
"e2e:run": "yarn start & yarn e2e:build && yarn e2e:deploy && yarn e2e:test",
|
||||||
"install:android": "./install-android.sh",
|
"install:android": "./install-android.sh",
|
||||||
"install:ios": "./install-ios.sh",
|
|
||||||
"log:android": "adb -s $DEVICE logcat | grep -E 'ReactNativeJS: '",
|
"log:android": "adb -s $DEVICE logcat | grep -E 'ReactNativeJS: '",
|
||||||
"log:ios:simulator": "xcrun simctl spawn booted log stream --level debug --predicate 'subsystem contains \"com.facebook.react.log\" and processImagePath contains \"AlerteSecours\"'",
|
"log:ios:simulator": "xcrun simctl spawn booted log stream --level debug --predicate 'subsystem contains \"com.facebook.react.log\" and processImagePath contains \"AlerteSecours\"'",
|
||||||
"log:ios": "idevicesyslog | grep -i 'AlerteSecours\\|ReactNative'",
|
"log:ios": "idevicesyslog | grep -i 'AlerteSecours\\|ReactNative'",
|
||||||
|
@ -50,8 +49,8 @@
|
||||||
"screenshot:android": "scripts/screenshot-android.sh"
|
"screenshot:android": "scripts/screenshot-android.sh"
|
||||||
},
|
},
|
||||||
"customExpoVersioning": {
|
"customExpoVersioning": {
|
||||||
"versionCode": 201,
|
"versionCode": 190,
|
||||||
"buildNumber": 201
|
"buildNumber": 190
|
||||||
},
|
},
|
||||||
"commit-and-tag-version": {
|
"commit-and-tag-version": {
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
import BackgroundGeolocation from "react-native-background-geolocation";
|
|
||||||
import { memoryAsyncStorage } from "~/storage/memoryAsyncStorage";
|
|
||||||
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 geolocBgLogger = createLogger({
|
|
||||||
service: "background-task",
|
|
||||||
task: "headless",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helper functions for persisting sync time
|
|
||||||
const getLastSyncTime = async () => {
|
|
||||||
try {
|
|
||||||
const value = await memoryAsyncStorage.getItem(
|
|
||||||
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
|
|
||||||
);
|
|
||||||
return value ? parseInt(value, 10) : Date.now();
|
|
||||||
} catch (error) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setLastSyncTime = async (time) => {
|
|
||||||
try {
|
|
||||||
await memoryAsyncStorage.setItem(
|
|
||||||
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
|
|
||||||
time.toString(),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
// silent error
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shared heartbeat logic - mutualized between Android and iOS
|
|
||||||
const executeSync = async () => {
|
|
||||||
let syncPerformed = false;
|
|
||||||
let syncSuccessful = false;
|
|
||||||
|
|
||||||
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 () => {
|
|
||||||
const lastSyncTime = await getLastSyncTime();
|
|
||||||
const now = Date.now();
|
|
||||||
const timeSinceLastSync = now - lastSyncTime;
|
|
||||||
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
|
||||||
geolocBgLogger.info("Forcing location sync");
|
|
||||||
try {
|
|
||||||
await Promise.race([
|
|
||||||
async () => {
|
|
||||||
await executeSync();
|
|
||||||
},
|
|
||||||
new Promise((_, reject) =>
|
|
||||||
setTimeout(() => reject(new Error("changePace timeout")), 10000),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await setLastSyncTime(now);
|
|
||||||
} catch (syncError) {
|
|
||||||
geolocBgLogger.error("Force sync failed", { error: syncError });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -132,7 +132,7 @@ export default async function trackLocation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackgroundGeolocation.onLocation(async (location) => {
|
BackgroundGeolocation.onLocation((location) => {
|
||||||
locationLogger.debug("Location update received", {
|
locationLogger.debug("Location update received", {
|
||||||
coords: location.coords,
|
coords: location.coords,
|
||||||
timestamp: location.timestamp,
|
timestamp: location.timestamp,
|
||||||
|
|
364
src/notifications/autoCancelExpired.js
Normal file
364
src/notifications/autoCancelExpired.js
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
import notifee from "@notifee/react-native";
|
||||||
|
import BackgroundFetch from "react-native-background-fetch";
|
||||||
|
import * as Sentry from "@sentry/react-native";
|
||||||
|
|
||||||
|
import useMount from "~/hooks/useMount";
|
||||||
|
import { createLogger } from "~/lib/logger";
|
||||||
|
|
||||||
|
const logger = createLogger({
|
||||||
|
service: "notifications",
|
||||||
|
task: "auto-cancel-expired",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Background task to cancel expired notifications
|
||||||
|
const backgroundTask = async () => {
|
||||||
|
await Sentry.startSpan(
|
||||||
|
{
|
||||||
|
name: "auto-cancel-expired-notifications",
|
||||||
|
op: "background-task",
|
||||||
|
},
|
||||||
|
async (span) => {
|
||||||
|
try {
|
||||||
|
logger.info("Starting auto-cancel expired notifications task");
|
||||||
|
|
||||||
|
Sentry.addBreadcrumb({
|
||||||
|
message: "Auto-cancel task started",
|
||||||
|
category: "notifications",
|
||||||
|
level: "info",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get displayed notifications with timeout protection
|
||||||
|
let notifications;
|
||||||
|
await Sentry.startSpan(
|
||||||
|
{
|
||||||
|
op: "get-displayed-notifications",
|
||||||
|
description: "Getting displayed notifications",
|
||||||
|
},
|
||||||
|
async (getNotificationsSpan) => {
|
||||||
|
try {
|
||||||
|
// Add timeout protection for the API call
|
||||||
|
notifications = await Promise.race([
|
||||||
|
notifee.getDisplayedNotifications(),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error("Timeout getting notifications")),
|
||||||
|
10000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
getNotificationsSpan.setStatus("ok");
|
||||||
|
} catch (error) {
|
||||||
|
getNotificationsSpan.setStatus("internal_error");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Array.isArray(notifications)) {
|
||||||
|
logger.warn("No notifications array received", { notifications });
|
||||||
|
Sentry.addBreadcrumb({
|
||||||
|
message: "No notifications array received",
|
||||||
|
category: "notifications",
|
||||||
|
level: "warning",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTime = Math.round(new Date() / 1000);
|
||||||
|
let cancelledCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
logger.info("Processing notifications", {
|
||||||
|
totalNotifications: notifications.length,
|
||||||
|
currentTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.addBreadcrumb({
|
||||||
|
message: "Processing notifications",
|
||||||
|
category: "notifications",
|
||||||
|
level: "info",
|
||||||
|
data: {
|
||||||
|
totalNotifications: notifications.length,
|
||||||
|
currentTime,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process notifications with individual error handling
|
||||||
|
for (const notification of notifications) {
|
||||||
|
try {
|
||||||
|
if (!notification || !notification.id) {
|
||||||
|
logger.warn("Invalid notification object", { notification });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expires = notification.data?.expires;
|
||||||
|
if (!expires) {
|
||||||
|
continue; // Skip notifications without expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof expires !== "number" || expires < currentTime) {
|
||||||
|
logger.debug("Cancelling expired notification", {
|
||||||
|
notificationId: notification.id,
|
||||||
|
expires,
|
||||||
|
currentTime,
|
||||||
|
expired: expires < currentTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel notification with timeout protection
|
||||||
|
await Promise.race([
|
||||||
|
notifee.cancelNotification(notification.id),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error("Timeout cancelling notification")),
|
||||||
|
5000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
cancelledCount++;
|
||||||
|
|
||||||
|
Sentry.addBreadcrumb({
|
||||||
|
message: "Notification cancelled",
|
||||||
|
category: "notifications",
|
||||||
|
level: "info",
|
||||||
|
data: {
|
||||||
|
notificationId: notification.id,
|
||||||
|
expires,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (notificationError) {
|
||||||
|
errorCount++;
|
||||||
|
logger.error("Failed to process notification", {
|
||||||
|
error: notificationError,
|
||||||
|
notificationId: notification?.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.captureException(notificationError, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "cancel-notification",
|
||||||
|
},
|
||||||
|
contexts: {
|
||||||
|
notification: {
|
||||||
|
id: notification?.id,
|
||||||
|
expires: notification?.data?.expires,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Auto-cancel task completed", {
|
||||||
|
totalNotifications: notifications.length,
|
||||||
|
cancelledCount,
|
||||||
|
errorCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.addBreadcrumb({
|
||||||
|
message: "Auto-cancel task completed",
|
||||||
|
category: "notifications",
|
||||||
|
level: "info",
|
||||||
|
data: {
|
||||||
|
totalNotifications: notifications.length,
|
||||||
|
cancelledCount,
|
||||||
|
errorCount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
span.setStatus("ok");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Auto-cancel task failed", { error });
|
||||||
|
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "background-task",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
span.setStatus("internal_error");
|
||||||
|
throw error; // Re-throw to be handled by caller
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAutoCancelExpired = () => {
|
||||||
|
useMount(() => {
|
||||||
|
// Initialize background fetch
|
||||||
|
BackgroundFetch.configure(
|
||||||
|
{
|
||||||
|
minimumFetchInterval: 180, // Fetch interval in minutes
|
||||||
|
stopOnTerminate: false,
|
||||||
|
startOnBoot: true,
|
||||||
|
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_NONE,
|
||||||
|
enableHeadless: true,
|
||||||
|
},
|
||||||
|
async (taskId) => {
|
||||||
|
logger.info("BackgroundFetch task started", { taskId });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await backgroundTask();
|
||||||
|
logger.info("BackgroundFetch task completed successfully", {
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("BackgroundFetch task failed", { taskId, error });
|
||||||
|
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "background-fetch-task",
|
||||||
|
taskId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// CRITICAL: Always call finish, even on error
|
||||||
|
try {
|
||||||
|
if (taskId) {
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
logger.debug("BackgroundFetch task finished", { taskId });
|
||||||
|
} else {
|
||||||
|
logger.error("Cannot finish BackgroundFetch task - no taskId");
|
||||||
|
}
|
||||||
|
} catch (finishError) {
|
||||||
|
// This is a critical error - the native side might be in a bad state
|
||||||
|
logger.error("CRITICAL: BackgroundFetch.finish() failed", {
|
||||||
|
taskId,
|
||||||
|
error: finishError,
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.captureException(finishError, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "background-fetch-finish",
|
||||||
|
critical: true,
|
||||||
|
},
|
||||||
|
contexts: {
|
||||||
|
task: { taskId },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
logger.error("BackgroundFetch failed to start", { error });
|
||||||
|
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "background-fetch-configure",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
BackgroundFetch.stop();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register headless task
|
||||||
|
BackgroundFetch.registerHeadlessTask(async (event) => {
|
||||||
|
const taskId = event?.taskId;
|
||||||
|
|
||||||
|
logger.info("Headless task started", { taskId, event });
|
||||||
|
|
||||||
|
// Add timeout protection for the entire headless task
|
||||||
|
const taskTimeout = setTimeout(() => {
|
||||||
|
logger.error("Headless task timeout", { taskId });
|
||||||
|
|
||||||
|
Sentry.captureException(new Error("Headless task timeout"), {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "headless-task-timeout",
|
||||||
|
taskId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force finish the task to prevent native side hanging
|
||||||
|
try {
|
||||||
|
if (taskId) {
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
logger.debug("Headless task force-finished due to timeout", { taskId });
|
||||||
|
}
|
||||||
|
} catch (finishError) {
|
||||||
|
logger.error("CRITICAL: Failed to force-finish timed out headless task", {
|
||||||
|
taskId,
|
||||||
|
error: finishError,
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.captureException(finishError, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "headless-task-timeout-finish",
|
||||||
|
critical: true,
|
||||||
|
},
|
||||||
|
contexts: {
|
||||||
|
task: { taskId },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 30000); // 30 second timeout
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!taskId) {
|
||||||
|
throw new Error("No taskId provided in headless task event");
|
||||||
|
}
|
||||||
|
|
||||||
|
await backgroundTask();
|
||||||
|
logger.info("Headless task completed successfully", { taskId });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Headless task failed", { taskId, error });
|
||||||
|
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "headless-task",
|
||||||
|
taskId,
|
||||||
|
},
|
||||||
|
contexts: {
|
||||||
|
event: {
|
||||||
|
taskId,
|
||||||
|
eventData: JSON.stringify(event),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// Clear the timeout
|
||||||
|
clearTimeout(taskTimeout);
|
||||||
|
|
||||||
|
// CRITICAL: Always call finish, even on error
|
||||||
|
try {
|
||||||
|
if (taskId) {
|
||||||
|
BackgroundFetch.finish(taskId);
|
||||||
|
logger.debug("Headless task finished", { taskId });
|
||||||
|
} else {
|
||||||
|
logger.error("Cannot finish headless task - no taskId", { event });
|
||||||
|
}
|
||||||
|
} catch (finishError) {
|
||||||
|
// This is a critical error - the native side might be in a bad state
|
||||||
|
logger.error(
|
||||||
|
"CRITICAL: BackgroundFetch.finish() failed in headless task",
|
||||||
|
{
|
||||||
|
taskId,
|
||||||
|
error: finishError,
|
||||||
|
event,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Sentry.captureException(finishError, {
|
||||||
|
tags: {
|
||||||
|
module: "auto-cancel-expired",
|
||||||
|
operation: "headless-task-finish",
|
||||||
|
critical: true,
|
||||||
|
},
|
||||||
|
contexts: {
|
||||||
|
task: { taskId },
|
||||||
|
event: { eventData: JSON.stringify(event) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,38 +0,0 @@
|
||||||
import { createLogger } from "~/lib/logger";
|
|
||||||
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
|
|
||||||
import { executeHeartbeatSync } from "~/location/backgroundTask";
|
|
||||||
|
|
||||||
const heartbeatLogger = createLogger({
|
|
||||||
module: BACKGROUND_SCOPES.NOTIFICATIONS,
|
|
||||||
feature: "geolocation-heartbeat-sync",
|
|
||||||
});
|
|
||||||
|
|
||||||
export default async function notifGeolocationHeartbeatSync(data) {
|
|
||||||
try {
|
|
||||||
heartbeatLogger.info(
|
|
||||||
"Received iOS geolocation heartbeat sync notification",
|
|
||||||
{
|
|
||||||
data,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// This is a silent notification - no visible notification is displayed
|
|
||||||
// Instead, we trigger the geolocation heartbeat sync directly
|
|
||||||
|
|
||||||
heartbeatLogger.info("Triggering geolocation heartbeat sync");
|
|
||||||
|
|
||||||
// Execute the heartbeat sync to force location update
|
|
||||||
await executeHeartbeatSync();
|
|
||||||
|
|
||||||
heartbeatLogger.info("Geolocation heartbeat sync completed successfully");
|
|
||||||
} catch (error) {
|
|
||||||
heartbeatLogger.error("Failed to execute geolocation heartbeat sync", {
|
|
||||||
error: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't throw the error - this is a background operation
|
|
||||||
// and we don't want to crash the notification handler
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ import notifSuggestKeepOpen from "./channels/notifSuggestKeepOpen";
|
||||||
import notifRelativeAllowAsk from "./channels/notifRelativeAllowAsk";
|
import notifRelativeAllowAsk from "./channels/notifRelativeAllowAsk";
|
||||||
import notifRelativeInvitation from "./channels/notifRelativeInvitation";
|
import notifRelativeInvitation from "./channels/notifRelativeInvitation";
|
||||||
import notifBackgroundGeolocationLost from "./channels/notifBackgroundGeolocationLost";
|
import notifBackgroundGeolocationLost from "./channels/notifBackgroundGeolocationLost";
|
||||||
import notifGeolocationHeartbeatSync from "./channels/notifGeolocationHeartbeatSync";
|
|
||||||
|
|
||||||
const displayLogger = createLogger({
|
const displayLogger = createLogger({
|
||||||
module: BACKGROUND_SCOPES.NOTIFICATIONS,
|
module: BACKGROUND_SCOPES.NOTIFICATIONS,
|
||||||
|
@ -23,7 +22,6 @@ const SUPPORTED_ACTIONS = {
|
||||||
"relative-allow-ask": notifRelativeAllowAsk,
|
"relative-allow-ask": notifRelativeAllowAsk,
|
||||||
"relative-invitation": notifRelativeInvitation,
|
"relative-invitation": notifRelativeInvitation,
|
||||||
"background-geolocation-lost": notifBackgroundGeolocationLost,
|
"background-geolocation-lost": notifBackgroundGeolocationLost,
|
||||||
"geolocation-heartbeat-sync": notifGeolocationHeartbeatSync,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function displayNotificationHandler(data) {
|
export default async function displayNotificationHandler(data) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import useMount from "~/hooks/useMount";
|
import useMount from "~/hooks/useMount";
|
||||||
import setActionCategories from "./setActionCategories";
|
import setActionCategories from "./setActionCategories";
|
||||||
import onMessageReceived from "./onMessageReceived";
|
import onMessageReceived from "./onMessageReceived";
|
||||||
|
import { useAutoCancelExpired } from "./autoCancelExpired";
|
||||||
import { requestFcmPermission, setupFcm } from "./firebase";
|
import { requestFcmPermission, setupFcm } from "./firebase";
|
||||||
import {
|
import {
|
||||||
requestNotifeePermission,
|
requestNotifeePermission,
|
||||||
|
@ -203,4 +204,6 @@ export function useFcm() {
|
||||||
notifLogger.debug("Badge count reset");
|
notifLogger.debug("Badge count reset");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useAutoCancelExpired();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue