New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

hap-node-client

Package Overview
Dependencies
Maintainers
1
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hap-node-client - npm Package Compare versions

Comparing version 0.2.8-beta.6 to 0.2.8-beta.7

1139

HAPNodeJSClient.js
'use strict';
// External Libraries
const ip = require('ip');
const bonjour = require('bonjour-hap')();
const axios = require('axios').default;
const inherits = require('node:util').inherits;
var hapRequest = require('./lib/hapRequest.js');
var EventEmitter = require('events').EventEmitter;
const axiosRetry = require('axios-retry').default;
const EventEmitter = require('node:events').EventEmitter;
var inherits = require('util').inherits;
var debug = require('debug')('hapNodeJSClient');
var bonjour = require('bonjour-hap')();
var ip = require('ip');
var normalizeUUID = require('./lib/util.js').normalizeUUID;
// Internal Libaries
const hapRequest = require('./lib/hapRequest.js');
const normalizeUUID = require('./lib/util.js').normalizeUUID;
const getAccessoryDump = require('./lib/getAccessoryDump').getAccessoryDump;
const MonitorBridgeUpdates = require('./lib/monitorBridgeUpdates').MonitorBridgeUpdates;
// Debug Monitoring
var debug = require('debug')('HAPNodeJSClient');
var debugDis = require('debug')('HAPNodeJSClient:Discover');
var debugDump = require('debug')('HAPNodeJSClient:Dump');
axiosRetry(axios, { retries: 3 });
var discovered = [];
// var mdnsCache = {};
var mdnsCache = {};
var populateCache = false;

@@ -34,2 +22,6 @@

