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 |
|
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 |
|
93 |
|
94 | let authRefreshTask = auth$1.expired
|
95 | ? auth$1.refreshAccessToken().then(() => {
|
96 | authRefreshTask = undefined;
|
97 | }, () => {
|
98 | authRefreshTask = undefined;
|
99 | })
|
100 | : undefined;
|
101 |
|
102 | const url = auth$1.wsUrl;
|
103 | function connect(triesLeft, promResolve, promReject) {
|
104 | const socket = new WebSocket(url);
|
105 |
|
106 | let invalidAuth = false;
|
107 | const closeMessage = () => {
|
108 |
|
109 | socket.removeEventListener("close", closeMessage);
|
110 | if (invalidAuth) {
|
111 | promReject(ERR_INVALID_AUTH);
|
112 | return;
|
113 | }
|
114 |
|
115 | if (triesLeft === 0) {
|
116 |
|
117 | promReject(ERR_CANNOT_CONNECT);
|
118 | return;
|
119 | }
|
120 | const newTries = triesLeft === -1 ? -1 : triesLeft - 1;
|
121 |
|
122 | setTimeout(() => connect(newTries, promResolve, promReject), 1000);
|
123 | };
|
124 |
|
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 |
|
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 |
|
165 |
|
166 |
|
167 | class Connection {
|
168 | constructor(socket, options) {
|
169 |
|
170 |
|
171 |
|
172 | this.options = options;
|
173 |
|
174 | this.commandId = 1;
|
175 |
|
176 | this.commands = new Map();
|
177 |
|
178 | this.eventListeners = new Map();
|
179 |
|
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 |
|
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 |
|
201 |
|
202 |
|
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 |
|
237 |
|
238 |
|
239 |
|
240 |
|
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 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | async subscribeMessage(callback, subscribeMessage) {
|
270 |
|
271 | const commandId = this._genCmdId();
|
272 | let info;
|
273 | await new Promise((resolve, reject) => {
|
274 |
|
275 |
|
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 |
|
292 |
|
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 |
|
312 | if (info) {
|
313 | if (message.success) {
|
314 | info.resolve(message.result);
|
315 |
|
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 |
|
339 | this.commands.forEach(info => {
|
340 |
|
341 |
|
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 |
|
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 |
|
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 |
|
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 |
|
413 |
|
414 |
|
415 | const l = typeof location !== "undefined" && location;
|
416 | if (l && l.protocol === "https:") {
|
417 |
|
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 ||
|
438 | resp.status === 403
|
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 |
|
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 |
|
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 |
|
486 | data.refresh_token = this.data.refresh_token;
|
487 | this.data = data;
|
488 | if (this._saveTokens)
|
489 | this._saveTokens(data);
|
490 | }
|
491 | |
492 |
|
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 |
|
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 |
|
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 |
|
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 |
|
537 | if (!data) {
|
538 | const query = parseQuery(location.search.substr(1));
|
539 |
|
540 | if ("auth_callback" in query) {
|
541 |
|
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 |
|
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 |
|
560 | redirectAuthorize(hassUrl, clientId, options.redirectUrl || genRedirectUrl(), encodeOAuthState({
|
561 | hassUrl,
|
562 | clientId
|
563 | }));
|
564 |
|
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 |
|
591 |
|
592 |
|
593 | return {
|
594 | get state() {
|
595 | return state;
|
596 | },
|
597 | |
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 | action(action) {
|
605 | function apply(result) {
|
606 | setState(result, false);
|
607 | }
|
608 |
|
609 | return function () {
|
610 | let args = [state];
|
611 | for (let i = 0; i < arguments.length; i++)
|
612 | args.push(arguments[i]);
|
613 |
|
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 |
|
624 |
|
625 |
|
626 |
|
627 | setState,
|
628 | |
629 |
|
630 |
|
631 |
|
632 |
|
633 | subscribe(listener) {
|
634 | listeners.push(listener);
|
635 | return () => {
|
636 | unsubscribe(listener);
|
637 | };
|
638 | }
|
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 |
|
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 |
|
658 |
|
659 |
|
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 |
|
672 | if (active === 1) {
|
673 | if (subscribeUpdates) {
|
674 | unsubProm = subscribeUpdates(conn, store);
|
675 | }
|
676 |
|
677 | conn.addEventListener("ready", refreshSwallow);
|
678 | refreshSwallow();
|
679 | }
|
680 | const unsub = store.subscribe(subscriber);
|
681 | if (store.state !== undefined) {
|
682 |
|
683 |
|
684 | setTimeout(() => subscriber(store.state), 0);
|
685 | }
|
686 | return () => {
|
687 | unsub();
|
688 | active--;
|
689 | if (!active) {
|
690 |
|
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 |
|
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 | })));
|