fix: Qibla not detecting device compass on iOS and Android
Two real bugs, both explaining 'didn't detect compass': 1. iOS: DeviceOrientationEvent.requestPermission() was called *after* the async geolocation round-trip resolved. Safari's user-activation grant for sensor permissions expires almost immediately, so by the time a GPS fix comes back (often several seconds), the permission call silently fails with no prompt ever shown. Now requested synchronously inside the button's click handler, before geolocation starts. 2. Android: the heading was only accepted when e.absolute === true, but many Android browsers fire plain 'deviceorientation' with absolute:false even though alpha is a perfectly usable heading — compass detection silently never succeeded on those devices at all. Now accepts alpha regardless of the absolute flag, falling back to it when webkitCompassHeading (iOS) isn't present. Also fixed a duplicate-listener bug: clicking 'Refresh location' repeatedly re-registered deviceorientation listeners without removing the previous ones, stacking duplicates on every retry. Verified live: dispatched a synthetic non-absolute deviceorientation event (the exact Android failure shape) against the deployed app — compass detection now activates correctly where it previously stayed silently stuck on the no-sensor fallback. Added as a permanent regression assertion in e2e-khairat-lifestyle.cjs (16/16).
This commit is contained in:
@@ -60,6 +60,22 @@ async function main() {
|
||||
const distanceVisible = await page.locator('.distance-note', { hasText: 'km to Makkah' }).isVisible().catch(() => false);
|
||||
record('Qibla: shows distance to Makkah', distanceVisible);
|
||||
|
||||
// Regression guard: many Android browsers fire plain 'deviceorientation'
|
||||
// with absolute:false even though alpha is a usable heading — a prior
|
||||
// version required absolute:true and silently never detected the compass
|
||||
// on those devices. Simulate exactly that event shape.
|
||||
const staticNoteBeforeSensor = await page.locator('.static-note').isVisible().catch(() => false);
|
||||
record('Qibla: shows the no-sensor fallback note before any orientation event', staticNoteBeforeSensor);
|
||||
await page.evaluate(() => {
|
||||
const evt = new Event('deviceorientation');
|
||||
Object.defineProperty(evt, 'alpha', { value: 90 });
|
||||
Object.defineProperty(evt, 'absolute', { value: false });
|
||||
window.dispatchEvent(evt);
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
const staticNoteAfterSensor = await page.locator('.static-note').isVisible().catch(() => false);
|
||||
record('Qibla: accepts a non-absolute deviceorientation event as a valid compass reading (Android)', !staticNoteAfterSensor);
|
||||
|
||||
// ── Prayer Times ──
|
||||
await gotoTab(page, 'Prayer Times');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
+40
-16
@@ -33,34 +33,58 @@
|
||||
distanceKm = Math.round(R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)));
|
||||
}
|
||||
|
||||
let listenersAttached = false;
|
||||
|
||||
function handleOrientation(e) {
|
||||
const heading = e.webkitCompassHeading ?? (e.absolute && e.alpha != null ? 360 - e.alpha : null);
|
||||
let heading = e.webkitCompassHeading;
|
||||
if (heading == null && e.alpha != null) {
|
||||
// Prefer a true geomagnetic reading when the browser provides one
|
||||
// (absolute: true), but don't require it — many Android browsers
|
||||
// never set absolute:true even though alpha is still a usable
|
||||
// heading, so requiring it strictly left the compass permanently
|
||||
// "undetected" on those devices.
|
||||
heading = 360 - e.alpha;
|
||||
}
|
||||
if (heading != null) { compassHeading = heading; compassSupported = true; }
|
||||
}
|
||||
|
||||
function attachOrientationListeners() {
|
||||
if (listenersAttached) return;
|
||||
listenersAttached = true;
|
||||
window.addEventListener('deviceorientationabsolute', handleOrientation, true);
|
||||
window.addEventListener('deviceorientation', handleOrientation, true);
|
||||
}
|
||||
|
||||
async function findQibla() {
|
||||
status = 'locating';
|
||||
errorMsg = '';
|
||||
compassSupported = false;
|
||||
compassHeading = null;
|
||||
|
||||
// Request device-orientation permission (iOS 13+) synchronously here,
|
||||
// inside this click handler — Safari expires the user-activation grant
|
||||
// almost immediately, so requesting it *after* the async geolocation
|
||||
// round-trip (which can take several seconds) reliably fails silently
|
||||
// with no prompt ever shown. Registering listeners for everyone else
|
||||
// immediately too, so no early orientation events are missed while
|
||||
// still waiting on the GPS fix.
|
||||
if (typeof DeviceOrientationEvent !== 'undefined') {
|
||||
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
|
||||
try {
|
||||
const perm = await DeviceOrientationEvent.requestPermission();
|
||||
if (perm === 'granted') attachOrientationListeners();
|
||||
} catch { /* denied or unsupported — static bearing still shown */ }
|
||||
} else {
|
||||
attachOrientationListeners();
|
||||
}
|
||||
}
|
||||
|
||||
if (!navigator.geolocation) {
|
||||
status = 'error'; errorMsg = 'Geolocation is not available on this device/browser.';
|
||||
return;
|
||||
}
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
async (pos) => {
|
||||
computeQibla(pos.coords.latitude, pos.coords.longitude);
|
||||
status = 'ready';
|
||||
// Compass heading requires an explicit permission prompt on iOS 13+,
|
||||
// which must be triggered by this same user gesture.
|
||||
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
|
||||
try {
|
||||
const perm = await DeviceOrientationEvent.requestPermission();
|
||||
if (perm === 'granted') window.addEventListener('deviceorientation', handleOrientation, true);
|
||||
} catch { /* permission denied or unsupported — static bearing still shown */ }
|
||||
} else if (typeof DeviceOrientationEvent !== 'undefined') {
|
||||
window.addEventListener('deviceorientationabsolute', handleOrientation, true);
|
||||
window.addEventListener('deviceorientation', handleOrientation, true);
|
||||
}
|
||||
},
|
||||
(pos) => { computeQibla(pos.coords.latitude, pos.coords.longitude); status = 'ready'; },
|
||||
(err) => { status = 'error'; errorMsg = err.message || 'Could not get your location.'; },
|
||||
{ enableHighAccuracy: true, timeout: 10000 }
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user