module.exports = {
HAPNodeJSClient: HAPNodeJSClient
};
/**

@@ -48,3 +40,2 @@ * HAPNodeJSClient - Client for Homebridge and HAP-NodeJS in insecure mode.

* @property {number} reqTimeout - Accessory request timeout, defaults to 7 seconds
* @property {number} type - Type of mDNS record to look for, defaults to `hap`, used for testing only.
* @example

@@ -54,556 +45,68 @@ *

var populateCacheTimeout;
function HAPNodeJSClient(options) {
// console.log('Options', options);
this.debug = options.debug || false;
this.refresh = options.refresh || 900;
this.timeout = options.timeout || 20;
this.reqTimeout = options.reqTimeout || 7000;
this.RegisterPin('default', options.pin || '031-45-154');
filter = options.filter || false;
if (this.debug) {
let debugEnable = require('debug');
let namespaces = debugEnable.disable();
class HAPNodeJSClient {
constructor(options) {
// console.log('Options', options);
this.options = options;
this.debug = options.debug || false;
this.refresh = options.refresh || 900;
this.timeout = options.timeout || 20;
this.reqTimeout = options.reqTimeout || 7000;
this.RegisterPin('default', options.pin || '031-45-154');
filter = options.filter || false;
if (this.debug) {
let debugEnable = require('debug');
let namespaces = debugEnable.disable();
// this.log('DEBUG-1', namespaces);
if (namespaces) {
namespaces = namespaces + ',hap*';
} else {
namespaces = 'hap*';
}
// this.log('DEBUG-2', namespaces);
debugEnable.enable(namespaces);
// this.log('DEBUG-1', namespaces);
if (namespaces) {
namespaces = namespaces + ',hap*';
} else {
namespaces = 'hap*';
}
// this.log('DEBUG-2', namespaces);
debugEnable.enable(namespaces);
}
this.eventRegistry = {};
this.eventRegistry = {};
_discovery.call(this);
this._eventBus = new EventEmitter();
setInterval(_discovery.bind(this), this.refresh * 1000);
this.monitorBridgeUpdates = new MonitorBridgeUpdates({ type: (options.type ? options.type : 'hap') });
/**
* HomeKit Accessory Characteristic event pass thru
*
* @event HAPNodeJSClient#Disconnected
* @Type {object}
* @property {string} server - IP Address and port of disconnected homebridge
* @example Sample Message
*
* { host: '192.168.1.4', port: 51826, aid: 16, iid: 11, status: false }
*/
this.monitorBridgeUpdates.on('up', this.bridgeUp.bind(this));
this.monitorBridgeUpdates.on('update', this.bridgeUpdate.bind(this));
this.monitorBridgeUpdates.on('Ready', this.bridgeReady.bind(this));
this._eventBus.on('Disconnected', _reconnectServer.bind(this));
// _discovery.call(this); // Inital discovery of devices
this._eventBus = new EventEmitter();
// this.discoveryTimer = setInterval(_discovery.bind(this), this.refresh * 1000);
this._eventBus.on('Event', function (events) {
debug('Events', JSON.stringify(events));
/**
* HomeKit Accessory Characteristic event pass thru
*
* @event HAPNodeJSClient#Disconnected
* @event HAPNodeJSClient#hapEvent
* @Type {object}
* @property {string} server - IP Address and port of disconnected homebridge
* @property {string} host - IP Address of homebridge instance generating event
* @property {number} port - Port of homebridge instance generating event
* @property {number} deviceID - deviceID of homebridge instance generating event
* @property {number} aid - Accessory ID of accessory generating event
* @property {number} iid - Instance ID of accessory characteristic generating event
* @property {object} value - Updated characteristic value
* @example Sample Message
*
* { host: '192.168.1.4', port: 51826, aid: 16, iid: 11, status: false }
* [{"host":"192.168.1.13","port":43787,"deviceID":"76:59:CE:25:B9:6E","aid":1,"iid":13,"value":true,"status":true}]
*/
this._eventBus.on('Disconnected', _reconnectServer.bind(this));
this._eventBus.on('Event', function (events) {
debug('Events', JSON.stringify(events));
/**
* HomeKit Accessory Characteristic event pass thru
*
* @event HAPNodeJSClient#hapEvent
* @Type {object}
* @property {string} host - IP Address of homebridge instance generating event
* @property {number} port - Port of homebridge instance generating event
* @property {number} deviceID - deviceID of homebridge instance generating event
* @property {number} aid - Accessory ID of accessory generating event
* @property {number} iid - Instance ID of accessory characteristic generating event
* @property {object} value - Updated characteristic value
* @example Sample Message
*
* [{"host":"192.168.1.13","port":43787,"deviceID":"76:59:CE:25:B9:6E","aid":1,"iid":13,"value":true,"status":true}]
*/
this.emit('hapEvent', events);
this.emit(events[0].host + events[0].port + events[0].aid, events);
events.forEach(function (event) {
// debug('hapEvent', event.host + event.port + event.aid + event.iid, event);
this.emit(event.host + event.port + event.aid + event.iid, event);
this.emit(event.deviceID + event.aid + event.iid, event);
}.bind(this));
this.emit('hapEvent', events);
this.emit(events[0].host + events[0].port + events[0].aid, events);
events.forEach(function (event) {
// debug('hapEvent', event.host + event.port + event.aid + event.iid, event);
this.emit(event.host + event.port + event.aid + event.iid, event);
this.emit(event.deviceID + event.aid + event.iid, event);
}.bind(this));
// debug('This', this);
}
/**
*
* @param {*} callback
*/
async bridgeUp(service) {
// debugDis('bridgeUp', service);
if (await this.getServiceDump(service)) {
}
}
/**
*
* @param {*} callback
*/
async bridgeUpdate(service) {
debugDis('bridgeUpdate', service);
if (await this.getServiceDump(service)) {
this.emit('Update', _deassociateArray(discovered));
}
}
/**
*
* @param {*} callback
*/
async bridgeReady(service) {
debugDis('bridgeReady %d Homebridge instances', Object.keys(discovered).length);
if (Object.keys(discovered).length > 0) {
this.emit('Ready', _deassociateArray(discovered));
}
}
/**
*
* @param {*} callback
*/
async getServiceDump(service) {
// debugDis('getServiceDump', service);
if ((filter && filter === service.host + ':' + service.port) || !filter) {
try {
let serviceDump = await getAccessoryDump(service);
if (serviceDump) {
// debugDis('discovered', service.deviceID, bridge);
discovered[service.deviceID] = serviceDump;
}
return serviceDump;
} catch (err) {
debugDis('ERROR:', err.message, service.name);
}
} else {
debugDump('Filtered HAP instance address: %s -> %s', service.txt.md, service.url);
}
}
/**
* RegisterPin - Register pin numbers ()
*
* @class
* @param {type} key Unique identifier of homebridge instance (ip:port or deviceID)
* @param {type} pin Homebridge PIN
* @return {type} bool updated
*/
RegisterPin(key, pin) {
if (!key || (key in pins && pins[key] === pin)) {
return false;
}
key = key.toLowerCase();
pins[key] = pin;
debug('Registered/updated PIN for `%s`: %s', key, pin);
return true;
}
/**
* HAPaccessories - Returns an array of all homebridge instances, and the accessories for each.
*
* @class
* @param {type} callback description
* @return {type} description
*/
HAPaccessories(callback) {
// This is a callback as in the future may need to call something
callback(_deassociateArray(discovered));
}
/**
* mdnsCache
*
* @returns mdnsCacheObject
*/
mdnsCache() {
return this.monitorBridgeUpdates.mdnsCache;
}
// curl -X PUT http://127.0.0.1:51826/characteristics --header "Content-Type:Application/json"
// --header "authorization: 031-45-154" --data "{ \"characteristics\": [{ \"aid\": 2, \"iid\": 9, \"value\": 0}] }"
/**
* HAPcontrolByDeviceID - Send a characteristic PUT Message to a particular homebridge instance
*
* @param {type} deviceID deviceID of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPcontrolByDeviceID(deviceID, body, callback) {
this._mdnsLookup(deviceID, function (err, instance) {
if (err) {
callback(err);
} else {
this.HAPcontrol.call(this, instance.host, instance.port, body, function (err, response) {
if (err) {
this._mdnsError(deviceID);
}
callback(err, response);
}, instance);
}
}.bind(this));
}
/**
* HAPcontrol - Send a characteristic PUT Message to a particular homebridge instance
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPcontrol(ipAddress, port, body, callback, instance) {
axios({
eventBus: this._eventBus,
method: 'PUT',
url: instance.url + '/characteristics',
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
data: body,
validateStatus: function (status) {
return true; // Resolve only if the status code is less than 500
}
}).then(function (response) {
// debug('HAPcontrol-then', response.status, response.statusText, response.headers, response.data, response.config);
switch (response.status) {
case 204:
callback(null, null);
break;
case 207:
callback(null, response.data);
break;
case 401:
case 470:
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, response.data);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
break;
default:
debug('Homebridge Control failed %s:%s Status: %s ', ipAddress, port, response.status, body, response.data);
callback(new Error('Homebridge control failed'));
}
}).catch(function (err) {
// Response s/b 200 OK
debug('HAPcontrol-catch', err);
debug('Homebridge Control failed %s:%s', ipAddress, port, body, err.message);
callback(err);
});
}
/**
* HAPeventByDeviceID - Send a characteristic PUT Message to a particular homebridge instance, this maintains a socket connection for use in returning Events
*
* @param {type} deviceID deviceID homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPeventByDeviceID(deviceID, body, callback) {
// console.log('This-0', this);
this._mdnsLookup(deviceID, function (err, instance) {
// debug('This-1', instance);
if (err) {
callback(err);
} else {
hapRequest({
eventBus: this._eventBus,
method: 'PUT',
deviceID: deviceID,
url: instance.url + '/characteristics',
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(deviceID),
'connection': 'keep-alive'
},
body: body
}, function (err, response) {
// Response s/b 200 OK
// debug('HAPeventByDeviceID %s:%s', instance.host, instance.port, response);
if (err) {
debug('Homebridge event reg failed %s:%s', instance.host, instance.port, body, err.message);
this._mdnsError(deviceID);
callback(err);
} else if (response.statusCode !== 207 && response.statusCode !== 204) {
if (response.statusCode === 401 || response.statusCode === 470) {
debug('Homebridge auth failed, invalid PIN %s', _findPinByKey(deviceID), deviceID, body, err, response.body);
this._mdnsError(deviceID);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(deviceID)));
} else {
debug('Homebridge event reg failed %s:%s Status: %s ', deviceID, response.statusCode, body, err, response.body);
this._mdnsError(deviceID);
callback(new Error('Homebridge event reg failed'));
}
} else {
var rsp;
if (!this.eventRegistry[deviceID]) {
this.eventRegistry[deviceID] = [];
}
// debug('1', JSON.parse(body).characteristics);
this.eventRegistry[deviceID] = this.eventRegistry[deviceID].concat(JSON.parse(body).characteristics);
// debug('2', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[deviceID].sort((a, b) => (JSON.stringify(a) > JSON.stringify(b)) ? 1 : ((JSON.stringify(b) > JSON.stringify(a)) ? -1 : 0));
// debug('3', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[deviceID] = Array.from(new Set(this.eventRegistry[deviceID].map(JSON.stringify))).map(JSON.parse);
// debug('4', JSON.stringify(this.eventRegistry[key]));
try {
rsp = JSON.parse(response.body);
} catch (ex) {
debug('Homebridge Response Failed %s:%s', deviceID, response.statusCode, response.statusMessage);
debug('Homebridge Response Failed %s:%s', deviceID, response.body, ex);
callback(new Error(ex));
return;
}
callback(null, rsp);
}
}.bind(this));
}
}.bind(this));
}
/**
* HAPevent - Send a characteristic PUT Message to a particular homebridge instance, this maintains a socket connection for use in returning Events
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPevent(ipAddress, port, body, callback, instance) {
hapRequest({
eventBus: this._eventBus,
method: 'PUT',
url: instance.url + '/characteristics',
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
body: body
}, function (err, response) {
// Response s/b 200 OK
if (err) {
debug('Homebridge event reg failed %s:%s', ipAddress, port, body, err.message);
callback(err);
} else if (response.statusCode !== 207 && response.statusCode !== 204) {
if (response.statusCode === 401 || response.statusCode === 470) {
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, err, response.body);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
} else {
debug('Homebridge event reg failed %s:%s Status: %s ', ipAddress, port, response.statusCode, body, err, response.body);
callback(new Error('Homebridge event reg failed'));
}
} else {
var rsp;
var key = ipAddress + ':' + port;
if (!this.eventRegistry[key]) {
this.eventRegistry[key] = [];
}
// debug('1', JSON.parse(body).characteristics);
this.eventRegistry[key] = this.eventRegistry[key].concat(JSON.parse(body).characteristics);
// debug('2', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[key].sort((a, b) => (JSON.stringify(a) > JSON.stringify(b)) ? 1 : ((JSON.stringify(b) > JSON.stringify(a)) ? -1 : 0));
// debug('3', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[key] = Array.from(new Set(this.eventRegistry[key].map(JSON.stringify))).map(JSON.parse);
// debug('4', JSON.stringify(this.eventRegistry[key]));
try {
rsp = JSON.parse(response.body);
} catch (ex) {
debug('Homebridge Response Failed %s:%s', ipAddress, port, response.statusCode, response.statusMessage);
debug('Homebridge Response Failed %s:%s', ipAddress, port, response.body, ex);
callback(new Error(ex));
return;
}
callback(null, rsp);
}
}.bind(this));
}
/**
* HAPresourceByDeviceID - Send a characteristic PUT Message to a particular homebridge instance using resource interface, ie camera
*
* @param {type} DeviceID DeviceID of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPresourceByDeviceID(deviceID, body, callback) {
// console.log('This-0', this);
this._mdnsLookup(deviceID, function (err, instance) {
// console.log('This-1', this);
if (err) {
callback(err);
} else {
this.HAPresource.call(this, instance.host, instance.port, body, function (err, response) {
if (err) {
this._mdnsError(deviceID);
}
callback(err, response);
}.bind(this), instance);
}
}.bind(this));
}
/**
* HAPresource - Send a characteristic PUT Message to a particular homebridge instance using resource interface, ie camera
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPresource(ipAddress, port, body, callback, instance) {
axios({
eventBus: this._eventBus,
method: 'POST',
url: instance.url + '/resource',
timeout: this.reqTimeout,
responseType: 'arraybuffer',
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
data: body,
validateStatus: function (status) {
return true; // Resolve only if the status code is less than 500
}
}).then(function (response) {
// debug('HAPcontrol-then', response.status, response.statusText, response.headers, response.config);
switch (response.status) {
case 200:
callback(null, response.data);
break;
case 401:
case 470:
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, response.data);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
break;
default:
debug('Homebridge Resource failed %s:%s Status: %s ', ipAddress, port, response.status, body, response.data);
callback(new Error('Homebridge Resource failed'));
}
}).catch(function (err) {
// Response s/b 200 OK
debug('HAPcontrol-catch', err);
debug('Homebridge Resource failed %s:%s', ipAddress, port, body, err.message);
callback(err);
});
}
/**
* HAPstatusByDeviceID - Get current status for characteristics
*
* @param {type} deviceID deviceID of homebridge instance
* @param {type} body description
* @param {type} callback Callback to execute upon completion of characteristic getting, function(err, response)
*/
HAPstatusByDeviceID(deviceID, body, callback) {
// console.log('This-0', this);
this._mdnsLookup(deviceID, function (err, instance) {
// console.log('This-1', this);
if (err) {
callback(err);
} else {
this.HAPstatus.call(this, instance.host, instance.port, body, function (err, response) {
if (err) {
this._mdnsError(deviceID);
}
callback(err, response);
}.bind(this), instance);
}
}.bind(this));
}
/**
* HAPstatus - Get current status for characteristics
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body description
* @param {type} callback Callback to execute upon completion of characteristic getting, function(err, response)
*/
HAPstatus(ipAddress, port, body, callback, instance) {
axios({
eventBus: this._eventBus,
method: 'GET',
url: instance.url + '/characteristics' + body,
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
validateStatus: function (status) {
return true; // Resolve only if the status code is less than 500
}
}).then(function (response) {
// debug('HAPstatus-then', response.status, response.statusText, response.headers, response.data, response.config);
switch (response.status) {
case 200:
callback(null, response.data);
break;
case 207:
callback(null, response.data);
break;
case 401:
case 470:
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, response.data);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
break;
default:
debug('Homebridge Status failed %s:%s Status: %s ', ipAddress, port, response.status, body, response.data);
callback(new Error('Homebridge Status failed'));
}
}).catch(function (err) {
// Response s/b 200 OK
debug('HAPstatus-catch', err);
debug('Homebridge Status failed %s:%s', ipAddress, port, body, err.message);
callback(err);
});
}
/**
* When a 'known' bridge is not found, force refresh the cache
*
* @param {*} deviceID
* @param {*} callback
*/
async _mdnsLookup(deviceID, callback) {
// debug('\nmdnsLookup start', serviceName);
if (this.monitorBridgeUpdates.mdnsCacheGet(deviceID)) {
// debug('cached', this.monitorBridgeUpdates.mdnsCacheGet(serviceName].url);
callback(null, this.monitorBridgeUpdates.mdnsCacheGet(deviceID));
} else {
debug('_mdnsLookup missing', deviceID);
await this.monitorBridgeUpdates.refreshCache();
if (this.monitorBridgeUpdates.mdnsCacheGet(deviceID)) {
// debug('cached', this.monitorBridgeUpdates.mdnsCacheGet(serviceName].url);
callback(null, this.monitorBridgeUpdates.mdnsCacheGet(deviceID));
} else {
callback(new Error('ERROR: HB Instance not found', deviceID), null);
}
};
}
async _mdnsError(deviceID) {
debug('_mdnsError ', deviceID);
this.monitorBridgeUpdates.mdnsCacheRemove(deviceID);
this.monitorBridgeUpdates.refreshCache();
}
/**
* Destroy and shutdown HAPNodeJSClient - Used by testing
*/
async destroy() {
clearInterval(this.discoveryTimer);
clearInterval(populateCacheTimeout);
bonjour.destroy();
this.monitorBridgeUpdates.destroy();
}
}.bind(this));
// debug('This', this);
}

