Abstract

This specification defines an API that lets webapps setup geographic boundaries around specific locations and then receive notifications when the hosting device enters or leaves those areas.

Status of This Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.

This document was published by the Geolocation Working Group as a First Public Working Draft. The Working Group expects to advance this Working Draft to Recommendation Status. If you wish to make comments regarding this document, please send them to public-geolocation@w3.org (subscribe, archives) with [Geofencing API] at the start of your email's subject. All comments are welcome.

Publication as a First Public Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 1 August 2014 W3C Process Document.

Table of Contents

1. Introduction

This section is non-normative.

The Geofencing API lets webapps setup geographic boundaries around specific locations and then receive notifications when the hosting device enters or leaves those areas. While it would be possible to implement something similar using the Geolocation API [GEOLOCATION-API], there are a few differences that could make this API a better choice:

The following code extracts illustrate how to use this API to be notified of geographic regions being entered or left.

Example 1: Monitor a region
// https://example.com/webapp.js
navigator.serviceWorker
  .register('serviceworker.js')
  .then((swRegistration) => {
    let region = new CircularGeofenceRegion({
      name: 'myfence',
      latitude: 37.421999,
      longitude: -122.084015,
      radius: 1000
    });
    let options = {
      includePosition: true
    };
    swRegistration.geofencing.add(region, options)
      .then(
        // If more than just a name needs to be stored with a geofence, now
        // would be the time to store this in some storage.
        (geofence) => console.log(geofence.id),
        (error) => console.log(error)
      );
  });
Example 2: Respond to a region being entered
// https://example.com/serviceworker.js
self.ongeofenceenter = (event) => {
  console.log(event.geofence.id);
  console.log(event.geofence.region.name);

  // If this is not a geofence of interest anymore, remove it.
  if (event.geofence.region.name !== "myfence") {
    event.waitUntil(event.geofence.remove());
  }
};
Example 3: Respond to an error condition
// https://example.com/serviceworker.js
self.ongeofenceerror = (event) => {
  console.log(event.geofence.id);
  console.log(event.geofence.region.name);
  console.log(event.error);

  // Some error condition occurred. The region is no longer monitored, and won't
  // trigger any more events.

  // Try to re-monitor, although depending on the error this might fail.
  event
    .waitUntil(self.registration.geofencing.add(event.geofence.region))
    .then((geofence) => {
      // re-monitoring succeeded, new geofence will have a different ID.
    }, (error) => {
      // re-monitoring failed.
    });
};
Example 4: Unmonitor a region in response to some other event
// https://example.com/serviceworker.js

// Either look geofence up by name:
self.onsomeevent = (event) => {
  event
    .waitUntil(
      self.registration.geofencing.getAll({
        name: 'myfence'
      })
    )
    .then(
      geofences => geofences.forEach(fence => fence.remove())
    );
};

// Or look geofence up by ID:
self.onsomeotherevent = (event) => {
  let geofence_id = ''; /* somehow get the ID of a geofence */
  event
    .waitUntil(self.registration.geofencing.getById(geofence_id))
    .then(geofence => geofence.remove());
};

2. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MAY, MUST, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119].

This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.

Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL].

3. Terminology

The geographic coordinate reference system used by the attributes in this API is the World Geodetic System (2d) [WGS84]. No other reference system is supported.

The terms event handler, event handler event type, queue a task, and fire a simple event are defined in [HTML5].

The types Promise, and RangeError are defined in [ECMASCRIPT].

EventInit, DOMException, InvalidStateError, NotFoundError, QuotaExceededError, SyntaxError, and steps for constructing events are defined in [DOM].

The terms service worker, service worker registration, installing worker, waiting worker, and active worker, and the types ServiceWorkerRegistration, ServiceWorkerGlobalScope, ExtendableEvent, and ExtendableEventInit are defined in [SERVICE-WORKERS].

The type Position is defined in [GEOLOCATION-API].

