From e151b21bdafa93804e6e9fd2d94356ead83bfa74 Mon Sep 17 00:00:00 2001 From: Jesper Pedersen Date: Mon, 23 Feb 2026 18:21:05 +0100 Subject: [PATCH] fix: replace getCurrentPosition with watchPosition for iOS Safari iOS Safari re-prompts geolocation permission on every getCurrentPosition call after a page reload. Replace the getCurrentPosition + setInterval pattern with watchPosition which triggers only one permission prompt and continuously delivers updates. Also guard navigator.permissions.query() which is unsupported on iOS Safari, and add error callbacks for permission denied, position unavailable, and timeout scenarios. --- CHANGELOG.md | 1 + src/App.jsx | 69 +++++++++++++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7424e7d..e67964f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Fix iOS Safari geolocation re-prompting on every page reload (use `watchPosition`, guard `permissions.query`) - Security and dependency updates (8 vulnerabilities fixed, ESLint v9, React/Prettier updated) ## [1.0.8] - 2025-28-01 diff --git a/src/App.jsx b/src/App.jsx index 0774d79..25c790b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -27,7 +27,6 @@ function App() { const [infoText, setInfoText] = useState(""); const [lat, setLat] = useState(null); const [long, setLong] = useState(null); - const locationUpdateInterval = 30000; const contextLatLong = useMemo( () => ({ @@ -37,42 +36,52 @@ function App() { [lat, long], ); - function handlePermissionInfoBanner() { - navigator.permissions.query({ name: "geolocation" }).then((result) => { - if (result.state !== "granted") { - setInfo(true); - setInfoText( - - Du har ikke accepteret, at vi må få adgang til din lokation. For at denne applikation skal fungere, skal den - bruge din lokation. Hvis du vil vide mere om hvordan du giver denne angang, kan du besøge{" "} - - Hjælp til navigation - - , - ); - } - }); - } + const permissionDeniedBanner = ( + + Du har ikke accepteret, at vi må få adgang til din lokation. For at denne applikation skal fungere, skal den bruge + din lokation. Hvis du vil vide mere om hvordan du giver denne adgang, kan du besøge{" "} + + Hjælp til navigation + + + ); useEffect(() => { - handlePermissionInfoBanner(); + if (navigator.permissions && navigator.permissions.query) { + navigator.permissions + .query({ name: "geolocation" }) + .then((result) => { + if (result.state === "denied") { + setInfo(true); + setInfoText(permissionDeniedBanner); + } + }) + .catch(() => {}); + } }, []); - function startLocationPrompter() { - setInterval(() => { - navigator.geolocation.getCurrentPosition((position) => { + useEffect(() => { + const watchId = navigator.geolocation.watchPosition( + (position) => { setLat(position.coords.latitude); setLong(position.coords.longitude); - }); - }, locationUpdateInterval); - } + }, + (err) => { + if (err.code === err.PERMISSION_DENIED) { + setInfo(true); + setInfoText(permissionDeniedBanner); + } else if (err.code === err.POSITION_UNAVAILABLE) { + setError(true); + setErrorText("Din lokation kunne ikke bestemmes."); + } else if (err.code === err.TIMEOUT) { + setError(true); + setErrorText("Forespørgslen om din lokation tog for lang tid."); + } + }, + { enableHighAccuracy: true, timeout: 30000, maximumAge: 10000 }, + ); - useEffect(() => { - navigator.geolocation.getCurrentPosition((position) => { - setLat(position.coords.latitude); - setLong(position.coords.longitude); - }); - startLocationPrompter(); + return () => navigator.geolocation.clearWatch(watchId); }, []); useEffect(() => {