Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
dist
.reify-cache
yarn-error.log
.rpt2_cache
6 changes: 0 additions & 6 deletions .hound.yml

This file was deleted.

1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
language: node_js
script:
- npm run lint
- npm run test
97 changes: 47 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,16 @@

This is a websocket client written in JavaScript that allows retrieving authentication tokens and communicate with the Home Assistant websocket API. It can be used to integrate Home Assistant into your apps. It has 0 dependencies.

```javascript
import { createConnection, subscribeEntities } from 'home-assistant-js-websocket';
## Trying it out

function stateChanged(event) {
console.log('state changed', event);
}
We've included an [example client](https://github.com/home-assistant/home-assistant-js-websocket/blob/master/example.html) based on this lib so that it's easy to try it out:

createConnection('ws://localhost:8123/api/websocket').then(
(conn) => {
console.log('Connection established!');
subscribeEntities(conn, entities => console.log('New entities!', entities));
},
err => console.error('Connection failed with code', err)
)
```bash
yarn build
npx http-server -o
# A browser will open, navigate to example.html
```

[Try it on JSFiddle.](https://jsfiddle.net/balloob/9w3oyswa/)

## Usage

### Initializing connection
Expand All @@ -32,8 +24,8 @@ import {
getAuth,
createConnection,
subscribeEntities,
ERR_HASS_HOST_REQUIRED,
} from 'home-assistant-js-websocket';
ERR_HASS_HOST_REQUIRED
} from "home-assistant-js-websocket";

async function connect() {
let auth;
Expand All @@ -42,7 +34,9 @@ async function connect() {
} catch (err) {
if (err === ERR_HASS_HOST_REQUIRED) {
const hassUrl = prompt(
"What host to connect to?", "http://localhost:8123");
"What host to connect to?",
"http://localhost:8123"
);
auth = await getAuth({ hassUrl });
} else {
alert(`Unknown error: ${err}`);
Expand All @@ -51,36 +45,40 @@ async function connect() {
}
const connection = await createConnection(auth);
subscribeEntities(connection, ent => console.log(ent));
};
}

connect();
```

Connections to the websocket API are initiated by calling `createConnection(url[, options])`. `createConnection` will return a promise that will resolve to either a `Connection` object or rejects with an error code.
Connections to the websocket API are initiated by calling `createConnection(auth[, options])`. This method will return a promise that will resolve to either a `Connection` object or rejects with error codes `ERR_INVALID_AUTH` or `ERR_CANNOT_CONNECT`.

#### Available options

Currently the following options are available:

| Option | Description |
| ------ | ----------- |
| setupRetry | Number of times to retry initial connection when it fails. -1 means infinite.
| Option | Description |
| ------------ | -------------------------------------------------------------------------------------------------------------------- |
| setupRetry | Number of times to retry initial connection when it fails. Set to -1 for infinite retries. Default is 0 (no retries) |
| createSocket | Override the createSocket method with your own. `(auth, options) => Promise<WebSocket>` |

#### Possible error codes

Currently the following error codes can be expected:

| Error | Description |
| ----- | ----------- |
| ERR_CANNOT_CONNECT | If the client was unable to connect to the websocket API.
| ERR_INVALID_AUTH | If the supplied authentication was invalid.
| ERR_CONNECTION_LOST | Raised if connection closed while waiting for a message to be returned.
| ERR_HASS_HOST_REQUIRED | If the authentication requires a host to be defined.
| Error | Description |
| ---------------------- | ----------------------------------------------------------------------- |
| ERR_CANNOT_CONNECT | If the client was unable to connect to the websocket API. |
| ERR_INVALID_AUTH | If the supplied authentication was invalid. |
| ERR_CONNECTION_LOST | Raised if connection closed while waiting for a message to be returned. |
| ERR_HASS_HOST_REQUIRED | If the authentication requires a host to be defined. |

You can import them into your code as follows:

```javascript
import { ERR_CANNOT_CONNECT, ERR_INVALID_AUTH } from 'home-assistant-js-websocket';
import {
ERR_CANNOT_CONNECT,
ERR_INVALID_AUTH
} from "home-assistant-js-websocket";
```

#### Automatic reconnecting
Expand All @@ -89,21 +87,21 @@ The connection object will automatically try to reconnect to the server when the

The `Connection` object implements three events related to the reconnecting logic.

| Event | Data | Description |
| ----- | ---- | ----------- |
| ready | - | Fired when authentication is successful and the connection is ready to take commands.
| disconnected | - | Fired when the connection is lost.
| reconnect-error | Error code | Fired when we encounter a fatal error when trying to reconnect. Currently limited to `ERR_INVALID_AUTH`.
| Event | Data | Description |
| --------------- | ---------- | -------------------------------------------------------------------------------------------------------- |
| ready | - | Fired when authentication is successful and the connection is ready to take commands. |
| disconnected | - | Fired when the connection is lost. |
| reconnect-error | Error code | Fired when we encounter a fatal error when trying to reconnect. Currently limited to `ERR_INVALID_AUTH`. |

You can attach and remove listeners as follows:

```javascript
function eventHandler(connection, data) {
console.log('Connection has been established again');
console.log("Connection has been established again");
}

conn.addEventListener('ready', eventHandler);
conn.removeEventListener('ready', eventHandler);
conn.addEventListener("ready", eventHandler);
conn.removeEventListener("ready", eventHandler);
```

### Entities
Expand All @@ -113,12 +111,11 @@ You can subscribe to the entities of Home Assistant. Your callback will be calle
The function `subscribeEntities` will return an unsubscribe function.

