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`

-
-
-
-
-
-
[[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"