Search code examples
javascriptangularcordovacompass-geolocationcordova-ios

Access to geolocation was blocked over insecure connection to ionic://localhost (on cordova-ios)


I'm trying to get mobile device's location and orientation (compass heading) with a "location.service" I created. It used to work both on android and ios, now on ios, I get the location, but with an error for device orientation.

[blocked] Access to geolocation was blocked over insecure connection to ionic://localhost.

Geolocation PositionError {code: 1, message: "Origin does not have permission to use Geolocation service"

I have tried all of these on *.plist file: enter image description here

Im using:

  • @angular/cli 7.3.9
  • [email protected]

    • platforms:
      • android 8.1.0
      • ios 5.1.1
    • plugins (shorten):
      • cordova-plugin-ionic-webview 4.1.1 "cordova-plugin-ionic-webview"
      • cordova-plugin-geolocation 4.0.2 "Geolocation"

When I cordova build ios, run it from Xcode on my iPhone (ios 13.4.1) and check Safari's develop console: enter image description here

enter image description here

location.service:

import {Injectable} from '@angular/core';
import {BehaviorSubject, combineLatest, fromEvent, Observable, of, timer} from 'rxjs';
import {HttpClient} from '@angular/common/http';
import {map, switchMap} from 'rxjs/operators';
import { Globals } from '../globals';

export interface DefiLocation {
  accuracy: number;
  altitude: number;
  altitudeAccuracy: number;
  heading: number;
  latitude: number;
  longitude: number;
  speed: number;
  compass: number;
}

@Injectable({
  providedIn: 'root'
})
export class LocationService {
  errorMessage$: string;
  currentLocation$: BehaviorSubject<{
    accuracy?: number,
    altitude: number,
    altitudeAccuracy: number,
    heading: number,
    latitude: number,
    longitude: number,
    speed: number
  }> = new BehaviorSubject({
    accuracy: 0,
    altitude: 0,
    altitudeAccuracy: 0,
    heading: 0,
    latitude: 32.5,
    longitude: 35,
    speed: 0,
  });

  currentCompass$: BehaviorSubject<number> = new BehaviorSubject(0);
  currentCompass: number = 0;
  currentPosition: {
    accuracy: 0,
    altitude: 0,
    altitudeAccuracy: 0,
    heading: 0,
    latitude: 32.5,
    longitude: 35,
    speed: 0,
  };
  locationTimer;
  sendLocationError: boolean = true;

  constructor(public globals: Globals) {
    this.isCurrentPosition$.next(true);
    this.trackMe();
  }

  private trackMe() {
    if (this.globals.iphone) {
        window.addEventListener('deviceorientation', (event) => {
          this.currentCompass$.next(Math.round(event['webkitCompassHeading']));
        });
      // }
    } else {
      window.addEventListener('deviceorientationabsolute', (event) => {
        this.currentCompass$.next(360 - Math.round(event['alpha']));
      }, true);
    }


    if (navigator.geolocation) {
      this.locationTimer = timer(0, 100).subscribe(tick => {
        navigator.geolocation.getCurrentPosition((position) => {
          this.currentLocation$.next(position.coords);
          this.isCurrentPosition$.next(true);
          this.sendLocationError = true;
        }, (err) => {
          this.isCurrentPosition$.next(false);
          this.sendError(err.message);
          console.log(err);
          this.errorMessage$ = err.message;
          console.log(this.errorMessage$);
        }, {
          enableHighAccuracy: true
        });
      });

    } else {
      alert('Geolocation is not supported by this browser.');
      this.sendError('navigator.geolocation = null ("Geolocation is not supported by this browser.")');
    }
  }

  getCurrentLocation$(): Observable<DefiLocation> {
    return combineLatest(this.currentCompass$.asObservable(), this.currentLocation$.asObservable())
      .pipe(
        map(([compass, location]) => {
            return {
              longitude: location.longitude,
              latitude: location.latitude,
              accuracy: location.accuracy,
              altitude: location.altitude,
              altitudeAccuracy: location.altitudeAccuracy,
              heading: location.heading,
              speed: location.speed,
              compass
            };
          }
        ));
  }

  sendError(error) {
    if (this.sendLocationError) {
      this.sendLocationError = false;
      window['cordova'].plugins.firebase.analytics.logEvent('user_location_failed', {param1: error});
      setTimeout(function() { this.sendLocationError = true; }, 5000);
    }
  }
}

Solution

  • Turns out the error message didn't relate to the device's location (when I fixed the location the error was still there). The orientation was a little more of a problem but I have an answer:

    1. Location - When I asked for all the right permission in the .plist file I received a proper location. Here is a nice explanation about the permissions. BTW, i'm using cordova-plugin-geolocation.
    2. Orientation (compass) - according to the "Safari 13 Release Notes":

    Added a permission API on iOS for DeviceMotionEvent and DeviceOrientationEvent.

    a. Permission API - Up-until now, Chrome and Safari only showed a warning about "asking device orientation without user gesture", but on ios 13+ apple took it another step and totally blocked it. The full w3c discussion. Example for the right way to ask premission in the last code sample below.

    b. The old/new alternative (cordova-plugin-device-orientation) - because this behaviour is really unnatural in an app, I tried an old deprecated plugin that uses native resources, which worked, and luckily, now the plugin is actually undeprecated!


    My complete code with fallbacks

    1. Trying to ask for permission on location and orientation from the browser on deviceready (with adaptation for android and ios).
    2. If not, trying orientation-plugin (native resource).
    3. If not, asking permission on user gesture as ios13+ web protocol state.

    This run's on deviceready:

    private trackMe() {
    
        // compass listener (also ios13+ fallback at "compassPremissioniOS13" funtion)
        if (typeof DeviceOrientationEvent['requestPermission'] !== 'function') {
          const deviceOrientationLestener = (event) => {
            if (this.globals.iphone) {
              this.currentCompass$.next(event['webkitCompassHeading']);
            } else {
              if (event.absolute) {
                this.currentCompass$.next(360 - event.alpha);
              } else {
                window.removeEventListener('deviceorientation', deviceOrientationLestener);
                window.addEventListener('deviceorientationabsolute', (eventB) => {
                  this.currentCompass$.next(360 - eventB['alpha']);
                }, true);
              }
            }
          };
          window.addEventListener('deviceorientation', deviceOrientationLestener);
        }  else {
          document.addEventListener('deviceready', () => {
            navigator['compass'].watchHeading((head) => {
              this.currentCompass$.next(head.trueHeading);
            }, (error) => console.log(error), {frequency: 10});
          }, false);
        }
    
        // GET LOCATION - energy saving and preformance inhancing for mobile, dumm and cyclic for desktop.
        if (this.globals.cordova) {
          document.addEventListener('deviceready', () => {
            if (navigator.geolocation) {
              navigator.geolocation.watchPosition((position) => {
                this.currentLocation$.next(position.coords);
                this.isCurrentPosition$.next(true);
                this.sendLocationError = true;
              }, (err) => {
                this.isCurrentPosition$.next(false);
                this.sendError(err.message);
                this.errorMessage$ = err.message;
              }, {
                enableHighAccuracy: true
              });
            } else {
              alert('Geolocation is not supported by this browser.');
              this.sendError('navigator.geolocation = null ("Geolocation is not supported by this browser.")');
            }
          }, false);
        } else {
          if (navigator.geolocation) { // fallback for desktop testing
            this.locationTimer = timer(0, 100).subscribe(tick => {
              navigator.geolocation.getCurrentPosition((position) => {
                this.currentLocation$.next(position.coords);
                this.isCurrentPosition$.next(true);
                this.sendLocationError = true;
              }, (err) => {
                this.isCurrentPosition$.next(false);
                this.sendError(err.message);
                this.errorMessage$ = err.message;
              }, {
                enableHighAccuracy: true
              });
            });
          } else {
            alert('Geolocation is not supported by this browser.');
            this.sendError('navigator.geolocation = null ("Geolocation is not supported by this browser.")');
          }
        }
      }
    

    This runs when user press on navigation button:

    compassPremissioniOS13$() {
        return new Promise((resolve, reject) => {
          if (navigator.geolocation) {
            if (typeof DeviceOrientationEvent['requestPermission'] === 'function') {
              DeviceOrientationEvent['requestPermission']()
                .then(permissionState => {
                  if (permissionState === 'granted') {
                    window.addEventListener('deviceorientation', (event) => {
                      this.currentCompass$.next(event['webkitCompassHeading']);
                    });
                    resolve('User accepted');
                  } else {
                    reject('User declined');
                  }
                })
                .catch(console.error);
            }
          } else {
            alert('deviceorientation is not supported by this browser.');
            this.sendError('deviceorientation = null ("deviceorientation is not supported by this browser.")');
          }
        });
      }