UNPKG

32.5 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 (global = global || self, factory(global.HAWS = {}));
5}(this, (function (exports) { 'use strict';
6
7 const ERR_CANNOT_CONNECT = 1;
8 const ERR_INVALID_AUTH = 2;
9 const ERR_CONNECTION_LOST = 3;
10 const ERR_HASS_HOST_REQUIRED = 4;
11 const ERR_INVALID_HTTPS_TO_HTTP = 5;
12
13 function auth(accessToken) {
14 return {
15 type: "auth",
16 access_token: accessToken
17 };
18 }
19 function states() {
20 return {
21 type: "get_states"
22 };
23 }
24 function config() {
25 return {
26 type: "get_config"
27 };
28 }
29 function services() {
30 return {
31 type: "get_services"
32 };
33 }
34 function user() {
35 return {
36 type: "auth/current_user"
37 };
38 }
39 function callService(domain, service, serviceData) {
40 const message = {
41 type: "call_service",
42 domain,
43 service
44 };
45 if (serviceData) {
46 message.service_data = serviceData;
47 }
48 return message;
49 }
50 function subscribeEvents(eventType) {
51 const message = {
52 type: "subscribe_events"
53 };
54 if (eventType) {
55 message.event_type = eventType;
56 }
57 return message;
58 }
59 function unsubscribeEvents(subscription) {
60 return {
61 type: "unsubscribe_events",
62 subscription
63 };
64 }
65 function ping() {
66 return {
67 type: "ping"
68 };
69 }
70 function error(code, message) {
71 return {
72 type: "result",
73 success: false,
74 error: {
75 code,
76 message
77 }
78 };
79 }
80
81 /**
82 * Create a web socket connection with a Home Assistant instance.
83 */
84 const MSG_TYPE_AUTH_REQUIRED = "auth_required";
85 const MSG_TYPE_AUTH_INVALID = "auth_invalid";
86 const MSG_TYPE_AUTH_OK = "auth_ok";
87 function createSocket(options) {
88 if (!options.auth) {
89 throw ERR_HASS_HOST_REQUIRED;
90 }
91 const auth$1 = options.auth;
92 // Start refreshing expired tokens even before the WS connection is open.
93 // We know that we will need auth anyway.
94 let authRefreshTask = auth$1.expired
95 ? auth$1.refreshAccessToken().then(() => {
96 authRefreshTask = undefined;
97 }, () => {
98 authRefreshTask = undefined;
99 })
100 : undefined;
101 // Convert from http:// -> ws://, https:// -> wss://
102 const url = auth$1.wsUrl;
103 function connect(triesLeft, promResolve, promReject) {
104 const socket = new WebSocket(url);
105 // If invalid auth, we will not try to reconnect.
106 let invalidAuth = false;
107 const closeMessage = () => {
108 // If we are in error handler make sure close handler doesn't also fire.
109 socket.removeEventListener("close", closeMessage);
110 if (invalidAuth) {
111 promReject(ERR_INVALID_AUTH);
112 return;
113 }
114 // Reject if we no longer have to retry
115 if (triesLeft === 0) {
116 // We never were connected and will not retry
117 promReject(ERR_CANNOT_CONNECT);
118 return;
119 }
120 const newTries = triesLeft === -1 ? -1 : triesLeft - 1;
121 // Try again in a second
122 setTimeout(() => connect(newTries, promResolve, promReject), 1000);
123 };
124 // Auth is mandatory, so we can send the auth message right away.
125 const handleOpen = async (event) => {
126 try {
127 if (auth$1.expired) {
128 await (authRefreshTask ? authRefreshTask : auth$1.refreshAccessToken());
129 }
130 socket.send(JSON.stringify(auth(auth$1.accessToken)));
131 }
132 catch (err) {
133 // Refresh token failed
134 invalidAuth = err === ERR_INVALID_AUTH;
135 socket.close();
136 }
137 };
138 const handleMessage = async (event) => {
139 const message = JSON.parse(event.data);
140 switch (message.type) {
141 case MSG_TYPE_AUTH_INVALID:
142 invalidAuth = true;
143 socket.close();
144 break;
145 case MSG_TYPE_AUTH_OK:
146 socket.removeEventListener("open", handleOpen);
147 socket.removeEventListener("message", handleMessage);
148 socket.removeEventListener("close", closeMessage);
149 socket.removeEventListener("error", closeMessage);
150 socket.haVersion = message.ha_version;
151 promResolve(socket);
152 break;
153 }
154 };
155 socket.addEventListener("open", handleOpen);
156 socket.addEventListener("message", handleMessage);
157 socket.addEventListener("close", closeMessage);
158 socket.addEventListener("error", closeMessage);
159 }
160 return new Promise((resolve, reject) => connect(options.setupRetry, resolve, reject));
161 }
162
163 /**
164 * Connection that wraps a socket and provides an interface to interact with
165 * the Home Assistant websocket API.
166 */
167 class Connection {
168 constructor(socket, options) {
169 // connection options
170 // - setupRetry: amount of ms to retry when unable to connect on initial setup
171 // - createSocket: create a new Socket connection
172 this.options = options;
173 // id if next command to send
174 this.commandId = 1;
175 // info about active subscriptions and commands in flight
176 this.commands = new Map();
177 // map of event listeners
178 this.eventListeners = new Map();
179 // true if a close is requested by the user
180 this.closeRequested = false;
181 this.setSocket(socket);
182 }
183 get haVersion() {
184 return this.socket.haVersion;
185 }
186 setSocket(socket) {
187 const oldSocket = this.socket;
188 this.socket = socket;
189 socket.addEventListener("message", ev => this._handleMessage(ev));
190 socket.addEventListener("close", ev => this._handleClose(ev));
191 if (oldSocket) {
192 const oldCommands = this.commands;
193 // reset to original state
194 this.commandId = 1;
195 this.commands = new Map();
196 oldCommands.forEach(info => {
197 if ("subscribe" in info) {
198 info.subscribe().then(unsub => {
199 info.unsubscribe = unsub;
200 // We need to resolve this in case it wasn't resolved yet.
201 // This allows us to subscribe while we're disconnected
202 // and recover properly.
203 info.resolve();
204 });
205 }
206 });
207 this.fireEvent("ready");
208 }
209 }
210 addEventListener(eventType, callback) {
211 let listeners = this.eventListeners.get(eventType);
212 if (!listeners) {
213 listeners = [];
214 this.eventListeners.set(eventType, listeners);
215 }
216 listeners.push(callback);
217 }
218 removeEventListener(eventType, callback) {
219 const listeners = this.eventListeners.get(eventType);
220 if (!listeners) {
221 return;
222 }
223 const index = listeners.indexOf(callback);
224 if (index !== -1) {
225 listeners.splice(index, 1);
226 }
227 }
228 fireEvent(eventType, eventData) {
229 (this.eventListeners.get(eventType) || []).forEach(callback => callback(this, eventData));
230 }
231 close() {
232 this.closeRequested = true;
233 this.socket.close();
234 }
235 /**
236 * Subscribe to a specific or all events.
237 *
238 * @param callback Callback to be called when a new event fires
239 * @param eventType
240 * @returns promise that resolves to an unsubscribe function
241 */
242 async subscribeEvents(callback, eventType) {
243 return this.subscribeMessage(callback, subscribeEvents(eventType));
244 }
245 ping() {
246 return this.sendMessagePromise(ping());
247 }
248 sendMessage(message, commandId) {
249 if (!commandId) {
250 commandId = this._genCmdId();
251 }
252 message.id = commandId;
253 this.socket.send(JSON.stringify(message));
254 }
255 sendMessagePromise(message) {
256 return new Promise((resolve, reject) => {
257 const commandId = this._genCmdId();
258 this.commands.set(commandId, { resolve, reject });
259 this.sendMessage(message, commandId);
260 });
261 }
262 /**
263 * Call a websocket command that starts a subscription on the backend.
264 *
265 * @param message the message to start the subscription
266 * @param callback the callback to be called when a new item arrives
267 * @returns promise that resolves to an unsubscribe function
268 */
269 async subscribeMessage(callback, subscribeMessage) {
270 // Command ID that will be used
271 const commandId = this._genCmdId();
272 let info;
273 await new Promise((resolve, reject) => {
274 // We store unsubscribe on info object. That way we can overwrite it in case
275 // we get disconnected and we have to subscribe again.
276 info = {
277 resolve,
278 reject,
279 callback,
280 subscribe: () => this.subscribeMessage(callback, subscribeMessage),
281 unsubscribe: async () => {
282 await this.sendMessagePromise(unsubscribeEvents(commandId));
283 this.commands.delete(commandId);
284 }
285 };
286 this.commands.set(commandId, info);
287 try {
288 this.sendMessage(subscribeMessage, commandId);
289 }
290 catch (err) {
291 // Happens when the websocket is already closing.
292 // Don't have to handle the error, reconnect logic will pick it up.
293 }
294 });
295 return () => info.unsubscribe();
296 }
297 _handleMessage(event) {
298 const message = JSON.parse(event.data);
299 const info = this.commands.get(message.id);
300 switch (message.type) {
301 case "event":
302 if (info) {
303 info.callback(message.event);
304 }
305 else {
306 console.warn(`Received event for unknown subscription ${message.id}. Unsubscribing.`);
307 this.sendMessagePromise(unsubscribeEvents(message.id));
308 }
309 break;
310 case "result":
311 // No info is fine. If just sendMessage is used, we did not store promise for result
312 if (info) {
313 if (message.success) {
314 info.resolve(message.result);
315 // Don't remove subscriptions.
316 if (!("subscribe" in info)) {
317 this.commands.delete(message.id);
318 }
319 }
320 else {
321 info.reject(message.error);
322 this.commands.delete(message.id);
323 }
324 }
325 break;
326 case "pong":
327 if (info) {
328 info.resolve();
329 this.commands.delete(message.id);
330 }
331 else {
332 console.warn(`Received unknown pong response ${message.id}`);
333 }
334 break;
335 }
336 }
337 _handleClose(ev) {
338 // Reject in-flight sendMessagePromise requests
339 this.commands.forEach(info => {
340 // We don't cancel subscribeEvents commands in flight
341 // as we will be able to recover them.
342 if (!("subscribe" in info)) {
343 info.reject(error(ERR_CONNECTION_LOST, "Connection lost"));
344 }
345 });
346 if (this.closeRequested) {
347 return;
348 }
349 this.fireEvent("disconnected");
350 // Disable setupRetry, we control it here with auto-backoff
351 const options = Object.assign(Object.assign({}, this.options), { setupRetry: 0 });
352 const reconnect = (tries) => {
353 setTimeout(async () => {
354 try {
355 const socket = await options.createSocket(options);
356 this.setSocket(socket);
357 }
358 catch (err) {
359 if (err === ERR_INVALID_AUTH) {
360 this.fireEvent("reconnect-error", err);
361 }
362 else {
363 reconnect(tries + 1);
364 }
365 }
366 }, Math.min(tries, 5) * 1000);
367 };
368 reconnect(0);
369 }
370 _genCmdId() {
371 return ++this.commandId;
372 }
373 }
374
375 function parseQuery(queryString) {
376 const query = {};
377 const items = queryString.split("&");
378 for (let i = 0; i < items.length; i++) {
379 const item = items[i].split("=");
380 const key = decodeURIComponent(item[0]);
381 const value = item.length > 1 ? decodeURIComponent(item[1]) : undefined;
382 query[key] = value;
383 }
384 return query;
385 }
386
387 const genClientId = () => `${location.protocol}//${location.host}/`;
388 const genExpires = (expires_in) => {
389 return expires_in * 1000 + Date.now();
390 };
391 function genRedirectUrl() {
392 // Get current url but without # part.
393 const { protocol, host, pathname, search } = location;
394 return `${protocol}//${host}${pathname}${search}`;
395 }
396 function genAuthorizeUrl(hassUrl, clientId, redirectUrl, state) {
397 let authorizeUrl = `${hassUrl}/auth/authorize?response_type=code&redirect_uri=${encodeURIComponent(redirectUrl)}`;
398 if (clientId !== null) {
399 authorizeUrl += `&client_id=${encodeURIComponent(clientId)}`;
400 }
401 if (state) {
402 authorizeUrl += `&state=${encodeURIComponent(state)}`;
403 }
404 return authorizeUrl;
405 }
406 function redirectAuthorize(hassUrl, clientId, redirectUrl, state) {
407 // Add either ?auth_callback=1 or &auth_callback=1
408 redirectUrl += (redirectUrl.includes("?") ? "&" : "?") + "auth_callback=1";
409 document.location.href = genAuthorizeUrl(hassUrl, clientId, redirectUrl, state);
410 }
411 async function tokenRequest(hassUrl, clientId, data) {
412 // Browsers don't allow fetching tokens from https -> http.
413 // Throw an error because it's a pain to debug this.
414 // Guard against not working in node.
415 const l = typeof location !== "undefined" && location;
416 if (l && l.protocol === "https:") {
417 // Ensure that the hassUrl is hosted on https.
418 const a = document.createElement("a");
419 a.href = hassUrl;
420 if (a.protocol === "http:" && a.hostname !== "localhost") {
421 throw ERR_INVALID_HTTPS_TO_HTTP;
422 }
423 }
424 const formData = new FormData();
425 if (clientId !== null) {
426 formData.append("client_id", clientId);
427 }
428 Object.keys(data).forEach(key => {
429 formData.append(key, data[key]);
430 });
431 const resp = await fetch(`${hassUrl}/auth/token`, {
432 method: "POST",
433 credentials: "same-origin",
434 body: formData
435 });
436 if (!resp.ok) {
437 throw resp.status === 400 /* auth invalid */ ||
438 resp.status === 403 /* user not active */
439 ? ERR_INVALID_AUTH
440 : new Error("Unable to fetch tokens");
441 }
442 const tokens = await resp.json();
443 tokens.hassUrl = hassUrl;
444 tokens.clientId = clientId;
445 tokens.expires = genExpires(tokens.expires_in);
446 return tokens;
447 }
448 function fetchToken(hassUrl, clientId, code) {
449 return tokenRequest(hassUrl, clientId, {
450 code,
451 grant_type: "authorization_code"
452 });
453 }
454 function encodeOAuthState(state) {
455 return btoa(JSON.stringify(state));
456 }
457 function decodeOAuthState(encoded) {
458 return JSON.parse(atob(encoded));
459 }
460 class Auth {
461 constructor(data, saveTokens) {
462 this.data = data;
463 this._saveTokens = saveTokens;
464 }
465 get wsUrl() {
466 // Convert from http:// -> ws://, https:// -> wss://
467 return `ws${this.data.hassUrl.substr(4)}/api/websocket`;
468 }
469 get accessToken() {
470 return this.data.access_token;
471 }
472 get expired() {
473 return Date.now() > this.data.expires;
474 }
475 /**
476 * Refresh the access token.
477 */
478 async refreshAccessToken() {
479 if (!this.data.refresh_token)
480 throw new Error("No refresh_token");
481 const data = await tokenRequest(this.data.hassUrl, this.data.clientId, {
482 grant_type: "refresh_token",
483 refresh_token: this.data.refresh_token
484 });
485 // Access token response does not contain refresh token.
486 data.refresh_token = this.data.refresh_token;
487 this.data = data;
488 if (this._saveTokens)
489 this._saveTokens(data);
490 }
491 /**
492 * Revoke the refresh & access tokens.
493 */
494 async revoke() {
495 if (!this.data.refresh_token)
496 throw new Error("No refresh_token to revoke");
497 const formData = new FormData();
498 formData.append("action", "revoke");
499 formData.append("token", this.data.refresh_token);
500 // There is no error checking, as revoke will always return 200
501 await fetch(`${this.data.hassUrl}/auth/token`, {
502 method: "POST",
503 credentials: "same-origin",
504 body: formData
505 });
506 if (this._saveTokens) {
507 this._saveTokens(null);
508 }
509 }
510 }
511 function createLongLivedTokenAuth(hassUrl, access_token) {
512 return new Auth({
513 hassUrl,
514 clientId: null,
515 expires: Date.now() + 1e11,
516 refresh_token: "",
517 access_token,
518 expires_in: 1e11
519 });
520 }
521 async function getAuth(options = {}) {
522 let data;
523 let hassUrl = options.hassUrl;
524 // Strip trailing slash.
525 if (hassUrl && hassUrl[hassUrl.length - 1] === "/") {
526 hassUrl = hassUrl.substr(0, hassUrl.length - 1);
527 }
528 const clientId = options.clientId !== undefined ? options.clientId : genClientId();
529 // Use auth code if it was passed in
530 if (!data && options.authCode && hassUrl) {
531 data = await fetchToken(hassUrl, clientId, options.authCode);
532 if (options.saveTokens) {
533 options.saveTokens(data);
534 }
535 }
536 // Check if we came back from an authorize redirect
537 if (!data) {
538 const query = parseQuery(location.search.substr(1));
539 // Check if we got redirected here from authorize page
540 if ("auth_callback" in query) {
541 // Restore state
542 const state = decodeOAuthState(query.state);
543 data = await fetchToken(state.hassUrl, state.clientId, query.code);
544 if (options.saveTokens) {
545 options.saveTokens(data);
546 }
547 }
548 }
549 // Check for stored tokens
550 if (!data && options.loadTokens) {
551 data = await options.loadTokens();
552 }
553 if (data) {
554 return new Auth(data, options.saveTokens);
555 }
556 if (hassUrl === undefined) {
557 throw ERR_HASS_HOST_REQUIRED;
558 }
559 // If no tokens found but a hassUrl was passed in, let's go get some tokens!
560 redirectAuthorize(hassUrl, clientId, options.redirectUrl || genRedirectUrl(), encodeOAuthState({
561 hassUrl,
562 clientId
563 }));
564 // Just don't resolve while we navigate to next page
565 return new Promise(() => { });
566 }
567
568 const createStore = (state) => {
569 let listeners = [];
570 function unsubscribe(listener) {
571 let out = [];
572 for (let i = 0; i < listeners.length; i++) {
573 if (listeners[i] === listener) {
574 listener = null;
575 }
576 else {
577 out.push(listeners[i]);
578 }
579 }
580 listeners = out;
581 }
582 function setState(update, overwrite) {
583 state = overwrite ? update : Object.assign(Object.assign({}, state), update);
584 let currentListeners = listeners;
585 for (let i = 0; i < currentListeners.length; i++) {
586 currentListeners[i](state);
587 }
588 }
589 /**
590 * An observable state container, returned from {@link createStore}
591 * @name store
592 */
593 return {
594 get state() {
595 return state;
596 },
597 /**
598 * Create a bound copy of the given action function.
599 * The bound returned function invokes action() and persists the result back to the store.
600 * If the return value of `action` is a Promise, the resolved value will be used as state.
601 * @param {Function} action An action of the form `action(state, ...args) -> stateUpdate`
602 * @returns {Function} boundAction()
603 */
604 action(action) {
605 function apply(result) {
606 setState(result, false);
607 }
608 // Note: perf tests verifying this implementation: https://esbench.com/bench/5a295e6299634800a0349500
609 return function () {
610 let args = [state];
611 for (let i = 0; i < arguments.length; i++)
612 args.push(arguments[i]);
613 // @ts-ignore
614 let ret = action.apply(this, args);
615 if (ret != null) {
616 if (ret.then)
617 return ret.then(apply);
618 return apply(ret);
619 }
620 };
621 },
622 /**
623 * Apply a partial state object to the current state, invoking registered listeners.
624 * @param {Object} update An object with properties to be merged into state
625 * @param {Boolean} [overwrite=false] If `true`, update will replace state instead of being merged into it
626 */
627 setState,
628 /**
629 * Register a listener function to be called whenever state is changed. Returns an `unsubscribe()` function.
630 * @param {Function} listener A function to call when state changes. Gets passed the new state.
631 * @returns {Function} unsubscribe()
632 */
633 subscribe(listener) {
634 listeners.push(listener);
635 return () => {
636 unsubscribe(listener);
637 };
638 }
639 // /**
640 // * Remove a previously-registered listener function.
641 // * @param {Function} listener The callback previously passed to `subscribe()` that should be removed.
642 // * @function
643 // */
644 // unsubscribe,
645 };
646 };
647
648 const getCollection = (conn, key, fetchCollection, subscribeUpdates) => {
649 if (conn[key]) {
650 return conn[key];
651 }
652 let active = 0;
653 let unsubProm;
654 let store = createStore();
655 const refresh = () => fetchCollection(conn).then(state => store.setState(state, true));
656 const refreshSwallow = () => refresh().catch((err) => {
657 // Swallow errors if socket is connecting, closing or closed.
658 // We will automatically call refresh again when we re-establish the connection.
659 // Using conn.socket.OPEN instead of WebSocket for better node support
660 if (conn.socket.readyState == conn.socket.OPEN) {
661 throw err;
662 }
663 });
664 conn[key] = {
665 get state() {
666 return store.state;
667 },
668 refresh,
669 subscribe(subscriber) {
670 active++;
671 // If this was the first subscriber, attach collection
672 if (active === 1) {
673 if (subscribeUpdates) {
674 unsubProm = subscribeUpdates(conn, store);
675 }
676 // Fetch when connection re-established.
677 conn.addEventListener("ready", refreshSwallow);
678 refreshSwallow();
679 }
680 const unsub = store.subscribe(subscriber);
681 if (store.state !== undefined) {
682 // Don't call it right away so that caller has time
683 // to initialize all the things.
684 setTimeout(() => subscriber(store.state), 0);
685 }
686 return () => {
687 unsub();
688 active--;
689 if (!active) {
690 // Unsubscribe from changes
691 if (unsubProm)
692 unsubProm.then(unsub => {
693 unsub();
694 });
695 conn.removeEventListener("ready", refresh);
696 }
697 };
698 }
699 };
700 return conn[key];
701 };
702 // Legacy name. It gets a collection and subscribes.
703 const createCollection = (key, fetchCollection, subscribeUpdates, conn, onChange) => getCollection(conn, key, fetchCollection, subscribeUpdates).subscribe(onChange);
704
705 const getStates = (connection) => connection.sendMessagePromise(states());
706 const getServices = (connection) => connection.sendMessagePromise(services());
707 const getConfig = (connection) => connection.sendMessagePromise(config());
708 const getUser = (connection) => connection.sendMessagePromise(user());
709 const callService$1 = (connection, domain, service, serviceData) => connection.sendMessagePromise(callService(domain, service, serviceData));
710
711 function processComponentLoaded(state, event) {
712 if (state === undefined)
713 return null;
714 return {
715 components: state.components.concat(event.data.component)
716 };
717 }
718 const fetchConfig = (conn) => getConfig(conn);
719 const subscribeUpdates = (conn, store) => Promise.all([
720 conn.subscribeEvents(store.action(processComponentLoaded), "component_loaded"),
721 conn.subscribeEvents(() => fetchConfig(conn).then(config => store.setState(config, true)), "core_config_updated")
722 ]).then(unsubs => () => unsubs.forEach(unsub => unsub()));
723 const configColl = (conn) => getCollection(conn, "_cnf", fetchConfig, subscribeUpdates);
724 const subscribeConfig = (conn, onChange) => configColl(conn).subscribe(onChange);
725
726 function processServiceRegistered(state, event) {
727 if (state === undefined)
728 return null;
729 const { domain, service } = event.data;
730 const domainInfo = Object.assign({}, state[domain], {
731 [service]: { description: "", fields: {} }
732 });
733 return { [domain]: domainInfo };
734 }
735 function processServiceRemoved(state, event) {
736 if (state === undefined)
737 return null;
738 const { domain, service } = event.data;
739 const curDomainInfo = state[domain];
740 if (!curDomainInfo || !(service in curDomainInfo))
741 return null;
742 const domainInfo = {};
743 Object.keys(curDomainInfo).forEach(sKey => {
744 if (sKey !== service)
745 domainInfo[sKey] = curDomainInfo[sKey];
746 });
747 return { [domain]: domainInfo };
748 }
749 const fetchServices = (conn) => getServices(conn);
750 const subscribeUpdates$1 = (conn, store) => Promise.all([
751 conn.subscribeEvents(store.action(processServiceRegistered), "service_registered"),
752 conn.subscribeEvents(store.action(processServiceRemoved), "service_removed")
753 ]).then(unsubs => () => unsubs.forEach(fn => fn()));
754 const servicesColl = (conn) => getCollection(conn, "_srv", fetchServices, subscribeUpdates$1);
755 const subscribeServices = (conn, onChange) => servicesColl(conn).subscribe(onChange);
756
757 function processEvent(store, event) {
758 const state = store.state;
759 if (state === undefined)
760 return;
761 const { entity_id, new_state } = event.data;
762 if (new_state) {
763 store.setState({ [new_state.entity_id]: new_state });
764 }
765 else {
766 const newEntities = Object.assign({}, state);
767 delete newEntities[entity_id];
768 store.setState(newEntities, true);
769 }
770 }
771 async function fetchEntities(conn) {
772 const states = await getStates(conn);
773 const entities = {};
774 for (let i = 0; i < states.length; i++) {
775 const state = states[i];
776 entities[state.entity_id] = state;
777 }
778 return entities;
779 }
780 const subscribeUpdates$2 = (conn, store) => conn.subscribeEvents(ev => processEvent(store, ev), "state_changed");
781 const entitiesColl = (conn) => getCollection(conn, "_ent", fetchEntities, subscribeUpdates$2);
782 const subscribeEntities = (conn, onChange) => entitiesColl(conn).subscribe(onChange);
783
784 async function createConnection(options) {
785 const connOptions = Object.assign({ setupRetry: 0, createSocket }, options);
786 const socket = await connOptions.createSocket(connOptions);
787 const conn = new Connection(socket, connOptions);
788 return conn;
789 }
790
791 exports.Auth = Auth;
792 exports.Connection = Connection;
793 exports.ERR_CANNOT_CONNECT = ERR_CANNOT_CONNECT;
794 exports.ERR_CONNECTION_LOST = ERR_CONNECTION_LOST;
795 exports.ERR_HASS_HOST_REQUIRED = ERR_HASS_HOST_REQUIRED;
796 exports.ERR_INVALID_AUTH = ERR_INVALID_AUTH;
797 exports.ERR_INVALID_HTTPS_TO_HTTP = ERR_INVALID_HTTPS_TO_HTTP;
798 exports.MSG_TYPE_AUTH_INVALID = MSG_TYPE_AUTH_INVALID;
799 exports.MSG_TYPE_AUTH_OK = MSG_TYPE_AUTH_OK;
800 exports.MSG_TYPE_AUTH_REQUIRED = MSG_TYPE_AUTH_REQUIRED;
801 exports.callService = callService$1;
802 exports.createCollection = createCollection;
803 exports.createConnection = createConnection;
804 exports.createLongLivedTokenAuth = createLongLivedTokenAuth;
805 exports.createSocket = createSocket;
806 exports.entitiesColl = entitiesColl;
807 exports.genClientId = genClientId;
808 exports.genExpires = genExpires;
809 exports.getAuth = getAuth;
810 exports.getCollection = getCollection;
811 exports.getConfig = getConfig;
812 exports.getServices = getServices;
813 exports.getStates = getStates;
814 exports.getUser = getUser;
815 exports.subscribeConfig = subscribeConfig;
816 exports.subscribeEntities = subscribeEntities;
817 exports.subscribeServices = subscribeServices;
818
819 Object.defineProperty(exports, '__esModule', { value: true });
820
821})));