Skip to content

Commit d53c0e7

Browse files
authored
Allow passing in WebSocket constructor (#87)
* Allow passing in WebSocket constructor * Fix TS error in CI
1 parent 62eddf9 commit d53c0e7

File tree

4 files changed

+82
-71
lines changed

4 files changed

+82
-71
lines changed

README.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ createConnection({ auth });
102102
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
103103
| auth | Auth object to use to create a connection. |
104104
| createSocket | Override the createSocket method with your own. `(options) => Promise<WebSocket>`. Needs to return a connection that is already authenticated. |
105+
| WebSocket | Constructor to use to initialize the WebSocket connection inside the built-in createSocket method. |
105106
| setupRetry | Number of times to retry initial connection when it fails. Set to -1 for infinite retries. Default is 0 (no retries) |
106107

107108
Currently the following error codes can be raised by createConnection:
@@ -371,20 +372,12 @@ import {
371372

372373
## Using this in NodeJS
373374

374-
To use this package in NodeJS, you will want to define your own `createSocket` method for `createConnection` to use. Your createSocket function will need to set up the web socket connection with Home Assistant and handle the auth.
375+
NodeJS does not have a WebSocket client built-in, but there are some good ones on NPM. We recommend `ws`. You can pass the WebSocket constructor to use as part of the options.
375376

376377
```js
377378
const WebSocket = require("ws");
378379

379380
createConnection({
380-
createSocket() {
381-
// Open connection
382-
const ws = new WebSocket("ws://localhost:8123");
383-
384-
// Functions to handle authentication with Home Assistant
385-
// Implement yourself :)
386-
387-
return ws;
388-
}
381+
WebSocket
389382
});
390383
```

example.html

Lines changed: 71 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,83 @@
11
<html>
2-
<!-- To try locally, run: `yarn build` and then `npx http-server -o` -->
2+
<!-- To try locally, run: `yarn build` and then `npx http-server -o` -->
33

4-
<body>
5-
<table>
6-
<tbody>
4+
<body>
5+
<table>
6+
<tbody></tbody>
7+
</table>
8+
<script type="module">
9+
import {
10+
getAuth,
11+
getUser,
12+
callService,
13+
createConnection,
14+
subscribeEntities,
15+
ERR_HASS_HOST_REQUIRED
16+
} from "./dist/haws.es.js";
717

8-
</tbody>
9-
</table>
10-
<script type='module'>
11-
import {
12-
getAuth,
13-
getUser,
14-
callService,
15-
createConnection,
16-
subscribeEntities,
17-
ERR_HASS_HOST_REQUIRED,
18-
} from './dist/haws.es.js';
19-
20-
(async () => {
21-
let auth;
22-
try {
23-
auth = await getAuth();
24-
} catch (err) {
25-
if (err === ERR_HASS_HOST_REQUIRED) {
26-
const hassUrl = prompt("What host to connect to?", "http://localhost:8123");
27-
if (!hassUrl) return;
28-
auth = await getAuth({ hassUrl });
29-
} else {
30-
alert(`Unknown error: ${err}`);
31-
return;
18+
(async () => {
19+
let auth;
20+
try {
21+
auth = await getAuth();
22+
} catch (err) {
23+
if (err === ERR_HASS_HOST_REQUIRED) {
24+
const hassUrl = prompt(
25+
"What host to connect to?",
26+
"http://localhost:8123"
27+
);
28+
if (!hassUrl) return;
29+
auth = await getAuth({ hassUrl });
30+
} else {
31+
alert(`Unknown error: ${err}`);
32+
return;
33+
}
3234
}
33-
}
34-
const connection = await createConnection({ auth });
35-
subscribeEntities(connection, entities => renderEntities(connection, entities));
35+
const connection = await createConnection({ auth });
36+
subscribeEntities(connection, entities =>
37+
renderEntities(connection, entities)
38+
);
3639

37-
// To play from the console
38-
window.auth = auth;
39-
window.connection = connection;
40-
getUser(connection).then(user => console.log("Logged in as", user));
41-
})();
40+
// To play from the console
41+
window.auth = auth;
42+
window.connection = connection;
43+
getUser(connection).then(user => console.log("Logged in as", user));
44+
})();
4245

43-
function renderEntities(connection, entities) {
44-
const root = document.querySelector('tbody');
45-
while (root.lastChild) root.removeChild(root.lastChild);
46+
function renderEntities(connection, entities) {
47+
const root = document.querySelector("tbody");
48+
while (root.lastChild) root.removeChild(root.lastChild);
4649

47-
Object.keys(entities).sort().forEach((entId) => {
48-
const tr = document.createElement('tr');
50+
Object.keys(entities)
51+
.sort()
52+
.forEach(entId => {
53+
const tr = document.createElement("tr");
4954

50-
const tdName = document.createElement('td');
51-
tdName.innerHTML = entId;
52-
tr.appendChild(tdName);
55+
const tdName = document.createElement("td");
56+
tdName.innerHTML = entId;
57+
tr.appendChild(tdName);
5358

54-
const tdState = document.createElement('td');
55-
const text = document.createTextNode(entities[entId].state);
56-
tdState.appendChild(text);
59+
const tdState = document.createElement("td");
60+
const text = document.createTextNode(entities[entId].state);
61+
tdState.appendChild(text);
5762

58-
if (['switch', 'light', 'input_boolean'].includes(entId.split('.', 1)[0])) {
59-
const button = document.createElement('button');
60-
button.innerHTML = 'toggle';
61-
button.onclick = () => callService(connection, 'homeassistant', 'toggle', { entity_id: entId });
62-
tdState.appendChild(button);
63-
}
64-
tr.appendChild(tdState);
65-
66-
root.appendChild(tr);
67-
});
68-
}
69-
</script>
70-
</body>
63+
if (
64+
["switch", "light", "input_boolean"].includes(
65+
entId.split(".", 1)[0]
66+
)
67+
) {
68+
const button = document.createElement("button");
69+
button.innerHTML = "toggle";
70+
button.onclick = () =>
71+
callService(connection, "homeassistant", "toggle", {
72+
entity_id: entId
73+
});
74+
tdState.appendChild(button);
75+
}
76+
tr.appendChild(tdState);
7177

78+
root.appendChild(tr);
79+
});
80+
}
81+
</script>
82+
</body>
7283
</html>

lib/socket.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function createSocket(options: ConnectionOptions): Promise<WebSocket> {
2020
throw ERR_HASS_HOST_REQUIRED;
2121
}
2222
const auth = options.auth;
23+
const wsConstructor = options.WebSocket || WebSocket;
2324

2425
// Start refreshing expired tokens even before the WS connection is open.
2526
// We know that we will need auth anyway.
@@ -50,7 +51,8 @@ export function createSocket(options: ConnectionOptions): Promise<WebSocket> {
5051
console.log("[Auth Phase] New connection", url);
5152
}
5253

53-
const socket = new WebSocket(url);
54+
// @ts-ignore
55+
const socket = new wsConstructor(url);
5456

5557
// If invalid auth, we will not try to reconnect.
5658
let invalidAuth = false;

lib/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Auth } from "./auth";
22

3+
type Constructor<T> = {
4+
new (...args: unknown[]): T;
5+
};
6+
37
export type Error = 1 | 2 | 3 | 4;
48

59
export type UnsubscribeFunc = () => void;
@@ -8,6 +12,7 @@ export type ConnectionOptions = {
812
setupRetry: number;
913
auth?: Auth;
1014
createSocket: (options: ConnectionOptions) => Promise<WebSocket>;
15+
WebSocket?: Constructor<WebSocket>;
1116
};
1217

1318
export type MessageBase = {

0 commit comments

Comments
 (0)