@@ -613,3 +116,2 @@

/*
function _discovery() {

@@ -624,7 +126,33 @@ debug('Starting Homebridge instance discovery');

}
*/
/*
function _mdnsLookup(deviceID, callback) {
// debug('\nmdnsLookup start', serviceName);
if (mdnsCache[deviceID]) {
// debug('cached', mdnsCache[serviceName].url);
callback(null, mdnsCache[deviceID]);
} else {
_populateCache(4, null, function () {
if (mdnsCache[deviceID]) {
// debug('refreshed', mdnsCache[deviceID]);
callback(null, mdnsCache[deviceID]);
} else {
callback(new Error('ERROR: HB Instance not found', deviceID), null);
}
});
}
}
function _mdnsError(deviceID) {
// debug('\_mdnsError ', deviceID);
mdnsCache[deviceID] = false;
_populateCache(4, null, function () {
if (mdnsCache[deviceID]) {
// debug('refreshed', mdnsCache[deviceID]);
}
});
}
function _populateCache(timeout, discovery, callback) {
debug('_populateCache', populateCache);
// debug('_populateCache', populateCache);
if (!populateCache) {

@@ -634,3 +162,3 @@ populateCache = true;

var browser = bonjour.find({
type: 'test'
type: 'hap'
}, function (result) {

@@ -655,3 +183,3 @@ if (result.txt) {

if (url) {
this.monitorBridgeUpdates.mdnsCacheUpdate({
mdnsCache[result.txt.id] = {
name: result.name,

@@ -663,5 +191,5 @@ host: ipAddress,

txt: result.txt
});
};
if (discovery) {
discovery.call(this, this.monitorBridgeUpdates.mdnsCacheGet(result.txt.id), function () { });
discovery.call(this, mdnsCache[result.txt.id], function () { });
}

@@ -675,3 +203,3 @@ } else {

});
populateCacheTimeout = setTimeout(function () {
setTimeout(function () {
// debug('Timeout:');

@@ -685,3 +213,3 @@ browser.stop();

}
}*/
}

