unifi-events
Advanced tools
Comparing version 0.4.3 to 2.0.0-beta.1
311
index.js
@@ -1,154 +0,199 @@ | ||
'use strict' | ||
const url = require('url'); | ||
const EventEmitter = require('eventemitter2').EventEmitter2; | ||
const WebSocket = require('ws'); | ||
const rp = require('request-promise'); | ||
const url = require('url') | ||
const rp = require('request-promise') | ||
const EventEmitter = require('events') | ||
const WebSocket = require('@oznu/ws-connect') | ||
module.exports = class UnifiEvents extends EventEmitter { | ||
constructor (opts) { | ||
super() | ||
this.opts = opts | ||
this.opts.site = this.opts.site || 'default' | ||
this.userAgent = 'UniFi Events' | ||
this.controller = url.parse(this.opts.controller) | ||
this.jar = rp.jar() | ||
this.rp = rp.defaults({ | ||
rejectUnauthorized: this.opts.rejectUnauthorized, | ||
jar: this.jar, | ||
headers: { | ||
'User-Agent': this.userAgent | ||
} | ||
}) | ||
constructor(opts) { | ||
super({ | ||
wildcard: true | ||
}); | ||
// login and start listening | ||
if (this.opts.listen !== false) { | ||
this.connect() | ||
this.opts = opts || {}; | ||
this.opts.host = this.opts.host || 'unifi'; | ||
this.opts.port = this.opts.port || 8443; | ||
this.opts.username = this.opts.username || 'admin'; | ||
this.opts.password = this.opts.password || 'ubnt'; | ||
this.opts.site = this.opts.site || 'default'; | ||
this.opts.unifios = this.opts.unifios || false; | ||
this.userAgent = 'node.js unifi-events UniFi Events'; | ||
this.controller = url.parse('https://' + this.opts.host + ':' + this.opts.port); | ||
this.jar = rp.jar(); | ||
this.rp = rp.defaults({ | ||
rejectUnauthorized: !this.opts.insecure, | ||
jar: this.jar, | ||
headers: { | ||
'User-Agent': this.userAgent | ||
}, | ||
json: true | ||
}); | ||
this.autoReconnectInterval = 5 * 1000; | ||
this.connect(); | ||
} | ||
// convenience emitters | ||
this.helpers = { | ||
'EVT_WU_Connected': 'connected', | ||
'EVT_WU_Disconnected': 'disconnected', | ||
'EVT_WG_Connected': 'connected', | ||
'EVT_WG_Disconnected': 'disconnected', | ||
'EVT_LU_CONNECTED': 'connected', | ||
'EVT_LU_DISCONNECTED': 'disconnected', | ||
'EVT_LG_CONNECTED': 'connected', | ||
'EVT_LG_DISCONNECTED': 'disconnected' | ||
connect(reconnect) { | ||
this.isClosed = false; | ||
return this._login(reconnect) | ||
.then(() => { | ||
return this._listen(); | ||
}); | ||
} | ||
} | ||
connect (reconnect) { | ||
return this._login(reconnect) | ||
.then(() => { | ||
return this._listen() | ||
}) | ||
} | ||
close() { | ||
this.isClosed = true; | ||
this.ws.close(); | ||
} | ||
_login (listen) { | ||
return this.rp.post(`${this.controller.href}api/login`, { | ||
resolveWithFullResponse: true, | ||
json: { | ||
username: this.opts.username, | ||
password: this.opts.password, | ||
strict: true | ||
} | ||
}) | ||
.then(() => { | ||
if (this.socket) { | ||
// inject new cookie into the ws handler | ||
this.socket.options.headers.Cookie = this.jar.getCookieString(this.controller.href) | ||
_login(reconnect) { | ||
let endpointUrl = `${this.controller.href}api/login`; | ||
if (this.opts.unifios) { | ||
// unifios using one authorisation endpoint for protect and network. | ||
endpointUrl = `${this.controller.href}api/auth/login`; | ||
} | ||
}) | ||
.catch((e) => { | ||
this.emit('websocket-status', `UniFi Events: Login Failed ${e.message}`) | ||
}) | ||
} | ||
_listen () { | ||
this.socket = new WebSocket(`wss://${this.controller.host}/wss/s/${this.opts.site}/events`, { | ||
options: { | ||
perMessageDeflate: false, | ||
rejectUnauthorized: this.opts.rejectUnauthorized, | ||
headers: { | ||
'User-Agent': this.userAgent, | ||
'Cookie': this.jar.getCookieString(this.controller.href) | ||
} | ||
}, | ||
beforeConnect: this._ensureLoggedIn.bind(this) | ||
}) | ||
return this.rp.post(endpointUrl, { | ||
resolveWithFullResponse: true, | ||
body: { | ||
username: this.opts.username, | ||
password: this.opts.password | ||
} | ||
}).catch(() => { | ||
if (!reconnect) { | ||
this._reconnect(); | ||
} | ||
}); | ||
} | ||
this.socket.on('json', (payload, flags) => { | ||
if ('data' in payload && Array.isArray(payload.data)) { | ||
payload.data.forEach((entry) => { | ||
this._event(entry) | ||
}) | ||
} | ||
}) | ||
_listen() { | ||
const cookies = this.jar.getCookieString(this.controller.href); | ||
let eventsUrl = `wss://${this.controller.host}/wss/s/${this.opts.site}/events`; | ||
if (this.opts.unifios) { | ||
eventsUrl = `wss://${this.controller.host}/proxy/network/wss/s/${this.opts.site}/events`; | ||
} | ||
this.ws = new WebSocket(eventsUrl, { | ||
perMessageDeflate: false, | ||
rejectUnauthorized: !this.opts.insecure, | ||
headers: { | ||
'User-Agent': this.userAgent, | ||
Cookie: cookies | ||
} | ||
}); | ||
this.socket.on('websocket-status', (status) => { | ||
this.emit('websocket-status', `UniFi Events: ${status}`) | ||
}) | ||
} | ||
const pingpong = setInterval(() => { | ||
this.ws.send('ping'); | ||
}, 15000); | ||
_event (data) { | ||
this.emit(data.key, data) | ||
this.emit('event', data) | ||
this.ws.on('open', () => { | ||
this.isReconnecting = false; | ||
this.emit('ctrl.connect'); | ||
}); | ||
// send to convenience emitters | ||
if (data.key in this.helpers) { | ||
this.emit(this.helpers[data.key], data) | ||
this.ws.on('message', data => { | ||
if (data === 'pong') { | ||
return; | ||
} | ||
try { | ||
const parsed = JSON.parse(data); | ||
if ('data' in parsed && Array.isArray(parsed.data)) { | ||
parsed.data.forEach(entry => { | ||
this._event(entry); | ||
}); | ||
} | ||
} catch (err) { | ||
this.emit('ctrl.error', err); | ||
} | ||
}); | ||
this.ws.on('close', () => { | ||
this.emit('ctrl.close'); | ||
clearInterval(pingpong); | ||
this._reconnect(); | ||
}); | ||
this.ws.on('error', err => { | ||
clearInterval(pingpong); | ||
this.emit('ctrl.error', err); | ||
this._reconnect(); | ||
}); | ||
} | ||
} | ||
_ensureLoggedIn () { | ||
return this.rp.get(`${this.controller.href}api/self`) | ||
.catch(() => { | ||
return this._login() | ||
}) | ||
} | ||
_reconnect() { | ||
if (!this.isReconnecting && !this.isClosed) { | ||
this.isReconnecting = true; | ||
setTimeout(() => { | ||
this.emit('ctrl.reconnect'); | ||
this.isReconnecting = false; | ||
this.connect(true); | ||
}, this.autoReconnectInterval); | ||
} | ||
} | ||
getClients () { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.get(`${this.controller.href}api/s/${this.opts.site}/stat/sta`, { | ||
json: true | ||
}) | ||
}) | ||
} | ||
_event(data) { | ||
if (data && data.key) { | ||
// TODO clarifiy what to do with events without key... | ||
const match = data.key.match(/EVT_([A-Z]{2})_(.*)/); | ||
if (match) { | ||
const [, group, event] = match; | ||
this.emit([group.toLowerCase(), event.toLowerCase()].join('.'), data); | ||
} | ||
} | ||
} | ||
getClient (mac) { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.get(`${this.controller.href}api/s/${this.opts.site}/stat/user/${mac}`, { | ||
json: true | ||
}) | ||
.then((data) => { | ||
return data.data[0] | ||
}) | ||
}) | ||
} | ||
_ensureLoggedIn() { | ||
return this.rp.get(`${this.controller.href}api/self`) | ||
.catch(() => { | ||
return this._login(); | ||
}); | ||
} | ||
getAp (mac) { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.get(`${this.controller.href}api/s/${this.opts.site}/stat/device/${mac}`, { | ||
json: true | ||
}) | ||
.then((data) => { | ||
return data.data[0] | ||
}) | ||
}) | ||
} | ||
_url(path) { | ||
if (this.opts.unifios) { | ||
// unifios using an proxy, set extra path | ||
if (path.indexOf('/') === 0) { | ||
return `${this.controller.href}proxy/network/${path}`; | ||
} | ||
return `${this.controller.href}proxy/network/api/s/${this.opts.site}/${path}`; | ||
} | ||
else { | ||
if (path.indexOf('/') === 0) { | ||
return `${this.controller.href}${path}`; | ||
} | ||
return `${this.controller.href}api/s/${this.opts.site}/${path}`; | ||
} | ||
} | ||
getSites () { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.get(`${this.controller.href}api/self/sites`, { | ||
json: true | ||
}) | ||
}) | ||
} | ||
} | ||
get(path) { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.get(this._url(path)); | ||
}); | ||
} | ||
del(path) { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.del(this._url(path)); | ||
}); | ||
} | ||
post(path, body) { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.post(this._url(path), {body}); | ||
}); | ||
} | ||
put(path, body) { | ||
return this._ensureLoggedIn() | ||
.then(() => { | ||
return this.rp.put(this._url(path), {body}); | ||
}); | ||
} | ||
}; |
{ | ||
"name": "unifi-events", | ||
"description": "UniFi Events is a Node.js module that allows you to listen for events generated by a UniFi Controller.", | ||
"version": "0.4.3", | ||
"description": "listen for events from and call methods on the UniFi API (Ubiquiti Wifi).", | ||
"version": "2.0.0-beta.1", | ||
"main": "index.js", | ||
"devDependencies": {}, | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"devDependencies": { | ||
"camo-purge": "latest" | ||
}, | ||
"engines": { | ||
"node": ">=6.0.0" | ||
}, | ||
"dependencies": { | ||
"@oznu/ws-connect": "^0.0.4", | ||
"request": "^2.88.0", | ||
"request-promise": "^4.2.1" | ||
"eventemitter2": "^5.0.1", | ||
"request": "^2.83.0", | ||
"request-promise": "^4.2.2", | ||
"ws": "^4.0.0" | ||
}, | ||
@@ -21,6 +24,13 @@ "repository": { | ||
"unifi", | ||
"node.js", | ||
"ubiquiti", | ||
"ubnt", | ||
"wifi", | ||
"api", | ||
"events" | ||
], | ||
"author": "oznu <dev@oz.nu>", | ||
"contributors": [ | ||
"oznu <dev@oz.nu>", | ||
"hobbyquaker <https://github.com/hobbyquaker>" | ||
], | ||
"license": "MIT", | ||
@@ -27,0 +37,0 @@ "bugs": { |
213
README.md
@@ -1,5 +0,13 @@ | ||
# UniFi Events | ||
# unifi-events | ||
UniFi Events is a Node.js module that allows you to listen for events generated by a UniFi Controller. | ||
[![NPM version](https://badge.fury.io/js/unifi-events.svg)](http://badge.fury.io/js/unifi-events) | ||
[![Dependency Status](https://img.shields.io/gemnasium/oznu/unifi-events.svg?maxAge=2592000)](https://gemnasium.com/github.com/oznu/unifi-events) | ||
[![Build Status](https://travis-ci.org/oznu/unifi-events.svg?branch=master)](https://travis-ci.org/oznu/unifi-events) | ||
[![License][mit-badge]][mit-url] | ||
> unifi-events is a Node.js module that allows you to listen for events from and call methods on the UniFi API (UniFi is | ||
Ubiquiti Networks wifi controller software). | ||
This is a merge-back from the fork [ubnt-unifi](https://github.com/hobbyquaker/ubnt-unifi) as it seems no longer maintained. | ||
## Requirements | ||
@@ -12,83 +20,178 @@ | ||
unifi-events can be installed using the following npm command: | ||
`$ npm install unifi-events` | ||
## Example | ||
```javascript | ||
const Unifi = require('unifi-events') | ||
const unifi = new Unifi({ | ||
host: 'unifi', // The hostname or ip address of the unifi controller (default: 'unifi') | ||
port: 8443, // Port of the unifi controller (default: 8443) | ||
username: 'admin', // Username (default: 'admin'). | ||
password: 'ubnt', // Password (default: 'ubnt'). | ||
site: 'default', // The UniFi site to connect to (default: 'default'). | ||
insecure: true, // Allow connections if SSL certificate check fails (default: false). | ||
unifios: false // For devices with UnifiOS turn this on | ||
}); | ||
// Listen for any event | ||
unifi.on('**', function (data) { | ||
console.log(this.event, data); | ||
}); | ||
``` | ||
npm install unifi-events | ||
``` | ||
or yarn: | ||
## Events | ||
unifi-events uses [EventEmitter2](https://github.com/asyncly/EventEmitter2) and namespaced events. | ||
### namespace `ctrl` | ||
These events indicate the status of the connection to the UniFi controller | ||
* `ctrl.connect` - emitted when the connection to the controller is established | ||
* `ctrl.disconnect` - emitted when the connection to the controller is lost | ||
* `ctrl.error` - | ||
* `ctrl.reconnect` - | ||
### namespaces `wu`, `wg`, `lu`, ... | ||
This JSON file shows all possible events: https://demo.ubnt.com/manage/locales/en/eventStrings.json?v=5.4.11.2 | ||
The prefix `EVT_` gets stripped, the first underscore is replaced by the namespace separating dot, everything is | ||
converted to lower case. Some events such as `EVT_AD_LOGIN` (Admin Login) are not emitted by the UniFi Controller. | ||
#### Example Wireless User events | ||
* `wu.connected` - Wireless User connected | ||
* `wu.disconnected` - Wireless User disconnected | ||
* `wu.roam` - Wireless User roamed from one AP to another | ||
* `wu.roam_radio` - Wireless User changed channel on the same AP | ||
#### Example Wireless Guest Events | ||
* `wg.connected` - Wireless Guest connected | ||
* `wg.disconnected` - Wireless Guest disconnected | ||
* `wg.roam` - Wireless Guest roamed from one AP to another | ||
* `wg.roam_radio` - Wireless Guest changed channel on the same AP | ||
* `wg.authorization_ended` - Wireless Guest became unauthorised | ||
#### Wildcard usage | ||
Example listing for events on Guest Wireless networks only: | ||
```javascript | ||
unifi.on('wg.*', function (data) { | ||
console.log(this.event, data); | ||
}) | ||
``` | ||
yarn add unifi-events | ||
``` | ||
## Example | ||
Example listening for connected events on all network types: | ||
```javascript | ||
const UnifiEvents = require('unifi-events') | ||
unifi.on('*.connected', function (data) { | ||
console.log(this.event, data); | ||
}); | ||
``` | ||
let unifi = new UnifiEvents({ | ||
controller: 'https://demo.ubnt.com', // Required. The url of the UniFi Controller | ||
username: 'superadmin', // Required. | ||
password: 'password', // Required. | ||
site: 'default', // Optional. The UniFi site to connect to, if not set will use the default site. | ||
rejectUnauthorized: true, // Optional. Set to false if you don't have a valid SSL | ||
listen: true // Optional. Set to false if you don't want to listen for events | ||
}) | ||
## Methods | ||
// Listen for users and guests connecting to the network | ||
unifi.on('connected', (data) => { | ||
console.log(data) | ||
}) | ||
#### connect() | ||
// Listen for users and guests disconnecting from the network | ||
unifi.on('disconnected', (data) => { | ||
console.log(data) | ||
}) | ||
Connect to the UniFi controller. Is called in the constructor, so normally you don't need to call it (except if you want | ||
to re-establish a connection that was closed before). | ||
// Listen for any event | ||
unifi.on('event', (data) => { | ||
console.log(data) | ||
}) | ||
#### close() | ||
Closes the connection to the UniFi controller | ||
### UniFi API Methods | ||
Following methods operate on the configured site. The path gets prefixed with | ||
`https://<host>:<port>/api/s/<site>/` if it does not start with a slash, otherwise it gets prefixed with | ||
`https://<host>:<port>`. To explore available API endpoints you can use the | ||
[UniFi-API-browser](https://github.com/Art-of-WiFi/UniFi-API-browser). | ||
These methods are returning a promise. | ||
#### get(path) | ||
Do a HTTP GET on the API. | ||
**Examples:** | ||
* Get a list of all clients | ||
```javascript | ||
unifi.get('stat/sta').then(console.log); | ||
``` | ||
## Events | ||
* Get infos of a specific client | ||
```javascript | ||
unifi.get('stat/user/<mac>').then(console.log); | ||
``` | ||
In addition to the ```connect```, ```disconnect``` and ```event``` event types, other UniFi event types are emitted using the UniFi Event Key ID. | ||
* Get alarms | ||
```javascript | ||
unifi.get('list/alarm').then(console.log); | ||
``` | ||
This JSON file shows all possible events: https://demo.ubnt.com/manage/locales/en/eventStrings.json?v=5.4.11.2 | ||
* Get wireless network IDs | ||
```javascript | ||
unifi.get('rest/wlanconf').then(res => { | ||
res.data.forEach(wlan => { | ||
console.log(wlan.name, wlan._id); | ||
}); | ||
}); | ||
``` | ||
Some events such as ```EVT_AD_LOGIN``` (Admin Login) are not emitted by the UniFi Controller. | ||
* Get device IDs | ||
```javascript | ||
unifi.get('stat/device').then(res => { | ||
res.data.forEach(dev => { | ||
console.log(dev.name, dev._id); | ||
}); | ||
}); | ||
``` | ||
Wireless User Events: | ||
#### del(path) | ||
* ```EVT_WU_Connected``` - Wireless User connected | ||
* ```EVT_WU_Disconnected``` - Wireless User disconnected | ||
* ```EVT_WU_ROAM``` - Wireless User roamed from one AP to another | ||
* ```EVT_WU_ROAM_RADIO``` - Wireless User changed channel on the same AP | ||
Do a HTTP DELETE on the API. | ||
Wireless Guest Events: | ||
#### post(path, body) | ||
* ```EVT_WG_Connected``` - Wireless Guest connected | ||
* ```EVT_WG_Disconnected``` - Wireless Guest disconnected | ||
* ```EVT_WG_ROAM``` - Wireless Guest roamed from one AP to another | ||
* ```EVT_WG_ROAM_RADIO``` - Wireless Guest changed channel on the same AP | ||
* ```EVT_WG_AUTHORIZATION_ENDED``` - Wireless Guest became unauthorised | ||
Do a HTTP POST on the API. | ||
LAN User Events: | ||
**Examples:** | ||
* ```EVT_LU_CONNECTED``` - LAN User connected to the network | ||
* ```EVT_LU_DISCONNECTED``` - LAN User disconnected from the network | ||
* Enable all LEDs of all APs | ||
```javascript | ||
unifi.post('set/setting/mgmt', {led_enabled: true}).then(console.log); | ||
``` | ||
LAN Guest Events: | ||
* Disable a WLAN | ||
```javascript | ||
unifi.post('upd/wlanconf/<wlan_id>', {enabled: false}).then(console.log); | ||
``` | ||
* ```EVT_LG_CONNECTED``` - LAN Guest connected to the network | ||
* ```EVT_LG_DISCONNECTED``` - LAN Guest disconnected from the network | ||
#### put(path, body) | ||
Example listing for connections made to Guest Wireless networks only: | ||
Do a HTTP PUT on the API. | ||
**Examples:** | ||
* Enable LED of AP | ||
```javascript | ||
unifi.on('EVT_WG_Connected', (data) => { | ||
console.log(data) | ||
}) | ||
unifi.put('rest/device/<device_id>', {led_override: 'on'}).then(console.log); | ||
``` | ||
## License | ||
MIT © 2018 [Sebastian Raff](https://github.com/hobbyquaker) | ||
MIT © 2017-2018 [oznu](https://github.com/oznu) | ||
[mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat | ||
[mit-url]: LICENSE |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
13249
197
4
1
172
1
+ Addedeventemitter2@^5.0.1
+ Addedws@^4.0.0
+ Addedeventemitter2@5.0.1(transitive)
- Removed@oznu/ws-connect@^0.0.4
- Removed@oznu/ws-connect@0.0.4(transitive)
Updatedrequest@^2.83.0
Updatedrequest-promise@^4.2.2