uci-ha/src/createSocket.js

104 lines
3.1 KiB
JavaScript

import { Client as WebSocket } from 'faye-websocket'
import delay from 'delay'
import to from 'await-to-js'
import logger from '@uci-utils/logger'
let log = logger({ name: 'HomeAssistant:createSocket'})
const MSG_TYPE_AUTH_REQUIRED = 'auth_required'
const MSG_TYPE_AUTH_INVALID = 'auth_invalid'
const MSG_TYPE_AUTH_OK = 'auth_ok'
function createSocket (url,opts) { // includes authentification
return new Promise(async (resolve, reject) => {
let [errws,socket] = await to(connectAndAuthenticate(url,opts))
if (errws) {
log.debug({msg:'error in establishing connection to socket', error:errws, retries:opts.retriesLeft})
if (opts.retriesLeft === 0) throw reject(errws)
log.debug(`retrying initial connection in ${opts.retryTimeout/1000} secs`)
opts.retriesLeft--
if (opts.retriesLeft >-1) log.debug(`${opts.retriesLeft} connection attemps remaining before aborting`)
else log.debug(`${-(opts.retriesLeft+1)} connection attempts`)
await delay(opts.retryTimeout)
resolve(await createSocket(url,opts))
}
resolve(socket)
}) // end promise
} // end createSocket
export default createSocket
function connectAndAuthenticate (url,opts) {
return new Promise((resolve, reject) => {
const auth = JSON.stringify({
type: 'auth',
access_token: opts.access_token,
api_password: opts.password
})
log.debug({msg:'[Auth Phase] Initializing', url:url})
let ws = new WebSocket(url,opts.ws)
const closing = reason => {
log.debug(`[Auth Phase] cleaning up and closing socket: ${reason}`)
cleanup()
ws.close(1000,reason)
reject(`socket closed, ${reason}`)
}
const cleanup = () => {
log.debug('[Auth Phase] removing authorization listeners')
ws.removeListener('message',authorize)
ws.removeListener('error', error)
clearTimeout(authTimeout)
}
const authTimeout = setTimeout( handleTimeout.bind(ws,opts.timeout || 5000), opts.timeout || 5000)
function handleTimeout(timeout) {
closing(`Unable to Authorize in ${timeout} seconds`)
}
ws.on('error', error)
ws.on('message',authorize)
function error(err) {
log.debug({msg:'[Auth Phase] socket error', error: err.message})
closing(`error occured on socket..aborting\n${err.message}`)
}
function authorize(event) {
let message = JSON.parse(event.data)
log.debug(`[Auth Phase] Message Received ${message}`)
switch (message.type) {
case MSG_TYPE_AUTH_REQUIRED:
try {
log.debug({msg:'[Auth Phase] sending authorization',auth:auth})
ws.send(auth)
} catch (err) {
log.debug({msg:'sending auth error', error:err})
closing(`error when sending authorization: ${err}`)
}
break
case MSG_TYPE_AUTH_OK:
cleanup()
resolve(ws)
break
case MSG_TYPE_AUTH_INVALID:
closing(`bad token or password = ${MSG_TYPE_AUTH_INVALID}`)
break
default:
log.debug({msg:'[Auth Phase] Unhandled message - ignoring', message:message})
}
} // end authorize
})
} //end authenticate