The term webapp refers to a Web application, i.e. an application implemented using Web technologies, and executing within the context of a Web user agent, e.g. a Web browser or other Web runtime environment.

The term geofence is used to refer to one specific registration of a geographic region as a geofence. A geofence is a tuple consisting of a geographic region, geofence ID and a include position flag.

A geofence ID is a string uniquely identifying a geofence. This ID is generated by the user agent and MUST be unique among all active geofences associated with all service worker registrations at the same origin. A user agent SHOULD NOT reuse the ID from an old geofence for a new one.

The term active geofences is used to refer to the collection of geofences associated with a particular service worker registration, that are currently being monitored by the user agent for breach events.

A geofence is said to be breached if the current geographical location changed from being inside the geographic region to outside (a leave event), or vice versa (an enter event).

4. Security and privacy considerations

The same security and privacy considerations that apply to the Geolocation API [GEOLOCATION-API] also apply to this API. Furthermore since this API effectively gives access to geographic location information after a user has stopped interacting with a webapp, a few other considerations should be taken into account.

4.1 Privacy considerations for implementers of the Geofencing API

TODO

4.2 Privacy considerations for recipients of location information

TODO

4.3 Additional implementation considerations

This section is non-normative.

TODO

5. API Description

5.1 Extensions to the ServiceWorkerRegistration interface

The Service Worker specification defines a ServiceWorkerRegistration interface, which this specification extends.

partial interface ServiceWorkerRegistration {
    readonly    attribute GeofenceManager geofencing;
};

5.2 GeofenceManager interface

The GeofenceManager interface defines operations that enable webapps to establish access to geofencing services.

dictionary GeofenceQueryOptions {
             DOMString? name;
};

dictionary GeofenceOptions { boolean includePosition = false; };

[NoInterfaceObject] interface GeofenceManager { Promise<Geofence> add (GeofenceRegion initialRegion, optional GeofenceOptions options); Promise<sequence<Geofence>> getAll (optional GeofenceQueryOptions options); Promise<Geofence> getById (DOMString id); };

The add method when invoked MUST run the following steps:

  1. Let promise be a new Promise.
  2. Run the following steps in parallel:
    1. If the total number of active geofences for all service worker registrations in the current origin is more than a user agent defined limit, reject promise with a DOMException whose name is "QuotaExceededError" and terminate these substeps. A user agent SHOULD allow at least 20 active geofences for an origin.
    2. Ask the user whether they allow the webapp to use geofences, unless a prearranged trust relationship applies or the user has already granted or denied permission explicitly for this webapp.
    3. If not granted, reject promise with a DOMException whose name is "PermissionDeniedError" and terminate these substeps.
    4. Let geofence be a new geofence.
    5. Set the geofence ID of the geofence to a newly generated value.
    6. Set the geographic region of the geofence to a GeofenceRegion instance of the same type and with the same attributes as the initialRegion passed to add.
    7. Set the include position flag of the geofence to options.includePosition, or false if no options were specified.
    8. Add geofence to the active geofences of the service worker registration associated with the webapp.
    9. If the current geographic position is inside the newly added region, fire a geofenceenter event.
    10. Resolve promise with a new Geofence instance representing the geofence.
  3. Return promise.
Issue 1

Somehow mention that the region that is saved as part of the registration can be slightly different from the region passed to register. An implementation may adjust parameters to be in range of what is possible, or otherwise modify the region.

Note

The iOS API takes a slightly different approach when permission is denied. It instead always treats registrations as a success, but won't actually track geofences/trigger events unless the user has granted permission.

If the includePosition attribute is set to true, GeofenceEvents for this registration will have a position attribute. When set to false, the position attribute will always be undefined.

The getAll method when invoked MUST run the following steps:

  1. Let promise be a new Promise.
  2. Run the following steps in parallel:
    1. Let geofences be a new sequence.
    2. For each geofence in the active geofences associated with this service worker registration, run the following substeps:
      1. If options is passed, has a non-null name attribute, and that name is not equal to the name of the geofence, skip the rest of these substeps and continue with the next registration.
      2. Let geofence be a new Geofence instance representing the geofence.
      3. Append geofence to geofences.
    3. Resolve promise with geofences.
  3. Return promise.

