Skip to content

Instantly share code, notes, and snippets.

@dkebler
Created July 29, 2019 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dkebler/fe872178e0de4a5874d6a6157b9f3537 to your computer and use it in GitHub Desktop.
Save dkebler/fe872178e0de4a5874d6a6157b9f3537 to your computer and use it in GitHub Desktop.
Access home assistant websocket api via nodej client and long lasting token
import to from 'await-to-js'
import ha from 'home-assistant-js-websocket'
import WebSocket from 'ws'
import { EventEmitter } from 'events'
import ndb from 'debug'
const debug = ndb('home-assistant:ws')
const MSG_TYPE_AUTH_REQUIRED = 'auth_required'
const MSG_TYPE_AUTH_INVALID = 'auth_invalid'
const MSG_TYPE_AUTH_OK = 'auth_ok'
class HomeAssistant extends EventEmitter {
constructor(config) {
super()
this.config = config
debug('config passed to websocket constructor', this.config)
this.socket = {} // node websocket if access needed
this.conn = {} // the connection object needed for api calls
debug('constructor\n', this.config)
}
async connect () {
let [errws,ws] = await to(createSocket(this.config))
if (errws) {
debug('socket create error', errws)
throw errws
}
debug('websocket ready at', ws.url)
let [err,conn] = await to(ha.createConnection({createSocket: () => {return ws}}))
if (err) {
debug('error in connection', err)
throw err
}
this.conn = conn
this.api = ha // to be replaced with merged api calls
debug('Connected to Home Assistant')
return 'success'
function createSocket (opts) {
return new Promise((resolve, reject) => {
if (!opts.hassUrl) {
throw ha.ERR_HASS_HOST_REQUIRED
}
const auth = JSON.stringify({
type: 'auth',
access_token: opts.access_token
})
let url = `${opts.hassUrl}/${opts.wssPath || 'api/websocket'}`
debug('[Auth Phase] Initializing', url)
setTimeout(() => reject(new Error('Unable to Authorize in 5 seconds')),5000)
let ws = new WebSocket(url,opts)
ws.on('error', error.bind(ws))
function error(err) {
this.removeAllListeners('message',authorize)
this.removeAllListeners('error', error)
reject(err)
}
ws.on('message',authorize.bind(ws))
function authorize(event) {
const message = JSON.parse(event)
debug('[Auth Phase] Message Received', message)
switch (message.type) {
case MSG_TYPE_AUTH_REQUIRED:
try {
debug('[Auth Phase] sending authorization\n',auth)
this.send(auth)
} catch (err) {
debug('sending auth error')
this.close()
reject(new Error('unable to send authorization'))
}
break
case MSG_TYPE_AUTH_OK:
this.removeAllListeners('message',authorize)
this.removeAllListeners('error', error)
resolve(this)
break
case MSG_TYPE_AUTH_INVALID:
this.close()
reject(new Error(MSG_TYPE_AUTH_INVALID))
break
default:
debug('[Auth Phase] Unhandled message', message)
this.close()
reject(new Error(`unhandled authorization error, ${message}`))
}
} // end authorize
})
} // end createSocket
}
} // end class
export default HomeAssistant
import to from 'await-to-js'
import Hass from './lib/homeassistant'
let opts = {
hassUrl: '<ws/http/https>://<your domain or ip>:8123',
access_token: 'your long lived token'
}
let hass = new Hass(opts)
;
(async () => {
await hass.connect()
// eventually do stuff now with hass like hass.subscribeEntities(handler)
// but for now like this
hass.api.subscribeEntities(hass.conn, entities => console.log('New entities!', entities))
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
process.kill(process.pid, 'SIGTERM')
})
@neontuna
Copy link

Hey just wanted to thank you for posting this. I was getting a little sad about all the existing libraries/documentation being so focused on front end only clients.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment