Missing files from commit
This commit is contained in:
@@ -10,7 +10,87 @@ export function haversineKm(lat1, lon1, lat2, lon2) {
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
}
|
||||
|
||||
export function getUserPosition() {
|
||||
// --- Position Cache (localStorage) ---
|
||||
const POSITION_CACHE_KEY = 'farmafinder_last_position';
|
||||
const POSITION_CACHE_MAX_AGE = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
function getCachedPosition() {
|
||||
try {
|
||||
const raw = localStorage.getItem(POSITION_CACHE_KEY);
|
||||
if (!raw) return null;
|
||||
const cached = JSON.parse(raw);
|
||||
if (Date.now() - cached.timestamp > POSITION_CACHE_MAX_AGE) return null;
|
||||
return { lat: cached.lat, lon: cached.lon };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setCachedPosition(lat, lon) {
|
||||
try {
|
||||
localStorage.setItem(POSITION_CACHE_KEY, JSON.stringify({ lat, lon, timestamp: Date.now() }));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function clearCachedPosition() {
|
||||
try {
|
||||
localStorage.removeItem(POSITION_CACHE_KEY);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// --- Position Manager ---
|
||||
let pendingPositionRequest = null;
|
||||
|
||||
/**
|
||||
* Get user position with caching strategy:
|
||||
* 1. Return cached position if fresh (< 5 min)
|
||||
* 2. Request browser geolocation in background
|
||||
* 3. If browser fails, fall back to cached (up to 15 min)
|
||||
*
|
||||
* This makes the first request nearly instant for repeat users.
|
||||
*/
|
||||
export function getUserPosition({ timeout = 15000, maximumAge = 300000, retries = 1 } = {}) {
|
||||
// Cancel any pending request before starting a new one
|
||||
if (pendingPositionRequest) {
|
||||
pendingPositionRequest.cancel();
|
||||
pendingPositionRequest = null;
|
||||
}
|
||||
|
||||
// 1. Fast path: return fresh cached position immediately
|
||||
const cached = getCachedPosition();
|
||||
if (cached) {
|
||||
// Also refresh in background (don't await)
|
||||
refreshPositionInBackground(timeout, maximumAge);
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
|
||||
// 2. Slow path: request from browser
|
||||
return requestBrowserPosition({ timeout, maximumAge, retries }).then(pos => {
|
||||
setCachedPosition(pos.lat, pos.lon);
|
||||
return pos;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh position silently in background — updates cache for next call.
|
||||
*/
|
||||
function refreshPositionInBackground(timeout, maximumAge) {
|
||||
if (!navigator.geolocation) return;
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
pos => {
|
||||
const newPos = { lat: pos.coords.latitude, lon: pos.coords.longitude };
|
||||
setCachedPosition(newPos.lat, newPos.lon);
|
||||
},
|
||||
() => {}, // silently fail — we already have a cached position
|
||||
{ timeout, maximumAge, enableHighAccuracy: false }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core browser geolocation request with retry logic.
|
||||
*/
|
||||
function requestBrowserPosition({ timeout = 15000, maximumAge = 300000, retries = 1 } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!navigator.geolocation) {
|
||||
reject(new Error('Geolocalización no compatible con este navegador'));
|
||||
@@ -20,17 +100,82 @@ export function getUserPosition() {
|
||||
reject(new Error('La geolocalización requiere HTTPS (o localhost)'));
|
||||
return;
|
||||
}
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
pos => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude }),
|
||||
err => {
|
||||
console.error('[geo] getCurrentPosition failed — code:', err && err.code, 'message:', err && err.message);
|
||||
|
||||
let attempts = 0;
|
||||
let watchId = null;
|
||||
let settled = false;
|
||||
|
||||
function cleanup() {
|
||||
if (watchId !== null) {
|
||||
navigator.geolocation.clearWatch(watchId);
|
||||
watchId = null;
|
||||
}
|
||||
if (pendingPositionRequest?.id === requestId) {
|
||||
pendingPositionRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
function succeed(pos) {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
cleanup();
|
||||
const newPos = { lat: pos.coords.latitude, lon: pos.coords.longitude };
|
||||
setCachedPosition(newPos.lat, newPos.lon);
|
||||
resolve(newPos);
|
||||
}
|
||||
|
||||
function fail(err) {
|
||||
if (settled) return;
|
||||
attempts++;
|
||||
if (attempts <= retries && err?.code === 3) {
|
||||
const delay = Math.min(1000 * Math.pow(2, attempts - 1), 5000);
|
||||
console.warn(`[geo] timeout, retrying in ${delay}ms (attempt ${attempts}/${retries})`);
|
||||
setTimeout(() => {
|
||||
if (!settled) startWatching();
|
||||
}, delay);
|
||||
} else {
|
||||
settled = true;
|
||||
cleanup();
|
||||
console.error('[geo] getCurrentPosition failed — code:', err?.code, 'message:', err?.message);
|
||||
reject(err);
|
||||
},
|
||||
{ timeout: 15000, maximumAge: 60000, enableHighAccuracy: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function startWatching() {
|
||||
if (settled) return;
|
||||
watchId = navigator.geolocation.watchPosition(succeed, fail, {
|
||||
timeout,
|
||||
maximumAge,
|
||||
enableHighAccuracy: false,
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (!settled && watchId !== null) {
|
||||
fail({ code: 3, message: 'Timeout' });
|
||||
}
|
||||
}, timeout + 2000);
|
||||
}
|
||||
|
||||
const requestId = Date.now();
|
||||
pendingPositionRequest = { id: requestId, cancel: () => { settled = true; cleanup(); } };
|
||||
|
||||
startWatching();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached position (e.g. on logout or when user explicitly wants fresh location).
|
||||
*/
|
||||
export function clearPositionCache() {
|
||||
clearCachedPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have any cached position (for UI hints).
|
||||
*/
|
||||
export function hasCachedPosition() {
|
||||
return getCachedPosition() !== null;
|
||||
}
|
||||
|
||||
export function formatDistance(km) {
|
||||
if (km < 1) return `${Math.round(km * 1000)} m`;
|
||||
if (km < 10) return `${km.toFixed(1)} km`;
|
||||
|
||||
Reference in New Issue
Block a user