The getById method when invoked MUST run the following steps:

  1. Let promise be a new Promise.
  2. Run the following steps in parallel:
    1. For each geofence in the active geofences associated with this service worker registration, run the following substeps:
      1. If the geofence ID of the current geofence is not equal to the passed in id, skip the rest of these substeps and continue with the next geofence.
      2. Let geofence be a new Geofence instance with attributes equal to those of the geofence.
      3. Resolve promise with geofence and abort the remainder of these substeps.
    2. If promise was not resolved, resolve promise with null.
  3. Return promise.

5.3 Geofence interface

An instance of the Geofence interface represents a geofence.

interface Geofence {
    readonly    attribute DOMString      id;
    readonly    attribute GeofenceRegion region;
    Promise<boolean> remove ();
};

When getting the id attribute, the user agent MUST return the geofence ID of the geofence.

When getting the region attribute, the user agent MUST return the geographic region of this geofence.

The remove method when invoked MUST run the following steps:

  1. Let promise be a new Promise.
  2. Run the following steps in parallel:
    1. If this geofence is not currently in the active geofences associated with a service worker registration, resolve promise with false and abort the remainder of these steps.
    2. Remove this geofence from the set of active geofences associated with the current service worker registration. No more events related to this geofence will be fired after this.
    3. Resolve promise with true.
  3. Return promise.

5.4 GeofenceRegion interface

dictionary GeofenceRegionInit {
             DOMString? name;
};

[Exposed=(Window,Worker)] interface GeofenceRegion { readonly attribute DOMString name; };

The name attribute MUST return the value it was initialized to. When the object is created, this attribute MUST be set to the value of the name property in the GeofenceRegionInit dictionary, or an empty string if that property wasn't set. A user agent MAY impose limits on the maximum size of the name attribute. If this limit is exceeded the constructor MUST throw a RangeError. If a user agent imposes a limit on the size of the name attribute, this limit SHOULD allow for at least 100 characters.

5.5 CircularGeofenceRegion interface

[Exposed=(Window,Worker)]
interface GeolocationPoint {
                attribute double latitude;
                attribute double longitude;
};

dictionary CircularGeofenceRegionInit { double latitude; double longitude; double radius; };

[Constructor(CircularGeofenceRegionInit), Exposed=(Window,Worker)] interface CircularGeofenceRegion : GeofenceRegion { readonly attribute GeolocationPoint center; readonly attribute double radius; };

The center attribute MUST return the value it was initialized to. When the object is created, this attribute MUST be set to a GeolocationPoint instance with its latitude and longitude properties set to the same values as those properties in the CircularGeofenceRegionInit dictionary. This represents the center of the circular region. Latitude must be between -90 and 90 inclusive. Longitude must be between -180 and 180 inclusive. If either of these properties is outside these ranges, the constructor will throw a RangeError.

The radius attribute MUST return the value it was initialized to. When the object is created, this attribute MUST be set to the value of the radius property in the CircularGeofenceRegionInit dictionary. This represents the radius of the circular region in meters.

The latitude property of a GeolocationPoint dictionary represents the latitude in circular degrees of a point.

The longitude property of a GeolocationPoint dictionary represents the longitude in circular degrees of a point.

Issue 2

The downside of doing error checks in the CircularGeofenceRegion constructor is that they will be actual exceptions instead of promise rejections when calling add. Would it make sense to move the validity checks to add?

5.6 Events

The Service Worker specification defines a ServiceWorkerGlobalScope interface, which this specification extends.

partial interface ServiceWorkerGlobalScope {
                attribute EventHandler ongeofenceenter;
                attribute EventHandler ongeofenceleave;
                attribute EventHandler ongeofenceerror;
};

The ongeofenceenter attribute is an event handler whose corresponding event handler event type is geofenceenter.

