diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 6f258b2d59c..7ef031a90cb 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -3,7 +3,7 @@ import asyncio import json import logging import os -from urllib.parse import urlparse +import pathlib from aiohttp import web import voluptuous as vol @@ -11,7 +11,6 @@ import jinja2 import homeassistant.helpers.config_validation as cv from homeassistant.components.http.view import HomeAssistantView -from homeassistant.components.http.const import KEY_AUTHENTICATED from homeassistant.components import websocket_api from homeassistant.config import find_config_file, load_yaml_config_file from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED @@ -27,14 +26,13 @@ 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 = 'auto' -JS_OPTIONS = ['es5', 'latest', 'auto'] DEFAULT_THEME_COLOR = '#03A9F4' MANIFEST_JSON = { 'background_color': '#FFFFFF', - 'description': 'Open-source home automation platform running on Python 3.', + 'description': + 'Home automation platform that puts local control and privacy first.', 'dir': 'ltr', 'display': 'standalone', 'icons': [], @@ -73,10 +71,9 @@ 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) + # We no longer use these options. + vol.Optional(CONF_EXTRA_HTML_URL_ES5): cv.match_all, + vol.Optional(CONF_JS_VERSION): cv.match_all, }), }, extra=vol.ALLOW_EXTRA) @@ -191,6 +188,15 @@ def add_manifest_json_key(key, val): MANIFEST_JSON[key] = val +def _frontend_root(dev_repo_path): + """Return root path to the frontend files.""" + if dev_repo_path is not None: + return pathlib.Path(dev_repo_path) / 'hass_frontend' + + import hass_frontend + return hass_frontend.where() + + async def async_setup(hass, config): """Set up the serving of the frontend.""" await async_setup_frontend_storage(hass) @@ -207,39 +213,28 @@ async def async_setup(hass, config): repo_path = conf.get(CONF_FRONTEND_REPO) is_dev = repo_path is not None - hass.data[DATA_JS_VERSION] = js_version = conf.get(CONF_JS_VERSION) + root_path = _frontend_root(repo_path) - if is_dev: - hass_frontend_path = os.path.join(repo_path, 'hass_frontend') - hass_frontend_es5_path = os.path.join(repo_path, 'hass_frontend_es5') - else: - import hass_frontend - import hass_frontend_es5 - hass_frontend_path = hass_frontend.where() - hass_frontend_es5_path = hass_frontend_es5.where() + for path, should_cache in ( + ("service_worker.js", False), + ("robots.txt", False), + ("onboarding.html", True), + ("static", True), + ("frontend_latest", True), + ("frontend_es5", True), + ): + hass.http.register_static_path( + "/{}".format(path), str(root_path / path), should_cache) hass.http.register_static_path( - "/service_worker_es5.js", - os.path.join(hass_frontend_es5_path, "service_worker.js"), False) - hass.http.register_static_path( - "/service_worker.js", - os.path.join(hass_frontend_path, "service_worker.js"), False) - hass.http.register_static_path( - "/robots.txt", - os.path.join(hass_frontend_path, "robots.txt"), False) - hass.http.register_static_path("/static", hass_frontend_path, not is_dev) - hass.http.register_static_path( - "/frontend_latest", hass_frontend_path, not is_dev) - hass.http.register_static_path( - "/frontend_es5", hass_frontend_es5_path, not is_dev) + "/auth/authorize", str(root_path / "authorize.html"), False) local = hass.config.path('www') if os.path.isdir(local): hass.http.register_static_path("/local", local, not is_dev) - index_view = IndexView(repo_path, js_version) + index_view = IndexView(repo_path) hass.http.register_view(index_view) - hass.http.register_view(AuthorizeView(repo_path, js_version)) @callback def async_finalize_panel(panel): @@ -263,13 +258,9 @@ async 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, False) - for url in conf.get(CONF_EXTRA_HTML_URL_ES5, []): - add_extra_html_url(hass, url, True) _async_setup_themes(hass, conf.get(CONF_THEMES)) @@ -327,36 +318,6 @@ def _async_setup_themes(hass, themes): hass.services.async_register(DOMAIN, SERVICE_RELOAD_THEMES, reload_themes) -class AuthorizeView(HomeAssistantView): - """Serve the frontend.""" - - url = '/auth/authorize' - name = 'auth:authorize' - requires_auth = False - - def __init__(self, repo_path, js_option): - """Initialize the frontend view.""" - self.repo_path = repo_path - self.js_option = js_option - - async def get(self, request: web.Request): - """Redirect to the authorize page.""" - latest = self.repo_path is not None or \ - _is_latest(self.js_option, request) - - if latest: - base = 'frontend_latest' - else: - base = 'frontend_es5' - - location = "/{}/authorize.html{}".format( - base, str(request.url.relative())[15:]) - - return web.Response(status=302, headers={ - 'location': location - }) - - class IndexView(HomeAssistantView): """Serve the frontend.""" @@ -364,70 +325,48 @@ class IndexView(HomeAssistantView): name = 'frontend:index' requires_auth = False - def __init__(self, repo_path, js_option): + def __init__(self, repo_path): """Initialize the frontend view.""" self.repo_path = repo_path - self.js_option = js_option - self._template_cache = {} + self._template_cache = None - def get_template(self, latest): + def get_template(self): """Get template.""" - if self.repo_path is not None: - root = os.path.join(self.repo_path, 'hass_frontend') - elif latest: - import hass_frontend - root = hass_frontend.where() - else: - import hass_frontend_es5 - root = hass_frontend_es5.where() - - tpl = self._template_cache.get(root) - + tpl = self._template_cache if tpl is None: - with open(os.path.join(root, 'index.html')) as file: + with open( + str(_frontend_root(self.repo_path) / 'index.html') + ) as file: tpl = jinja2.Template(file.read()) # Cache template if not running from repository if self.repo_path is None: - self._template_cache[root] = tpl + self._template_cache = tpl return tpl async def get(self, request, extra=None): """Serve the index view.""" hass = request.app['hass'] - latest = self.repo_path is not None or \ - _is_latest(self.js_option, request) if not hass.components.onboarding.async_is_onboarded(): - if latest: - location = '/frontend_latest/onboarding.html' - else: - location = '/frontend_es5/onboarding.html' - return web.Response(status=302, headers={ - 'location': location + 'location': '/onboarding.html' }) - no_auth = '1' - if not request[KEY_AUTHENTICATED]: - # do not try to auto connect on load - no_auth = '0' + template = self._template_cache - template = await hass.async_add_job(self.get_template, latest) + if template is None: + template = await hass.async_add_executor_job(self.get_template) - extra_key = DATA_EXTRA_HTML_URL if latest else DATA_EXTRA_HTML_URL_ES5 - - template_params = dict( - no_auth=no_auth, - theme_color=MANIFEST_JSON['theme_color'], - extra_urls=hass.data[extra_key], - use_oauth='1' + return web.Response( + text=template.render( + theme_color=MANIFEST_JSON['theme_color'], + extra_urls=hass.data[DATA_EXTRA_HTML_URL], + ), + content_type='text/html' ) - return web.Response(text=template.render(**template_params), - content_type='text/html') - class ManifestJSONView(HomeAssistantView): """View to return a manifest.json.""" @@ -443,38 +382,6 @@ class ManifestJSONView(HomeAssistantView): return web.Response(text=msg, content_type="application/manifest+json") -def _is_latest(js_option, request): - """ - Return whether we should serve latest untranspiled code. - - Set according to user's preference and URL override. - """ - import hass_frontend - - if request is None: - return 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' - - useragent = request.headers.get('User-Agent') - - return useragent and hass_frontend.version(useragent) - - @callback def websocket_get_panels(hass, connection, msg): """Handle get panels command. diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 0a82a36536f..674a778eb80 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/components/frontend", "requirements": [ - "home-assistant-frontend==20190427.0" + "home-assistant-frontend==20190502.0" ], "dependencies": [ "api", diff --git a/requirements_all.txt b/requirements_all.txt index 97169c1f737..2b04204cb6e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -560,7 +560,7 @@ hole==0.3.0 holidays==0.9.10 # homeassistant.components.frontend -home-assistant-frontend==20190427.0 +home-assistant-frontend==20190502.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5f3afebdf6a..b402b9bd009 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -145,7 +145,7 @@ hdate==0.8.7 holidays==0.9.10 # homeassistant.components.frontend -home-assistant-frontend==20190427.0 +home-assistant-frontend==20190502.0 # homeassistant.components.homekit_controller homekit[IP]==0.14.0 diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 1fc72b4149b..ee10b986697 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -89,10 +89,6 @@ def test_frontend_and_static(mock_http_client, mock_onboarded): @asyncio.coroutine def test_dont_cache_service_worker(mock_http_client): """Test that we don't cache the service worker.""" - resp = yield from mock_http_client.get('/service_worker_es5.js') - assert resp.status == 200 - assert 'cache-control' not in resp.headers - resp = yield from mock_http_client.get('/service_worker.js') assert resp.status == 200 assert 'cache-control' not in resp.headers @@ -233,16 +229,7 @@ def test_extra_urls(mock_http_client_with_urls, mock_onboarded): 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, mock_onboarded): - """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 + assert text.find('href="https://domain.com/my_extra_url.html"') >= 0 async def test_get_panels(hass, hass_ws_client): @@ -330,15 +317,17 @@ async def test_auth_authorize(mock_http_client): resp = await mock_http_client.get( '/auth/authorize?response_type=code&client_id=https://localhost/&' 'redirect_uri=https://localhost/&state=123%23456') + assert resp.status == 200 + # No caching of auth page. + assert 'cache-control' not in resp.headers - assert str(resp.url.relative()) == ( - '/frontend_es5/authorize.html?response_type=code&client_id=' - 'https://localhost/&redirect_uri=https://localhost/&state=123%23456') + text = await resp.text() - resp = await mock_http_client.get( - '/auth/authorize?latest&response_type=code&client_id=' - 'https://localhost/&redirect_uri=https://localhost/&state=123%23456') + # Test we can retrieve authorize.js + authorizejs = re.search( + r'(?P\/frontend_latest\/authorize.[A-Za-z0-9]{8}.js)', text) - assert str(resp.url.relative()) == ( - '/frontend_latest/authorize.html?latest&response_type=code&client_id=' - 'https://localhost/&redirect_uri=https://localhost/&state=123%23456') + assert authorizejs is not None, text + resp = await mock_http_client.get(authorizejs.groups(0)[0]) + assert resp.status == 200 + assert 'public' in resp.headers.get('cache-control')