feat: enhance location and orientation tracking with detailed debug logging
All checks were successful
Migrate supabase / migrate (push) Successful in 15s

This commit is contained in:
2026-03-08 18:15:08 +01:00
parent 811ac1964e
commit 5fc1093a79

View File

@@ -33,11 +33,28 @@
let userLat = $state<number | null>(null); let userLat = $state<number | null>(null);
let userLon = $state<number | null>(null); let userLon = $state<number | null>(null);
let heading = $state<number | null>(null); let heading = $state<number | null>(null);
let watchId: number | null = null; let watchId = $state<number | null>(null);
let locationError = $state<string | null>(null); let locationError = $state<string | null>(null);
let locationPermission = $state<'prompt' | 'granted' | 'denied' | 'checking'>('prompt'); let locationPermission = $state<'prompt' | 'granted' | 'denied' | 'checking'>('prompt');
let distance = $state<number | null>(null); let distance = $state<number | null>(null);
let arrowRotation = $state<number>(0); let arrowRotation = $state<number>(0);
type DebugLogEntry = {
ts: string;
event: string;
details?: Record<string, unknown>;
};
let debugLogs = $state<DebugLogEntry[]>([]);
let lastOrientationEvent = $state<string | null>(null);
function addDebug(event: string, details?: Record<string, unknown>) {
const entry: DebugLogEntry = {
ts: new Date().toISOString(),
event,
details
};
debugLogs = [entry, ...debugLogs].slice(0, 80);
console.log('[location-debug]', event, details ?? {});
}
// Calculate distance between two coordinates in meters (Haversine formula) // Calculate distance between two coordinates in meters (Haversine formula)
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number { function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
@@ -95,17 +112,31 @@
// If we have device heading, rotate relative to it; otherwise just use absolute bearing // If we have device heading, rotate relative to it; otherwise just use absolute bearing
arrowRotation = heading !== null ? bearing - heading : bearing; arrowRotation = heading !== null ? bearing - heading : bearing;
addDebug('computed_distance_bearing', {
distance,
bearing,
heading,
arrowRotation
});
} else { } else {
distance = null; distance = null;
arrowRotation = 0; arrowRotation = 0;
addDebug('reset_distance_rotation', {
stepType: currentStep?.type ?? null,
isCurrentActiveStep,
hasUserCoords: userLat !== null && userLon !== null,
hasStepCoords: currentStep?.latitude != null && currentStep?.longitude != null
});
} }
}); });
// Check and request location permission // Check and request location permission
async function checkLocationPermission() { async function checkLocationPermission() {
addDebug('check_location_permission_start');
if (!('geolocation' in navigator)) { if (!('geolocation' in navigator)) {
locationError = 'Geolocation is not supported by your device'; locationError = 'Geolocation is not supported by your device';
locationPermission = 'denied'; locationPermission = 'denied';
addDebug('check_location_permission_no_geolocation');
return false; return false;
} }
@@ -114,25 +145,37 @@
try { try {
const result = await navigator.permissions.query({ name: 'geolocation' }); const result = await navigator.permissions.query({ name: 'geolocation' });
locationPermission = result.state as 'granted' | 'denied' | 'prompt'; locationPermission = result.state as 'granted' | 'denied' | 'prompt';
addDebug('check_location_permission_result', {
permission: result.state
});
// Listen for permission changes // Listen for permission changes
result.addEventListener('change', () => { result.addEventListener('change', () => {
locationPermission = result.state as 'granted' | 'denied' | 'prompt'; locationPermission = result.state as 'granted' | 'denied' | 'prompt';
addDebug('check_location_permission_changed', {
permission: result.state
});
}); });
return result.state === 'granted'; return result.state === 'granted';
} catch (e) { } catch (e) {
// Some browsers don't support permissions API for geolocation // Some browsers don't support permissions API for geolocation
console.log('Permissions API not fully supported', e); console.log('Permissions API not fully supported', e);
addDebug('check_location_permission_api_error', {
error: e instanceof Error ? e.message : String(e)
});
} }
} }
addDebug('check_location_permission_fallback_prompt');
return false; return false;
} }
// Request device orientation permission (iOS 13+) // Request device orientation permission (iOS 13+)
async function requestOrientationPermission() { async function requestOrientationPermission() {
addDebug('request_orientation_permission_start');
if (typeof DeviceOrientationEvent === 'undefined') { if (typeof DeviceOrientationEvent === 'undefined') {
addDebug('request_orientation_permission_no_api');
return false; return false;
} }
@@ -144,19 +187,26 @@
if (typeof DeviceOrientationEventTyped.requestPermission === 'function') { if (typeof DeviceOrientationEventTyped.requestPermission === 'function') {
try { try {
const permission = await DeviceOrientationEventTyped.requestPermission(); const permission = await DeviceOrientationEventTyped.requestPermission();
addDebug('request_orientation_permission_result', { permission });
return permission === 'granted'; return permission === 'granted';
} catch (error) { } catch (error) {
console.error('Error requesting orientation permission:', error); console.error('Error requesting orientation permission:', error);
addDebug('request_orientation_permission_error', {
error: error instanceof Error ? error.message : String(error)
});
return false; return false;
} }
} else { } else {
// Permission not required on this device // Permission not required on this device
addDebug('request_orientation_permission_not_required');
return true; return true;
} }
} }
function startLocationTracking() { function startLocationTracking() {
addDebug('start_location_tracking_called');
if (!('geolocation' in navigator)) { if (!('geolocation' in navigator)) {
addDebug('start_location_tracking_no_geolocation');
return; return;
} }
@@ -169,6 +219,13 @@
userLon = position.coords.longitude; userLon = position.coords.longitude;
locationError = null; locationError = null;
locationPermission = 'granted'; locationPermission = 'granted';
addDebug('watch_position_success', {
lat: userLat,
lon: userLon,
accuracy: position.coords.accuracy,
speed: position.coords.speed,
headFromGps: position.coords.heading
});
// Try to get device heading if available from GPS // Try to get device heading if available from GPS
if (position.coords.heading !== null && position.coords.heading >= 0) { if (position.coords.heading !== null && position.coords.heading >= 0) {
@@ -177,6 +234,10 @@
}, },
(error) => { (error) => {
console.error('Geolocation error:', error); console.error('Geolocation error:', error);
addDebug('watch_position_error', {
code: error.code,
message: error.message
});
if (error.code === error.PERMISSION_DENIED) { if (error.code === error.PERMISSION_DENIED) {
locationError = 'Location access was denied. Please enable location in your browser settings.'; locationError = 'Location access was denied. Please enable location in your browser settings.';
@@ -198,13 +259,19 @@
timeout: 10000 timeout: 10000
} }
); );
addDebug('watch_position_started', {
watchId
});
} }
async function startOrientationTracking() { async function startOrientationTracking() {
addDebug('start_orientation_tracking_called');
// Request permission first // Request permission first
const hasPermission = await requestOrientationPermission(); const hasPermission = await requestOrientationPermission();
if (!hasPermission) { if (!hasPermission) {
console.log('Orientation permission denied or not available'); console.log('Orientation permission denied or not available');
addDebug('start_orientation_tracking_permission_denied');
return; return;
} }
@@ -212,31 +279,51 @@
if (typeof DeviceOrientationEvent !== 'undefined') { if (typeof DeviceOrientationEvent !== 'undefined') {
const hasAbsoluteOrientation = 'ondeviceorientationabsolute' in window; const hasAbsoluteOrientation = 'ondeviceorientationabsolute' in window;
const hasOrientation = 'ondeviceorientation' in window; const hasOrientation = 'ondeviceorientation' in window;
addDebug('start_orientation_tracking_capabilities', {
hasAbsoluteOrientation,
hasOrientation
});
if (hasAbsoluteOrientation) { if (hasAbsoluteOrientation) {
window.addEventListener('deviceorientationabsolute', handleOrientation as EventListener); window.addEventListener('deviceorientationabsolute', handleOrientation as EventListener);
lastOrientationEvent = 'deviceorientationabsolute';
addDebug('start_orientation_tracking_listener_added', {
event: 'deviceorientationabsolute'
});
} else if (hasOrientation) { } else if (hasOrientation) {
window.addEventListener('deviceorientation', handleOrientation as EventListener); window.addEventListener('deviceorientation', handleOrientation as EventListener);
lastOrientationEvent = 'deviceorientation';
addDebug('start_orientation_tracking_listener_added', {
event: 'deviceorientation'
});
} }
} }
} }
function startAllTracking() { function startAllTracking() {
addDebug('start_all_tracking_called');
startLocationTracking(); startLocationTracking();
void startOrientationTracking(); void startOrientationTracking();
} }
function stopLocationTracking() { function stopLocationTracking() {
addDebug('stop_tracking_called', { watchId });
if (watchId !== null) { if (watchId !== null) {
navigator.geolocation.clearWatch(watchId); navigator.geolocation.clearWatch(watchId);
watchId = null; watchId = null;
addDebug('watch_position_stopped');
} }
window.removeEventListener('deviceorientationabsolute', handleOrientation as EventListener); window.removeEventListener('deviceorientationabsolute', handleOrientation as EventListener);
window.removeEventListener('deviceorientation', handleOrientation as EventListener); window.removeEventListener('deviceorientation', handleOrientation as EventListener);
addDebug('orientation_listeners_removed');
} }
// Initialize location tracking // Initialize location tracking
onMount(() => { onMount(() => {
addDebug('component_mounted', {
stepType: currentStep?.type ?? null,
isCurrentActiveStep
});
if (currentStep?.type === 'location' && isCurrentActiveStep) { if (currentStep?.type === 'location' && isCurrentActiveStep) {
checkLocationPermission().then(() => { checkLocationPermission().then(() => {
startAllTracking(); startAllTracking();
@@ -252,23 +339,50 @@
$effect(() => { $effect(() => {
if (currentStep?.type === 'location' && isCurrentActiveStep) { if (currentStep?.type === 'location' && isCurrentActiveStep) {
if (watchId === null) { if (watchId === null) {
addDebug('reactive_start_tracking', {
stepId: currentStep?.id ?? null
});
startAllTracking(); startAllTracking();
} }
} else if (watchId !== null) { } else if (watchId !== null) {
addDebug('reactive_stop_tracking', {
stepType: currentStep?.type ?? null,
isCurrentActiveStep
});
stopLocationTracking(); stopLocationTracking();
} }
}); });
function handleOrientation(event: Event) { function handleOrientation(event: Event) {
const e = event as DeviceOrientationEvent; const e = event as DeviceOrientationEvent;
lastOrientationEvent = event.type;
if (e.absolute && e.alpha !== null) { if (e.absolute && e.alpha !== null) {
heading = 360 - e.alpha; // Convert to compass heading heading = 360 - e.alpha; // Convert to compass heading
addDebug('orientation_update_absolute', {
eventType: event.type,
alpha: e.alpha,
heading
});
} else if (e.alpha !== null) { } else if (e.alpha !== null) {
// Fallback for iOS with webkitCompassHeading // Fallback for iOS with webkitCompassHeading
const webkit = e as DeviceOrientationEvent & { webkitCompassHeading?: number }; const webkit = e as DeviceOrientationEvent & { webkitCompassHeading?: number };
if (webkit.webkitCompassHeading !== undefined) { if (webkit.webkitCompassHeading !== undefined) {
heading = webkit.webkitCompassHeading; heading = webkit.webkitCompassHeading;
addDebug('orientation_update_webkit', {
eventType: event.type,
webkitCompassHeading: webkit.webkitCompassHeading,
heading
});
} else {
addDebug('orientation_update_no_heading', {
eventType: event.type,
alpha: e.alpha
});
} }
} else {
addDebug('orientation_event_without_alpha', {
eventType: event.type
});
} }
} }
@@ -536,6 +650,42 @@
{/if} {/if}
{/if} {/if}
</div> </div>
<details class="rounded-lg border border-gray-300 bg-gray-50 p-3 text-xs text-gray-800">
<summary class="cursor-pointer font-semibold">Client Debug Info</summary>
<div class="mt-3 space-y-2 font-mono leading-relaxed">
<div>step.id: {currentStep.id}</div>
<div>step.type: {currentStep.type}</div>
<div>isCurrentActiveStep: {String(isCurrentActiveStep)}</div>
<div>locationPermission: {locationPermission}</div>
<div>locationError: {locationError ?? 'null'}</div>
<div>watchId: {watchId ?? 'null'}</div>
<div>userLat: {userLat ?? 'null'}</div>
<div>userLon: {userLon ?? 'null'}</div>
<div>stepLat: {currentStep.latitude ?? 'null'}</div>
<div>stepLon: {currentStep.longitude ?? 'null'}</div>
<div>proximityRadius: {currentStep.proximityRadius ?? 'null'}</div>
<div>distance: {distance ?? 'null'}</div>
<div>heading: {heading ?? 'null'}</div>
<div>arrowRotation: {arrowRotation}</div>
<div>lastOrientationEvent: {lastOrientationEvent ?? 'null'}</div>
<div class="pt-1 font-semibold">Recent logs ({debugLogs.length})</div>
<div class="max-h-64 overflow-auto rounded border border-gray-200 bg-white p-2">
{#if debugLogs.length === 0}
<div>No logs yet</div>
{:else}
{#each debugLogs as log (log.ts + log.event)}
<div class="mb-2 border-b border-gray-100 pb-2 last:mb-0 last:border-b-0 last:pb-0">
<div>[{log.ts}] {log.event}</div>
{#if log.details}
<pre class="mt-1 whitespace-pre-wrap break-all text-[11px]">{JSON.stringify(log.details, null, 2)}</pre>
{/if}
</div>
{/each}
{/if}
</div>
</div>
</details>
</form> </form>
{#if currentStep.hint} {#if currentStep.hint}