The ongeofenceleave attribute is an event handler whose corresponding event handler event type is geofenceleave.

The ongeofenceerror attribute is an event handler whose corresponding event handler event type is geofenceerror.

5.6.1 The geofenceenter and geofenceleave events

The GeofenceEvent interface represents a geofence being breached.

[Exposed=ServiceWorker]
interface GeofenceEvent : ExtendableEvent {
    readonly    attribute Geofence  geofence;
    readonly    attribute Position? position;
};

Upon detecting a breach of a geofence, the user agent MUST run the following steps to fire a geofence event:

  1. If the Service Worker associated with the webapp is not running, start it.
  2. Let scope be the ServiceWorkerGlobalScope of the Service Worker associated with the webapp.
  3. Let event be a new GeofenceEvent, whose geofence attribute is a new Geofence instance representing the geofence that was breached.
  4. If the geofences includePosition attribute is true, set event.position to the current geographical position.
  5. Let eventName be geofenceenter or geofenceleave, corresponding to the type of breach event being processed.
  6. Queue a task to fire event as a simple event named eventName at scope.

The user agent MAY wait with firing a geofence event until some time has passed after the breach was detected, or until the for some time or distance after detecting a breach of a monitored geofence before firing an event to make sure that the geofence really was breached.

5.6.2 GeofenceErrorEvent

[Exposed=ServiceWorker]
interface GeofenceErrorEvent : ExtendableEvent {
    readonly    attribute Geofence       geofence;
    readonly    attribute unsigned short code;
    readonly    attribute DOMString      message;
};
Issue 3

This needs more work. I'm really not sure what attributes make sense to expose here. Also I'm not sure if is desirable/okay for all errors to be fatal/cause the geofence to be unmonitored.

Upon detecting some situation in which the user agent won't be able to detect future breached of a geofence, the user agent MUST run the following steps:

  1. If the Service Worker associated with the webapp is not running, start it.
  2. Let scope be the ServiceWorkerGlobalScope of the Service Worker associated with the webapp.
  3. Let event be a new GeofenceErrorEvent, whose geofence attribute is a new Geofence instance representing the geofence for which an error was detected.
  4. Let event.code be an error code.
  5. Let event.message be a descriptive message for the error that occurred.
  6. Remove the geofence for which an error was detected from the set of active geofences associated with the current service worker registration.
  7. Queue a task to fire event as a simple event named geofenceerror at scope.

A. References

A.1 Normative references

[DOM]
Anne van Kesteren; Aryeh Gregor; Ms2ger; Alex Russell; Robin Berjon. W3C DOM4. 28 April 2015. W3C Last Call Working Draft. URL: http://www.w3.org/TR/dom/
[ECMASCRIPT]
Allen Wirfs-Brock. ECMAScript 2015 Language Specification. Draft. URL: https://people.mozilla.org/~jorendorff/es6-draft.html
[GEOLOCATION-API]
Andrei Popescu. Geolocation API Specification. 24 October 2013. W3C Recommendation. URL: http://www.w3.org/TR/geolocation-API/
[HTML5]
Ian Hickson; Robin Berjon; Steve Faulkner; Travis Leithead; Erika Doyle Navara; Edward O'Connor; Silvia Pfeiffer. HTML5. 28 October 2014. W3C Recommendation. URL: http://www.w3.org/TR/html5/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SERVICE-WORKERS]
Alex Russell; Jungkee Song; Jake Archibald. Service Workers. 5 February 2015. W3C Working Draft. URL: http://www.w3.org/TR/service-workers/
[WEBIDL]
Cameron McCormack. Web IDL. 19 April 2012. W3C Candidate Recommendation. URL: http://www.w3.org/TR/WebIDL/
[WGS84]
National Imagery and Mapping Agency Technical Report 8350.2, Third Edition. 3 January 2000. URL: http://earth-info.nga.mil/GandG/publications/tr8350.2/wgs84fin.pdf