diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index d209955cfac..74090c78107 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -23,7 +23,7 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED from homeassistant.core import callback from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20171127.0'] +REQUIREMENTS = ['home-assistant-frontend==20171127.0', 'user-agents==1.1.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] @@ -32,6 +32,7 @@ URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html' CONF_THEMES = 'themes' CONF_EXTRA_HTML_URL = 'extra_html_url' +CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5' CONF_FRONTEND_REPO = 'development_repo' CONF_JS_VERSION = 'javascript_version' JS_DEFAULT_OPTION = 'es5' @@ -63,6 +64,7 @@ DATA_FINALIZE_PANEL = 'frontend_finalize_panel' DATA_PANELS = 'frontend_panels' DATA_JS_VERSION = 'frontend_js_version' DATA_EXTRA_HTML_URL = 'frontend_extra_html_url' +DATA_EXTRA_HTML_URL_ES5 = 'frontend_extra_html_url_es5' DATA_THEMES = 'frontend_themes' DATA_DEFAULT_THEME = 'frontend_default_theme' DEFAULT_THEME = 'default' @@ -79,6 +81,8 @@ CONFIG_SCHEMA = vol.Schema({ }), vol.Optional(CONF_EXTRA_HTML_URL): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_EXTRA_HTML_URL_ES5): + vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_JS_VERSION, default=JS_DEFAULT_OPTION): vol.In(JS_OPTIONS) }), @@ -269,11 +273,12 @@ def async_register_panel(hass, component_name, path, md5=None, @bind_hass @callback -def add_extra_html_url(hass, url): +def add_extra_html_url(hass, url, es5=False): """Register extra html url to load.""" - url_set = hass.data.get(DATA_EXTRA_HTML_URL) + key = DATA_EXTRA_HTML_URL_ES5 if es5 else DATA_EXTRA_HTML_URL + url_set = hass.data.get(key) if url_set is None: - url_set = hass.data[DATA_EXTRA_HTML_URL] = set() + url_set = hass.data[key] = set() url_set.add(url) @@ -358,9 +363,13 @@ def async_setup(hass, config): if DATA_EXTRA_HTML_URL not in hass.data: hass.data[DATA_EXTRA_HTML_URL] = set() + if DATA_EXTRA_HTML_URL_ES5 not in hass.data: + hass.data[DATA_EXTRA_HTML_URL_ES5] = set() for url in conf.get(CONF_EXTRA_HTML_URL, []): - add_extra_html_url(hass, url) + add_extra_html_url(hass, url, False) + for url in conf.get(CONF_EXTRA_HTML_URL_ES5, []): + add_extra_html_url(hass, url, True) yield from async_setup_themes(hass, conf.get(CONF_THEMES)) @@ -488,12 +497,14 @@ class IndexView(HomeAssistantView): template = yield from hass.async_add_job(self.get_template, latest) + extra_key = DATA_EXTRA_HTML_URL if latest else DATA_EXTRA_HTML_URL_ES5 + resp = template.render( no_auth=no_auth, panel_url=panel_url, panels=hass.data[DATA_PANELS], theme_color=MANIFEST_JSON['theme_color'], - extra_urls=hass.data[DATA_EXTRA_HTML_URL], + extra_urls=hass.data[extra_key], ) return web.Response(text=resp, content_type='text/html') @@ -545,10 +556,36 @@ def _is_latest(js_option, request): """ if request is None: return js_option == 'latest' - latest_in_query = 'latest' in request.query or ( - request.headers.get('Referer') and - 'latest' in urlparse(request.headers['Referer']).query) - es5_in_query = 'es5' in request.query or ( - request.headers.get('Referer') and - 'es5' in urlparse(request.headers['Referer']).query) - return latest_in_query or (not es5_in_query and js_option == 'latest') + + # latest in query + if 'latest' in request.query or ( + request.headers.get('Referer') and + 'latest' in urlparse(request.headers['Referer']).query): + return True + + # es5 in query + if 'es5' in request.query or ( + request.headers.get('Referer') and + 'es5' in urlparse(request.headers['Referer']).query): + return False + + # non-auto option in config + if js_option != 'auto': + return js_option == 'latest' + + from user_agents import parse + useragent = parse(request.headers.get('User-Agent')) + + # on iOS every browser is a Safari which we support from version 10. + if useragent.os.family == 'iOS': + return useragent.os.version[0] >= 10 + + family_min_version = { + 'Chrome': 50, # Probably can reduce this + 'Firefox': 41, # Destructuring added in 41 + 'Opera': 40, # Probably can reduce this + 'Edge': 14, # Maybe can reduce this + 'Safari': 10, # many features not supported by 9 + } + version = family_min_version.get(useragent.browser.family) + return version and useragent.browser.version[0] >= version diff --git a/requirements_all.txt b/requirements_all.txt index 3c22494199f..47cc3952ed5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1100,6 +1100,9 @@ uber_rides==0.6.0 # homeassistant.components.sensor.ups upsmychoice==1.0.6 +# homeassistant.components.frontend +user-agents==1.1.0 + # homeassistant.components.camera.uvc uvcclient==0.10.1 diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index bd2d8afc209..c4ade7f5c19 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -7,7 +7,8 @@ import pytest from homeassistant.setup import async_setup_component from homeassistant.components.frontend import ( - DOMAIN, CONF_THEMES, CONF_EXTRA_HTML_URL, DATA_PANELS) + DOMAIN, CONF_JS_VERSION, CONF_THEMES, CONF_EXTRA_HTML_URL, + CONF_EXTRA_HTML_URL_ES5, DATA_PANELS) @pytest.fixture @@ -36,7 +37,10 @@ def mock_http_client_with_urls(hass, test_client): """Start the Hass HTTP component.""" hass.loop.run_until_complete(async_setup_component(hass, 'frontend', { DOMAIN: { - CONF_EXTRA_HTML_URL: ["https://domain.com/my_extra_url.html"] + CONF_JS_VERSION: 'auto', + CONF_EXTRA_HTML_URL: ["https://domain.com/my_extra_url.html"], + CONF_EXTRA_HTML_URL_ES5: + ["https://domain.com/my_extra_url_es5.html"] }})) return hass.loop.run_until_complete(test_client(hass.http.app)) @@ -163,12 +167,21 @@ def test_missing_themes(mock_http_client): @asyncio.coroutine def test_extra_urls(mock_http_client_with_urls): """Test that extra urls are loaded.""" - resp = yield from mock_http_client_with_urls.get('/states') + resp = yield from mock_http_client_with_urls.get('/states?latest') assert resp.status == 200 text = yield from resp.text() assert text.find('href="https://domain.com/my_extra_url.html"') >= 0 +@asyncio.coroutine +def test_extra_urls_es5(mock_http_client_with_urls): + """Test that es5 extra urls are loaded.""" + resp = yield from mock_http_client_with_urls.get('/states?es5') + assert resp.status == 200 + text = yield from resp.text() + assert text.find('href="https://domain.com/my_extra_url_es5.html"') >= 0 + + @asyncio.coroutine def test_panel_without_path(hass): """Test panel registration without file path."""