From 318e8425235b1c37b9c76d05d5887db639b5810c Mon Sep 17 00:00:00 2001 From: andrey-git Date: Fri, 24 Nov 2017 16:02:59 +0200 Subject: [PATCH 1/3] Add useragent-based detection of JS version --- homeassistant/components/frontend/__init__.py | 65 +++++++++++++++---- requirements_all.txt | 3 + tests/components/test_frontend.py | 18 ++++- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 751a4e2cde32d0..e7ed859fb9d7ce 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -23,7 +23,7 @@ from homeassistant.core import callback from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20171121.1'] +REQUIREMENTS = ['home-assistant-frontend==20171121.1', 'user-agents==1.1.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] @@ -32,9 +32,10 @@ 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' +JS_DEFAULT_OPTION = 'auto' JS_OPTIONS = ['es5', 'latest', 'auto'] DEFAULT_THEME_COLOR = '#03A9F4' @@ -63,6 +64,7 @@ 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 @@ }), 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 finalize_panel(panel): 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 @@ def get(self, request, extra=None): 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 25ddb2ec2d1435..b8b07fdab3822c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1097,6 +1097,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 bd2d8afc209a1d..ec1806110a98aa 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -7,7 +7,8 @@ from homeassistant.setup import async_setup_component from homeassistant.components.frontend import ( - DOMAIN, CONF_THEMES, CONF_EXTRA_HTML_URL, DATA_PANELS) + DOMAIN, CONF_THEMES, CONF_EXTRA_HTML_URL, CONF_EXTRA_HTML_URL_ES5, + DATA_PANELS) @pytest.fixture @@ -36,7 +37,9 @@ 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_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 +166,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.""" From 082654075c9bce387f56685ebe624a64ab3a6b14 Mon Sep 17 00:00:00 2001 From: andrey-git Date: Tue, 28 Nov 2017 11:21:07 +0200 Subject: [PATCH 2/3] Keep es5 as default meanwhile --- homeassistant/components/frontend/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index e7ed859fb9d7ce..fd3e382423a855 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -35,7 +35,7 @@ CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5' CONF_FRONTEND_REPO = 'development_repo' CONF_JS_VERSION = 'javascript_version' -JS_DEFAULT_OPTION = 'auto' +JS_DEFAULT_OPTION = 'es5' JS_OPTIONS = ['es5', 'latest', 'auto'] DEFAULT_THEME_COLOR = '#03A9F4' From d2251178552e4cab51b73053d674a45877b92124 Mon Sep 17 00:00:00 2001 From: andrey-git Date: Tue, 28 Nov 2017 20:28:59 +0200 Subject: [PATCH 3/3] Update test --- tests/components/test_frontend.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index ec1806110a98aa..c4ade7f5c19d6d 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -7,8 +7,8 @@ from homeassistant.setup import async_setup_component from homeassistant.components.frontend import ( - DOMAIN, CONF_THEMES, CONF_EXTRA_HTML_URL, CONF_EXTRA_HTML_URL_ES5, - DATA_PANELS) + DOMAIN, CONF_JS_VERSION, CONF_THEMES, CONF_EXTRA_HTML_URL, + CONF_EXTRA_HTML_URL_ES5, DATA_PANELS) @pytest.fixture @@ -37,6 +37,7 @@ 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_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"]