1 |
|
2 |
|
3 |
|
4 | import { ERR_INVALID_AUTH, ERR_CANNOT_CONNECT, ERR_HASS_HOST_REQUIRED } from "./errors.js";
|
5 | import * as messages from "./messages.js";
|
6 | const DEBUG = false;
|
7 | export const MSG_TYPE_AUTH_REQUIRED = "auth_required";
|
8 | export const MSG_TYPE_AUTH_INVALID = "auth_invalid";
|
9 | export const MSG_TYPE_AUTH_OK = "auth_ok";
|
10 | export function createSocket(options) {
|
11 | if (!options.auth) {
|
12 | throw ERR_HASS_HOST_REQUIRED;
|
13 | }
|
14 | const auth = options.auth;
|
15 |
|
16 |
|
17 | let authRefreshTask = auth.expired
|
18 | ? auth.refreshAccessToken().then(() => {
|
19 | authRefreshTask = undefined;
|
20 | }, () => {
|
21 | authRefreshTask = undefined;
|
22 | })
|
23 | : undefined;
|
24 |
|
25 | const url = auth.wsUrl;
|
26 | if (DEBUG) {
|
27 | console.log("[Auth phase] Initializing", url);
|
28 | }
|
29 | function connect(triesLeft, promResolve, promReject) {
|
30 | if (DEBUG) {
|
31 | console.log("[Auth Phase] New connection", url);
|
32 | }
|
33 | const socket = new WebSocket(url);
|
34 |
|
35 | let invalidAuth = false;
|
36 | const closeMessage = () => {
|
37 |
|
38 | socket.removeEventListener("close", closeMessage);
|
39 | if (invalidAuth) {
|
40 | promReject(ERR_INVALID_AUTH);
|
41 | return;
|
42 | }
|
43 |
|
44 | if (triesLeft === 0) {
|
45 |
|
46 | promReject(ERR_CANNOT_CONNECT);
|
47 | return;
|
48 | }
|
49 | const newTries = triesLeft === -1 ? -1 : triesLeft - 1;
|
50 |
|
51 | setTimeout(() => connect(newTries, promResolve, promReject), 1000);
|
52 | };
|
53 |
|
54 | const handleOpen = async (event) => {
|
55 | try {
|
56 | if (auth.expired) {
|
57 | await (authRefreshTask ? authRefreshTask : auth.refreshAccessToken());
|
58 | }
|
59 | socket.send(JSON.stringify(messages.auth(auth.accessToken)));
|
60 | }
|
61 | catch (err) {
|
62 |
|
63 | invalidAuth = err === ERR_INVALID_AUTH;
|
64 | socket.close();
|
65 | }
|
66 | };
|
67 | const handleMessage = async (event) => {
|
68 | const message = JSON.parse(event.data);
|
69 | if (DEBUG) {
|
70 | console.log("[Auth phase] Received", message);
|
71 | }
|
72 | switch (message.type) {
|
73 | case MSG_TYPE_AUTH_INVALID:
|
74 | invalidAuth = true;
|
75 | socket.close();
|
76 | break;
|
77 | case MSG_TYPE_AUTH_OK:
|
78 | socket.removeEventListener("open", handleOpen);
|
79 | socket.removeEventListener("message", handleMessage);
|
80 | socket.removeEventListener("close", closeMessage);
|
81 | socket.removeEventListener("error", closeMessage);
|
82 | socket.haVersion = message.ha_version;
|
83 | promResolve(socket);
|
84 | break;
|
85 | default:
|
86 | if (DEBUG) {
|
87 |
|
88 | if (message.type !== MSG_TYPE_AUTH_REQUIRED) {
|
89 | console.warn("[Auth phase] Unhandled message", message);
|
90 | }
|
91 | }
|
92 | }
|
93 | };
|
94 | socket.addEventListener("open", handleOpen);
|
95 | socket.addEventListener("message", handleMessage);
|
96 | socket.addEventListener("close", closeMessage);
|
97 | socket.addEventListener("error", closeMessage);
|
98 | }
|
99 | return new Promise((resolve, reject) => connect(options.setupRetry, resolve, reject));
|
100 | }
|