@@ -698,2 +226,121 @@ function _findPinByKey(key) {

/**
* HAPNodeJSClient.prototype.RegisterPin - Register pin numbers ()
*
* @class
* @param {type} key Unique identifier of homebridge instance (ip:port or deviceID)
* @param {type} pin Homebridge PIN
* @return {type} bool updated
*/
HAPNodeJSClient.prototype.RegisterPin = function (key, pin) {
if (!key || (key in pins && pins[key] === pin)) {
return false;
}
key = key.toLowerCase();
pins[key] = pin;
debug('Registered/updated PIN for `%s`: %s', key, pin);
return true;
};
/**
* HAPNodeJSClient.prototype.HAPaccessories - Returns an array of all homebridge instances, and the accessories for each.
*
* @class
* @param {type} callback description
* @return {type} description
*/
HAPNodeJSClient.prototype.HAPaccessories = function (callback) {
// This is a callback as in the future may need to call something
callback(discovered);
};
/**
* HAPNodeJSClient.prototype.mdnsCache
*
* @returns mdnsCacheObject
*/
HAPNodeJSClient.prototype.mdnsCache = function () {
return mdnsCache;
};
// curl -X PUT http://127.0.0.1:51826/characteristics --header "Content-Type:Application/json"
// --header "authorization: 031-45-154" --data "{ \"characteristics\": [{ \"aid\": 2, \"iid\": 9, \"value\": 0}] }"
/**
* HAPNodeJSClient.prototype.HAPcontrolByDeviceID - Send a characteristic PUT Message to a particular homebridge instance
*
* @param {type} deviceID deviceID of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPcontrolByDeviceID = function (deviceID, body, callback) {
_mdnsLookup(deviceID, function (err, instance) {
if (err) {
callback(err);
} else {
HAPNodeJSClient.prototype.HAPcontrol.call(this, instance.host, instance.port, body, function (err, response) {
if (err) {
_mdnsError(deviceID);
}
callback(err, response);
}, instance);
}
}.bind(this));
};
/**
* HAPNodeJSClient.prototype.HAPcontrol - Send a characteristic PUT Message to a particular homebridge instance
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPcontrol = function (ipAddress, port, body, callback, instance) {
axios({
eventBus: this._eventBus,
method: 'PUT',
url: instance.url + '/characteristics',
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
data: body,
validateStatus: function (status) {
return true; // Resolve only if the status code is less than 500
}
}).then(function (response) {
// debug('HAPcontrol-then', response.status, response.statusText, response.headers, response.data, response.config);
switch (response.status) {
case 204:
callback(null, null);
break;
case 207:
callback(null, response.data);
break;
case 401:
case 470:
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, response.data);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
break;
default:
debug('Homebridge Control failed %s:%s Status: %s ', ipAddress, port, response.status, body, response.data);
callback(new Error('Homebridge control failed'));
}
}).catch(function (err) {
// Response s/b 200 OK
debug('HAPcontrol-catch', err);
debug('Homebridge Control failed %s:%s', ipAddress, port, body, err.message);
callback(err);
});
};
/**
* _reconnectServer - Reconnect to event server

@@ -706,15 +353,13 @@ *

function _reconnectServer(server) {
debug('HAPevent events Reregister', server, this.eventRegistry);
debug('HAPevent events Reregister', server);
// debug('This', this, server);
var events = [];
if (this.eventRegistry.length) {
this.eventRegistry[server.deviceID].forEach(function (device) {
events.push({
deviceID: server.deviceID,
aid: device.aid,
iid: device.iid,
status: -70402
});
this.eventRegistry[server.deviceID].forEach(function (device) {
events.push({
deviceID: server.deviceID,
aid: device.aid,
iid: device.iid,
status: -70402
});
}
});
this.emit('hapEvent', events);

@@ -751,12 +396,10 @@ // this.emit(events[0].host + events[0].port + events[0].aid, events);

debug('clearTimer', server, this.eventRegistry[server.deviceID]);
if (this.eventRegistry.length) {
this.eventRegistry[server.deviceID].forEach(function (device) {
events.push({
deviceID: server.deviceID,
aid: device.aid,
iid: device.iid,
status: -70402
});
this.eventRegistry[server.deviceID].forEach(function (device) {
events.push({
deviceID: server.deviceID,
aid: device.aid,
iid: device.iid,
status: -70402
});
}
});
this.emit('hapEvent', events);

@@ -771,12 +414,10 @@ // this.emit(events[0].host + events[0].port + events[0].aid, events);

debug('HAPevent event reregister succeeded', server);
if (this.eventRegistry.length) {
this.eventRegistry[server.deviceID].forEach(function (device) {
events.push({
deviceID: server.deviceID,
aid: device.aid,
iid: device.iid,
status: true
});
this.eventRegistry[server.deviceID].forEach(function (device) {
events.push({
deviceID: server.deviceID,
aid: device.aid,
iid: device.iid,
status: true
});
}
});
this.emit('hapEvent', events);

@@ -794,7 +435,289 @@ // this.emit(events[0].host + events[0].port + events[0].aid, events);

module.exports = {
HAPNodeJSClient: HAPNodeJSClient
/**
* HAPNodeJSClient.prototype.HAPeventByDeviceID - Send a characteristic PUT Message to a particular homebridge instance, this maintains a socket connection for use in returning Events
*
* @param {type} deviceID deviceID homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPeventByDeviceID = function (deviceID, body, callback) {
// console.log('This-0', this);
_mdnsLookup(deviceID, function (err, instance) {
// debug('This-1', instance);
if (err) {
callback(err);
} else {
hapRequest({
eventBus: this._eventBus,
method: 'PUT',
deviceID: deviceID,
url: instance.url + '/characteristics',
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(deviceID),
'connection': 'keep-alive'
},
body: body
}, function (err, response) {
// Response s/b 200 OK
if (err) {
debug('Homebridge event reg failed %s:%s', instance.host, instance.port, body, err.message);
_mdnsError(deviceID);
callback(err);
} else if (response.statusCode !== 207 && response.statusCode !== 204) {
if (response.statusCode === 401 || response.statusCode === 470) {
debug('Homebridge auth failed, invalid PIN %s', _findPinByKey(deviceID), deviceID, body, err, response.body);
_mdnsError(deviceID);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(deviceID)));
} else {
debug('Homebridge event reg failed %s:%s Status: %s ', deviceID, response.statusCode, body, err, response.body);
_mdnsError(deviceID);
callback(new Error('Homebridge event reg failed'));
}
} else {
var rsp;
if (!this.eventRegistry[deviceID]) {
this.eventRegistry[deviceID] = [];
}
// debug('1', JSON.parse(body).characteristics);
this.eventRegistry[deviceID] = this.eventRegistry[deviceID].concat(JSON.parse(body).characteristics);
// debug('2', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[deviceID].sort((a, b) => (JSON.stringify(a) > JSON.stringify(b)) ? 1 : ((JSON.stringify(b) > JSON.stringify(a)) ? -1 : 0));
// debug('3', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[deviceID] = Array.from(new Set(this.eventRegistry[deviceID].map(JSON.stringify))).map(JSON.parse);
// debug('4', JSON.stringify(this.eventRegistry[key]));
try {
rsp = JSON.parse(response.body);
} catch (ex) {
debug('Homebridge Response Failed %s:%s', deviceID, response.statusCode, response.statusMessage);
debug('Homebridge Response Failed %s:%s', deviceID, response.body, ex);
callback(new Error(ex));
return;
}
callback(null, rsp);
}
}.bind(this));
}
}.bind(this));
};
/*
/**
* HAPNodeJSClient.prototype.HAPevent - Send a characteristic PUT Message to a particular homebridge instance, this maintains a socket connection for use in returning Events
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPevent = function (ipAddress, port, body, callback, instance) {
hapRequest({
eventBus: this._eventBus,
method: 'PUT',
url: instance.url + '/characteristics',
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
body: body
}, function (err, response) {
// Response s/b 200 OK
if (err) {
debug('Homebridge event reg failed %s:%s', ipAddress, port, body, err.message);
callback(err);
} else if (response.statusCode !== 207 && response.statusCode !== 204) {
if (response.statusCode === 401 || response.statusCode === 470) {
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, err, response.body);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
} else {
debug('Homebridge event reg failed %s:%s Status: %s ', ipAddress, port, response.statusCode, body, err, response.body);
callback(new Error('Homebridge event reg failed'));
}
} else {
var rsp;
var key = ipAddress + ':' + port;
if (!this.eventRegistry[key]) {
this.eventRegistry[key] = [];
}
// debug('1', JSON.parse(body).characteristics);
this.eventRegistry[key] = this.eventRegistry[key].concat(JSON.parse(body).characteristics);
// debug('2', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[key].sort((a, b) => (JSON.stringify(a) > JSON.stringify(b)) ? 1 : ((JSON.stringify(b) > JSON.stringify(a)) ? -1 : 0));
// debug('3', JSON.stringify(this.eventRegistry[key]));
this.eventRegistry[key] = Array.from(new Set(this.eventRegistry[key].map(JSON.stringify))).map(JSON.parse);
// debug('4', JSON.stringify(this.eventRegistry[key]));
try {
rsp = JSON.parse(response.body);
} catch (ex) {
debug('Homebridge Response Failed %s:%s', ipAddress, port, response.statusCode, response.statusMessage);
debug('Homebridge Response Failed %s:%s', ipAddress, port, response.body, ex);
callback(new Error(ex));
return;
}
callback(null, rsp);
}
}.bind(this));
};
/**
* HAPNodeJSClient.prototype.HAPresourceByDeviceID - Send a characteristic PUT Message to a particular homebridge instance using resource interface, ie camera
*
* @param {type} DeviceID DeviceID of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPresourceByDeviceID = function (deviceID, body, callback) {
// console.log('This-0', this);
_mdnsLookup(deviceID, function (err, instance) {
// console.log('This-1', this);
if (err) {
callback(err);
} else {
HAPNodeJSClient.prototype.HAPresource.call(this, instance.host, instance.port, body, function (err, response) {
if (err) {
_mdnsError(deviceID);
}
callback(err, response);
}, instance);
}
}.bind(this));
};
/**
* HAPNodeJSClient.prototype.HAPresource - Send a characteristic PUT Message to a particular homebridge instance using resource interface, ie camera
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body An array of HomeKit characteristic updates, [{ \"aid\": 2, \"iid\": 9, \"value\": 0}]
* @param {type} callback Callback to execute upon completion of characteristic setting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPresource = function (ipAddress, port, body, callback, instance) {
axios({
eventBus: this._eventBus,
method: 'POST',
url: instance.url + '/resource',
timeout: this.reqTimeout,
responseType: 'arraybuffer',
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
data: body,
validateStatus: function (status) {
return true; // Resolve only if the status code is less than 500
}
}).then(function (response) {
// debug('HAPcontrol-then', response.status, response.statusText, response.headers, response.config);
switch (response.status) {
case 200:
callback(null, response.data);
break;
case 401:
case 470:
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, response.data);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
break;
default:
debug('Homebridge Resource failed %s:%s Status: %s ', ipAddress, port, response.status, body, response.data);
callback(new Error('Homebridge Resource failed'));
}
}).catch(function (err) {
// Response s/b 200 OK
debug('HAPcontrol-catch', err);
debug('Homebridge Resource failed %s:%s', ipAddress, port, body, err.message);
callback(err);
});
};
/**
* HAPNodeJSClient.prototype.HAPstatusByDeviceID - Get current status for characteristics
*
* @param {type} deviceID deviceID of homebridge instance
* @param {type} body description
* @param {type} callback Callback to execute upon completion of characteristic getting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPstatusByDeviceID = function (deviceID, body, callback) {
// console.log('This-0', this);
_mdnsLookup(deviceID, function (err, instance) {
// console.log('This-1', this);
if (err) {
callback(err);
} else {
HAPNodeJSClient.prototype.HAPstatus.call(this, instance.host, instance.port, body, function (err, response) {
if (err) {
_mdnsError(deviceID);
}
callback(err, response);
}, instance);
}
}.bind(this));
};
/**
* HAPNodeJSClient.prototype.HAPstatus - Get current status for characteristics
*
* @param {type} ipAddress IP Address of homebridge instance
* @param {type} port Port of homebridge instance
* @param {type} body description
* @param {type} callback Callback to execute upon completion of characteristic getting, function(err, response)
*/
HAPNodeJSClient.prototype.HAPstatus = function (ipAddress, port, body, callback, instance) {
axios({
eventBus: this._eventBus,
method: 'GET',
url: instance.url + '/characteristics' + body,
timeout: this.reqTimeout,
headers: {
'Content-Type': 'Application/json',
'authorization': _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port),
'connection': 'keep-alive'
},
validateStatus: function (status) {
return true; // Resolve only if the status code is less than 500
}
}).then(function (response) {
// debug('HAPstatus-then', response.status, response.statusText, response.headers, response.data, response.config);
switch (response.status) {
case 200:
callback(null, response.data);
break;
case 207:
callback(null, response.data);
break;
case 401:
case 470:
debug('Homebridge auth failed, invalid PIN %s %s:%s', _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port), ipAddress, port, body, response.data);
callback(new Error('Homebridge auth failed, invalid PIN ' + _findPinByKey(instance ? instance.deviceID : ipAddress + ':' + port)));
break;
default:
debug('Homebridge Status failed %s:%s Status: %s ', ipAddress, port, response.status, body, response.data);
callback(new Error('Homebridge Status failed'));
}
}).catch(function (err) {
// Response s/b 200 OK
debug('HAPstatus-catch', err);
debug('Homebridge Status failed %s:%s', ipAddress, port, body, err.message);
callback(err);
});
};
function _getAccessories(instance, callback) {

@@ -868,15 +791,3 @@ // debug('_getAccessories()', filter, instance.url + '/accessories');

}
*/
function _deassociateArray(original) {
var result = [];
for (var item in original) {
result.push(original[item]);
}
// console.log('Result', result);
return (result);
}
/**

@@ -883,0 +794,0 @@ * This checks the instance pin matches

@@ -108,3 +108,3 @@ const { _responseLineRegex } = require('./lib/httpParser');

homebridges.HAPstatusByDeviceID(testDeviceID, "{ a: 1 }", function (err, response) {
console.log("HAPstatusByDeviceID", err.message, response);
// console.log("HAPstatusByDeviceID", err.message, response);
expect(err.message).toEqual('Homebridge Status failed');

@@ -125,5 +125,5 @@ expect(response).toBeUndefined();

test("valid deviceID, and valid body On", done => {
console.log("HAPcontrolByDeviceID - start");
homebridges.HAPcontrolByDeviceID(testDeviceID, testAccessoryControlOn, function (err, response) {
console.log("HAPcontrolByDeviceID", err, response);
// console.log("HAPcontrolByDeviceID", err, response);
expect(err).toBeNull();

@@ -315,10 +315,2 @@ expect(response).toBeNull();

});
afterAll(async () => {
await sleep(1000);
await homebridges.destroy();
}, 30000);
});
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
});
// Monkey patch before you require http for the first time.
const parser = require('./httpParser.js');
var once = require('once');
var URL = require('node:url');
var debug = require('debug')('HAPNodeJSClient:eventedHttpClient');
const net = require('node:net');
var URL = require('url');
var debug = require('debug')('hapHttpClient');
const net = require('net');
var Queue = require('better-queue');

@@ -37,14 +37,2 @@

this.client.setTimeout(request.timeout);
this.client.on('timeout', () => {
this.connected = false;
// console.log('Timeout: from server', this.context.host);
if (!this.callback.called) {
this.callback(new Error('Timeout: from server ' + this.context.host));
} else {
debug('eventedHttpClient: Timeout from server', this.context.host);
}
});
this.client.on('data', (data) => {

@@ -95,3 +83,3 @@ // If in the middle of a chunked response

if (!this.callback.called) {
this.callback(err);
this.callback(new Error("Error:", err));
}

@@ -98,0 +86,0 @@ });

"use strict";
var extend = require('extend');
var URL = require('node:url');
var URL = require('url');
var EventedHttpClient = require('./eventedHttpClient');

@@ -6,0 +6,0 @@ var debug = require('debug')('hapRequest');

{
"name": "hap-node-client",
"version": "0.2.8-beta.6",
"version": "0.2.8-beta.7",
"description": "Client for Hap-NodeJS",

@@ -15,4 +15,2 @@ "main": "HAPNodeJSClient.js",

"devDependencies": {
"@types/jest": "^29.5.10",
"@types/node": "^20.10.0",
"documentation": "^14.0.3",

@@ -28,3 +26,3 @@ "hap-nodejs": "^0.11.2",

"better-queue": "3.8.12",
"bonjour-hap": "3.7.1",
"bonjour-hap": "3.6.5",
"buffer": "6.0.3",

@@ -40,3 +38,3 @@ "debug": "4.3.4",

"ignore": [],
"exec": "DEBUG=HAPNodeJSClient* jest --detectOpenHandles",
"exec": "jest",
"signal": "SIGTERM",

@@ -43,0 +41,0 @@ "env": {

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