Compare commits
20 commits
bf957ba115
...
6378758f9b
Author | SHA1 | Date | |
---|---|---|---|
6378758f9b | |||
47f11d1b88 | |||
d4de0b4541 | |||
b5ae235ba4 | |||
5bf3f9b6f9 | |||
27ead01714 | |||
a83d423c77 | |||
9b272bea61 | |||
be0cd62cb9 | |||
f39875b810 | |||
6e290bdb69 | |||
0001a50a5f | |||
e47a33bcd8 | |||
6af58755c1 | |||
b10ff5a6e7 | |||
cf61de639c | |||
7ab708a536 | |||
0ac28515df | |||
09ea8cd563 | |||
644480182d |
24 changed files with 425 additions and 463 deletions
|
@ -3,6 +3,9 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE"/>
|
<uses-permission android:name="android.permission.CALL_PHONE"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
@ -44,6 +47,8 @@
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://expo-updates.alertesecours.fr/api/manifest?project=alerte-secours&channel=release"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://expo-updates.alertesecours.fr/api/manifest?project=alerte-secours&channel=release"/>
|
||||||
|
<service android:name="com.transistorsoft.locationmanager.service.LocationRequestService" android:foregroundServiceType="location|dataSync" android:enabled="true" android:exported="false" tools:replace="android:foregroundServiceType"/>
|
||||||
|
<service android:name="com.transistorsoft.backgroundfetch.BackgroundFetchService" android:foregroundServiceType="dataSync" android:enabled="true" android:exported="false"/>
|
||||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
@ -73,4 +78,4 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
187
index.js
187
index.js
|
@ -20,6 +20,7 @@ import onMessageReceived from "~/notifications/onMessageReceived";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import * as Sentry from "@sentry/react-native";
|
import * as Sentry from "@sentry/react-native";
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
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);
|
||||||
|
@ -31,14 +32,16 @@ messaging().setBackgroundMessageHandler(onMessageReceived);
|
||||||
registerRootComponent(App);
|
registerRootComponent(App);
|
||||||
|
|
||||||
// Constants for persistence
|
// Constants for persistence
|
||||||
const LAST_SYNC_TIME_KEY = "@geolocation_last_sync_time";
|
|
||||||
// const FORCE_SYNC_INTERVAL = 24 * 60 * 60 * 1000;
|
// const FORCE_SYNC_INTERVAL = 24 * 60 * 60 * 1000;
|
||||||
const FORCE_SYNC_INTERVAL = 60 * 60 * 1000; // DEBUGGING
|
// const FORCE_SYNC_INTERVAL = 60 * 60 * 1000; // DEBUGGING
|
||||||
|
const FORCE_SYNC_INTERVAL = 5 * 60 * 1000; // DEBUGGING
|
||||||
|
|
||||||
// Helper functions for persisting sync time
|
// Helper functions for persisting sync time
|
||||||
const getLastSyncTime = async () => {
|
const getLastSyncTime = async () => {
|
||||||
try {
|
try {
|
||||||
const value = await AsyncStorage.getItem(LAST_SYNC_TIME_KEY);
|
const value = await AsyncStorage.getItem(
|
||||||
|
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
|
||||||
|
);
|
||||||
return value ? parseInt(value, 10) : Date.now();
|
return value ? parseInt(value, 10) : Date.now();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error, {
|
Sentry.captureException(error, {
|
||||||
|
@ -50,7 +53,10 @@ const getLastSyncTime = async () => {
|
||||||
|
|
||||||
const setLastSyncTime = async (time) => {
|
const setLastSyncTime = async (time) => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem(LAST_SYNC_TIME_KEY, time.toString());
|
await AsyncStorage.setItem(
|
||||||
|
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
|
||||||
|
time.toString(),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error, {
|
Sentry.captureException(error, {
|
||||||
tags: { module: "headless-task", operation: "set-last-sync-time" },
|
tags: { module: "headless-task", operation: "set-last-sync-time" },
|
||||||
|
@ -119,111 +125,24 @@ const HeadlessTask = async (event) => {
|
||||||
throw new Error("Invalid event name received");
|
throw new Error("Invalid event name received");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add initial breadcrumb
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "HeadlessTask started",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
eventName: name,
|
|
||||||
params: params ? JSON.stringify(params) : null,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
geolocBgLogger.info("HeadlessTask event received", { name, params });
|
geolocBgLogger.info("HeadlessTask event received", { name, params });
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "heartbeat":
|
case "heartbeat":
|
||||||
// Add breadcrumb for heartbeat event
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Heartbeat event received",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
timestamp: Date.now() / 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get persisted last sync time
|
// Get persisted last sync time
|
||||||
const lastSyncTime = await getLastSyncTime();
|
const lastSyncTime = await getLastSyncTime();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const timeSinceLastSync = now - lastSyncTime;
|
const timeSinceLastSync = now - lastSyncTime;
|
||||||
|
|
||||||
// Add context about sync timing
|
|
||||||
Sentry.setContext("sync-timing", {
|
|
||||||
lastSyncTime: new Date(lastSyncTime).toISOString(),
|
|
||||||
currentTime: new Date(now).toISOString(),
|
|
||||||
timeSinceLastSync: timeSinceLastSync,
|
|
||||||
timeSinceLastSyncHours: (
|
|
||||||
timeSinceLastSync /
|
|
||||||
(1000 * 60 * 60)
|
|
||||||
).toFixed(2),
|
|
||||||
needsForceSync: timeSinceLastSync >= FORCE_SYNC_INTERVAL,
|
|
||||||
});
|
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Sync timing calculated",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
timeSinceLastSyncHours: (
|
|
||||||
timeSinceLastSync /
|
|
||||||
(1000 * 60 * 60)
|
|
||||||
).toFixed(2),
|
|
||||||
needsForceSync: timeSinceLastSync >= FORCE_SYNC_INTERVAL,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get current position with performance tracking
|
// Get current position with performance tracking
|
||||||
const locationStartTime = Date.now();
|
|
||||||
const location = await getCurrentPosition();
|
const location = await getCurrentPosition();
|
||||||
const locationDuration = Date.now() - locationStartTime;
|
|
||||||
|
|
||||||
const isLocationError = location && location.code !== undefined;
|
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "getCurrentPosition completed",
|
|
||||||
category: "headless-task",
|
|
||||||
level: isLocationError ? "warning" : "info",
|
|
||||||
data: {
|
|
||||||
success: !isLocationError,
|
|
||||||
error: isLocationError ? location : undefined,
|
|
||||||
coords: !isLocationError ? location?.coords : undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
geolocBgLogger.debug("getCurrentPosition result", { location });
|
geolocBgLogger.debug("getCurrentPosition result", { location });
|
||||||
|
|
||||||
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
|
||||||
geolocBgLogger.info("Forcing location sync after 24h");
|
geolocBgLogger.info("Forcing location sync after 24h");
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Force sync triggered",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
timeSinceLastSyncHours: (
|
|
||||||
timeSinceLastSync /
|
|
||||||
(1000 * 60 * 60)
|
|
||||||
).toFixed(2),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get pending records count before sync with timeout
|
|
||||||
const pendingCount = await Promise.race([
|
|
||||||
BackgroundGeolocation.getCount(),
|
|
||||||
new Promise((_, reject) =>
|
|
||||||
setTimeout(() => reject(new Error("getCount timeout")), 10000),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Pending records count",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: { pendingCount },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change pace to ensure location updates with timeout
|
// Change pace to ensure location updates with timeout
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
BackgroundGeolocation.changePace(true),
|
BackgroundGeolocation.changePace(true),
|
||||||
|
@ -235,12 +154,6 @@ const HeadlessTask = async (event) => {
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "changePace completed",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Perform sync with timeout
|
// Perform sync with timeout
|
||||||
const syncResult = await Promise.race([
|
const syncResult = await Promise.race([
|
||||||
BackgroundGeolocation.sync(),
|
BackgroundGeolocation.sync(),
|
||||||
|
@ -249,26 +162,8 @@ const HeadlessTask = async (event) => {
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Sync completed successfully",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
syncResult: Array.isArray(syncResult)
|
|
||||||
? `${syncResult.length} records`
|
|
||||||
: "completed",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update last sync time after successful sync
|
// Update last sync time after successful sync
|
||||||
await setLastSyncTime(now);
|
await setLastSyncTime(now);
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Last sync time updated",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: { newSyncTime: new Date(now).toISOString() },
|
|
||||||
});
|
|
||||||
} catch (syncError) {
|
} catch (syncError) {
|
||||||
Sentry.captureException(syncError, {
|
Sentry.captureException(syncError, {
|
||||||
tags: {
|
tags: {
|
||||||
|
@ -286,22 +181,6 @@ const HeadlessTask = async (event) => {
|
||||||
|
|
||||||
geolocBgLogger.error("Force sync failed", { error: syncError });
|
geolocBgLogger.error("Force sync failed", { error: syncError });
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Force sync not needed",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
timeSinceLastSyncHours: (
|
|
||||||
timeSinceLastSync /
|
|
||||||
(1000 * 60 * 60)
|
|
||||||
).toFixed(2),
|
|
||||||
nextSyncInHours: (
|
|
||||||
(FORCE_SYNC_INTERVAL - timeSinceLastSync) /
|
|
||||||
(1000 * 60 * 60)
|
|
||||||
).toFixed(2),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -312,17 +191,6 @@ const HeadlessTask = async (event) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Location update received",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
coords: params.location?.coords,
|
|
||||||
activity: params.location?.activity,
|
|
||||||
hasLocation: !!params.location,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
geolocBgLogger.debug("Location update received", {
|
geolocBgLogger.debug("Location update received", {
|
||||||
location: params.location,
|
location: params.location,
|
||||||
});
|
});
|
||||||
|
@ -338,17 +206,6 @@ const HeadlessTask = async (event) => {
|
||||||
const httpStatus = params.response?.status;
|
const httpStatus = params.response?.status;
|
||||||
const isHttpSuccess = httpStatus === 200;
|
const isHttpSuccess = httpStatus === 200;
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "HTTP response received",
|
|
||||||
category: "headless-task",
|
|
||||||
level: isHttpSuccess ? "info" : "warning",
|
|
||||||
data: {
|
|
||||||
status: httpStatus,
|
|
||||||
success: params.response?.success,
|
|
||||||
hasResponse: !!params.response,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
geolocBgLogger.debug("HTTP response received", {
|
geolocBgLogger.debug("HTTP response received", {
|
||||||
response: params.response,
|
response: params.response,
|
||||||
});
|
});
|
||||||
|
@ -358,13 +215,6 @@ const HeadlessTask = async (event) => {
|
||||||
try {
|
try {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
await setLastSyncTime(now);
|
await setLastSyncTime(now);
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Last sync time updated (HTTP success)",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: { newSyncTime: new Date(now).toISOString() },
|
|
||||||
});
|
|
||||||
} catch (syncTimeError) {
|
} catch (syncTimeError) {
|
||||||
geolocBgLogger.error("Failed to update sync time", {
|
geolocBgLogger.error("Failed to update sync time", {
|
||||||
error: syncTimeError,
|
error: syncTimeError,
|
||||||
|
@ -381,26 +231,11 @@ const HeadlessTask = async (event) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Sentry.addBreadcrumb({
|
break;
|
||||||
message: "Unknown event type",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "warning",
|
|
||||||
data: { eventName: name },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task completed successfully
|
// Task completed successfully
|
||||||
const taskDuration = Date.now() - taskStartTime;
|
const taskDuration = Date.now() - taskStartTime;
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "HeadlessTask completed successfully",
|
|
||||||
category: "headless-task",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
eventName: name,
|
|
||||||
duration: taskDuration,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const taskDuration = Date.now() - taskStartTime;
|
const taskDuration = Date.now() - taskStartTime;
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,11 @@
|
||||||
F7ADCC68A8E44BA69FCA849E /* Fix Xcode 15 Bug */,
|
F7ADCC68A8E44BA69FCA849E /* Fix Xcode 15 Bug */,
|
||||||
B1AB92A327A24FB294681EDD /* Fix Xcode 15 Bug */,
|
B1AB92A327A24FB294681EDD /* Fix Xcode 15 Bug */,
|
||||||
0E26E4D25E2E49C3AB2723FA /* Fix Xcode 15 Bug */,
|
0E26E4D25E2E49C3AB2723FA /* Fix Xcode 15 Bug */,
|
||||||
8589214E888941E1817F4C9F /* Remove signature files (Xcode workaround) */,
|
5D0A324371BA4A5385A92DF5 /* Fix Xcode 15 Bug */,
|
||||||
|
40472AFA41A8495E9D557630 /* Fix Xcode 15 Bug */,
|
||||||
|
771057F6078145908B36B18B /* Fix Xcode 15 Bug */,
|
||||||
|
7C1CC306C4DF48D4B5E1BDFB /* Fix Xcode 15 Bug */,
|
||||||
|
2401E852B4D64D59BD803280 /* Remove signature files (Xcode workaround) */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -1099,6 +1103,142 @@ fi";
|
||||||
shellScript = "
|
shellScript = "
|
||||||
echo \"Remove signature files (Xcode workaround)\";
|
echo \"Remove signature files (Xcode workaround)\";
|
||||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||||
|
";
|
||||||
|
};
|
||||||
|
5D0A324371BA4A5385A92DF5 /* 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";
|
||||||
|
};
|
||||||
|
C637A42109E14A1AA86AF639 /* 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\";
|
||||||
|
";
|
||||||
|
};
|
||||||
|
40472AFA41A8495E9D557630 /* 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";
|
||||||
|
};
|
||||||
|
B79BB2C3F48A4CC4B6830286 /* 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\";
|
||||||
|
";
|
||||||
|
};
|
||||||
|
771057F6078145908B36B18B /* 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";
|
||||||
|
};
|
||||||
|
83ACE65C55FE44EC820FD39A /* 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\";
|
||||||
|
";
|
||||||
|
};
|
||||||
|
7C1CC306C4DF48D4B5E1BDFB /* 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";
|
||||||
|
};
|
||||||
|
2401E852B4D64D59BD803280 /* 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 */
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { createLogger } from "~/lib/logger";
|
||||||
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
|
||||||
import { authActions, permissionWizardActions } from "~/stores";
|
import { authActions, permissionWizardActions } from "~/stores";
|
||||||
import { secureStore } from "~/lib/memorySecureStore";
|
import { secureStore } from "~/storage/memorySecureStore";
|
||||||
import memoryAsyncStorage from "~/lib/memoryAsyncStorage";
|
import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
|
||||||
import "~/lib/mapbox";
|
import "~/lib/mapbox";
|
||||||
import "~/i18n";
|
import "~/i18n";
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { secureStore } from "~/lib/memorySecureStore";
|
import { secureStore } from "~/storage/memorySecureStore";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import uuidGenerator from "react-native-uuid";
|
import uuidGenerator from "react-native-uuid";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { FEATURE_SCOPES } from "~/lib/logger/scopes";
|
import { FEATURE_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
@ -21,12 +22,12 @@ async function getDeviceUuid() {
|
||||||
// Create a new promise for this generation attempt
|
// Create a new promise for this generation attempt
|
||||||
uuidGenerationPromise = (async () => {
|
uuidGenerationPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
let deviceUuid = await secureStore.getItemAsync("deviceUuid");
|
let deviceUuid = await secureStore.getItemAsync(STORAGE_KEYS.DEVICE_UUID);
|
||||||
|
|
||||||
if (!deviceUuid) {
|
if (!deviceUuid) {
|
||||||
deviceLogger.info("No device UUID found, generating new one");
|
deviceLogger.info("No device UUID found, generating new one");
|
||||||
deviceUuid = uuidGenerator.v4();
|
deviceUuid = uuidGenerator.v4();
|
||||||
await secureStore.setItemAsync("deviceUuid", deviceUuid);
|
await secureStore.setItemAsync(STORAGE_KEYS.DEVICE_UUID, deviceUuid);
|
||||||
deviceLogger.info("New device UUID generated and stored", {
|
deviceLogger.info("New device UUID generated and stored", {
|
||||||
uuid: deviceUuid.substring(0, 8) + "...",
|
uuid: deviceUuid.substring(0, 8) + "...",
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View, ScrollView, StyleSheet, Platform } from "react-native";
|
import { View, ScrollView, StyleSheet, Platform } from "react-native";
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
|
|
||||||
import Text from "../Text";
|
import Text from "../Text";
|
||||||
|
|
||||||
|
@ -64,14 +65,12 @@ Ce Contrat constitue l'intégralité de l'accord entre vous et nous concernant l
|
||||||
Si vous avez des questions concernant ce Contrat, veuillez nous contacter à :
|
Si vous avez des questions concernant ce Contrat, veuillez nous contacter à :
|
||||||
Email : contact@alertesecours.fr`;
|
Email : contact@alertesecours.fr`;
|
||||||
|
|
||||||
const EULA_STORAGE_KEY = "@eula_accepted";
|
|
||||||
|
|
||||||
const EULA = ({ onAccept, visible = true }) => {
|
const EULA = ({ onAccept, visible = true }) => {
|
||||||
if (!visible || Platform.OS !== "ios") return null;
|
if (!visible || Platform.OS !== "ios") return null;
|
||||||
|
|
||||||
const handleAccept = async () => {
|
const handleAccept = async () => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem(EULA_STORAGE_KEY, "true");
|
await AsyncStorage.setItem(STORAGE_KEYS.EULA_ACCEPTED, "true");
|
||||||
onAccept();
|
onAccept();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving EULA acceptance:", error);
|
console.error("Error saving EULA acceptance:", error);
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {
|
||||||
usePermissionWizardState,
|
usePermissionWizardState,
|
||||||
useNetworkState,
|
useNetworkState,
|
||||||
} from "~/stores";
|
} from "~/stores";
|
||||||
import { secureStore } from "~/lib/memorySecureStore";
|
import { secureStore } from "~/storage/memorySecureStore";
|
||||||
import memoryAsyncStorage from "~/lib/memoryAsyncStorage";
|
import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
|
||||||
import requestPermissionLocationBackground from "~/permissions/requestPermissionLocationBackground";
|
import requestPermissionLocationBackground from "~/permissions/requestPermissionLocationBackground";
|
||||||
import requestPermissionLocationForeground from "~/permissions/requestPermissionLocationForeground";
|
import requestPermissionLocationForeground from "~/permissions/requestPermissionLocationForeground";
|
||||||
|
|
12
src/env.js
12
src/env.js
|
@ -1,8 +1,6 @@
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { secureStore } from "~/lib/secureStore";
|
import { secureStore } from "~/storage/memorySecureStore";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
// Key for storing staging setting in secureStore
|
|
||||||
const STAGING_SETTING_KEY = "env.isStaging";
|
|
||||||
|
|
||||||
// Logging configuration
|
// Logging configuration
|
||||||
const LOG_SCOPES = process.env.APP_LOG_SCOPES;
|
const LOG_SCOPES = process.env.APP_LOG_SCOPES;
|
||||||
|
@ -97,7 +95,7 @@ export const setStaging = async (enabled) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist the staging setting
|
// Persist the staging setting
|
||||||
await secureStore.setItemAsync(STAGING_SETTING_KEY, String(enabled));
|
await secureStore.setItemAsync(STORAGE_KEYS.ENV_IS_STAGING, String(enabled));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize with default values
|
// Initialize with default values
|
||||||
|
@ -106,7 +104,9 @@ const env = { ...envMap };
|
||||||
// Load the staging setting from secureStore
|
// Load the staging setting from secureStore
|
||||||
export const initializeEnv = async () => {
|
export const initializeEnv = async () => {
|
||||||
try {
|
try {
|
||||||
const storedStaging = await secureStore.getItemAsync(STAGING_SETTING_KEY);
|
const storedStaging = await secureStore.getItemAsync(
|
||||||
|
STORAGE_KEYS.ENV_IS_STAGING,
|
||||||
|
);
|
||||||
if (storedStaging !== null) {
|
if (storedStaging !== null) {
|
||||||
const isStaging = storedStaging === "true";
|
const isStaging = storedStaging === "true";
|
||||||
if (isStaging) {
|
if (isStaging) {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
const EULA_STORAGE_KEY = "@eula_accepted";
|
|
||||||
|
|
||||||
export const useEULA = () => {
|
export const useEULA = () => {
|
||||||
const [eulaAccepted, setEulaAccepted] = useState(true);
|
const [eulaAccepted, setEulaAccepted] = useState(true);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
@ -16,7 +15,7 @@ export const useEULA = () => {
|
||||||
|
|
||||||
const checkEULA = async () => {
|
const checkEULA = async () => {
|
||||||
try {
|
try {
|
||||||
const accepted = await AsyncStorage.getItem(EULA_STORAGE_KEY);
|
const accepted = await AsyncStorage.getItem(STORAGE_KEYS.EULA_ACCEPTED);
|
||||||
setEulaAccepted(!!accepted);
|
setEulaAccepted(!!accepted);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking EULA status:", error);
|
console.error("Error checking EULA status:", error);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import BackgroundGeolocation from "react-native-background-geolocation";
|
import BackgroundGeolocation from "react-native-background-geolocation";
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
|
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
|
||||||
const EMULATOR_MODE_KEY = "emulator_mode_enabled";
|
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
let emulatorIntervalId = null;
|
let emulatorIntervalId = null;
|
||||||
let isEmulatorModeEnabled = false;
|
let isEmulatorModeEnabled = false;
|
||||||
|
@ -18,7 +17,9 @@ const emulatorLogger = createLogger({
|
||||||
// Initialize emulator mode based on stored preference
|
// Initialize emulator mode based on stored preference
|
||||||
export const initEmulatorMode = async () => {
|
export const initEmulatorMode = async () => {
|
||||||
try {
|
try {
|
||||||
const storedValue = await AsyncStorage.getItem(EMULATOR_MODE_KEY);
|
const storedValue = await AsyncStorage.getItem(
|
||||||
|
STORAGE_KEYS.EMULATOR_MODE_ENABLED,
|
||||||
|
);
|
||||||
emulatorLogger.debug("Initializing emulator mode", { storedValue });
|
emulatorLogger.debug("Initializing emulator mode", { storedValue });
|
||||||
|
|
||||||
if (storedValue === "true") {
|
if (storedValue === "true") {
|
||||||
|
@ -58,7 +59,7 @@ export const enableEmulatorMode = async () => {
|
||||||
isEmulatorModeEnabled = true;
|
isEmulatorModeEnabled = true;
|
||||||
|
|
||||||
// Persist the setting
|
// Persist the setting
|
||||||
await AsyncStorage.setItem(EMULATOR_MODE_KEY, "true");
|
await AsyncStorage.setItem(STORAGE_KEYS.EMULATOR_MODE_ENABLED, "true");
|
||||||
emulatorLogger.debug("Emulator mode setting saved");
|
emulatorLogger.debug("Emulator mode setting saved");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emulatorLogger.error("Failed to enable emulator mode", {
|
emulatorLogger.error("Failed to enable emulator mode", {
|
||||||
|
@ -81,7 +82,7 @@ export const disableEmulatorMode = async () => {
|
||||||
|
|
||||||
// Persist the setting
|
// Persist the setting
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem(EMULATOR_MODE_KEY, "false");
|
await AsyncStorage.setItem(STORAGE_KEYS.EMULATOR_MODE_ENABLED, "false");
|
||||||
emulatorLogger.debug("Emulator mode setting saved");
|
emulatorLogger.debug("Emulator mode setting saved");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emulatorLogger.error("Failed to save emulator mode setting", {
|
emulatorLogger.error("Failed to save emulator mode setting", {
|
||||||
|
|
|
@ -4,17 +4,8 @@ import { createLogger } from "~/lib/logger";
|
||||||
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
|
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
|
||||||
import jwtDecode from "jwt-decode";
|
import jwtDecode from "jwt-decode";
|
||||||
import { initEmulatorMode } from "./emulatorService";
|
import { initEmulatorMode } from "./emulatorService";
|
||||||
import * as Sentry from "@sentry/react-native";
|
|
||||||
import { SPAN_STATUS_OK, SPAN_STATUS_ERROR } from "@sentry/react-native";
|
|
||||||
|
|
||||||
import throttle from "lodash.throttle";
|
import { getAuthState, subscribeAuthState, permissionsActions } from "~/stores";
|
||||||
|
|
||||||
import {
|
|
||||||
getAuthState,
|
|
||||||
subscribeAuthState,
|
|
||||||
authActions,
|
|
||||||
permissionsActions,
|
|
||||||
} from "~/stores";
|
|
||||||
|
|
||||||
import setLocationState from "~/location/setLocationState";
|
import setLocationState from "~/location/setLocationState";
|
||||||
import { storeLocation } from "~/utils/location/storage";
|
import { storeLocation } from "~/utils/location/storage";
|
||||||
|
@ -76,9 +67,6 @@ export default async function trackLocation() {
|
||||||
isStaging: env.IS_STAGING,
|
isStaging: env.IS_STAGING,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Throttling configuration for auth reload only
|
|
||||||
const AUTH_RELOAD_THROTTLE = 5000; // 5 seconds throttle
|
|
||||||
|
|
||||||
// Handle auth function - no throttling or cooldown
|
// Handle auth function - no throttling or cooldown
|
||||||
async function handleAuth(userToken) {
|
async function handleAuth(userToken) {
|
||||||
locationLogger.info("Handling auth token update", {
|
locationLogger.info("Handling auth token update", {
|
||||||
|
@ -108,25 +96,6 @@ export default async function trackLocation() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the current configuration
|
|
||||||
try {
|
|
||||||
const currentConfig = await BackgroundGeolocation.getConfig();
|
|
||||||
locationLogger.debug("Current background geolocation config", {
|
|
||||||
hasHeaders: !!currentConfig.headers,
|
|
||||||
headerKeys: currentConfig.headers
|
|
||||||
? Object.keys(currentConfig.headers)
|
|
||||||
: [],
|
|
||||||
authHeader: currentConfig.headers?.Authorization
|
|
||||||
? currentConfig.headers.Authorization.substring(0, 15) + "..."
|
|
||||||
: "Not set",
|
|
||||||
url: currentConfig.url,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
locationLogger.error("Failed to get background geolocation config", {
|
|
||||||
error: error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = await BackgroundGeolocation.getState();
|
const state = await BackgroundGeolocation.getState();
|
||||||
try {
|
try {
|
||||||
const decodedToken = jwtDecode(userToken);
|
const decodedToken = jwtDecode(userToken);
|
||||||
|
@ -171,19 +140,6 @@ export default async function trackLocation() {
|
||||||
battery: location.battery,
|
battery: location.battery,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Sentry breadcrumb for location updates
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Location update in trackLocation",
|
|
||||||
category: "geolocation",
|
|
||||||
level: "info",
|
|
||||||
data: {
|
|
||||||
coords: location.coords,
|
|
||||||
activity: location.activity?.type,
|
|
||||||
battery: location.battery?.level,
|
|
||||||
isMoving: location.isMoving,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
location.coords &&
|
location.coords &&
|
||||||
location.coords.latitude &&
|
location.coords.latitude &&
|
||||||
|
@ -195,100 +151,12 @@ export default async function trackLocation() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// The core auth reload function that will be throttled
|
|
||||||
function _reloadAuth() {
|
|
||||||
locationLogger.info("Refreshing authentication token");
|
|
||||||
authActions.reload(); // should retriger sync in handleAuth via subscribeAuthState when done
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create throttled version of auth reload with lodash
|
|
||||||
const reloadAuth = throttle(_reloadAuth, AUTH_RELOAD_THROTTLE, {
|
|
||||||
leading: true,
|
|
||||||
trailing: false, // Prevent trailing calls to avoid duplicate refreshes
|
|
||||||
});
|
|
||||||
|
|
||||||
BackgroundGeolocation.onHttp(async (response) => {
|
BackgroundGeolocation.onHttp(async (response) => {
|
||||||
// Log the full response including headers if available
|
// log status code and response
|
||||||
locationLogger.debug("HTTP response received", {
|
locationLogger.debug("HTTP response received", {
|
||||||
status: response?.status,
|
status: response?.status,
|
||||||
success: response?.success,
|
|
||||||
responseText: response?.responseText,
|
responseText: response?.responseText,
|
||||||
url: response?.url,
|
|
||||||
method: response?.method,
|
|
||||||
isSync: response?.isSync,
|
|
||||||
requestHeaders:
|
|
||||||
response?.request?.headers || "Headers not available in response",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Sentry breadcrumb for HTTP responses
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Background geolocation HTTP response",
|
|
||||||
category: "geolocation-http",
|
|
||||||
level: response?.status === 200 ? "info" : "warning",
|
|
||||||
data: {
|
|
||||||
status: response?.status,
|
|
||||||
success: response?.success,
|
|
||||||
url: response?.url,
|
|
||||||
isSync: response?.isSync,
|
|
||||||
recordCount: response?.count,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Log the current auth token for comparison
|
|
||||||
const { userToken } = getAuthState();
|
|
||||||
locationLogger.debug("Current auth state token", {
|
|
||||||
tokenAvailable: !!userToken,
|
|
||||||
tokenPrefix: userToken ? userToken.substring(0, 10) + "..." : null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const statusCode = response?.status;
|
|
||||||
|
|
||||||
switch (statusCode) {
|
|
||||||
case 410:
|
|
||||||
// Token expired, logout
|
|
||||||
locationLogger.info("Auth token expired (410), logging out");
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Auth token expired - logging out",
|
|
||||||
category: "geolocation-auth",
|
|
||||||
level: "warning",
|
|
||||||
});
|
|
||||||
authActions.logout();
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
// Unauthorized, use throttled reload
|
|
||||||
locationLogger.info("Unauthorized (401), attempting to refresh token");
|
|
||||||
|
|
||||||
// Add more detailed logging of the error response
|
|
||||||
try {
|
|
||||||
const errorBody = response?.responseText
|
|
||||||
? JSON.parse(response.responseText)
|
|
||||||
: null;
|
|
||||||
locationLogger.debug("Unauthorized error details", {
|
|
||||||
errorBody,
|
|
||||||
errorType: errorBody?.error?.type,
|
|
||||||
errorMessage: errorBody?.error?.message,
|
|
||||||
errorPath: errorBody?.error?.errors?.[0]?.path,
|
|
||||||
});
|
|
||||||
|
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message: "Unauthorized - refreshing token",
|
|
||||||
category: "geolocation-auth",
|
|
||||||
level: "warning",
|
|
||||||
data: {
|
|
||||||
errorType: errorBody?.error?.type,
|
|
||||||
errorMessage: errorBody?.error?.message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
locationLogger.debug("Failed to parse error response", {
|
|
||||||
error: e.message,
|
|
||||||
responseText: response?.responseText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadAuth();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -9,15 +9,15 @@ export default function getStatusCode({ networkError, graphQLErrors }) {
|
||||||
if (graphQLErrors) {
|
if (graphQLErrors) {
|
||||||
let code;
|
let code;
|
||||||
for (const err of graphQLErrors) {
|
for (const err of graphQLErrors) {
|
||||||
if (err.extensions.http) {
|
if (err.extensions?.http) {
|
||||||
code = err.extensions.http;
|
code = err.extensions.http;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (err.extensions.statusCode) {
|
if (err.extensions?.statusCode) {
|
||||||
code = err.extensions.statusCode;
|
code = err.extensions.statusCode;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (err.extensions.code) {
|
if (err.extensions?.code) {
|
||||||
code = err.extensions.code;
|
code = err.extensions.code;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ const getReleaseVersion = () => {
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: env.SENTRY_DSN,
|
dsn: env.SENTRY_DSN,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 0.1,
|
||||||
debug: __DEV__,
|
debug: __DEV__,
|
||||||
// Configure release to match ios-archive.sh format
|
// Configure release to match ios-archive.sh format
|
||||||
release: getReleaseVersion(),
|
release: getReleaseVersion(),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
import { getAsyncStorageKeys } from "./storageKeys";
|
||||||
|
|
||||||
const storageLogger = createLogger({
|
const storageLogger = createLogger({
|
||||||
module: SYSTEM_SCOPES.STORAGE,
|
module: SYSTEM_SCOPES.STORAGE,
|
||||||
|
@ -29,15 +30,8 @@ export const memoryAsyncStorage = {
|
||||||
|
|
||||||
storageLogger.info("Initializing memory async storage");
|
storageLogger.info("Initializing memory async storage");
|
||||||
|
|
||||||
// List of known keys that need to be cached
|
// Get all registered AsyncStorage keys from the registry
|
||||||
const knownKeys = [
|
const knownKeys = getAsyncStorageKeys();
|
||||||
"permission_wizard_completed",
|
|
||||||
"override_messages",
|
|
||||||
"last_known_location",
|
|
||||||
"eula_accepted",
|
|
||||||
"last_update_check",
|
|
||||||
"emulator_mode_enabled",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Load all known keys into memory
|
// Load all known keys into memory
|
||||||
for (const key of knownKeys) {
|
for (const key of knownKeys) {
|
||||||
|
@ -152,19 +146,20 @@ export const memoryAsyncStorage = {
|
||||||
storageLogger.debug("Set in memory cache", { key });
|
storageLogger.debug("Set in memory cache", { key });
|
||||||
|
|
||||||
// Try to persist to AsyncStorage
|
// Try to persist to AsyncStorage
|
||||||
try {
|
(async () => {
|
||||||
await AsyncStorage.setItem(key, value);
|
try {
|
||||||
storageLogger.debug("Persisted to AsyncStorage", { key });
|
await AsyncStorage.setItem(key, value);
|
||||||
} catch (error) {
|
storageLogger.debug("Persisted to AsyncStorage", { key });
|
||||||
storageLogger.warn(
|
} catch (error) {
|
||||||
"Failed to persist to AsyncStorage, kept in memory only",
|
storageLogger.warn(
|
||||||
{
|
"Failed to persist to AsyncStorage, kept in memory only",
|
||||||
key,
|
{
|
||||||
error: error.message,
|
key,
|
||||||
},
|
error: error.message,
|
||||||
);
|
},
|
||||||
// Continue - value is at least in memory
|
);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -178,16 +173,18 @@ export const memoryAsyncStorage = {
|
||||||
storageLogger.debug("Deleted from memory cache", { key });
|
storageLogger.debug("Deleted from memory cache", { key });
|
||||||
|
|
||||||
// Try to delete from AsyncStorage
|
// Try to delete from AsyncStorage
|
||||||
try {
|
(async () => {
|
||||||
await AsyncStorage.removeItem(key);
|
try {
|
||||||
storageLogger.debug("Deleted from AsyncStorage", { key });
|
await AsyncStorage.removeItem(key);
|
||||||
} catch (error) {
|
storageLogger.debug("Deleted from AsyncStorage", { key });
|
||||||
storageLogger.warn("Failed to delete from AsyncStorage", {
|
} catch (error) {
|
||||||
key,
|
storageLogger.warn("Failed to delete from AsyncStorage", {
|
||||||
error: error.message,
|
key,
|
||||||
});
|
error: error.message,
|
||||||
// Continue - at least removed from memory
|
});
|
||||||
}
|
// Continue - at least removed from memory
|
||||||
|
}
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -242,14 +239,16 @@ export const memoryAsyncStorage = {
|
||||||
storageLogger.info("Cleared memory cache");
|
storageLogger.info("Cleared memory cache");
|
||||||
|
|
||||||
// Try to clear AsyncStorage
|
// Try to clear AsyncStorage
|
||||||
try {
|
(async () => {
|
||||||
await AsyncStorage.clear();
|
try {
|
||||||
storageLogger.info("Cleared AsyncStorage");
|
await AsyncStorage.clear();
|
||||||
} catch (error) {
|
storageLogger.info("Cleared AsyncStorage");
|
||||||
storageLogger.warn("Failed to clear AsyncStorage", {
|
} catch (error) {
|
||||||
error: error.message,
|
storageLogger.warn("Failed to clear AsyncStorage", {
|
||||||
});
|
error: error.message,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -1,6 +1,7 @@
|
||||||
import { secureStore as originalSecureStore } from "./secureStore";
|
import { secureStore as originalSecureStore } from "./secureStore";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
import { getSecureStoreKeys } from "./storageKeys";
|
||||||
|
|
||||||
const storageLogger = createLogger({
|
const storageLogger = createLogger({
|
||||||
module: SYSTEM_SCOPES.STORAGE,
|
module: SYSTEM_SCOPES.STORAGE,
|
||||||
|
@ -29,16 +30,8 @@ export const memorySecureStore = {
|
||||||
|
|
||||||
storageLogger.info("Initializing memory secure store");
|
storageLogger.info("Initializing memory secure store");
|
||||||
|
|
||||||
// List of known keys that need to be cached
|
// Get all registered secure store keys from the registry
|
||||||
const knownKeys = [
|
const knownKeys = getSecureStoreKeys();
|
||||||
"deviceUuid",
|
|
||||||
"authToken",
|
|
||||||
"userToken",
|
|
||||||
"dev.authToken",
|
|
||||||
"dev.userToken",
|
|
||||||
"anon.authToken",
|
|
||||||
"anon.userToken",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Load all known keys into memory
|
// Load all known keys into memory
|
||||||
for (const key of knownKeys) {
|
for (const key of knownKeys) {
|
83
src/storage/storageKeys.js
Normal file
83
src/storage/storageKeys.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* Storage Keys Registry
|
||||||
|
*
|
||||||
|
* This file maintains a registry of all storage keys used throughout the application.
|
||||||
|
* By defining keys as constants here, they are automatically included in memory storage
|
||||||
|
* initialization, eliminating the need for manual maintenance of key lists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const secureStoreKeys = new Set();
|
||||||
|
const asyncStorageKeys = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a secure store key and return it as a constant
|
||||||
|
* @param {string} key - The storage key to register for secure store
|
||||||
|
* @returns {string} The same key, now registered for secure store
|
||||||
|
*/
|
||||||
|
export const registerSecureStoreKey = (key) => {
|
||||||
|
secureStoreKeys.add(key);
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an AsyncStorage key and return it as a constant
|
||||||
|
* @param {string} key - The storage key to register for AsyncStorage
|
||||||
|
* @returns {string} The same key, now registered for AsyncStorage
|
||||||
|
*/
|
||||||
|
export const registerAsyncStorageKey = (key) => {
|
||||||
|
asyncStorageKeys.add(key);
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all secure store keys
|
||||||
|
* @returns {string[]} Array of secure store keys
|
||||||
|
*/
|
||||||
|
export const getSecureStoreKeys = () => Array.from(secureStoreKeys);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all AsyncStorage keys
|
||||||
|
* @returns {string[]} Array of AsyncStorage keys
|
||||||
|
*/
|
||||||
|
export const getAsyncStorageKeys = () => Array.from(asyncStorageKeys);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered storage keys (both types)
|
||||||
|
* @returns {string[]} Array of all registered keys
|
||||||
|
*/
|
||||||
|
export const getAllRegisteredKeys = () => [
|
||||||
|
...Array.from(secureStoreKeys),
|
||||||
|
...Array.from(asyncStorageKeys),
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage key constants
|
||||||
|
* All storage keys used throughout the application should be defined here.
|
||||||
|
*/
|
||||||
|
export const STORAGE_KEYS = {
|
||||||
|
// Secure Store Keys - Authentication & Security
|
||||||
|
DEVICE_UUID: registerSecureStoreKey("deviceUuid"),
|
||||||
|
AUTH_TOKEN: registerSecureStoreKey("authToken"),
|
||||||
|
USER_TOKEN: registerSecureStoreKey("userToken"),
|
||||||
|
DEV_AUTH_TOKEN: registerSecureStoreKey("dev.authToken"),
|
||||||
|
DEV_USER_TOKEN: registerSecureStoreKey("dev.userToken"),
|
||||||
|
ANON_AUTH_TOKEN: registerSecureStoreKey("anon.authToken"),
|
||||||
|
ANON_USER_TOKEN: registerSecureStoreKey("anon.userToken"),
|
||||||
|
FCM_TOKEN_STORED: registerSecureStoreKey("fcmTokenStored"),
|
||||||
|
FCM_TOKEN_STORED_DEVICE_ID: registerSecureStoreKey("fcmTokenStoredDeviceId"),
|
||||||
|
ENV_IS_STAGING: registerSecureStoreKey("env.isStaging"),
|
||||||
|
|
||||||
|
// AsyncStorage Keys - App State & Preferences
|
||||||
|
GEOLOCATION_LAST_SYNC_TIME: registerAsyncStorageKey(
|
||||||
|
"@geolocation_last_sync_time",
|
||||||
|
),
|
||||||
|
EULA_ACCEPTED: registerAsyncStorageKey("@eula_accepted"),
|
||||||
|
OVERRIDE_MESSAGES: registerAsyncStorageKey("@override_messages"),
|
||||||
|
PERMISSION_WIZARD_COMPLETED: registerAsyncStorageKey(
|
||||||
|
"@permission_wizard_completed",
|
||||||
|
),
|
||||||
|
LAST_UPDATE_CHECK_TIME: registerAsyncStorageKey("lastUpdateCheckTime"),
|
||||||
|
LAST_KNOWN_LOCATION: registerAsyncStorageKey("@last_known_location"),
|
||||||
|
EULA_ACCEPTED_SIMPLE: registerAsyncStorageKey("eula_accepted"),
|
||||||
|
EMULATOR_MODE_ENABLED: registerAsyncStorageKey("emulator_mode_enabled"),
|
||||||
|
};
|
|
@ -1,8 +1,7 @@
|
||||||
import { createAtom } from "~/lib/atomic-zustand";
|
import { createAtom } from "~/lib/atomic-zustand";
|
||||||
import debounce from "lodash.debounce";
|
import debounce from "lodash.debounce";
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
const OVERRIDE_MESSAGES_STORAGE_KEY = "@override_messages";
|
|
||||||
|
|
||||||
export default createAtom(({ merge, set, get, reset }) => {
|
export default createAtom(({ merge, set, get, reset }) => {
|
||||||
const overrideMessagesCache = {};
|
const overrideMessagesCache = {};
|
||||||
|
@ -10,7 +9,7 @@ export default createAtom(({ merge, set, get, reset }) => {
|
||||||
const initCache = async () => {
|
const initCache = async () => {
|
||||||
try {
|
try {
|
||||||
const storedData = await AsyncStorage.getItem(
|
const storedData = await AsyncStorage.getItem(
|
||||||
OVERRIDE_MESSAGES_STORAGE_KEY,
|
STORAGE_KEYS.OVERRIDE_MESSAGES,
|
||||||
);
|
);
|
||||||
const storedMessages = storedData ? JSON.parse(storedData) : {};
|
const storedMessages = storedData ? JSON.parse(storedData) : {};
|
||||||
Object.entries(storedMessages).forEach(([messageId, data]) => {
|
Object.entries(storedMessages).forEach(([messageId, data]) => {
|
||||||
|
@ -24,7 +23,7 @@ export default createAtom(({ merge, set, get, reset }) => {
|
||||||
const saveOverrideMessagesToStorage = async () => {
|
const saveOverrideMessagesToStorage = async () => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem(
|
await AsyncStorage.setItem(
|
||||||
OVERRIDE_MESSAGES_STORAGE_KEY,
|
STORAGE_KEYS.OVERRIDE_MESSAGES,
|
||||||
JSON.stringify(overrideMessagesCache),
|
JSON.stringify(overrideMessagesCache),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { secureStore } from "~/lib/memorySecureStore";
|
import { secureStore } from "~/storage/memorySecureStore";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import jwtDecode from "jwt-decode";
|
import jwtDecode from "jwt-decode";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { FEATURE_SCOPES } from "~/lib/logger/scopes";
|
import { FEATURE_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
@ -10,13 +11,13 @@ import isExpired from "~/lib/time/isExpired";
|
||||||
import { registerUser, loginUserToken } from "~/auth/actions";
|
import { registerUser, loginUserToken } from "~/auth/actions";
|
||||||
|
|
||||||
// DEV
|
// DEV
|
||||||
// SecureStore.deleteItemAsync("userToken");
|
// SecureStore.deleteItemAsync(STORAGE_KEYS.USER_TOKEN);
|
||||||
// SecureStore.deleteItemAsync("authToken");
|
// SecureStore.deleteItemAsync(STORAGE_KEYS.AUTH_TOKEN);
|
||||||
// SecureStore.deleteItemAsync("dev.userToken");
|
// SecureStore.deleteItemAsync(STORAGE_KEYS.DEV_USER_TOKEN);
|
||||||
// SecureStore.deleteItemAsync("dev.authToken");
|
// SecureStore.deleteItemAsync(STORAGE_KEYS.DEV_AUTH_TOKEN);
|
||||||
// SecureStore.deleteItemAsync("anon.userToken");
|
// SecureStore.deleteItemAsync(STORAGE_KEYS.ANON_USER_TOKEN);
|
||||||
// SecureStore.deleteItemAsync("anon.authToken");
|
// SecureStore.deleteItemAsync(STORAGE_KEYS.ANON_AUTH_TOKEN);
|
||||||
// SecureStore.getItemAsync("userToken").then((t) => authLogger.debug("User token", { token: t }));
|
// SecureStore.getItemAsync(STORAGE_KEYS.USER_TOKEN).then((t) => authLogger.debug("User token", { token: t }));
|
||||||
|
|
||||||
const authLogger = createLogger({
|
const authLogger = createLogger({
|
||||||
module: FEATURE_SCOPES.AUTH,
|
module: FEATURE_SCOPES.AUTH,
|
||||||
|
@ -68,7 +69,7 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
authLogger.info("Attempting to login with auth token");
|
authLogger.info("Attempting to login with auth token");
|
||||||
const { userToken } = await loginUserToken({ authToken });
|
const { userToken } = await loginUserToken({ authToken });
|
||||||
authLogger.info("Successfully obtained user token");
|
authLogger.info("Successfully obtained user token");
|
||||||
await secureStore.setItemAsync("userToken", userToken);
|
await secureStore.setItemAsync(STORAGE_KEYS.USER_TOKEN, userToken);
|
||||||
endLoading({
|
endLoading({
|
||||||
userToken,
|
userToken,
|
||||||
});
|
});
|
||||||
|
@ -81,8 +82,8 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
"Auth token expired, clearing tokens and reinitializing",
|
"Auth token expired, clearing tokens and reinitializing",
|
||||||
);
|
);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.deleteItemAsync("authToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.AUTH_TOKEN),
|
||||||
secureStore.deleteItemAsync("userToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
return init();
|
return init();
|
||||||
}
|
}
|
||||||
|
@ -93,8 +94,8 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
authLogger.debug("Initializing auth state");
|
authLogger.debug("Initializing auth state");
|
||||||
let { userToken, authToken } = await promiseObject({
|
let { userToken, authToken } = await promiseObject({
|
||||||
userToken: secureStore.getItemAsync("userToken"),
|
userToken: secureStore.getItemAsync(STORAGE_KEYS.USER_TOKEN),
|
||||||
authToken: secureStore.getItemAsync("authToken"),
|
authToken: secureStore.getItemAsync(STORAGE_KEYS.AUTH_TOKEN),
|
||||||
});
|
});
|
||||||
// await delay(5);
|
// await delay(5);
|
||||||
// authLogger.debug("Auth tokens", { userToken, authToken });
|
// authLogger.debug("Auth tokens", { userToken, authToken });
|
||||||
|
@ -121,7 +122,7 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
const res = await registerUser();
|
const res = await registerUser();
|
||||||
authLogger.info("Successfully registered new user");
|
authLogger.info("Successfully registered new user");
|
||||||
authToken = res.authToken;
|
authToken = res.authToken;
|
||||||
await secureStore.setItemAsync("authToken", authToken);
|
await secureStore.setItemAsync(STORAGE_KEYS.AUTH_TOKEN, authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userToken && authToken) {
|
if (!userToken && authToken) {
|
||||||
|
@ -165,7 +166,7 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
startLoading();
|
startLoading();
|
||||||
|
|
||||||
authLogger.debug("Deleting userToken for refresh");
|
authLogger.debug("Deleting userToken for refresh");
|
||||||
await secureStore.deleteItemAsync("userToken");
|
await secureStore.deleteItemAsync(STORAGE_KEYS.USER_TOKEN);
|
||||||
|
|
||||||
await init();
|
await init();
|
||||||
return true;
|
return true;
|
||||||
|
@ -183,7 +184,7 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
const { onReloadAuthToken: authToken } = get();
|
const { onReloadAuthToken: authToken } = get();
|
||||||
|
|
||||||
if (authToken) {
|
if (authToken) {
|
||||||
await secureStore.setItemAsync("authToken", authToken);
|
await secureStore.setItemAsync(STORAGE_KEYS.AUTH_TOKEN, authToken);
|
||||||
await loadUserJWT(authToken);
|
await loadUserJWT(authToken);
|
||||||
} else {
|
} else {
|
||||||
await init();
|
await init();
|
||||||
|
@ -204,12 +205,12 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
// backup anon tokens
|
// backup anon tokens
|
||||||
const [anonAuthToken, anonUserToken] = await Promise.all([
|
const [anonAuthToken, anonUserToken] = await Promise.all([
|
||||||
secureStore.getItemAsync("authToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.AUTH_TOKEN),
|
||||||
secureStore.getItemAsync("userToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.setItemAsync("anon.authToken", anonAuthToken),
|
secureStore.setItemAsync(STORAGE_KEYS.ANON_AUTH_TOKEN, anonAuthToken),
|
||||||
secureStore.setItemAsync("anon.userToken", anonUserToken),
|
secureStore.setItemAsync(STORAGE_KEYS.ANON_USER_TOKEN, anonUserToken),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
merge({ onReloadAuthToken: authTokenJwt });
|
merge({ onReloadAuthToken: authTokenJwt });
|
||||||
|
@ -219,12 +220,12 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
const impersonate = async ({ authTokenJwt }) => {
|
const impersonate = async ({ authTokenJwt }) => {
|
||||||
authLogger.info("Starting impersonation");
|
authLogger.info("Starting impersonation");
|
||||||
const [anonAuthToken, anonUserToken] = await Promise.all([
|
const [anonAuthToken, anonUserToken] = await Promise.all([
|
||||||
secureStore.getItemAsync("authToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.AUTH_TOKEN),
|
||||||
secureStore.getItemAsync("userToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.setItemAsync("dev.authToken", anonAuthToken),
|
secureStore.setItemAsync(STORAGE_KEYS.DEV_AUTH_TOKEN, anonAuthToken),
|
||||||
secureStore.setItemAsync("dev.userToken", anonUserToken),
|
secureStore.setItemAsync(STORAGE_KEYS.DEV_USER_TOKEN, anonUserToken),
|
||||||
]);
|
]);
|
||||||
merge({ onReloadAuthToken: authTokenJwt });
|
merge({ onReloadAuthToken: authTokenJwt });
|
||||||
triggerReload();
|
triggerReload();
|
||||||
|
@ -234,29 +235,29 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
authLogger.info("Initiating logout");
|
authLogger.info("Initiating logout");
|
||||||
const [devAuthToken, devUserToken, anonAuthToken, anonUserToken] =
|
const [devAuthToken, devUserToken, anonAuthToken, anonUserToken] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.getItemAsync("dev.authToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.DEV_AUTH_TOKEN),
|
||||||
secureStore.getItemAsync("dev.userToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.DEV_USER_TOKEN),
|
||||||
secureStore.getItemAsync("anon.authToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.ANON_AUTH_TOKEN),
|
||||||
secureStore.getItemAsync("anon.userToken"),
|
secureStore.getItemAsync(STORAGE_KEYS.ANON_USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
if (devAuthToken && devUserToken) {
|
if (devAuthToken && devUserToken) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.setItemAsync("authToken", devAuthToken),
|
secureStore.setItemAsync(STORAGE_KEYS.AUTH_TOKEN, devAuthToken),
|
||||||
secureStore.setItemAsync("userToken", devUserToken),
|
secureStore.setItemAsync(STORAGE_KEYS.USER_TOKEN, devUserToken),
|
||||||
secureStore.deleteItemAsync("dev.authToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.DEV_AUTH_TOKEN),
|
||||||
secureStore.deleteItemAsync("dev.userToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.DEV_USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
} else if (anonAuthToken && anonUserToken) {
|
} else if (anonAuthToken && anonUserToken) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.setItemAsync("authToken", anonAuthToken),
|
secureStore.setItemAsync(STORAGE_KEYS.AUTH_TOKEN, anonAuthToken),
|
||||||
secureStore.setItemAsync("userToken", anonUserToken),
|
secureStore.setItemAsync(STORAGE_KEYS.USER_TOKEN, anonUserToken),
|
||||||
secureStore.deleteItemAsync("anon.authToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.ANON_AUTH_TOKEN),
|
||||||
secureStore.deleteItemAsync("anon.userToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.ANON_USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
secureStore.deleteItemAsync("authToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.AUTH_TOKEN),
|
||||||
secureStore.deleteItemAsync("userToken"),
|
secureStore.deleteItemAsync(STORAGE_KEYS.USER_TOKEN),
|
||||||
]);
|
]);
|
||||||
merge({
|
merge({
|
||||||
userOffMode: true,
|
userOffMode: true,
|
||||||
|
@ -275,6 +276,31 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
triggerReload();
|
triggerReload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setUserToken = async (userToken) => {
|
||||||
|
authLogger.info("Setting user token", {
|
||||||
|
hasToken: !!userToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update secure storage
|
||||||
|
await secureStore.setItemAsync(STORAGE_KEYS.USER_TOKEN, userToken);
|
||||||
|
|
||||||
|
// Update in-memory state
|
||||||
|
merge({ userToken });
|
||||||
|
|
||||||
|
// Update session from JWT
|
||||||
|
if (userToken) {
|
||||||
|
const jwtData = jwtDecode(userToken);
|
||||||
|
sessionActions.loadSessionFromJWT(jwtData);
|
||||||
|
}
|
||||||
|
|
||||||
|
authLogger.debug("User token updated successfully");
|
||||||
|
} catch (error) {
|
||||||
|
authLogger.error("Failed to set user token", { error: error.message });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
userToken: null,
|
userToken: null,
|
||||||
|
@ -294,6 +320,7 @@ export default createAtom(({ get, merge, getActions }) => {
|
||||||
logout,
|
logout,
|
||||||
onReload,
|
onReload,
|
||||||
userOnMode,
|
userOnMode,
|
||||||
|
setUserToken,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createAtom } from "~/lib/atomic-zustand";
|
import { createAtom } from "~/lib/atomic-zustand";
|
||||||
import { secureStore } from "~/lib/secureStore";
|
import { secureStore } from "~/storage/memorySecureStore";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
|
|
||||||
export default createAtom(({ merge, reset }) => {
|
export default createAtom(({ merge, reset }) => {
|
||||||
const setFcmToken = (token) => {
|
const setFcmToken = (token) => {
|
||||||
|
@ -9,8 +10,11 @@ export default createAtom(({ merge, reset }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const setFcmTokenStored = ({ fcmToken, deviceId }) => {
|
const setFcmTokenStored = ({ fcmToken, deviceId }) => {
|
||||||
secureStore.setItemAsync("fcmTokenStored", fcmToken);
|
secureStore.setItemAsync(STORAGE_KEYS.FCM_TOKEN_STORED, fcmToken);
|
||||||
secureStore.setItemAsync("fcmTokenStoredDeviceId", deviceId.toString());
|
secureStore.setItemAsync(
|
||||||
|
STORAGE_KEYS.FCM_TOKEN_STORED_DEVICE_ID,
|
||||||
|
deviceId.toString(),
|
||||||
|
);
|
||||||
merge({
|
merge({
|
||||||
fcmTokenStored: fcmToken,
|
fcmTokenStored: fcmToken,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
@ -18,9 +22,11 @@ export default createAtom(({ merge, reset }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const fcmTokenStored = await secureStore.getItemAsync("fcmTokenStored");
|
const fcmTokenStored = await secureStore.getItemAsync(
|
||||||
|
STORAGE_KEYS.FCM_TOKEN_STORED,
|
||||||
|
);
|
||||||
const fcmTokenStoredDeviceId = await secureStore.getItemAsync(
|
const fcmTokenStoredDeviceId = await secureStore.getItemAsync(
|
||||||
"fcmTokenStoredDeviceId",
|
STORAGE_KEYS.FCM_TOKEN_STORED_DEVICE_ID,
|
||||||
);
|
);
|
||||||
const deviceId = fcmTokenStoredDeviceId
|
const deviceId = fcmTokenStoredDeviceId
|
||||||
? parseInt(fcmTokenStoredDeviceId, 10)
|
? parseInt(fcmTokenStoredDeviceId, 10)
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default createAtom(({ get, merge, reset }) => {
|
||||||
...m,
|
...m,
|
||||||
routeName,
|
routeName,
|
||||||
});
|
});
|
||||||
navLogger.info("Route updated", { routeName });
|
navLogger.debug("Route updated", { routeName });
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
|
@ -78,11 +78,11 @@ export default createAtom(({ get, merge, reset }) => {
|
||||||
default: initialValues,
|
default: initialValues,
|
||||||
actions: {
|
actions: {
|
||||||
reset: () => {
|
reset: () => {
|
||||||
navLogger.info("Resetting navigation state to initial values");
|
navLogger.debug("Resetting navigation state to initial values");
|
||||||
reset();
|
reset();
|
||||||
},
|
},
|
||||||
updateRouteFromRootStack: (state) => {
|
updateRouteFromRootStack: (state) => {
|
||||||
navLogger.info("Updating route from root stack", { state });
|
navLogger.debug("Updating route from root stack", { state });
|
||||||
const { index, routeNames } = state;
|
const { index, routeNames } = state;
|
||||||
const rootRouteName = routeNames[index];
|
const rootRouteName = routeNames[index];
|
||||||
updateRoute({
|
updateRoute({
|
||||||
|
@ -90,7 +90,7 @@ export default createAtom(({ get, merge, reset }) => {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateRouteFromDrawer: (state) => {
|
updateRouteFromDrawer: (state) => {
|
||||||
navLogger.info("Updating route from drawer", { state });
|
navLogger.debug("Updating route from drawer", { state });
|
||||||
const { index, routeNames } = state;
|
const { index, routeNames } = state;
|
||||||
const drawerRouteName = routeNames[index];
|
const drawerRouteName = routeNames[index];
|
||||||
updateRoute({
|
updateRoute({
|
||||||
|
@ -98,7 +98,7 @@ export default createAtom(({ get, merge, reset }) => {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateRouteFromMain: (state) => {
|
updateRouteFromMain: (state) => {
|
||||||
navLogger.info("Updating route from main", { state });
|
navLogger.debug("Updating route from main", { state });
|
||||||
const { index, routeNames } = state;
|
const { index, routeNames } = state;
|
||||||
const mainRouteName = routeNames[index];
|
const mainRouteName = routeNames[index];
|
||||||
updateRoute({
|
updateRoute({
|
||||||
|
@ -106,13 +106,13 @@ export default createAtom(({ get, merge, reset }) => {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setNextNavigation: (nextNavigation) => {
|
setNextNavigation: (nextNavigation) => {
|
||||||
navLogger.info("Setting next navigation", { nextNavigation });
|
navLogger.debug("Setting next navigation", { nextNavigation });
|
||||||
merge({
|
merge({
|
||||||
nextNavigation,
|
nextNavigation,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setMessageViewFocus: (isFocused, alertId = null) => {
|
setMessageViewFocus: (isFocused, alertId = null) => {
|
||||||
navLogger.info("Setting message view focus", { isFocused, alertId });
|
navLogger.debug("Setting message view focus", { isFocused, alertId });
|
||||||
merge({
|
merge({
|
||||||
isOnMessageView: isFocused,
|
isOnMessageView: isFocused,
|
||||||
currentMessageAlertId: isFocused ? alertId : null,
|
currentMessageAlertId: isFocused ? alertId : null,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { createAtom } from "~/lib/atomic-zustand";
|
import { createAtom } from "~/lib/atomic-zustand";
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
const WIZARD_COMPLETED_KEY = "@permission_wizard_completed";
|
|
||||||
|
|
||||||
export default createAtom(({ set, get }) => {
|
export default createAtom(({ set, get }) => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
try {
|
try {
|
||||||
const wizardCompleted = await AsyncStorage.getItem(WIZARD_COMPLETED_KEY);
|
const wizardCompleted = await AsyncStorage.getItem(
|
||||||
|
STORAGE_KEYS.PERMISSION_WIZARD_COMPLETED,
|
||||||
|
);
|
||||||
if (wizardCompleted === "true") {
|
if (wizardCompleted === "true") {
|
||||||
set("completed", true);
|
set("completed", true);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +28,10 @@ export default createAtom(({ set, get }) => {
|
||||||
setCompleted: (completed) => {
|
setCompleted: (completed) => {
|
||||||
set("completed", completed);
|
set("completed", completed);
|
||||||
if (completed) {
|
if (completed) {
|
||||||
AsyncStorage.setItem(WIZARD_COMPLETED_KEY, "true").catch((error) => {
|
AsyncStorage.setItem(
|
||||||
|
STORAGE_KEYS.PERMISSION_WIZARD_COMPLETED,
|
||||||
|
"true",
|
||||||
|
).catch((error) => {
|
||||||
console.error("Error saving permission wizard status:", error);
|
console.error("Error saving permission wizard status:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Alert } from "react-native";
|
import { Alert } from "react-native";
|
||||||
import * as Updates from "expo-updates";
|
import * as Updates from "expo-updates";
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import useNow from "~/hooks/useNow";
|
import useNow from "~/hooks/useNow";
|
||||||
import * as Sentry from "@sentry/react-native";
|
import * as Sentry from "@sentry/react-native";
|
||||||
|
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import { treeActions } from "~/stores";
|
import { treeActions } from "~/stores";
|
||||||
|
|
||||||
const LAST_UPDATE_CHECK_KEY = "lastUpdateCheckTime";
|
|
||||||
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
const applyUpdate = async () => {
|
const applyUpdate = async () => {
|
||||||
|
@ -28,12 +27,17 @@ const checkForUpdate = async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const lastCheckString = await AsyncStorage.getItem(LAST_UPDATE_CHECK_KEY);
|
const lastCheckString = await AsyncStorage.getItem(
|
||||||
|
STORAGE_KEYS.LAST_UPDATE_CHECK_TIME,
|
||||||
|
);
|
||||||
const lastCheck = lastCheckString ? new Date(lastCheckString) : null;
|
const lastCheck = lastCheckString ? new Date(lastCheckString) : null;
|
||||||
const nowDate = new Date();
|
const nowDate = new Date();
|
||||||
|
|
||||||
if (!lastCheck || nowDate - lastCheck > UPDATE_CHECK_INTERVAL) {
|
if (!lastCheck || nowDate - lastCheck > UPDATE_CHECK_INTERVAL) {
|
||||||
await AsyncStorage.setItem(LAST_UPDATE_CHECK_KEY, nowDate.toISOString());
|
await AsyncStorage.setItem(
|
||||||
|
STORAGE_KEYS.LAST_UPDATE_CHECK_TIME,
|
||||||
|
nowDate.toISOString(),
|
||||||
|
);
|
||||||
|
|
||||||
const update = await Updates.checkForUpdateAsync();
|
const update = await Updates.checkForUpdateAsync();
|
||||||
if (!update.isAvailable) {
|
if (!update.isAvailable) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import AsyncStorage from "~/lib/memoryAsyncStorage";
|
import AsyncStorage from "~/storage/memoryAsyncStorage";
|
||||||
|
import { STORAGE_KEYS } from "~/storage/storageKeys";
|
||||||
import { createLogger } from "~/lib/logger";
|
import { createLogger } from "~/lib/logger";
|
||||||
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
|
||||||
|
@ -7,8 +8,6 @@ const storageLogger = createLogger({
|
||||||
feature: "location-cache",
|
feature: "location-cache",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LOCATION_STORAGE_KEY = "@last_known_location";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores location data in AsyncStorage with timestamp
|
* Stores location data in AsyncStorage with timestamp
|
||||||
* @param {Object} coords - Location coordinates object
|
* @param {Object} coords - Location coordinates object
|
||||||
|
@ -36,7 +35,7 @@ export async function storeLocation(
|
||||||
});
|
});
|
||||||
|
|
||||||
await AsyncStorage.setItem(
|
await AsyncStorage.setItem(
|
||||||
LOCATION_STORAGE_KEY,
|
STORAGE_KEYS.LAST_KNOWN_LOCATION,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
coords,
|
coords,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
@ -58,7 +57,7 @@ export async function storeLocation(
|
||||||
export async function getStoredLocation() {
|
export async function getStoredLocation() {
|
||||||
try {
|
try {
|
||||||
storageLogger.debug("Retrieving stored location data");
|
storageLogger.debug("Retrieving stored location data");
|
||||||
const stored = await AsyncStorage.getItem(LOCATION_STORAGE_KEY);
|
const stored = await AsyncStorage.getItem(STORAGE_KEYS.LAST_KNOWN_LOCATION);
|
||||||
|
|
||||||
if (!stored) {
|
if (!stored) {
|
||||||
storageLogger.debug("No stored location data found");
|
storageLogger.debug("No stored location data found");
|
||||||
|
|
Loading…
Add table
Reference in a new issue