Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

motion-sensors-polyfill

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

motion-sensors-polyfill - npm Package Compare versions

Comparing version 0.3.1 to 0.3.7

6

package.json
{
"name": "motion-sensors-polyfill",
"version": "0.3.1",
"version": "0.3.7",
"description": "A polyfill for the motion sensors based on the W3C Generic Sensor API",

@@ -27,3 +27,7 @@ "main": "motion-sensors.js",

"url": "git+https://github.com/kenchris/sensor-polyfills.git"
},
"devDependencies": {
"eslint": "^5.3.0",
"eslint-config-google": "^0.9.1"
}
}

46

README.md
W3C Generic Sensor API polyfills
===
*Beware, we're still under active development. Expect rough edges.*
This is a polyfill for [Generic Sensor](https://w3c.github.io/sensors/)-based [motions sensors](https://w3c.github.io/motion-sensors/) to make migration from the old [DeviceOrientationEvent](https://w3c.github.io/deviceorientation/spec-source-orientation.html#deviceorientation)/[DeviceMotionEvent](https://w3c.github.io/deviceorientation/spec-source-orientation.html#devicemotion) to the new APIs a smoother experience.
In particular, this polyfill will allow the users of modern browsers to get a feel of the new API shape before it ships ([Chrome 63 has a native implementation](#how-to-enable-the-native-implementation-in-chrome)).
In particular, this polyfill will allow the users of modern browsers to get a feel of the new API shape before it ships more broadly.

@@ -20,2 +18,6 @@ `src/motion-sensors.js` implements the following interfaces:

`src/geolocation.js` implements the following interface:
- [`GeolocationSensor`](https://w3c.github.io/geolocation-sensor/#geolocationsensor-interface)
How to use the polyfill

@@ -44,31 +46,5 @@ ===

There are two ways: *Origin Trial* and *Enable via `chrome://flags`*.
*Chrome 67 or later:* the native implementation is enabled by default.
## Origin Trial
Generic Sensor APIs are currently available as an [Origin Trial](https://bit.ly/OriginTrials) in Chrome 63+.
To enable native Generic Sensor API implementation for all Chrome users on your site:
1. Go to https://bit.ly/OriginTrialsSignup to get a token.
2. Add the token to your web page as follows (replace `...` with your token):
```
<!-- Origin Trial Token, feature = Generic Sensors, origin = https://example.org, expires ="2018-01-18" -->
<meta http-equiv="origin-trial" data-feature="Generic Sensors" data-expires="2018-01-18" content="...">
```
3. Optional: add `motion-sensors.js` polyfill to cater for non-Chrome users (see [How to use the polyfill](#how-to-use-the-polyfill)).
## Enable via `chrome://flags`
The native implementation is behind the following feature flags in Chrome 63+:
Generic Sensor (`chrome://flags/#enable-generic-sensor`):
- `Accelerometer`
- `Gyroscope`
- `LinearAccelerationSensor`
- `AbsoluteOrientationSensor`
- `RelativeOrientationSensor`
Generic Sensor Extra Classes (`chrome://flags/#enable-generic-sensor-extra-classes`):
The Generic Sensor Extra Classes (`chrome://flags/#enable-generic-sensor-extra-classes`) feature flag can be activated to enable a few additional sensor types:
- `AmbientLightSensor`

@@ -82,7 +58,5 @@ - `Magnetometer`

Known issues
===
- `GravitySensor` and `LinearAccelerationSensor` polyfills do not work on Android with Pixel 2, since [`DeviceMotionEvent`](http://w3c.github.io/deviceorientation/spec-source-orientation.html#devicemotion_event)`.acceleration` returns only null values, see [Chromium bug 796518](https://crbug.com/796518).
- `AbsoluteOrientationSensor` on iOS uses non-standard [`webkitCompassHeading`](https://developer.apple.com/documentation/webkitjs/deviceorientationevent/1804777-webkitcompassheading) that reports wrong readings if the device is held in its [`portrait-secondary`](https://w3c.github.io/screen-orientation/#dom-orientationtype-portrait-secondary) orientation. Specifically, the `webkitCompassHeading` flips by 180 degrees when tilted only slightly.

@@ -93,2 +67,6 @@

- [Sensors For The Web article on Google's Web Fundaments](https://developers.google.com/web/updates/2017/09/sensors-for-the-web) - a web developer-oriented article explaining how to use the Generic Sensor-based APIs.
- [Sensors For The Web article on Google's Web Fundaments](https://developers.google.com/web/updates/2017/09/sensors-for-the-web) - a web developer-oriented article explaining how to use the Generic Sensor-based APIs.
Reporting a security issue
===
If you have information about a security issue or vulnerability with an Intel-maintained open source project on https://github.com/intel, please send an e-mail to secure@intel.com. Encrypt sensitive information using our PGP public key. For issues related to Intel products, please visit https://security-center.intel.com.
// @ts-check
import { __sensor__, Sensor } from "./sensor.js";
import {
__sensor__,
Sensor,
SensorErrorEvent,
activateCallback,
deactivateCallback,
notifyActivatedState,
notifyError,
// AbortController,
// AbortSignal,
} from './sensor.js';
const slot = __sensor__;
class GeolocationSensorSingleton {
constructor() {
if (!this.constructor.instance) {
this.constructor.instance = this;
}
this.sensors = new Set();
this.watchId = null;
this.accuracy = null;
this.lastPosition = null;
return this.constructor.instance;
async function obtainPermission() {
let state = 'prompt'; // Default for geolocation.
// @ts-ignore
if (navigator.permissions) {
// @ts-ignore
const permission = await navigator.permissions.query({name: 'geolocation'});
state = permission.state;
}
async obtainPermission() {
let state = "prompt"; // Default for geolocation.
// @ts-ignore
if (navigator.permissions) {
// @ts-ignore
const permission = await navigator.permissions.query({ name:"geolocation"});
state = permission.state;
return new Promise((resolve) => {
if (state === 'granted') {
return resolve(state);
}
return new Promise(resolve => {
const successFn = position => {
this.lastPosition = position;
resolve("granted");
}
const errorFn = err => {
if (err.code === err.PERMISSION_DENIED) {
resolve("denied");
} else {
resolve(state);
}
const successFn = (_) => {
resolve('granted');
};
const errorFn = (err) => {
if (err.code === err.PERMISSION_DENIED) {
resolve('denied');
} else {
resolve(state);
}
};
const options = { maximumAge: Infinity, timeout: 10 };
navigator.geolocation.getCurrentPosition(successFn, errorFn, options);
});
}
const options = {maximumAge: Infinity, timeout: 0};
navigator.geolocation.getCurrentPosition(successFn, errorFn, options);
});
}
calculateAccuracy() {
let enableHighAccuracy = false;
function register(options, onreading, onerror, onactivated) {
const handleEvent = (position) => {
const timestamp = position.timestamp - performance.timing.navigationStart;
const coords = position.coords;
for (const sensor of this.sensors) {
if (sensor[slot].options.accuracy === "high") {
enableHighAccuracy = true;
onreading(timestamp, coords);
};
const handleError = (error) => {
let type;
switch (error.code) {
case error.TIMEOUT:
type = 'TimeoutError';
break;
}
case error.PERMISSION_DENIED:
type = 'NotAllowedError';
break;
case error.POSITION_UNAVAILABLE:
type = 'NotReadableError';
break;
default:
type = 'UnknownError';
}
return enableHighAccuracy;
}
onerror(error.message, type);
};
async register(sensor) {
const permission = await this.obtainPermission();
if (permission !== "granted") {
sensor[slot].notifyError("Permission denied.", "NowAllowedError");
return;
}
const watchOptions = {
enableHighAccuracy: false,
maximumAge: 0,
timeout: Infinity,
};
if (this.lastPosition) {
const age = performance.now() - this.lastPosition.timeStamp;
const maxAge = sensor[slot].options.maxAge;
if (maxAge == null || age <= maxAge) {
sensor[slot].handleEvent(age, this.lastPosition.coords);
}
}
const watchId = navigator.geolocation.watchPosition(
handleEvent, handleError, watchOptions
);
this.sensors.add(sensor);
return watchId;
}
// Check whether we need to reconfigure our navigation.geolocation
// watch, ie. tear it down and recreate.
const accuracy = this.calculateAccuracy();
if (this.watchId && this.accuracy === accuracy) {
// We don't need to reset, return.
return;
}
function deregister(watchId) {
navigator.geolocation.clearWatch(watchId);
}
if (this.watchId) {
navigator.geolocation.clearWatch(this.watchId);
// Old geolocation API is FILO; on Chrome at least.
class FIFOGeolocationEvents {
constructor() {
if (!this.constructor.instance) {
this.constructor.instance = this;
}
const handleEvent = position => {
this.lastPosition = position;
// You can iterate through the elements of a map in insertion order.
this.subscribers = new Map();
this.options = {};
this.watchId = null;
const timestamp = position.timestamp - performance.timing.navigationStart;
const coords = position.coords;
this.lastReading = null;
for (const sensor of this.sensors) {
sensor[slot].handleEvent(timestamp, coords);
}
return this.constructor.instance;
}
unsubscribe(obj) {
this.subscribers.delete(obj);
if (!this.subscribers.size && this.watchId) {
deregister(this.watchId);
this.watchId = null;
}
}
const handleError = error => {
let type;
switch(error.code) {
case error.TIMEOUT:
type = "TimeoutError";
break;
case error.PERMISSION_DENIED:
type = "NotAllowedError";
break;
case error.POSITION_UNAVAILABLE:
type = "NotReadableError";
break;
default:
type = "UnknownError";
subscribe(obj, options, onreading, onerror) {
const fifoOnReading = (...args) => {
this.lastReading = args;
for ({onreading} of this.subscribers.values()) {
if (typeof onreading === 'function') {
onreading(...args);
}
}
for (const sensor of this.sensors) {
sensor[slot].handleError(error.message, type);
};
const fifoOnError = (...args) => {
for ({onerror} of this.subscribers.values()) {
if (typeof onerror === 'function') {
onerror(...args);
}
}
}
};
const options = {
enableHighAccuracy: accuracy,
maximumAge: 0,
timeout: Infinity
// TODO(spec): Generate the most precise options here
// ie. lowest maximum-age and highest precision.
this.options = options;
if (this.watchId) {
deregister(this.watchId);
}
this.watchId = navigator.geolocation.watchPosition(
handleEvent, handleError, options
// TODO(spec): Ensure lastReading is still valid.
if (this.lastReading && typeof onreading === 'function') {
onreading(...this.lastReading);
}
this.subscribers.set(obj, {onreading, onerror});
this.watchId = register(this.options,
fifoOnReading, fifoOnError
);
}
deregister(sensor) {
this.sensors.delete(sensor);
if (!this.sensors.size && this.watchId) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
}

@@ -143,2 +154,48 @@

class GeolocationSensor extends Sensor {
static async read(options = {}) {
return new Promise(async (resolve, reject) => {
const onerror = (message, name) => {
let error = new SensorErrorEvent('error', {
error: new DOMException(message, name),
});
deregister(watchId);
reject(error);
};
const onreading = (timestamp, coords) => {
const reading = {
timestamp,
accuracy: coords.accuracy,
altitude: coords.altitude,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
latitude: coords.latitude,
longitude: coords.longitude,
speed: coords.speed,
};
deregister(watchId);
resolve(reading);
};
const signal = options.signal;
if (signal && signal.aborted) {
return reject(new DOMException('Read was cancelled', 'AbortError'));
}
const permission = await obtainPermission();
if (permission !== 'granted') {
onerror('Permission denied.', 'NowAllowedError');
return;
}
const watchId = register(options, onreading, onerror);
if (signal) {
signal.addEventListener('abort', () => {
deregister(watchId);
reject(new DOMException('Read was cancelled', 'AbortError'));
});
}
});
};
constructor(options = {}) {

@@ -148,2 +205,3 @@ super(options);

this[slot].options = options;
this[slot].fifo = new FIFOGeolocationEvents();

@@ -157,18 +215,17 @@ const props = {

heading: null,
speed: null
}
speed: null,
};
const propertyBag = this[slot];
/* eslint-disable-next-line guard-for-in */
for (const propName in props) {
propertyBag[propName] = props[propName];
Object.defineProperty(this, propName, {
get: () => propertyBag[propName]
get: () => propertyBag[propName],
});
}
}
this[slot].handleEvent = (timestamp, coords) => {
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
async [activateCallback]() {
const onreading = (timestamp, coords) => {
this[slot].timestamp = timestamp;

@@ -185,17 +242,39 @@

this[slot].hasReading = true;
this.dispatchEvent(new Event("reading"));
}
this.dispatchEvent(new Event('reading'));
};
this[slot].handleError = (message, type) => {
this[slot].notifyError(message, type);
}
const onerror = (message, type) => {
this[notifyError](message, type);
};
this[slot].activateCallback = () => {
(new GeolocationSensorSingleton()).register(this);
const permission = await obtainPermission();
if (permission !== 'granted') {
onerror('Permission denied.', 'NowAllowedError');
return;
}
this[slot].deactivateCallback = () => {
(new GeolocationSensorSingleton()).deregister(this);
this[slot].fifo.subscribe(
this, this[slot].options,
onreading, onerror
);
if (!this[slot].activated) {
this[notifyActivatedState]();
}
}
}
[deactivateCallback]() {
this[slot].fifo.unsubscribe(this);
this[slot].timestamp = null;
this[slot].accuracy = null;
this[slot].altitude = null;
this[slot].altitudeAccuracy = null;
this[slot].heading = null;
this[slot].latitude = null;
this[slot].longitude = null;
this[slot].speed = null;
this[slot].hasReading = false;
}
};
// @ts-check
import { __sensor__, Sensor, defineReadonlyProperties } from "./sensor.js";
import {
Sensor,
defineReadonlyProperties,
__sensor__,
notifyError,
notifyActivatedState,
activateCallback,
deactivateCallback,
} from './sensor.js';
const slot = __sensor__;
const handleEventCallback = Symbol('handleEvent');

@@ -16,7 +25,23 @@ let orientation;

orientation = {};
Object.defineProperty(orientation, "angle", {
get: () => { return (window.orientation || 0) }
Object.defineProperty(orientation, 'angle', {
get: () => {
return (window.orientation || 0);
},
});
}
const rotationToRadian = (function() {
// Returns Chrome version, or null if not Chrome.
const match = navigator.userAgent.match(/.*Chrome\/([0-9]+)/);
const chromeVersion = match ? parseInt(match[1], 10) : null;
// DeviceMotion/Orientation API return deg/s (except Chrome,
// but fixing in M66). Gyroscope needs rad/s.
const returnsDegrees = chromeVersion === null || chromeVersion >= 66;
const conversion = returnsDegrees ? Math.PI / 180 : 1.0;
return function(value) {
return value * conversion;
};
})();
const DeviceOrientationMixin = (superclass, ...eventNames) => class extends superclass {

@@ -33,10 +58,10 @@ constructor(...args) {

}
}
this[slot].activateCallback = () => {
window.addEventListener(this[slot].eventName, this[slot].handleEvent, { capture: true });
}
[activateCallback]() {
window.addEventListener(this[slot].eventName, this[handleEventCallback].bind(this), {capture: true});
}
this[slot].deactivateCallback = () => {
window.removeEventListener(this[slot].eventName, this[slot].handleEvent, { capture: true });
}
[deactivateCallback]() {
window.removeEventListener(this[slot].eventName, this[handleEventCallback].bind(this), {capture: true});
}

@@ -46,3 +71,3 @@ };

function toQuaternionFromEuler(alpha, beta, gamma) {
const degToRad = Math.PI / 180
const degToRad = Math.PI / 180;

@@ -76,3 +101,3 @@ const x = (beta || 0) * degToRad;

axis[2] * sHalfAngle,
cHalfAngle
cHalfAngle,
];

@@ -95,3 +120,3 @@

return quat.map(v => v / length);
return quat.map((v) => v / length);
}

@@ -130,3 +155,3 @@

function worldToScreen(quaternion) {
function deviceToScreen(quaternion) {
return !quaternion ? null :

@@ -142,50 +167,50 @@ rotateQuaternionByAxisAngle(

export const RelativeOrientationSensor = window.RelativeOrientationSensor ||
class RelativeOrientationSensor extends DeviceOrientationMixin(Sensor, "deviceorientation") {
class RelativeOrientationSensor extends DeviceOrientationMixin(Sensor, 'deviceorientation') {
constructor(options = {}) {
super(options);
switch (options.coordinateSystem || 'world') {
switch (options.referenceFrame || 'device') {
case 'screen':
Object.defineProperty(this, "quaternion", {
get: () => worldToScreen(this[slot].quaternion)
Object.defineProperty(this, 'quaternion', {
get: () => deviceToScreen(this[slot].quaternion),
});
break;
case 'world':
default:
Object.defineProperty(this, "quaternion", {
get: () => this[slot].quaternion
default: // incl. case 'device'
Object.defineProperty(this, 'quaternion', {
get: () => this[slot].quaternion,
});
}
}
this[slot].handleEvent = event => {
// If there is no sensor we will get values equal to null.
if (event.absolute || event.alpha === null) {
// Spec: The implementation can still decide to provide
// absolute orientation if relative is not available or
// the resulting data is more accurate. In either case,
// the absolute property must be set accordingly to reflect
// the choice.
this[slot].notifyError("Could not connect to a sensor", "NotReadableError");
return;
}
[handleEventCallback](event) {
// If there is no sensor we will get values equal to null.
if (event.absolute || event.alpha === null) {
// Spec: The implementation can still decide to provide
// absolute orientation if relative is not available or
// the resulting data is more accurate. In either case,
// the absolute property must be set accordingly to reflect
// the choice.
this[notifyError]('Could not connect to a sensor', 'NotReadableError');
return;
}
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
if (!this[slot].activated) {
this[notifyActivatedState]();
}
this[slot].timestamp = performance.now();
this[slot].timestamp = performance.now();
this[slot].quaternion = toQuaternionFromEuler(
event.alpha,
event.beta,
event.gamma
);
this[slot].quaternion = toQuaternionFromEuler(
event.alpha,
event.beta,
event.gamma
);
this[slot].hasReading = true;
this.dispatchEvent(new Event("reading"));
}
this[slot].hasReading = true;
this.dispatchEvent(new Event('reading'));
}
this[slot].deactivateCallback = () => {
this[slot].quaternion = null;
}
[deactivateCallback]() {
super[deactivateCallback]();
this[slot].quaternion = null;
}

@@ -196,3 +221,3 @@

}
}
};

@@ -202,54 +227,54 @@ // @ts-ignore

class AbsoluteOrientationSensor extends DeviceOrientationMixin(
Sensor, "deviceorientationabsolute", "deviceorientation") {
Sensor, 'deviceorientationabsolute', 'deviceorientation') {
constructor(options = {}) {
super(options);
switch (options.coordinateSystem || 'world') {
switch (options.referenceFrame || 'device') {
case 'screen':
Object.defineProperty(this, "quaternion", {
get: () => worldToScreen(this[slot].quaternion)
Object.defineProperty(this, 'quaternion', {
get: () => deviceToScreen(this[slot].quaternion),
});
break;
case 'world':
default:
Object.defineProperty(this, "quaternion", {
get: () => this[slot].quaternion
default: // incl. case 'device'
Object.defineProperty(this, 'quaternion', {
get: () => this[slot].quaternion,
});
}
}
this[slot].handleEvent = event => {
// If absolute is set, or webkitCompassHeading exists,
// absolute values should be available.
const isAbsolute = event.absolute === true || "webkitCompassHeading" in event;
const hasValue = event.alpha !== null || event.webkitCompassHeading !== undefined;
[handleEventCallback](event) {
// If absolute is set, or webkitCompassHeading exists,
// absolute values should be available.
const isAbsolute = event.absolute === true || 'webkitCompassHeading' in event;
const hasValue = event.alpha !== null || event.webkitCompassHeading !== undefined;
if (!isAbsolute || !hasValue) {
// Spec: If an implementation can never provide absolute
// orientation information, the event should be fired with
// the alpha, beta and gamma attributes set to null.
this[slot].notifyError("Could not connect to a sensor", "NotReadableError");
return;
}
if (!isAbsolute || !hasValue) {
// Spec: If an implementation can never provide absolute
// orientation information, the event should be fired with
// the alpha, beta and gamma attributes set to null.
this[notifyError]('Could not connect to a sensor', 'NotReadableError');
return;
}
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
if (!this[slot].activated) {
this[notifyActivatedState]();
}
this[slot].hasReading = true;
this[slot].timestamp = performance.now();
this[slot].hasReading = true;
this[slot].timestamp = performance.now();
const heading = event.webkitCompassHeading != null ? 360 - event.webkitCompassHeading : event.alpha;
const heading = event.webkitCompassHeading != null ? 360 - event.webkitCompassHeading : event.alpha;
this[slot].quaternion = toQuaternionFromEuler(
heading,
event.beta,
event.gamma
);
this[slot].quaternion = toQuaternionFromEuler(
heading,
event.beta,
event.gamma
);
this.dispatchEvent(new Event("reading"));
}
this.dispatchEvent(new Event('reading'));
}
this[slot].deactivateCallback = () => {
this[slot].quaternion = null;
}
[deactivateCallback]() {
super[deactivateCallback]();
this[slot].quaternion = null;
}

@@ -260,162 +285,166 @@

}
}
};
// @ts-ignore
export const Gyroscope = window.Gyroscope ||
class Gyroscope extends DeviceOrientationMixin(Sensor, "devicemotion") {
class Gyroscope extends DeviceOrientationMixin(Sensor, 'devicemotion') {
constructor(options) {
super(options);
this[slot].handleEvent = event => {
// If there is no sensor we will get values equal to null.
if (event.rotationRate.alpha === null) {
this[slot].notifyError("Could not connect to a sensor", "NotReadableError");
return;
}
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
this[slot].timestamp = performance.now();
this[slot].x = event.rotationRate.alpha;
this[slot].y = event.rotationRate.beta;
this[slot].z = event.rotationRate.gamma;
this[slot].hasReading = true;
this.dispatchEvent(new Event("reading"));
}
defineReadonlyProperties(this, slot, {
x: null,
y: null,
z: null
z: null,
});
}
this[slot].deactivateCallback = () => {
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
[handleEventCallback](event) {
// If there is no sensor we will get values equal to null.
if (event.rotationRate.alpha === null) {
this[notifyError]('Could not connect to a sensor', 'NotReadableError');
return;
}
if (!this[slot].activated) {
this[notifyActivatedState]();
}
this[slot].timestamp = performance.now();
this[slot].x = rotationToRadian(event.rotationRate.alpha);
this[slot].y = rotationToRadian(event.rotationRate.beta);
this[slot].z = rotationToRadian(event.rotationRate.gamma);
this[slot].hasReading = true;
this.dispatchEvent(new Event('reading'));
}
}
[deactivateCallback]() {
super[deactivateCallback]();
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
}
};
// @ts-ignore
export const Accelerometer = window.Accelerometer ||
class Accelerometer extends DeviceOrientationMixin(Sensor, "devicemotion") {
class Accelerometer extends DeviceOrientationMixin(Sensor, 'devicemotion') {
constructor(options) {
super(options);
this[slot].handleEvent = event => {
// If there is no sensor we will get values equal to null.
if (event.accelerationIncludingGravity.x === null) {
this[slot].notifyError("Could not connect to a sensor", "NotReadableError");
return;
}
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
this[slot].timestamp = performance.now();
this[slot].x = event.accelerationIncludingGravity.x;
this[slot].y = event.accelerationIncludingGravity.y;
this[slot].z = event.accelerationIncludingGravity.z;
this[slot].hasReading = true;
this.dispatchEvent(new Event("reading"));
}
defineReadonlyProperties(this, slot, {
x: null,
y: null,
z: null
z: null,
});
}
this[slot].deactivateCallback = () => {
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
[handleEventCallback](event) {
// If there is no sensor we will get values equal to null.
if (event.accelerationIncludingGravity.x === null) {
this[notifyError]('Could not connect to a sensor', 'NotReadableError');
return;
}
if (!this[slot].activated) {
this[notifyActivatedState]();
}
this[slot].timestamp = performance.now();
this[slot].x = event.accelerationIncludingGravity.x;
this[slot].y = event.accelerationIncludingGravity.y;
this[slot].z = event.accelerationIncludingGravity.z;
this[slot].hasReading = true;
this.dispatchEvent(new Event('reading'));
}
}
[deactivateCallback]() {
super[deactivateCallback]();
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
}
};
// @ts-ignore
export const LinearAccelerationSensor = window.LinearAccelerationSensor ||
class LinearAccelerationSensor extends DeviceOrientationMixin(Sensor, "devicemotion") {
class LinearAccelerationSensor extends DeviceOrientationMixin(Sensor, 'devicemotion') {
constructor(options) {
super(options);
this[slot].handleEvent = event => {
// If there is no sensor we will get values equal to null.
if (event.acceleration.x === null) {
this[slot].notifyError("Could not connect to a sensor", "NotReadableError");
return;
}
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
this[slot].timestamp = performance.now();
this[slot].x = event.acceleration.x;
this[slot].y = event.acceleration.y;
this[slot].z = event.acceleration.z;
this[slot].hasReading = true;
this.dispatchEvent(new Event("reading"));
}
defineReadonlyProperties(this, slot, {
x: null,
y: null,
z: null
z: null,
});
}
this[slot].deactivateCallback = () => {
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
[handleEventCallback](event) {
// If there is no sensor we will get values equal to null.
if (event.acceleration.x === null) {
this[notifyError]('Could not connect to a sensor', 'NotReadableError');
return;
}
if (!this[slot].activated) {
this[notifyActivatedState]();
}
this[slot].timestamp = performance.now();
this[slot].x = event.acceleration.x;
this[slot].y = event.acceleration.y;
this[slot].z = event.acceleration.z;
this[slot].hasReading = true;
this.dispatchEvent(new Event('reading'));
}
}
[deactivateCallback]() {
super[deactivateCallback]();
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
}
};
// @ts-ignore
export const GravitySensor = window.GravitySensor ||
class GravitySensor extends DeviceOrientationMixin(Sensor, "devicemotion") {
class GravitySensor extends DeviceOrientationMixin(Sensor, 'devicemotion') {
constructor(options) {
super(options);
this[slot].handleEvent = event => {
// If there is no sensor we will get values equal to null.
if (event.acceleration.x === null || event.accelerationIncludingGravity.x === null) {
this[slot].notifyError("Could not connect to a sensor", "NotReadableError");
return;
}
if (!this[slot].activated) {
this[slot].notifyActivatedState();
}
this[slot].timestamp = performance.now();
this[slot].x = event.accelerationIncludingGravity.x - event.acceleration.x;
this[slot].y = event.accelerationIncludingGravity.y - event.acceleration.y;
this[slot].z = event.accelerationIncludingGravity.z - event.acceleration.z;
this[slot].hasReading = true;
this.dispatchEvent(new Event("reading"));
}
defineReadonlyProperties(this, slot, {
x: null,
y: null,
z: null
z: null,
});
}
this[slot].deactivateCallback = () => {
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
[handleEventCallback](event) {
// If there is no sensor we will get values equal to null.
if (event.acceleration.x === null || event.accelerationIncludingGravity.x === null) {
this[notifyError]('Could not connect to a sensor', 'NotReadableError');
return;
}
if (!this[slot].activated) {
this[notifyActivatedState]();
}
this[slot].timestamp = performance.now();
this[slot].x = event.accelerationIncludingGravity.x - event.acceleration.x;
this[slot].y = event.accelerationIncludingGravity.y - event.acceleration.y;
this[slot].z = event.accelerationIncludingGravity.z - event.acceleration.z;
this[slot].hasReading = true;
this.dispatchEvent(new Event('reading'));
}
}
[deactivateCallback]() {
super[deactivateCallback]();
this[slot].x = null;
this[slot].y = null;
this[slot].z = null;
}
};
// @ts-check
export const __sensor__ = Symbol("__sensor__");
const slot = __sensor__;
function defineProperties(target, descriptions) {
/* eslint-disable-next-line guard-for-in */
for (const property in descriptions) {
Object.defineProperty(target, property, {
configurable: true,
value: descriptions[property]
value: descriptions[property],
});

@@ -15,2 +13,4 @@ }

const privates = new WeakMap();
export const EventTargetMixin = (superclass, ...eventNames) => class extends superclass {

@@ -21,44 +21,104 @@ constructor(...args) {

const eventTarget = document.createDocumentFragment();
privates.set(this, eventTarget);
}
this.addEventListener = (type, ...args) => {
return eventTarget.addEventListener(type, ...args);
addEventListener(type, ...args) {
const eventTarget = privates.get(this);
return eventTarget.addEventListener(type, ...args);
}
removeEventListener(...args) {
const eventTarget = privates.get(this);
// @ts-ignore
return eventTarget.removeEventListener(...args);
}
dispatchEvent(event) {
defineProperties(event, {currentTarget: this});
if (!event.target) {
defineProperties(event, {target: this});
}
this.removeEventListener = (...args) => {
// @ts-ignore
return eventTarget.removeEventListener(...args);
const eventTarget = privates.get(this);
const retValue = eventTarget.dispatchEvent(event);
if (retValue && this.parentNode) {
this.parentNode.dispatchEvent(event);
}
this.dispatchEvent = (event) => {
defineProperties(event, { currentTarget: this });
if (!event.target) {
defineProperties(event, { target: this });
}
defineProperties(event, {currentTarget: null, target: null});
return retValue;
}
};
export class EventTarget extends EventTargetMixin(Object) {}
const __abort__ = Symbol('__abort__');
export class AbortSignal extends EventTarget {
constructor() {
super();
this[__abort__] = {
aborted: false,
};
defineOnEventListener(this, 'abort');
Object.defineProperty(this, 'aborted', {
get: () => this[__abort__].aborted,
});
}
dispatchEvent(event) {
if (event.type === 'abort') {
this[__abort__].aborted = true;
const methodName = `on${event.type}`;
if (typeof this[methodName] == "function") {
if (typeof this[methodName] == 'function') {
this[methodName](event);
}
}
super.dispatchEvent(event);
}
const retValue = eventTarget.dispatchEvent(event);
toString() {
return '[object AbortSignal]';
}
}
if (retValue && this.parentNode) {
this.parentNode.dispatchEvent(event);
}
export class AbortController {
constructor() {
const signal = new AbortSignal();
Object.defineProperty(this, 'signal', {
get: () => signal,
});
}
defineProperties(event, { currentTarget: null, target: null });
abort() {
let abort = new Event('abort');
this.signal.dispatchEvent(abort);
}
return retValue;
}
toString() {
return '[object AbortController]';
}
};
}
export class EventTarget extends EventTargetMixin(Object) {};
function defineOnEventListener(target, name) {
Object.defineProperty(target, `on${name}`, {
enumerable: true,
configurable: false,
writable: true,
value: null,
});
}
export function defineReadonlyProperties(target, slot, descriptions) {
const propertyBag = target[slot];
/* eslint-disable-next-line guard-for-in */
for (const property in descriptions) {
propertyBag[property] = descriptions[property];
Object.defineProperty(target, property, {
get: () => propertyBag[property]
get: () => propertyBag[property],
});

@@ -68,3 +128,3 @@ }

class SensorErrorEvent extends Event {
export class SensorErrorEvent extends Event {
constructor(type, errorEventInitDict) {

@@ -75,22 +135,13 @@ super(type, errorEventInitDict);

throw TypeError(
"Failed to construct 'SensorErrorEvent':" +
"2nd argument much contain 'error' property"
'Failed to construct \'SensorErrorEvent\':' +
'2nd argument much contain \'error\' property'
);
}
Object.defineProperty(this, "error", {
Object.defineProperty(this, 'error', {
configurable: false,
writable: false,
value: errorEventInitDict.error
value: errorEventInitDict.error,
});
}
};
function defineOnEventListener(target, name) {
Object.defineProperty(target, `on${name}`, {
enumerable: true,
configurable: false,
writable: true,
value: null
});
}

@@ -102,46 +153,68 @@

ACTIVE: 3,
}
};
export const __sensor__ = Symbol('__sensor__');
const slot = __sensor__;
export const notifyError = Symbol('Sensor.notifyError');
export const notifyActivatedState = Symbol('Sensor.notifyActivatedState');
export const activateCallback = Symbol('Sensor.activateCallback');
export const deactivateCallback = Symbol('Sensor.deactivateCallback');
export class Sensor extends EventTarget {
[activateCallback]() {}
[deactivateCallback]() {}
[notifyError](message, name) {
let error = new SensorErrorEvent('error', {
error: new DOMException(message, name),
});
this.dispatchEvent(error);
this.stop();
}
[notifyActivatedState]() {
let activate = new Event('activate');
this[slot].activated = true;
this.dispatchEvent(activate);
this[slot].state = SensorState.ACTIVE;
}
constructor(options) {
super();
this[slot] = new WeakMap;
defineOnEventListener(this, "reading");
defineOnEventListener(this, "activate");
defineOnEventListener(this, "error");
this[__sensor__] = {
// Internal slots
state: SensorState.IDLE,
frequency: null,
defineReadonlyProperties(this, slot, {
// Property backing
activated: false,
hasReading: false,
timestamp: null
})
timestamp: null,
};
this[slot].state = SensorState.IDLE;
defineOnEventListener(this, 'reading');
defineOnEventListener(this, 'activate');
defineOnEventListener(this, 'error');
this[slot].notifyError = (message, name) => {
let error = new SensorErrorEvent("error", {
error: new DOMException(message, name)
});
this.dispatchEvent(error);
this.stop();
}
Object.defineProperty(this, 'activated', {
get: () => this[slot].activated,
});
Object.defineProperty(this, 'hasReading', {
get: () => this[slot].hasReading,
});
Object.defineProperty(this, 'timestamp', {
get: () => this[slot].timestamp,
});
this[slot].notifyActivatedState = () => {
let activate = new Event("activate");
this[slot].activated = true;
this.dispatchEvent(activate);
this[slot].state = SensorState.ACTIVE;
}
this[slot].activateCallback = () => {};
this[slot].deactivateCallback = () => {};
this[slot].frequency = null;
if (window && window.parent != window.top) {
throw new DOMException("Only instantiable in a top-level browsing context", "SecurityError");
throw new DOMException(
'Only instantiable in a top-level browsing context',
'SecurityError'
);
}
if (options && typeof(options.frequency) == "number") {
if (options && typeof(options.frequency) == 'number') {
if (options.frequency > 60) {

@@ -153,8 +226,27 @@ this.frequency = options.frequency;

dispatchEvent(event) {
switch (event.type) {
case 'reading':
case 'error':
case 'activate':
{
const methodName = `on${event.type}`;
if (typeof this[methodName] == 'function') {
this[methodName](event);
}
super.dispatchEvent(event);
break;
}
default:
super.dispatchEvent(event);
}
}
start() {
if (this[slot].state === SensorState.ACTIVATING || this[slot].state === SensorState.ACTIVE) {
if (this[slot].state === SensorState.ACTIVATING ||
this[slot].state === SensorState.ACTIVE) {
return;
}
this[slot].state = SensorState.ACTIVATING;
this[slot].activateCallback();
this[activateCallback]();
}

@@ -169,6 +261,6 @@

this[slot].timestamp = null;
this[slot].deactivateCallback();
this[deactivateCallback]();
this[slot].state = SensorState.IDLE;
}
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc