diff --git a/hassio/.gitignore b/hassio/.gitignore new file mode 100644 index 0000000000..c3f61fee32 --- /dev/null +++ b/hassio/.gitignore @@ -0,0 +1 @@ +hassio-icons.html diff --git a/package.json b/package.json index 7a37ff543f..a87644e674 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "es6-object-assign": "^1.1.0", "eslint-import-resolver-webpack": "^0.10.0", "fecha": "^2.3.3", - "home-assistant-js-websocket": "^2.1.0", + "home-assistant-js-websocket": "^3.0.0", "intl-messageformat": "^2.2.0", "js-yaml": "^3.12.0", "leaflet": "^1.3.1", diff --git a/src/cards/ha-weather-card.js b/src/cards/ha-weather-card.js index d5cec7e758..7978a1625d 100644 --- a/src/cards/ha-weather-card.js +++ b/src/cards/ha-weather-card.js @@ -203,7 +203,7 @@ class HaWeatherCard extends } getUnit(measure) { - const lengthUnit = this.hass.config.core.unit_system.length || ''; + const lengthUnit = this.hass.config.unit_system.length || ''; switch (measure) { case 'air_pressure': return lengthUnit === 'km' ? 'hPa' : 'inHg'; @@ -212,7 +212,7 @@ class HaWeatherCard extends case 'precipitation': return lengthUnit === 'km' ? 'mm' : 'in'; default: - return this.hass.config.core.unit_system[measure] || ''; + return this.hass.config.unit_system[measure] || ''; } } diff --git a/src/common/auth/token.js b/src/common/auth/token.js deleted file mode 100644 index 66ade7ae49..0000000000 --- a/src/common/auth/token.js +++ /dev/null @@ -1,76 +0,0 @@ -import { storeTokens, loadTokens } from './token_storage.js'; - -function genClientId() { - return `${location.protocol}//${location.host}/`; -} - - -export function redirectLogin() { - document.location.href = `/auth/authorize?response_type=code&client_id=${encodeURIComponent(genClientId())}&redirect_uri=${encodeURIComponent(location.toString())}`; - return new Promise((() => {})); -} - - -function fetchTokenRequest(code) { - const data = new FormData(); - data.append('client_id', genClientId()); - data.append('grant_type', 'authorization_code'); - data.append('code', code); - return fetch('/auth/token', { - credentials: 'same-origin', - method: 'POST', - body: data, - }).then((resp) => { - if (!resp.ok) throw new Error('Unable to fetch tokens'); - return resp.json().then((tokens) => { - tokens.expires = (tokens.expires_in * 1000) + Date.now(); - return tokens; - }); - }); -} - -function refreshTokenRequest(tokens) { - const data = new FormData(); - data.append('client_id', genClientId()); - data.append('grant_type', 'refresh_token'); - data.append('refresh_token', tokens.refresh_token); - return fetch('/auth/token', { - credentials: 'same-origin', - method: 'POST', - body: data, - }).then((resp) => { - if (!resp.ok) throw new Error('Unable to fetch tokens'); - return resp.json().then((newTokens) => { - newTokens.expires = (newTokens.expires_in * 1000) + Date.now(); - return newTokens; - }); - }); -} - -export function resolveCode(code) { - return fetchTokenRequest(code).then((tokens) => { - storeTokens(tokens); - history.replaceState(null, null, location.pathname); - return tokens; - }, (err) => { - // eslint-disable-next-line - console.error('Resolve token failed', err); - alert('Unable to fetch tokens'); - redirectLogin(); - }); -} - - -export function refreshToken() { - const tokens = loadTokens(); - - if (tokens === null) { - return redirectLogin(); - } - - return refreshTokenRequest(tokens).then((accessTokenResp) => { - const newTokens = Object.assign({}, tokens, accessTokenResp); - storeTokens(newTokens); - return newTokens; - }, () => redirectLogin()); -} diff --git a/src/common/auth/token_storage.js b/src/common/auth/token_storage.js index 17ba919821..ede418da4c 100644 --- a/src/common/auth/token_storage.js +++ b/src/common/auth/token_storage.js @@ -13,24 +13,26 @@ export function askWrite() { return tokenCache.tokens !== undefined && tokenCache.writeEnabled === undefined; } -export function storeTokens(tokens) { +export function saveTokens(tokens) { tokenCache.tokens = tokens; if (tokenCache.writeEnabled) { try { - storage.tokens = JSON.stringify(tokens); + storage.hassTokens = JSON.stringify(tokens); } catch (err) {} // eslint-disable-line } } export function enableWrite() { tokenCache.writeEnabled = true; - storeTokens(tokenCache.tokens); + saveTokens(tokenCache.tokens); } export function loadTokens() { if (tokenCache.tokens === undefined) { try { - const tokens = storage.tokens; + // Delete the old token cache. + delete storage.tokens; + const tokens = storage.hassTokens; if (tokens) { tokenCache.tokens = JSON.parse(tokens); tokenCache.writeEnabled = true; diff --git a/src/common/config/is_component_loaded.js b/src/common/config/is_component_loaded.js index c371674524..318eb56936 100644 --- a/src/common/config/is_component_loaded.js +++ b/src/common/config/is_component_loaded.js @@ -1,4 +1,4 @@ /** Return if a component is loaded. */ export default function isComponentLoaded(hass, component) { - return hass && hass.config.core.components.indexOf(component) !== -1; + return hass && hass.config.components.indexOf(component) !== -1; } diff --git a/src/common/config/location_name.js b/src/common/config/location_name.js index f069a50ef8..83d8fc32a4 100644 --- a/src/common/config/location_name.js +++ b/src/common/config/location_name.js @@ -1,4 +1,4 @@ /** Get the location name from a hass object. */ export default function computeLocationName(hass) { - return hass && hass.config.core.location_name; + return hass && hass.config.location_name; } diff --git a/src/common/entity/can_toggle_domain.js b/src/common/entity/can_toggle_domain.js index 48e41e8297..3bd05e6a3e 100644 --- a/src/common/entity/can_toggle_domain.js +++ b/src/common/entity/can_toggle_domain.js @@ -1,5 +1,5 @@ export default function canToggleDomain(hass, domain) { - const services = hass.config.services[domain]; + const services = hass.services[domain]; if (!services) { return false; } if (domain === 'lock') { diff --git a/src/common/util/parse_query.js b/src/common/util/parse_query.js deleted file mode 100644 index b654b0172f..0000000000 --- a/src/common/util/parse_query.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function parseQuery(queryString) { - const query = {}; - const items = queryString.split('&'); - for (let i = 0; i < items.length; i++) { - const item = items[i].split('='); - const key = decodeURIComponent(item[0]); - const value = item.length > 1 ? decodeURIComponent(item[1]) : undefined; - query[key] = value; - } - return query; -} diff --git a/src/components/ha-climate-state.js b/src/components/ha-climate-state.js index 7dd4be6909..cba7f3a966 100644 --- a/src/components/ha-climate-state.js +++ b/src/components/ha-climate-state.js @@ -60,7 +60,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) { computeCurrentStatus(hass, stateObj) { if (!hass || !stateObj) return null; if (stateObj.attributes.current_temperature != null) { - return `${stateObj.attributes.current_temperature} ${hass.config.core.unit_system.temperature}`; + return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`; } if (stateObj.attributes.current_humidity != null) { return `${stateObj.attributes.current_humidity} %`; @@ -73,9 +73,9 @@ class HaClimateState extends LocalizeMixin(PolymerElement) { // We're using "!= null" on purpose so that we match both null and undefined. if (stateObj.attributes.target_temp_low != null && stateObj.attributes.target_temp_high != null) { - return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.core.unit_system.temperature}`; + return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`; } else if (stateObj.attributes.temperature != null) { - return `${stateObj.attributes.temperature} ${hass.config.core.unit_system.temperature}`; + return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`; } else if (stateObj.attributes.target_humidity_low != null && stateObj.attributes.target_humidity_high != null) { return `${stateObj.attributes.target_humidity_low} - ${stateObj.attributes.target_humidity_high} %`; diff --git a/src/components/ha-service-description.js b/src/components/ha-service-description.js index 99dea3b006..bcf8537262 100644 --- a/src/components/ha-service-description.js +++ b/src/components/ha-service-description.js @@ -17,7 +17,7 @@ class HaServiceDescription extends PolymerElement { } _getDescription(hass, domain, service) { - var domainServices = hass.config.services[domain]; + var domainServices = hass.services[domain]; if (!domainServices) return ''; var serviceObject = domainServices[service]; if (!serviceObject) return ''; diff --git a/src/components/ha-service-picker.js b/src/components/ha-service-picker.js index 4003de341c..a11ebd6d28 100644 --- a/src/components/ha-service-picker.js +++ b/src/components/ha-service-picker.js @@ -34,13 +34,13 @@ class HaServicePicker extends LocalizeMixin(PolymerElement) { if (!hass) { this._services = []; return; - } else if (oldHass && hass.config.services === oldHass.config.services) { + } else if (oldHass && hass.services === oldHass.services) { return; } const result = []; - Object.keys(hass.config.services).sort().forEach((domain) => { - const services = Object.keys(hass.config.services[domain]).sort(); + Object.keys(hass.services).sort().forEach((domain) => { + const services = Object.keys(hass.services[domain]).sort(); for (let i = 0; i < services.length; i++) { result.push(`${domain}.${services[i]}`); diff --git a/src/components/ha-sidebar.js b/src/components/ha-sidebar.js index fcc29b9d6c..31066e123a 100644 --- a/src/components/ha-sidebar.js +++ b/src/components/ha-sidebar.js @@ -10,6 +10,7 @@ import '../components/ha-icon.js'; import '../util/hass-translation.js'; import LocalizeMixin from '../mixins/localize-mixin.js'; +import isComponentLoaded from '../common/config/is_component_loaded.js'; /* * @appliesMixin LocalizeMixin @@ -250,7 +251,7 @@ class HaSidebar extends LocalizeMixin(PolymerElement) { } _mqttLoaded(hass) { - return hass.config.core.components.indexOf('mqtt') !== -1; + return isComponentLoaded(hass, 'mqtt'); } _computeUserName(user) { diff --git a/src/data/ws-panels.js b/src/data/ws-panels.js new file mode 100644 index 0000000000..9dd04dd800 --- /dev/null +++ b/src/data/ws-panels.js @@ -0,0 +1,10 @@ +import { createCollection } from 'home-assistant-js-websocket'; + +export const subscribePanels = (conn, onChange) => + createCollection( + '_pnl', + conn_ => conn_.sendMessagePromise({ type: 'get_panels' }), + null, + conn, + onChange + ); diff --git a/src/data/ws-themes.js b/src/data/ws-themes.js new file mode 100644 index 0000000000..95960389e1 --- /dev/null +++ b/src/data/ws-themes.js @@ -0,0 +1,20 @@ +import { createCollection } from 'home-assistant-js-websocket'; + +const fetchThemes = conn => conn.sendMessagePromise({ + type: 'frontend/get_themes' +}); + +const subscribeUpdates = (conn, store) => + conn.subscribeEvents( + event => store.setState(event.data, true), + 'themes_updated' + ); + +export const subscribeThemes = (conn, onChange) => + createCollection( + '_thm', + fetchThemes, + subscribeUpdates, + conn, + onChange + ); diff --git a/src/data/ws-user.js b/src/data/ws-user.js new file mode 100644 index 0000000000..7acae08cd9 --- /dev/null +++ b/src/data/ws-user.js @@ -0,0 +1,10 @@ +import { createCollection, getUser } from 'home-assistant-js-websocket'; + +export const subscribeUser = (conn, onChange) => + createCollection( + '_usr', + conn_ => getUser(conn_), + null, + conn, + onChange + ); diff --git a/src/dialogs/more-info/controls/more-info-climate.js b/src/dialogs/more-info/controls/more-info-climate.js index 700f8da5fa..463b6b48e1 100644 --- a/src/dialogs/more-info/controls/more-info-climate.js +++ b/src/dialogs/more-info/controls/more-info-climate.js @@ -125,13 +125,13 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
[[localize('ui.card.climate.target_temperature')]]
@@ -293,7 +293,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) { computeTemperatureStepSize(hass, stateObj) { if (stateObj.attributes.target_temp_step) { return stateObj.attributes.target_temp_step; - } else if (hass.config.core.unit_system.temperature.indexOf('F') !== -1) { + } else if (hass.config.unit_system.temperature.indexOf('F') !== -1) { return 1; } return 0.5; diff --git a/src/dialogs/more-info/controls/more-info-media_player.js b/src/dialogs/more-info/controls/more-info-media_player.js index e0043d76c3..3891b523d0 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.js +++ b/src/dialogs/more-info/controls/more-info-media_player.js @@ -304,7 +304,7 @@ class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) { } sendTTS() { - const services = this.hass.config.services.tts; + const services = this.hass.services.tts; const serviceKeys = Object.keys(services).sort(); let service; let i; diff --git a/src/dialogs/more-info/controls/more-info-weather.js b/src/dialogs/more-info/controls/more-info-weather.js index 2b53467d00..1d9a488a90 100644 --- a/src/dialogs/more-info/controls/more-info-weather.js +++ b/src/dialogs/more-info/controls/more-info-weather.js @@ -156,7 +156,7 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) { } getUnit(measure) { - const lengthUnit = this.hass.config.core.unit_system.length || ''; + const lengthUnit = this.hass.config.unit_system.length || ''; switch (measure) { case 'air_pressure': return lengthUnit === 'km' ? 'hPa' : 'inHg'; @@ -165,7 +165,7 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) { case 'precipitation': return lengthUnit === 'km' ? 'mm' : 'in'; default: - return this.hass.config.core.unit_system[measure] || ''; + return this.hass.config.unit_system[measure] || ''; } } diff --git a/src/entrypoints/core.js b/src/entrypoints/core.js index 3361d98d12..71a87bd5c3 100644 --- a/src/entrypoints/core.js +++ b/src/entrypoints/core.js @@ -1,78 +1,39 @@ import { - ERR_INVALID_AUTH, + getAuth, createConnection, subscribeConfig, subscribeEntities, + subscribeServices, } from 'home-assistant-js-websocket'; -import { redirectLogin, resolveCode, refreshToken } from '../common/auth/token.js'; -// import refreshToken_ from '../common/auth/refresh_token.js'; -import parseQuery from '../common/util/parse_query.js'; -import { loadTokens } from '../common/auth/token_storage.js'; +import { loadTokens, saveTokens } from '../common/auth/token_storage.js'; +import { subscribePanels } from '../data/ws-panels.js'; +import { subscribeThemes } from '../data/ws-themes.js'; +import { subscribeUser } from '../data/ws-user.js'; -const init = window.createHassConnection = function (password, accessToken) { - const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; - const url = `${proto}://${window.location.host}/api/websocket?${__BUILD__}`; - const options = { - setupRetry: 10, - }; - if (password) { - options.authToken = password; - } else if (accessToken) { - options.accessToken = accessToken.access_token; - options.expires = accessToken.expires; +window.hassAuth = getAuth({ + hassUrl: `${location.protocol}//${location.host}`, + saveTokens, + loadTokens: () => Promise.resolve(loadTokens()), +}); + +window.hassConnection = window.hassAuth.then((auth) => { + if (location.search.includes('auth_callback=1')) { + history.replaceState(null, null, location.pathname); } - return createConnection(url, options) - .then(function (conn) { - subscribeEntities(conn); - subscribeConfig(conn); - return conn; - }); -}; + return createConnection({ auth }); +}); -function main() { - if (location.search) { - const query = parseQuery(location.search.substr(1)); - if (query.code) { - window.hassConnection = resolveCode(query.code).then(newTokens => init(null, newTokens)); - return; - } - } - const tokens = loadTokens(); - - if (tokens == null) { - redirectLogin(); - return; - } - - if (Date.now() + 30000 > tokens.expires) { - // refresh access token if it will expire in 30 seconds to avoid invalid auth event - window.hassConnection = refreshToken().then(newTokens => init(null, newTokens)); - return; - } - - window.hassConnection = init(null, tokens).catch((err) => { - if (err !== ERR_INVALID_AUTH) throw err; - - return refreshToken().then(newTokens => init(null, newTokens)); - }); -} - -function mainLegacy() { - if (window.noAuth === '1') { - window.hassConnection = init(); - } else if (window.localStorage.authToken) { - window.hassConnection = init(window.localStorage.authToken); - } else { - window.hassConnection = null; - } -} - -if (window.useOAuth === '1') { - main(); -} else { - mainLegacy(); -} +// Start fetching some of the data that we will need. +window.hassConnection.then((conn) => { + const noop = () => {}; + subscribeEntities(conn, noop); + subscribeConfig(conn, noop); + subscribeServices(conn, noop); + subscribePanels(conn, noop); + subscribeThemes(conn, noop); + subscribeUser(conn, noop); +}); window.addEventListener('error', (e) => { const homeAssistant = document.querySelector('home-assistant'); diff --git a/src/layouts/app/auth-mixin.js b/src/layouts/app/auth-mixin.js index 430ccc6346..3f11df863a 100644 --- a/src/layouts/app/auth-mixin.js +++ b/src/layouts/app/auth-mixin.js @@ -1,6 +1,7 @@ import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; import { clearState } from '../../util/ha-pref-storage.js'; import { askWrite } from '../../common/auth/token_storage.js'; +import { subscribeUser } from '../../data/ws-user.js'; export default superClass => class extends superClass { ready() { @@ -20,17 +21,7 @@ export default superClass => class extends superClass { hassConnected() { super.hassConnected(); - - this._getCurrentUser(); - } - - _getCurrentUser() { - // only for new auth - if (this.hass.connection.options.accessToken) { - this.hass.callWS({ - type: 'auth/current_user', - }).then(user => this._updateHass({ user }), () => {}); - } + subscribeUser(this.hass.connection, user => this._updateHass({ user })); } _handleLogout() { diff --git a/src/layouts/app/connection-mixin.js b/src/layouts/app/connection-mixin.js index 7fb6fb7f8d..78cebb5eb8 100644 --- a/src/layouts/app/connection-mixin.js +++ b/src/layouts/app/connection-mixin.js @@ -2,6 +2,8 @@ import { ERR_INVALID_AUTH, subscribeEntities, subscribeConfig, + subscribeServices, + callService, } from 'home-assistant-js-websocket'; import translationMetadata from '../../../build-translations/translationMetadata.json'; @@ -9,44 +11,24 @@ import translationMetadata from '../../../build-translations/translationMetadata import LocalizeMixin from '../../mixins/localize-mixin.js'; import EventsMixin from '../../mixins/events-mixin.js'; -import { refreshToken } from '../../common/auth/token.js'; import { getState } from '../../util/ha-pref-storage.js'; import { getActiveTranslation } from '../../util/hass-translation.js'; import hassCallApi from '../../util/hass-call-api.js'; import computeStateName from '../../common/entity/compute_state_name.js'; +import { subscribePanels } from '../../data/ws-panels'; export default superClass => class extends EventsMixin(LocalizeMixin(superClass)) { - constructor() { - super(); - this.unsubFuncs = []; - } - ready() { super.ready(); - this.addEventListener('try-connection', e => - this._handleNewConnProm(e.detail.connProm)); - if (window.hassConnection) { - this._handleNewConnProm(window.hassConnection); - } + this._handleConnProm(); } - async _handleNewConnProm(connProm) { - this.connectionPromise = connProm; + async _handleConnProm() { + const [auth, conn] = await Promise.all([window.hassAuth, window.hassConnection]); - let conn; - - try { - conn = await connProm; - } catch (err) { - this.connectionPromise = null; - return; - } - this._setConnection(conn); - } - - _setConnection(conn) { this.hass = Object.assign({ + auth, connection: conn, connected: true, states: null, @@ -64,7 +46,7 @@ export default superClass => moreInfoEntityId: null, callService: async (domain, service, serviceData = {}) => { try { - await conn.callService(domain, service, serviceData); + await callService(conn, domain, service, serviceData); let message; let name; @@ -100,24 +82,20 @@ export default superClass => }, callApi: async (method, path, parameters) => { const host = window.location.protocol + '//' + window.location.host; - const auth = conn.options; - try { - // Refresh token if it will expire in 30 seconds - if (auth.accessToken && Date.now() + 30000 > auth.expires) { - const accessToken = await refreshToken(); - conn.options.accessToken = accessToken.access_token; - conn.options.expires = accessToken.expires; - } - return await hassCallApi(host, auth, method, path, parameters); - } catch (err) { - if (!err || err.status_code !== 401 || !auth.accessToken) throw err; - // If we connect with access token and get 401, refresh token and try again - const accessToken = await refreshToken(); - conn.options.accessToken = accessToken.access_token; - conn.options.expires = accessToken.expires; - return await hassCallApi(host, auth, method, path, parameters); + try { + if (auth.expired) await auth.refreshAccessToken(); + } catch (err) { + if (err === ERR_INVALID_AUTH) { + // Trigger auth flow + location.reload(); + // ensure further JS is not executed + await new Promise(() => {}); + } + throw err; } + + return await hassCallApi(host, auth, method, path, parameters); }, // For messages that do not get a response sendWS: (msg) => { @@ -138,9 +116,7 @@ export default superClass => err => console.log('Error', err), ); } - // In the future we'll do this as a breaking change - // inside home-assistant-js-websocket - return resp.then(result => result.result); + return resp; }, }, getState()); @@ -152,56 +128,26 @@ export default superClass => const conn = this.hass.connection; - const reconnected = () => this.hassReconnected(); - const disconnected = () => this.hassDisconnected(); - const reconnectError = async (_conn, err) => { - if (err !== ERR_INVALID_AUTH) return; - - while (this.unsubFuncs.length) { - this.unsubFuncs.pop()(); - } - const accessToken = await refreshToken(); - const newConn = window.createHassConnection(null, accessToken); - newConn.then(() => this.hassReconnected()); - this._handleNewConnProm(newConn); - }; - - conn.addEventListener('ready', reconnected); - conn.addEventListener('disconnected', disconnected); - // If we reconnect after losing connection and access token is no longer - // valid. - conn.addEventListener('reconnect-error', reconnectError); - - this.unsubFuncs.push(() => { - conn.removeEventListener('ready', reconnected); - conn.removeEventListener('disconnected', disconnected); - conn.removeEventListener('reconnect-error', reconnectError); + conn.addEventListener('ready', () => this.hassReconnected()); + conn.addEventListener('disconnected', () => this.hassDisconnected()); + // If we reconnect after losing connection and auth is no longer valid. + conn.addEventListener('reconnect-error', (_conn, err) => { + if (err === ERR_INVALID_AUTH) location.reload(); }); - subscribeEntities(conn, states => this._updateHass({ states })) - .then(unsub => this.unsubFuncs.push(unsub)); - - subscribeConfig(conn, config => this._updateHass({ config })) - .then(unsub => this.unsubFuncs.push(unsub)); - - this._loadPanels(); + subscribeEntities(conn, states => this._updateHass({ states })); + subscribeConfig(conn, config => this._updateHass({ config })); + subscribeServices(conn, services => this._updateHass({ services })); + subscribePanels(conn, panels => this._updateHass({ panels })); } hassReconnected() { super.hassReconnected(); this._updateHass({ connected: true }); - this._loadPanels(); } hassDisconnected() { super.hassDisconnected(); this._updateHass({ connected: false }); } - - async _loadPanels() { - const panels = await this.hass.callWS({ - type: 'get_panels' - }); - this._updateHass({ panels }); - } }; diff --git a/src/layouts/app/home-assistant.js b/src/layouts/app/home-assistant.js index fc662929da..7bba5ad5b7 100644 --- a/src/layouts/app/home-assistant.js +++ b/src/layouts/app/home-assistant.js @@ -6,6 +6,7 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js'; import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; import '../../layouts/home-assistant-main.js'; +import '../../layouts/ha-init-page.js'; import '../../resources/ha-style.js'; import registerServiceWorker from '../../util/register-service-worker.js'; @@ -20,8 +21,6 @@ import ConnectionMixin from './connection-mixin.js'; import NotificationMixin from './notification-mixin.js'; import DisconnectToastMixin from './disconnect-toast-mixin.js'; -import(/* webpackChunkName: "login-form" */ '../../layouts/login-form.js'); - const ext = (baseClass, mixins) => mixins.reduceRight((base, mixin) => mixin(base), baseClass); class HomeAssistant extends ext(PolymerElement, [ @@ -52,21 +51,13 @@ class HomeAssistant extends ext(PolymerElement, [ `; } static get properties() { return { - connectionPromise: { - type: Object, - value: null, - }, hass: { type: Object, value: null, @@ -91,13 +82,7 @@ class HomeAssistant extends ext(PolymerElement, [ } computeShowMain(hass) { - return hass && hass.states && hass.config && hass.panels; - } - - computeShowLoading(connectionPromise, hass) { - // Show loading when connecting or when connected but not all pieces loaded yet - return (connectionPromise != null - || (hass && hass.connection && (!hass.states || !hass.config))); + return hass && hass.states && hass.config && hass.panels && hass.services; } computePanelUrl(routeData) { diff --git a/src/layouts/app/themes-mixin.js b/src/layouts/app/themes-mixin.js index c35a1ff717..c465461645 100644 --- a/src/layouts/app/themes-mixin.js +++ b/src/layouts/app/themes-mixin.js @@ -1,46 +1,33 @@ import applyThemesOnElement from '../../common/dom/apply_themes_on_element.js'; import { storeState } from '../../util/ha-pref-storage.js'; +import { subscribeThemes } from '../../data/ws-themes.js'; export default superClass => class extends superClass { ready() { super.ready(); - this.addEventListener('settheme', e => this._setTheme(e)); + + this.addEventListener('settheme', (ev) => { + this._updateHass({ selectedTheme: ev.detail }); + this._applyTheme(); + storeState(this.hass); + }); } hassConnected() { super.hassConnected(); - this.hass.callWS({ - type: 'frontend/get_themes', - }).then((themes) => { + subscribeThemes(this.hass.connection, (themes) => { this._updateHass({ themes }); - applyThemesOnElement( - document.documentElement, - themes, - this.hass.selectedTheme, - true - ); + this._applyTheme(); }); - - this.hass.connection.subscribeEvents((event) => { - this._updateHass({ themes: event.data }); - applyThemesOnElement( - document.documentElement, - event.data, - this.hass.selectedTheme, - true - ); - }, 'themes_updated').then(unsub => this.unsubFuncs.push(unsub)); } - _setTheme(event) { - this._updateHass({ selectedTheme: event.detail }); + _applyTheme() { applyThemesOnElement( document.documentElement, this.hass.themes, this.hass.selectedTheme, true ); - storeState(this.hass); } }; diff --git a/src/layouts/login-form.js b/src/layouts/ha-init-page.js similarity index 55% rename from src/layouts/login-form.js rename to src/layouts/ha-init-page.js index ddaefd3a41..fcb8d2a6a6 100644 --- a/src/layouts/login-form.js +++ b/src/layouts/ha-init-page.js @@ -14,102 +14,24 @@ import EventsMixin from '../mixins/events-mixin.js'; /* * @appliesMixin LocalizeMixin */ -class LoginForm extends EventsMixin(LocalizeMixin(PolymerElement)) { +class HaInitPage extends EventsMixin(LocalizeMixin(PolymerElement)) { static get template() { return html`
- -
-
- -
- [[localize('ui.login-form.remember')]] - [[localize('ui.login-form.log_in')]] -
-
-
-
-
[[computeLoadingMsg(isValidating)]]
-
-
+ + Loading data
`; } - static get properties() { - return { - hass: { - type: Object, - }, - - connectionPromise: { - type: Object, - notify: true, - observer: 'handleConnectionPromiseChanged', - }, - - errorMessage: { - type: String, - value: '', - }, - - isValidating: { - type: Boolean, - observer: 'isValidatingChanged', - value: false, - }, - - showLoading: { - type: Boolean, - value: false, - }, - - showSpinner: { - type: Boolean, - computed: 'computeShowSpinner(showLoading, isValidating)', - }, - - password: { - type: String, - value: '', - }, - }; - } - ready() { super.ready(); this.addEventListener('keydown', ev => this.passwordKeyDown(ev)); @@ -183,4 +105,4 @@ class LoginForm extends EventsMixin(LocalizeMixin(PolymerElement)) { } } -customElements.define('login-form', LoginForm); +customElements.define('ha-init-page', HaInitPage); diff --git a/src/panels/dev-info/ha-panel-dev-info.js b/src/panels/dev-info/ha-panel-dev-info.js index 17fd3d235c..71abb887c5 100644 --- a/src/panels/dev-info/ha-panel-dev-info.js +++ b/src/panels/dev-info/ha-panel-dev-info.js @@ -122,10 +122,10 @@ class HaPanelDevInfo extends PolymerElement {


Home Assistant
- [[hass.config.core.version]] + [[hass.config.version]]

- Path to configuration.yaml: [[hass.config.core.config_dir]] + Path to configuration.yaml: [[hass.config.config_dir]]

diff --git a/src/panels/dev-service/ha-panel-dev-service.js b/src/panels/dev-service/ha-panel-dev-service.js index c87a26726e..2cbab6ccd9 100644 --- a/src/panels/dev-service/ha-panel-dev-service.js +++ b/src/panels/dev-service/ha-panel-dev-service.js @@ -230,7 +230,7 @@ class HaPanelDevService extends PolymerElement { } _computeAttributesArray(hass, domain, service) { - const serviceDomains = hass.config.services; + const serviceDomains = hass.services; if (!(domain in serviceDomains)) return []; if (!(service in serviceDomains[domain])) return []; @@ -241,7 +241,7 @@ class HaPanelDevService extends PolymerElement { } _computeDescription(hass, domain, service) { - const serviceDomains = hass.config.services; + const serviceDomains = hass.services; if (!(domain in serviceDomains)) return undefined; if (!(service in serviceDomains[domain])) return undefined; return serviceDomains[domain][service].description; diff --git a/src/panels/lovelace/cards/hui-map-card.js b/src/panels/lovelace/cards/hui-map-card.js index 64aa8786f5..5e1925a142 100644 --- a/src/panels/lovelace/cards/hui-map-card.js +++ b/src/panels/lovelace/cards/hui-map-card.js @@ -162,7 +162,7 @@ class HuiMapCard extends PolymerElement { const zoom = this._config.default_zoom; if (this._mapItems.length === 0) { this._map.setView( - new Leaflet.LatLng(this.hass.config.core.latitude, this.hass.config.core.longitude), + new Leaflet.LatLng(this.hass.config.latitude, this.hass.config.longitude), zoom || 14 ); return; diff --git a/src/panels/map/ha-panel-map.js b/src/panels/map/ha-panel-map.js index 025aa8eafd..cebf313a30 100644 --- a/src/panels/map/ha-panel-map.js +++ b/src/panels/map/ha-panel-map.js @@ -79,7 +79,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) { if (this._mapItems.length === 0) { this._map.setView( - new Leaflet.LatLng(this.hass.config.core.latitude, this.hass.config.core.longitude), + new Leaflet.LatLng(this.hass.config.latitude, this.hass.config.longitude), 14 ); } else { diff --git a/src/util/hass-call-api.js b/src/util/hass-call-api.js index 747843e665..4d31f14054 100644 --- a/src/util/hass-call-api.js +++ b/src/util/hass-call-api.js @@ -4,12 +4,7 @@ export default function hassCallApi(host, auth, method, path, parameters) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open(method, url, true); - - if (auth.authToken) { - req.setRequestHeader('X-HA-access', auth.authToken); - } else if (auth.accessToken) { - req.setRequestHeader('authorization', `Bearer ${auth.accessToken}`); - } + req.setRequestHeader('authorization', `Bearer ${auth.accessToken}`); req.onload = function () { let body = req.responseText; diff --git a/test-mocha/common/entity/can_toggle_domain_test.js b/test-mocha/common/entity/can_toggle_domain_test.js index 26bf5d99a4..d0b5a0cd42 100644 --- a/test-mocha/common/entity/can_toggle_domain_test.js +++ b/test-mocha/common/entity/can_toggle_domain_test.js @@ -4,19 +4,17 @@ import canToggleDomain from '../../../src/common/entity/can_toggle_domain'; describe('canToggleDomain', () => { const hass = { - config: { - services: { - light: { - turn_on: null, // Service keys only need to be present for test - turn_off: null, - }, - lock: { - lock: null, - unlock: null, - }, - sensor: { - custom_service: null, - }, + services: { + light: { + turn_on: null, // Service keys only need to be present for test + turn_off: null, + }, + lock: { + lock: null, + unlock: null, + }, + sensor: { + custom_service: null, }, }, }; diff --git a/test-mocha/common/entity/can_toggle_state_test.js b/test-mocha/common/entity/can_toggle_state_test.js index 0f17c84f55..ece1f66946 100644 --- a/test-mocha/common/entity/can_toggle_state_test.js +++ b/test-mocha/common/entity/can_toggle_state_test.js @@ -4,12 +4,10 @@ import canToggleState from '../../../src/common/entity/can_toggle_state'; describe('canToggleState', () => { const hass = { - config: { - services: { - light: { - turn_on: null, // Service keys only need to be present for test - turn_off: null, - }, + services: { + light: { + turn_on: null, // Service keys only need to be present for test + turn_off: null, }, }, }; diff --git a/test-mocha/common/entity/state_card_type_test.js b/test-mocha/common/entity/state_card_type_test.js index c655db0981..89fca1b647 100644 --- a/test-mocha/common/entity/state_card_type_test.js +++ b/test-mocha/common/entity/state_card_type_test.js @@ -4,12 +4,10 @@ import stateCardType from '../../../src/common/entity/state_card_type.js'; describe('stateCardType', () => { const hass = { - config: { - services: { - light: { - turn_on: null, // Service keys only need to be present for test - turn_off: null, - }, + services: { + light: { + turn_on: null, // Service keys only need to be present for test + turn_off: null, }, }, }; diff --git a/test-mocha/common/util/parse_query_test.js b/test-mocha/common/util/parse_query_test.js deleted file mode 100644 index 849c60c2bc..0000000000 --- a/test-mocha/common/util/parse_query_test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { assert } from 'chai'; - -import parseQuery from '../../../src/common/util/parse_query.js'; - -describe('parseQuery', () => { - it('works', () => { - assert.deepEqual(parseQuery('hello=world'), { hello: 'world' }); - assert.deepEqual(parseQuery('hello=world&drink=soda'), { - hello: 'world', - drink: 'soda', - }); - assert.deepEqual(parseQuery('hello=world&no_value&drink=soda'), { - hello: 'world', - no_value: undefined, - drink: 'soda', - }); - }); -}); diff --git a/yarn.lock b/yarn.lock index 2274f169aa..2d02284e75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6513,9 +6513,9 @@ hoek@4.x.x: version "4.2.1" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" -home-assistant-js-websocket@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-2.1.0.tgz#192f4e8cef248882bc62b70d56a12e8113d41c3b" +home-assistant-js-websocket@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.0.0.tgz#498828a29827bdd1f3e99cf3b5e152694cededbf" home-or-tmp@^2.0.0: version "2.0.0"