chore: add location debug helpers

This commit is contained in:
devthejo 2025-05-04 12:39:03 +02:00
parent 40b27bff29
commit 5398f94f49
3 changed files with 200 additions and 1 deletions

View file

@ -0,0 +1,110 @@
import BackgroundGeolocation from "react-native-background-geolocation";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createLogger } from "~/lib/logger";
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
const EMULATOR_MODE_KEY = "emulator_mode_enabled";
// Global variables
let emulatorIntervalId = null;
let isEmulatorModeEnabled = false;
// Create a logger for the emulator service
const emulatorLogger = createLogger({
module: BACKGROUND_SCOPES.GEOLOCATION,
feature: "emulator",
});
// Initialize emulator mode based on stored preference
export const initEmulatorMode = async () => {
try {
const storedValue = await AsyncStorage.getItem(EMULATOR_MODE_KEY);
emulatorLogger.debug("Initializing emulator mode", { storedValue });
if (storedValue === "true") {
await enableEmulatorMode();
}
} catch (error) {
emulatorLogger.error("Failed to initialize emulator mode", {
error: error.message,
stack: error.stack,
});
}
};
// Enable emulator mode
export const enableEmulatorMode = async () => {
emulatorLogger.info("Enabling emulator mode");
// Clear existing interval if any
if (emulatorIntervalId) {
clearInterval(emulatorIntervalId);
}
try {
// Call immediately once
await BackgroundGeolocation.changePace(true);
emulatorLogger.debug("Initial changePace call successful");
// Then set up interval
emulatorIntervalId = setInterval(
() => {
BackgroundGeolocation.changePace(true);
emulatorLogger.debug("Interval changePace call executed");
},
30 * 60 * 1000,
); // 30 minutes
isEmulatorModeEnabled = true;
// Persist the setting
await AsyncStorage.setItem(EMULATOR_MODE_KEY, "true");
emulatorLogger.debug("Emulator mode setting saved");
} catch (error) {
emulatorLogger.error("Failed to enable emulator mode", {
error: error.message,
stack: error.stack,
});
}
};
// Disable emulator mode
export const disableEmulatorMode = async () => {
emulatorLogger.info("Disabling emulator mode");
if (emulatorIntervalId) {
clearInterval(emulatorIntervalId);
emulatorIntervalId = null;
}
isEmulatorModeEnabled = false;
// Persist the setting
try {
await AsyncStorage.setItem(EMULATOR_MODE_KEY, "false");
emulatorLogger.debug("Emulator mode setting saved");
} catch (error) {
emulatorLogger.error("Failed to save emulator mode setting", {
error: error.message,
stack: error.stack,
});
}
};
// Get current emulator mode state
export const getEmulatorModeState = () => {
return isEmulatorModeEnabled;
};
// Toggle emulator mode
export const toggleEmulatorMode = async (enabled) => {
emulatorLogger.info("Toggling emulator mode", { enabled });
if (enabled) {
await enableEmulatorMode();
} else {
await disableEmulatorMode();
}
return isEmulatorModeEnabled;
};

View file

@ -3,6 +3,7 @@ import { TRACK_MOVE } from "~/misc/devicePrefs";
import { createLogger } from "~/lib/logger"; 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 { import {
getAuthState, getAuthState,
@ -271,4 +272,7 @@ export default async function trackLocation() {
// Check for pending records after a short delay to ensure everything is initialized // Check for pending records after a short delay to ensure everything is initialized
setTimeout(checkPendingRecords, 5000); setTimeout(checkPendingRecords, 5000);
// Initialize emulator mode if previously enabled
initEmulatorMode();
} }

View file

@ -1,6 +1,7 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { View, StyleSheet, ScrollView } from "react-native"; import { View, StyleSheet, ScrollView } from "react-native";
import * as Sentry from "@sentry/react-native"; import * as Sentry from "@sentry/react-native";
import BackgroundGeolocation from "react-native-background-geolocation";
import { import {
Button, Button,
Card, Card,
@ -12,6 +13,11 @@ import {
import { createStyles } from "~/theme"; import { createStyles } from "~/theme";
import env, { setStaging } from "~/env"; import env, { setStaging } from "~/env";
import { authActions } from "~/stores"; import { authActions } from "~/stores";
import {
getEmulatorModeState,
toggleEmulatorMode as toggleEmulatorModeService,
initEmulatorMode,
} from "~/location/emulatorService";
const reset = async () => { const reset = async () => {
await authActions.logout(); await authActions.logout();
@ -29,7 +35,50 @@ const Section = ({ title, children }) => {
export default function Developer() { export default function Developer() {
const styles = useStyles(); const styles = useStyles();
const { colors } = useTheme();
const [isStaging, setIsStaging] = useState(env.IS_STAGING); const [isStaging, setIsStaging] = useState(env.IS_STAGING);
const [emulatorMode, setEmulatorMode] = useState(false);
const [syncStatus, setSyncStatus] = useState(null); // null, 'syncing', 'success', 'error'
const [syncResult, setSyncResult] = useState("");
// Initialize emulator mode when component mounts
useEffect(() => {
// Initialize the emulator service
initEmulatorMode();
// Set the initial state based on the global service
setEmulatorMode(getEmulatorModeState());
}, []);
// Handle toggling emulator mode
const handleEmulatorModeToggle = async (enabled) => {
const newState = await toggleEmulatorModeService(enabled);
setEmulatorMode(newState);
};
// Function to trigger geolocation sync
const triggerGeolocSync = async () => {
try {
setSyncStatus("syncing");
setSyncResult("");
// Get the count of pending records first
const count = await BackgroundGeolocation.getCount();
// Perform the sync
const records = await BackgroundGeolocation.sync();
const result = `Synced ${
records?.length || 0
} records (${count} pending)`;
setSyncResult(result);
setSyncStatus("success");
} catch (error) {
console.error("Geolocation sync failed:", error);
setSyncResult(`Sync failed: ${error.message}`);
setSyncStatus("error");
}
};
const triggerNullError = () => { const triggerNullError = () => {
try { try {
// Wrap the null error in try-catch // Wrap the null error in try-catch
@ -101,6 +150,13 @@ export default function Developer() {
}} }}
/> />
</View> </View>
<View style={styles.settingRow}>
<Text variant="bodyLarge">Emulator Mode</Text>
<Switch
value={emulatorMode}
onValueChange={handleEmulatorModeToggle}
/>
</View>
</Section> </Section>
<Section title="Environment URLs"> <Section title="Environment URLs">
@ -150,6 +206,35 @@ export default function Developer() {
<Divider style={styles.divider} /> <Divider style={styles.divider} />
<Section title="Location Controls">
<Button
onPress={triggerGeolocSync}
style={styles.button}
contentStyle={styles.buttonContent}
labelStyle={styles.buttonLabel}
loading={syncStatus === "syncing"}
disabled={syncStatus === "syncing"}
>
Trigger Geolocation Sync
</Button>
{syncStatus && syncResult && (
<Text
style={[
styles.statusText,
{
color: syncStatus === "success" ? colors.primary : colors.error,
marginTop: 8,
},
]}
>
{syncResult}
</Text>
)}
</Section>
<Divider style={styles.divider} />
<Section title="Error Testing"> <Section title="Error Testing">
<Button <Button
onPress={triggerNullError} onPress={triggerNullError}