Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Javascript library for interfacing with the GroovTube peripheral using the Web Bluetooth API
Javascript library for interfacing with the GroovTube peripheral using the Web Bluetooth API.
The GroovTube is a unique device which makes breathing and oral motor skills visible and analyzable in a fun way. It turns your breath into real-time image effects, for people with breathing, speech, or oral motor disabilities. For more details see: https://www.groovtube.nl/en
To install the library, create a new Node.js project and install it as a dependency:
mkdir myproject
cd myproject
npm init
npm install --save groovtube
If you are using TypeScript you will also need the @types/web-bluetooth
package as a dev dependency:
npm install --save-dev @types/web-bluetooth
Next, there are two options for loading the library.
A UMD (Universal Module Definition) bundle is provided which lets you load this library using a single javascript file. This file can be loaded directly into a HTML file, after which the library is exposed as the global variable groovtube
:
mkdir example
cp node_modules/groovtube/umd.js example/groovtube.js
example/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
...
<script src="groovtube.js"></script>
<script>
(() => {
const {GroovTubeBle} = groovtube;
const gt = new GroovTubeBle(navigator.bluetooth, console.log);
// ...
})();
</script>
</body>
The second option is to use a module bundler such as webpack, esbuild, browserify and others.
Configuring such a bundler is out of scope for this readme. However, once setup properly you can simply import the library from your own javascript or typescript module:
import GroovTubeBle from 'groovtube';
const gt = new GroovTubeBle(navigator.bluetooth, console.log);
// ...
An example using webpack can be found in the "demo" directory.
This library exposes a class named GroovTubeBle
, instances of which maintain a connection to a single GroovTube device. Once connected, it will continuously poll for breath samples from the device.
Note: this guide assumes you are familiar with promises and async/await.
The first step is to construct an instance of GroovTubeBle
. The constructor must be provided with the value of navigator.bluetooth
, that is provided by web browsers supporting the Web Bluetooth API. Optionally, you can also provide a callback function which will receive log messages.
const gt = new GroovTubeBle(navigator.bluetooth, console.log);
The instance will not perform any work, such as connecting to the device, until one of its methods is called.
The second step is to prompt the user to pick a device using the requestDevice()
method:
await gt.requestDevice();
This will pop up a browser dialog, presenting the user with a list of nearby bluetooth devices. After selecting a device, the promise returned by the method will resolve. Or, if the user cancels the dialog, the promise will reject.
The list that is presented to the user will be filtered by this library. This means that unrelated bluetooth devices will not be visible. Although, do note that other bluetooth devices may be presented to the user, if they are using the same off-the-shelf hardware module as the GroovTube.
This function must be called as a direct result of a (trusted) user input event (for example a button "click"). This is a browser limitation imposed to prevent sites from spamming the user with popups (similar to how popup blockers work).
This will work:
someButton.addEventListener('click', () => {
gt.requestDevice().catch(/* ... */);
});
However, this will have no effect:
someButton.addEventListener('click', () => {
setTimeout(() => {
gt.requestDevice().catch(/* ... */);
}, 0);
});
The third step is to actually connect to the device using the connect()
method, at which point sensor values will be read continuously from the device.
await gt.connect();
If the device is turned off or goes out of range, this library will attempt to reconnect automatically.
When the disconnect()
method is called, any active connection is dropped and the library will no longer attempt to reconnect:
await gt.disconnect();
While a connection is active, the instance will emit "breath" events. The event handler is passed a single argument, which represents the strength of the sip or puff. Sips are represented by a negative fraction, puffs by a positive fraction. Some examples:
Value | |
---|---|
-1.0 | Sip at full strength |
-0.5 | Sip at half strength |
0.0 | Neutral |
0.5 | Puff at half strength |
1.0 | Puff at full strength |
You can listen for breath events by registering an event handler callback using on("breath", (number) => void)
:
gt.on('breath', (breath) => {
console.log(`Breath is now ${Math.round(breath * 100)}%`);
});
Sensors are not perfect. The sensor may suffer from noise. And it is likely that the neutral value reported by the device is offset from the intended value. This library offers two strategies for dealing with these issues.
Firstly, to help deal with noise, the library imposes a dead zone on the sensor value. Basically this means that slight variations around the neutral value are considered to be exactly neutral. When constructing an instance of GroovTubeBle
, a default value for the dead zone is set that should usually be good enough. The dead zone can be get/set using the deadZone
property:
gt.deadZone = 0.025;
Secondly, to help deal with an offset neutral, the calibrate()
method should be used. When called it causes the library to gather sensor values for some time. During this time the end-user should make sure the device is at rest. The average of the gathered sensor values are then used to offset all future breath values.
await gt.calibrate();
A minimal example:
import GroovTubeBle from 'groovtube';
const gt = new GroovTubeBle(navigator.bluetooth, console.log);
gt.on('breath', (breath) => {
console.log(`Breath is now ${Math.round(breath * 100)}%`);
});
connectButton.addEventListener('click', async () => {
try {
await gt.requestDevice();
await gt.connect();
await gt.calibrate();
} catch (err) {
console.error(err);
}
});
Also check out the "demo" directory.
Example:
import GroovTubeBle, {GroovTubeBleError, isGroovTubeBleError} from 'groovtube';
Constructor:
Properties:
deadZone: number
readyState: string
canRequestDevice: boolean
canConnect: boolean
canDisconnect: boolean
isCalibrating: boolean
breathValue: number | null
Methods:
on("readyStateChange", (string) => void)
on("calibrationStateChange", (string) => void)
on("breath", (number) => void)
on("error", (GroovTubeBleError) => void)
removeListener(string, ?Function)
requestDevice(): Promise<void>
connect(): Promise<void>
disconnect(): Promise<void>
calibrate(): Promise<void>
constructor(Bluetooth, (object) => void)
Parameters:
bluetooth: Bluetooth
- Must be set to navigator.bluetooth
.logger?: (object) => void
- May be set to a function which will receive log messages. Each message is an object containing further details.deadZone: number
A value between 0 and 1. If the sip or puff strength is lower than this value, the strength is assumed to be 0. A deadZone of 0 has no effect. A deadZone of 0.5 means that any sip or puff below 50% strength is assumed to be 0%.
console.log('The dead zone is', gt.deadZone);
gt.deadZone = 0.025;
readyState: string
Returns the current state of device discovery and connectivity. It will be set to one of the following values:
Value | |
---|---|
"no-device" | This is the initial state. requestDevice() has not been called yet, or was not successful. |
"requesting-device" | requestDevice() has just been called and we are waiting for the user to select a device. |
"have-device" | The user has picked a device, requestDevice() has just been resolved successfully. |
"connecting" | Attempting to connect to the device that has been picked by the user. Either connect() has just been called, or the library is attempting to automatically reconnect. |
"ready" | The connection has been established. Breath values will be polled for continuously while in the "ready" state. |
GroovTubeBle
instances will also emit an event whenever the ready state changes. See: on("readyStateChange", (string) => void)
.
console.log('The ready state is', gt.readyState);
canRequestDevice: boolean
Returns whether the instance is in a valid state for requestDevice()
to be called. This is a convenience for checking if the readyState
is "no-device"
or "have-device"
.
canConnect: boolean
Returns whether the instance is in a valid state for connect()
to be called. This is a convenience for checking if the readyState
is "have-device"
.
canDisconnect: boolean
Returns whether the instance is in a valid state for disconnect()
to be called. This is a convenience for checking if the readyState
is "connecting"
or "ready"
.
isCalibrating: boolean
Returns whether the instance is currently calibrating the device. This is a convenient alternative to tracking the Promise
returned by calibrate()
.
breathValue: number | null
Returns the last read breath sensor value. This value is between -1 and 1, it represents the strength of the sip or puff. Sips are represented by a negative fraction, puffs by a positive fraction. If no value has been read, if there is no active connection the value will be null
.
Some examples:
Value | |
---|---|
-1.0 | Sip at full strength |
-0.5 | Sip at half strength |
0.0 | Neutral |
0.5 | Puff at half strength |
1.0 | Puff at full strength |
console.log(`The breath value is ${Math.round(gt.breathValue * 100)}%`);
on("readyStateChange", (string) => void)
Parameters:
eventName: "readyStateChange"
listener: (readyState: string) => void
Registers a function to be called whenever the readyState
on this instance changes. The new value for readyState
will be passed as the first argument.
gt.on('readyStateChange', readyState => {
console.log('The ready state has changed to', readyState);
});
on("calibrationStateChange", (string) => void)
Parameters:
eventName: "calibrationStateChange"
listener: (calibrating: boolean) => void
Registers a function to be called whenever the isCalibrating
value on this instance changes. The new value for isCalibrating
will be passed as the first argument.
gt.on('readyStateChange', readyState => {
console.log('The ready state has changed to', readyState);
});
on("breath", (number) => void)
Parameters:
eventName: "breath"
listener: (breathValue: number) => void
Registers a function to be called whenever the breathValue
on this instance changes. The new value for breathValue
will be passed as the first argument.
gt.on('breath', breathValue => {
console.log(`The breath value is now ${Math.round(breathValue * 100)}%`);
});
on("error", (GroovTubeBleError) => void)
Parameters:
eventName: "error"
listener: (error: GroovTubeBleError) => void
Registers a function to be called whenever an error occurs that can not be reported in another way. For example if a reconnection attempt fails.
gt.on('error', error => {
console.error('An error occurred', error);
});
removeListener(string, ?Function)
Parameters:
eventName: string
listener?: Function
Removes the registration of a function that has been previously registered using the on()
method. If the listener
parameter is not given, all registrations for that event name are removed.
function handleError(error) {
console.error('An error occurred', error);
}
gt.on('error', handleError);
// ...
gt.removeListener('error', handleError);
requestDevice(): Promise<void>
Ask the user to select the GroovTube BLE peripheral. This will pop up a browser dialog, presenting the user with a list of nearby bluetooth devices. After selecting a device, the promise returned by the method will resolve. Or, if the user cancels the dialog, the promise will reject.
The list that is presented to the user will be filtered by this library. This means that unrelated bluetooth devices will not be visible. Although, do note that other bluetooth devices may be presented to the user, if they are using the same off-the-shelf hardware module.
This function must be called as a direct result of a (trusted) user input event (for example a button "click"). This is a browser limitation imposed to prevent sites from spamming the user with popups (similar to how popup blockers work).
await gt.requestDevice();
connect(): Promise<void>
Attempt to connect to the peripheral and start reading from the breath sensor.
If the connection is dropped, this instance will attempt to reconnect after a short delay, until disconnect()
is
called. This method will resolve as soon as the first successful connection has been made. The requestDevice()
method must have been successfully called first.
await gt.connect();
disconnect(): Promise<void>
Drop any active connection to the peripheral and no longer attempt to reconnect.
await gt.disconnect();
calibrate(): Promise<void>
Collect a number of samples from the device to determine the neutral value of the breath sensor.
The user should not be sipping or puffing during the calibration. It is recommended to perform calibration at least once. This method will resolve when enough samples have been collected. No "breath" events will be emitted while this method is busy.
await gt.calibrate();
Extends Error
Properties:
name: "GroovTubeBleError"
message: string
cause: unknown
All error values which originate from this library will be an instance of GroovTubeBleError
.
The cause
property might contain further details, this is usually an instance of Error
. For example, if the error originated from the bluetooth api of the browser, the cause
will be set to that error value.
Tests if the given value looks like an instance of GroovTubeBleError
. In most cases it is not necessary to use this function, instead you can simply use value instanceof GroovTubeBleError
. However, it may be useful if you are dealing with multiple javascript realms.
FAQs
Javascript library for interfacing with the GroovTube peripheral using the Web Bluetooth API
We found that groovtube demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.