```javascript
import { subscribeEntities } from 'home-assistant-js-websocket';
import { subscribeEntities } from "home-assistant-js-websocket";

// conn is the connection from earlier.

subscribeEntities(
conn, entities => console.log('New entities!', entities));
subscribeEntities(conn, entities => console.log("New entities!", entities));
```

### Config
Expand All @@ -128,12 +125,11 @@ You can subscribe to the config of Home Assistant. Config can change when either
The function `subscribeConfig` will return an unsubscribe function.

```javascript
import { subscribeConfig } from 'home-assistant-js-websocket';
import { subscribeConfig } from "home-assistant-js-websocket";

// conn is the connection from earlier.

subscribeConfig(
conn, config => console.log('New config!', config));
subscribeConfig(conn, config => console.log("New config!", config));
```

### Services
Expand All @@ -143,12 +139,11 @@ You can subscribe to the available services of Home Assistant. Services can chan
The function `subscribeServices` will return an unsubscribe function.

```javascript
import { subscribeServices } from 'home-assistant-js-websocket';
import { subscribeServices } from "home-assistant-js-websocket";

// conn is the connection from earlier.

subscribeServices(
conn, services => console.log('New services!', services));
subscribeServices(conn, services => console.log("New services!", services));
```

## Connection API Reference
Expand Down Expand Up @@ -184,18 +179,20 @@ Listen for events on the connection. [See docs.](#automatic-reconnecting)
To use this package in NodeJS, install the [ws package](https://www.npmjs.com/package/ws) and make it available as `WebSocket` on the `global` object before importing this package.

```js
const WebSocket = require('ws');
const WebSocket = require("ws");
global.WebSocket = WebSocket;
const HAWS = require("home-assistant-js-websocket");

const getWsUrl = haUrl => `ws://${haUrl}/api/websocket`;

HAWS.createConnection(getWsUrl('localhost:8123')).then(conn => {
HAWS.createConnection(getWsUrl("localhost:8123")).then(conn => {
HAWS.subscribeEntities(conn, logEntities);
});

function logEntities(entities) {
Object.keys(entities).forEach(key => console.log(`${key}: ${entities[key].state}`));
console.log('')
Object.keys(entities).forEach(key =>
console.log(`${key}: ${entities[key].state}`)
);
console.log("");
}
```
118 changes: 60 additions & 58 deletions example.html
Original file line number Diff line number Diff line change
@@ -1,62 +1,64 @@
<html>
<!-- Run `yarn build` and then `npx http-server -o` -->
<body>
<table>
<tbody>

</tbody>
</table>
<script type='module'>
import {
getAuth,
createConnection,
subscribeEntities,
ERR_HASS_HOST_REQUIRED,
} from './dist/haws.es.js';

(async () => {
let auth;
try {
auth = await getAuth();
} catch (err) {
if (err === ERR_HASS_HOST_REQUIRED) {
const hassUrl = prompt("What host to connect to?", "http://localhost:8123");
auth = await getAuth({ hassUrl });
} else {
alert(`Unknown error: ${err}`);
return;
}
<!-- To try locally, run: `yarn build` and then `npx http-server -o` -->

<body>
<table>
<tbody>

</tbody>
</table>
<script type='module'>
import {
getAuth,
createConnection,
subscribeEntities,
ERR_HASS_HOST_REQUIRED,
} from './dist/haws.es.js';

(async () => {
let auth;
try {
auth = await getAuth();
} catch (err) {
if (err === ERR_HASS_HOST_REQUIRED) {
const hassUrl = prompt("What host to connect to?", "http://localhost:8123");
auth = await getAuth({ hassUrl });
} else {
alert(`Unknown error: ${err}`);
return;
}
const connection = await createConnection(auth);
subscribeEntities(connection, entities => renderEntities(connection, entities));
})();

function renderEntities(connection, entities) {
const root = document.querySelector('tbody');
while(root.lastChild) root.removeChild(root.lastChild);

Object.keys(entities).sort().forEach((entId) => {
const tr = document.createElement('tr');

const tdName = document.createElement('td');
tdName.innerHTML = entId;
tr.appendChild(tdName);

const tdState = document.createElement('td');
const text = document.createTextNode(entities[entId].state);
tdState.appendChild(text);

if (['switch', 'light', 'input_boolean'].includes(entId.split('.', 1)[0])) {
const button = document.createElement('button');
button.innerHTML = 'toggle';
button.onclick = () => connection.callService('homeassistant', 'toggle', {entity_id: entId});
tdState.appendChild(button);
}
tr.appendChild(tdState);

root.appendChild(tr);
});
}
</script>
</body>
const connection = await createConnection(auth);
subscribeEntities(connection, entities => renderEntities(connection, entities));
})();

function renderEntities(connection, entities) {
const root = document.querySelector('tbody');
while (root.lastChild) root.removeChild(root.lastChild);

Object.keys(entities).sort().forEach((entId) => {
const tr = document.createElement('tr');

const tdName = document.createElement('td');
tdName.innerHTML = entId;
tr.appendChild(tdName);

const tdState = document.createElement('td');
const text = document.createTextNode(entities[entId].state);
tdState.appendChild(text);

if (['switch', 'light', 'input_boolean'].includes(entId.split('.', 1)[0])) {
const button = document.createElement('button');
button.innerHTML = 'toggle';
button.onclick = () => connection.callService('homeassistant', 'toggle', { entity_id: entId });
tdState.appendChild(button);
}
tr.appendChild(tdState);

root.appendChild(tr);
});
}
</script>
</body>

</html>
Loading